Skip to content

Get digest of multi-arch images #4475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 0 additions & 59 deletions pkg/skaffold/docker/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import (

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
Expand Down Expand Up @@ -367,63 +365,6 @@ func TestImageExists(t *testing.T) {
}
}

func TestInsecureRegistry(t *testing.T) {
tests := []struct {
description string
image string
insecureRegistries map[string]bool
scheme string
shouldErr bool
}{
{
description: "secure image",
image: "gcr.io/secure/image",
insecureRegistries: map[string]bool{},
scheme: "https",
},
{
description: "insecure image",
image: "my.insecure.registry/image",
insecureRegistries: map[string]bool{
"my.insecure.registry": true,
},
scheme: "http",
},
{
description: "insecure image not provided by user",
image: "my.insecure.registry/image",
shouldErr: true,
},
{
description: "secure image provided in insecure registries list",
image: "gcr.io/secure/image",
insecureRegistries: map[string]bool{
"gcr.io": true,
},
shouldErr: true,
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
t.Override(&getRemoteImageImpl, func(ref name.Reference) (v1.Image, error) {
return &fakeImage{Reference: ref}, nil
})

img, err := remoteImage(test.image, test.insecureRegistries)

t.CheckNoError(err)
if !test.shouldErr {
t.CheckDeepEqual(test.scheme, img.(*fakeImage).Reference.Context().Registry.Scheme())
}
})
}
}

type fakeImage struct {
v1.Image
Reference name.Reference
}

