-
Notifications
You must be signed in to change notification settings - Fork 165
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
Remove dependency on buildpack/imgutil #133
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package registry | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/google/go-containerregistry/pkg/authn" | ||
"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/pkg/errors" | ||
) | ||
|
||
type GoContainerRegistryImage struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we rename this file/struct ... maybe RemoteRegistryImage? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thought is the consumers of this object will consume it through the RemoteImage interface. What makes this struct unique is that it implements that interface using GoContainerRegistry library. |
||
image v1.Image | ||
repoName string | ||
} | ||
|
||
func NewGoContainerRegistryImage(repoName string, keychain authn.Keychain) (*GoContainerRegistryImage, error) { | ||
image, err := newV1Image(keychain, repoName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
ri := &GoContainerRegistryImage{ | ||
repoName: repoName, | ||
image: image, | ||
} | ||
|
||
return ri, nil | ||
} | ||
|
||
func newV1Image(keychain authn.Keychain, repoName string) (v1.Image, error) { | ||
var auth authn.Authenticator | ||
ref, err := name.ParseReference(repoName, name.WeakValidation) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "parse reference '%s'", repoName) | ||
} | ||
|
||
auth, err = keychain.Resolve(ref.Context().Registry) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "resolving keychain for '%s'", ref.Context().Registry) | ||
} | ||
|
||
image, err := remote.Image(ref, remote.WithAuth(auth), remote.WithTransport(http.DefaultTransport)) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "connect to registry store '%s'", repoName) | ||
} | ||
|
||
return image, nil | ||
} | ||
|
||
func (i *GoContainerRegistryImage) CreatedAt() (time.Time, error) { | ||
cfg, err := i.configFile() | ||
if err != nil { | ||
return time.Time{}, err | ||
} | ||
return cfg.Created.UTC(), nil | ||
} | ||
|
||
func (i *GoContainerRegistryImage) Env(key string) (string, error) { | ||
cfg, err := i.configFile() | ||
if err != nil { | ||
return "", err | ||
} | ||
for _, envVar := range cfg.Config.Env { | ||
parts := strings.Split(envVar, "=") | ||
if parts[0] == key { | ||
return parts[1], nil | ||
} | ||
} | ||
return "", nil | ||
} | ||
|
||
func (i *GoContainerRegistryImage) Label(key string) (string, error) { | ||
cfg, err := i.configFile() | ||
if err != nil { | ||
return "", err | ||
} | ||
labels := cfg.Config.Labels | ||
return labels[key], nil | ||
} | ||
|
||
func (i *GoContainerRegistryImage) Identifier() (string, error) { | ||
ref, err := name.ParseReference(i.repoName, name.WeakValidation) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
digest, err := i.image.Digest() | ||
if err != nil { | ||
return "", errors.Wrapf(err, "failed to get digest for image '%s'", i.repoName) | ||
} | ||
|
||
return fmt.Sprintf("%s@%s", ref.Context().Name(), digest), nil | ||
} | ||
|
||
func (i *GoContainerRegistryImage) configFile() (*v1.ConfigFile, error) { | ||
cfg, err := i.image.ConfigFile() | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "failed to get config for image '%s'", i.repoName) | ||
} else if cfg == nil { | ||
return nil, errors.Errorf("failed to get config for image '%s'", i.repoName) | ||
} | ||
|
||
return cfg, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package registry_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/google/go-containerregistry/pkg/authn" | ||
"github.com/sclevine/spec" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/pivotal/kpack/pkg/registry" | ||
) | ||
|
||
func TestGGCRImage(t *testing.T) { | ||
spec.Run(t, "GGCR Image", testGGCRImage) | ||
} | ||
|
||
func testGGCRImage(t *testing.T, when spec.G, it spec.S) { | ||
when("#CreatedAt", func() { | ||
it("returns created at from the image", func() { | ||
image, err := registry.NewGoContainerRegistryImage("cloudfoundry/cnb:bionic@sha256:33c3ad8676530f864d51d78483b510334ccc4f03368f7f5bb9d517ff4cbd630f", authn.DefaultKeychain) | ||
require.NoError(t, err) | ||
|
||
createdAt, err := image.CreatedAt() | ||
require.NoError(t, err) | ||
|
||
require.NotEqual(t, time.Time{}, createdAt) | ||
}) | ||
}) | ||
|
||
when("#Label", func() { | ||
it("returns created at from the image", func() { | ||
image, err := registry.NewGoContainerRegistryImage("cloudfoundry/cnb:bionic@sha256:33c3ad8676530f864d51d78483b510334ccc4f03368f7f5bb9d517ff4cbd630f", authn.DefaultKeychain) | ||
require.NoError(t, err) | ||
|
||
metadata, err := image.Label("io.buildpacks.builder.metadata") | ||
require.NoError(t, err) | ||
|
||
require.NotEmpty(t, metadata) | ||
}) | ||
}) | ||
|
||
when("#Env", func() { | ||
it("returns created at from the image", func() { | ||
image, err := registry.NewGoContainerRegistryImage("cloudfoundry/cnb:bionic@sha256:33c3ad8676530f864d51d78483b510334ccc4f03368f7f5bb9d517ff4cbd630f", authn.DefaultKeychain) | ||
require.NoError(t, err) | ||
|
||
cnbUserId, err := image.Env("CNB_USER_ID") | ||
require.NoError(t, err) | ||
|
||
require.NotEmpty(t, cnbUserId) | ||
}) | ||
}) | ||
|
||
when("#identifer", func() { | ||
it("includes digest if repoName does not have a digest", func() { | ||
image, err := registry.NewGoContainerRegistryImage("cloudfoundry/cnb:bionic", authn.DefaultKeychain) | ||
require.NoError(t, err) | ||
|
||
identifier, err := image.Identifier() | ||
require.NoError(t, err) | ||
require.Len(t, identifier, 104) | ||
require.Equal(t, identifier[0:40], "index.docker.io/cloudfoundry/cnb@sha256:") | ||
}) | ||
|
||
it("includes digest if repoName already has a digest", func() { | ||
image, err := registry.NewGoContainerRegistryImage("cloudfoundry/cnb:bionic@sha256:33c3ad8676530f864d51d78483b510334ccc4f03368f7f5bb9d517ff4cbd630f", authn.DefaultKeychain) | ||
require.NoError(t, err) | ||
|
||
identifier, err := image.Identifier() | ||
require.NoError(t, err) | ||
require.Equal(t, identifier, "index.docker.io/cloudfoundry/cnb@sha256:33c3ad8676530f864d51d78483b510334ccc4f03368f7f5bb9d517ff4cbd630f") | ||
}) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,19 +3,16 @@ package registry | |
import ( | ||
"time" | ||
|
||
"github.com/buildpack/imgutil" | ||
"github.com/buildpack/imgutil/remote" | ||
"github.com/google/go-containerregistry/pkg/authn" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type ImageFactory struct { | ||
KeychainFactory KeychainFactory | ||
} | ||
|
||
func (f *ImageFactory) NewRemote(imageRef ImageRef) (RemoteImage, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an extension of our previous comment, can we update this as well to say |
||
remote, err := remote.NewImage(imageRef.Tag(), f.KeychainFactory.KeychainForImageRef(imageRef), remote.FromBaseImage(imageRef.Tag())) | ||
return remote, errors.Wrapf(err, "could not create remote image from ref %s", imageRef.Tag()) | ||
remoteImage, err := NewGoContainerRegistryImage(imageRef.Identifier(), f.KeychainFactory.KeychainForImageRef(imageRef)) | ||
return remoteImage, err | ||
} | ||
|
||
type KeychainFactory interface { | ||
|
@@ -25,25 +22,25 @@ type KeychainFactory interface { | |
type ImageRef interface { | ||
ServiceAccount() string | ||
Namespace() string | ||
Tag() string | ||
Identifier() string | ||
HasSecret() bool | ||
SecretName() string | ||
} | ||
|
||
type noAuthImageRef struct { | ||
repoName string | ||
identifier string | ||
} | ||
|
||
func (na *noAuthImageRef) SecretName() string { | ||
return "" | ||
} | ||
|
||
func NewNoAuthImageRef(repoName string) *noAuthImageRef { | ||
return &noAuthImageRef{repoName: repoName} | ||
func NewNoAuthImageRef(identifier string) *noAuthImageRef { | ||
return &noAuthImageRef{identifier: identifier} | ||
} | ||
|
||
func (na *noAuthImageRef) Tag() string { | ||
return na.repoName | ||
func (na *noAuthImageRef) Identifier() string { | ||
return na.identifier | ||
} | ||
|
||
func (noAuthImageRef) ServiceAccount() string { | ||
|
@@ -60,7 +57,7 @@ func (noAuthImageRef) Namespace() string { | |
|
||
type RemoteImage interface { | ||
CreatedAt() (time.Time, error) | ||
Identifier() (imgutil.Identifier, error) | ||
Identifier() (string, error) | ||
Label(labelName string) (string, error) | ||
Env(key string) (string, error) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be Image too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an actual built image identifier. Not the same thing as an image because it always includes the digest