Skip to content

Commit

Permalink
feat(docker): reference images by digest
Browse files Browse the repository at this point in the history
  • Loading branch information
jlegrone committed Dec 13, 2019
1 parent cea7353 commit 92fe083
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 53 deletions.
9 changes: 9 additions & 0 deletions bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ func (i *BaseImage) DeepCopy() *BaseImage {
return &i2
}

// DigestedRef returns an image reference which can be used to pull by digest using Docker or Kubernetes.
// If no digest is available, only the image will be returned and the boolean value will be false.
func (i *BaseImage) DigestedRef() (string, bool) {
if i.Digest == "" {
return i.Image, false
}
return fmt.Sprintf("%s@%s", i.Image, i.Digest), true
}

// Image describes a container image in the bundle
type Image struct {
BaseImage `yaml:",inline"`
Expand Down
61 changes: 61 additions & 0 deletions bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bundle

import (
"bytes"
"fmt"
"io/ioutil"
"testing"

Expand Down Expand Up @@ -680,3 +681,63 @@ func TestImageDeepCopy(t *testing.T) {
assert.Equal(t, map[string]string{"origLabel": "newLabelValue"}, newImg.Labels)
assert.Equal(t, "123abcd", newImg.Digest)
}

func ExampleBaseImage_DigestedRef() {
image := BaseImage{
Image: "foo/bar:latest",
Digest: "sha256:7ea254518cab82f53938523eab0dc6601a3dd1edf700f1f6235e66e27cbc7986",
}

imageRef, ok := image.DigestedRef()
if !ok {
fmt.Println("no digest")
}

fmt.Println(imageRef)
// Output: foo/bar:latest@sha256:7ea254518cab82f53938523eab0dc6601a3dd1edf700f1f6235e66e27cbc7986
}

func TestBaseImage_DigestedRef(t *testing.T) {
cases := map[string]struct {
expected string
hasDigest bool
image BaseImage
}{
"no tag": {
expected: "foo",
image: BaseImage{
Image: "foo",
},
},
"tag only": {
expected: "foo:bar",
image: BaseImage{
Image: "foo:bar",
},
},
"digest only": {
expected: "foo@sha256:7ea254518",
hasDigest: true,
image: BaseImage{
Image: "foo",
Digest: "sha256:7ea254518",
},
},
"digest and tag": {
expected: "foo:bar@sha256:7ea254518",
hasDigest: true,
image: BaseImage{
Image: "foo:bar",
Digest: "sha256:7ea254518",
},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ref, ok := tc.image.DigestedRef()
assert.Equal(t, tc.hasDigest, ok)
assert.Equal(t, tc.expected, ref)
})
}
}
13 changes: 8 additions & 5 deletions driver/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,21 @@ func (d *Driver) exec(op *driver.Operation) (driver.OperationResult, error) {
if d.Simulate {
return driver.OperationResult{}, nil
}
if d.config["PULL_ALWAYS"] == "1" {
if err := pullImage(ctx, cli, op.Image.Image); err != nil {

imageRef, ok := op.Image.DigestedRef()
if !ok && d.config["PULL_ALWAYS"] == "1" {
if err := pullImage(ctx, cli, imageRef); err != nil {
return driver.OperationResult{}, err
}
}

var env []string
for k, v := range op.Environment {
env = append(env, fmt.Sprintf("%s=%v", k, v))
}

cfg := &container.Config{
Image: op.Image.Image,
Image: imageRef,
Env: env,
Entrypoint: strslice.StrSlice{"/cnab/app/run"},
AttachStderr: true,
Expand All @@ -173,8 +176,8 @@ func (d *Driver) exec(op *driver.Operation) (driver.OperationResult, error) {
resp, err := cli.Client().ContainerCreate(ctx, cfg, hostCfg, nil, "")
switch {
case client.IsErrNotFound(err):
fmt.Fprintf(cli.Err(), "Unable to find image '%s' locally\n", op.Image.Image)
if err := pullImage(ctx, cli, op.Image.Image); err != nil {
fmt.Fprintf(cli.Err(), "Unable to find image '%s' locally\n", cfg.Image)
if err := pullImage(ctx, cli, cfg.Image); err != nil {
return driver.OperationResult{}, err
}
if resp, err = cli.Client().ContainerCreate(ctx, cfg, hostCfg, nil, ""); err != nil {
Expand Down
11 changes: 2 additions & 9 deletions driver/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"

"github.com/cnabio/cnab-go/bundle"
"github.com/cnabio/cnab-go/driver"
)

Expand Down Expand Up @@ -166,9 +165,10 @@ func (k *Driver) Run(op *driver.Operation) (driver.OperationResult, error) {
},
},
}
digestedRef, _ := op.Image.DigestedRef()
container := v1.Container{
Name: k8sContainerName,
Image: imageWithDigest(op.Image),
Image: digestedRef,
Command: []string{"/cnab/app/run"},
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
Expand Down Expand Up @@ -444,10 +444,3 @@ func homeDir() string {
}
return os.Getenv("USERPROFILE") // windows
}

func imageWithDigest(img bundle.InvocationImage) string {
if img.Digest == "" {
return img.Image
}
return img.Image + "@" + img.Digest
}
39 changes: 0 additions & 39 deletions driver/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"os"
"testing"

"github.com/cnabio/cnab-go/bundle"
"github.com/cnabio/cnab-go/driver"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -40,44 +39,6 @@ func TestDriver_Run(t *testing.T) {
assert.Equal(t, len(secretList.Items), 1, "expected one secret to be created")
}

func TestImageWithDigest(t *testing.T) {
testCases := map[string]bundle.InvocationImage{
"foo": {
BaseImage: bundle.BaseImage{
Image: "foo",
},
},
"foo/bar": {
BaseImage: bundle.BaseImage{
Image: "foo/bar",
},
},
"foo/bar:baz": {
BaseImage: bundle.BaseImage{
Image: "foo/bar:baz",
},
},
"foo/bar:baz@sha:a1b2c3": {
BaseImage: bundle.BaseImage{
Image: "foo/bar:baz",
Digest: "sha:a1b2c3",
},
},
"foo/bar@sha:a1b2c3": {
BaseImage: bundle.BaseImage{
Image: "foo/bar",
Digest: "sha:a1b2c3",
},
},
}

for expectedImageRef, img := range testCases {
t.Run(expectedImageRef, func(t *testing.T) {
assert.Equal(t, expectedImageRef, imageWithDigest(img))
})
}
}

func TestGenerateNameTemplate(t *testing.T) {
testCases := map[string]struct {
op *driver.Operation
Expand Down

0 comments on commit 92fe083

Please sign in to comment.