func TestConfigFile(t *testing.T) {
api := (&testutil.FakeAPIClient{}).Add("gcr.io/image", "sha256:imageIDabcab")

Expand Down
51 changes: 35 additions & 16 deletions pkg/skaffold/docker/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ import (

// for testing
var (
getRemoteImageImpl = getRemoteImage
RemoteDigest = getRemoteDigest
RemoteDigest = getRemoteDigest
remoteImage = remote.Image
remoteIndex = remote.Index
)

func AddRemoteTag(src, target string, insecureRegistries map[string]bool) error {
logrus.Debugf("attempting to add tag %s to src %s", target, src)
img, err := remoteImage(src, insecureRegistries)
img, err := getRemoteImage(src, insecureRegistries)
if err != nil {
return fmt.Errorf("getting image: %w", err)
}
Expand All @@ -50,22 +51,22 @@ func AddRemoteTag(src, target string, insecureRegistries map[string]bool) error
}

func getRemoteDigest(identifier string, insecureRegistries map[string]bool) (string, error) {
img, err := remoteImage(identifier, insecureRegistries)
if err != nil {
return "", fmt.Errorf("getting image: %w", err)
idx, err := getRemoteIndex(identifier, insecureRegistries)
if err == nil {
return digest(idx)
}

h, err := img.Digest()
img, err := getRemoteImage(identifier, insecureRegistries)
if err != nil {
return "", fmt.Errorf("getting digest: %w", err)
return "", fmt.Errorf("getting image: %w", err)
}

return h.String(), nil
return digest(img)
}

// RetrieveRemoteConfig retrieves the remote config file for an image
func RetrieveRemoteConfig(identifier string, insecureRegistries map[string]bool) (*v1.ConfigFile, error) {
img, err := remoteImage(identifier, insecureRegistries)
img, err := getRemoteImage(identifier, insecureRegistries)
if err != nil {
return nil, err
}
Expand All @@ -92,24 +93,29 @@ func Push(tarPath, tag string, insecureRegistries map[string]bool) (string, erro
return getRemoteDigest(tag, insecureRegistries)
}

func remoteImage(identifier string, insecureRegistries map[string]bool) (v1.Image, error) {
func getRemoteImage(identifier string, insecureRegistries map[string]bool) (v1.Image, error) {
ref, err := parseReference(identifier, insecureRegistries)
if err != nil {
return nil, err
}

return remoteImage(ref, remote.WithAuthFromKeychain(primaryKeychain))
}

func getRemoteIndex(identifier string, insecureRegistries map[string]bool) (v1.ImageIndex, error) {
ref, err := parseReference(identifier, insecureRegistries)
if err != nil {
return nil, err
}

return getRemoteImageImpl(ref)
return remoteIndex(ref, remote.WithAuthFromKeychain(primaryKeychain))
}

// IsInsecure tests if an image is pulled from an insecure registry; default is false
func IsInsecure(ref name.Reference, insecureRegistries map[string]bool) bool {
return insecureRegistries[ref.Context().Registry.Name()]
}

func getRemoteImage(ref name.Reference) (v1.Image, error) {
return remote.Image(ref, remote.WithAuthFromKeychain(primaryKeychain))
}

func parseReference(s string, insecureRegistries map[string]bool, opts ...name.Option) (name.Reference, error) {
ref, err := name.ParseReference(s, opts...)
if err != nil {
Expand All @@ -125,3 +131,16 @@ func parseReference(s string, insecureRegistries map[string]bool, opts ...name.O

return ref, nil
}

type digester interface {
Digest() (v1.Hash, error)
}

func digest(d digester) (string, error) {
h, err := d.Digest()
if err != nil {
return "", fmt.Errorf("getting digest: %w", err)
}

return h.String(), nil
}
126 changes: 126 additions & 0 deletions pkg/skaffold/docker/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ limitations under the License.
package docker

import (
"fmt"
"testing"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/types"

"github.com/GoogleContainerTools/skaffold/testutil"
)
Expand Down Expand Up @@ -47,3 +51,125 @@ func TestIsInsecure(t *testing.T) {
})
}
}

func TestRemoteImage(t *testing.T) {
tests := []struct {
description string
image string
insecureRegistries map[string]bool
expectedScheme string
shouldErr bool
}{
{
description: "secure",
image: "gcr.io/secure/image",
expectedScheme: "https",
},
{
description: "insecure",
image: "my.insecure.registry/image",
insecureRegistries: map[string]bool{
"my.insecure.registry": true,
},
expectedScheme: "http",
},
{
description: "invalid",
image: "invalid image",
shouldErr: true,
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
t.Override(&remoteImage, func(ref name.Reference, options ...remote.Option) (v1.Image, error) {
return &fakeImage{
Reference: ref,
}, nil
})

img, err := getRemoteImage(test.image, test.insecureRegistries)

t.CheckError(test.shouldErr, err)
if !test.shouldErr {
t.CheckDeepEqual(test.expectedScheme, img.(*fakeImage).Reference.Context().Registry.Scheme())
}
})
}
}

func TestRemoteDigest(t *testing.T) {
tests := []struct {
description string
image string
hash v1.Hash
shouldErr bool
expectedDigest string
}{
{
description: "OCI v1 image",
image: "image",
expectedDigest: "sha256:abacab",
},
{
description: "OCI image index",
image: "index",
expectedDigest: "sha256:cdefcdef",
},
{
description: "image not found",
image: "notfound",
shouldErr: true,
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
t.Override(&remoteIndex, func(ref name.Reference, options ...remote.Option) (v1.ImageIndex, error) {
if ref.Name() != "index.docker.io/library/index:latest" {
return nil, fmt.Errorf("not found: %s", ref.Name())
}

return &fakeImageIndex{
Hash: v1.Hash{Algorithm: "sha256", Hex: "cdefcdef"},
}, nil
})
t.Override(&remoteImage, func(ref name.Reference, options ...remote.Option) (v1.Image, error) {
if ref.Name() != "index.docker.io/library/image:latest" {
return nil, fmt.Errorf("not found: %s", ref.Name())
}

return &fakeImage{
Hash: v1.Hash{Algorithm: "sha256", Hex: "abacab"},
}, nil
})

digest, err := RemoteDigest(test.image, nil)

t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedDigest, digest)
})
}
}

type fakeImage struct {
v1.Image
Reference name.Reference
Hash v1.Hash
}

func (i *fakeImage) Digest() (v1.Hash, error) {
return i.Hash, nil
}

type fakeImageIndex struct {
Hash v1.Hash
}

func (i *fakeImageIndex) Digest() (v1.Hash, error) {
return i.Hash, nil
}

func (i *fakeImageIndex) MediaType() (types.MediaType, error) { return "", nil }
func (i *fakeImageIndex) Size() (int64, error) { return 0, nil }
func (i *fakeImageIndex) IndexManifest() (*v1.IndexManifest, error) { return nil, nil }
func (i *fakeImageIndex) RawManifest() ([]byte, error) { return nil, nil }
func (i *fakeImageIndex) Image(v1.Hash) (v1.Image, error) { return nil, nil }
func (i *fakeImageIndex) ImageIndex(v1.Hash) (v1.ImageIndex, error) { return nil, nil }