diff --git a/cmd/warmer/cmd/root.go b/cmd/warmer/cmd/root.go index ea07cf5796..0ccfdd1421 100644 --- a/cmd/warmer/cmd/root.go +++ b/cmd/warmer/cmd/root.go @@ -75,6 +75,14 @@ func addKanikoOptionsFlags() { RootCmd.PersistentFlags().StringVarP(&opts.CacheDir, "cache-dir", "c", "/cache", "Directory of the cache.") RootCmd.PersistentFlags().BoolVarP(&opts.Force, "force", "f", false, "Force cache overwriting.") RootCmd.PersistentFlags().DurationVarP(&opts.CacheTTL, "cache-ttl", "", time.Hour*336, "Cache timeout in hours. Defaults to two weeks.") + RootCmd.PersistentFlags().BoolVarP(&opts.InsecurePull, "insecure-pull", "", false, "Pull from insecure registry using plain HTTP") + RootCmd.PersistentFlags().BoolVarP(&opts.SkipTLSVerifyPull, "skip-tls-verify-pull", "", false, "Pull from insecure registry ignoring TLS verify") + RootCmd.PersistentFlags().VarP(&opts.InsecureRegistries, "insecure-registry", "", "Insecure registry using plain HTTP to pull. Set it repeatedly for multiple registries.") + RootCmd.PersistentFlags().VarP(&opts.SkipTLSVerifyRegistries, "skip-tls-verify-registry", "", "Insecure registry ignoring TLS verify to pull. Set it repeatedly for multiple registries.") + opts.RegistriesCertificates = make(map[string]string) + RootCmd.PersistentFlags().VarP(&opts.RegistriesCertificates, "registry-certificate", "", "Use the provided certificate for TLS communication with the given registry. Expected format is 'my.registry.url=/path/to/the/server/certificate'.") + RootCmd.PersistentFlags().VarP(&opts.RegistryMirrors, "registry-mirror", "", "Registry mirror to use as pull-through cache instead of docker.io. Set it repeatedly for multiple mirrors.") + RootCmd.PersistentFlags().StringVarP(&opts.CustomPlatform, "customPlatform", "", "", "Specify the build platform if different from the current host") } // addHiddenFlags marks certain flags as hidden from the executor help text diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 70fca8b1d5..94187a341b 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -66,7 +66,7 @@ func (rc *RegistryCache) RetrieveLayer(ck string) (v1.Image, error) { cacheRef.Repository.Registry = newReg } - tr := util.MakeTransport(rc.Opts, registryName) + tr := util.MakeTransport(rc.Opts.RegistryOptions, registryName) img, err := remote.Image(cacheRef, remote.WithTransport(tr), remote.WithAuthFromKeychain(creds.GetKeychain())) if err != nil { diff --git a/pkg/cache/doc_test.go b/pkg/cache/doc_test.go index 49443a3ef6..2dfe3ba1fb 100644 --- a/pkg/cache/doc_test.go +++ b/pkg/cache/doc_test.go @@ -21,14 +21,14 @@ import ( "log" "github.com/GoogleContainerTools/kaniko/pkg/config" - "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/GoogleContainerTools/kaniko/pkg/image/remote" ) func ExampleWarmer_Warm() { tarBuf := new(bytes.Buffer) manifestBuf := new(bytes.Buffer) w := &Warmer{ - Remote: remote.Image, + Remote: remote.RetrieveRemoteImage, Local: LocalSource, TarWriter: tarBuf, ManifestWriter: manifestBuf, diff --git a/pkg/cache/warm.go b/pkg/cache/warm.go index ea753d4f8d..95f12d3d6c 100644 --- a/pkg/cache/warm.go +++ b/pkg/cache/warm.go @@ -20,16 +20,13 @@ import ( "bytes" "io" "io/ioutil" - "net/http" "os" "path" - "runtime" "github.com/GoogleContainerTools/kaniko/pkg/config" - "github.com/GoogleContainerTools/kaniko/pkg/creds" + "github.com/GoogleContainerTools/kaniko/pkg/image/remote" "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/tarball" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -42,18 +39,18 @@ func WarmCache(opts *config.WarmerOptions) error { logrus.Debugf("%s\n", cacheDir) logrus.Debugf("%s\n", images) - for _, image := range images { + for _, img := range images { tarBuf := new(bytes.Buffer) manifestBuf := new(bytes.Buffer) cw := &Warmer{ - Remote: remote.Image, + Remote: remote.RetrieveRemoteImage, Local: LocalSource, TarWriter: tarBuf, ManifestWriter: manifestBuf, } - digest, err := cw.Warm(image, opts) + digest, err := cw.Warm(img, opts) if err != nil { if !IsAlreadyCached(err) { return err @@ -68,7 +65,7 @@ func WarmCache(opts *config.WarmerOptions) error { return err } - logrus.Debugf("Wrote %s to cache", image) + logrus.Debugf("Wrote %s to cache", img) } return nil } @@ -93,9 +90,9 @@ func writeBufsToFile(cachePath string, tarBuf, manifestBuf *bytes.Buffer) error } // FetchRemoteImage retrieves a Docker image manifest from a remote source. -// github.com/google/go-containerregistry/pkg/v1/remote.Image can be used as +// github.com/GoogleContainerTools/kaniko/image/remote.RetrieveRemoteImage can be used as // this type. -type FetchRemoteImage func(name.Reference, ...remote.Option) (v1.Image, error) +type FetchRemoteImage func(image string, opts config.RegistryOptions, customPlatform string) (v1.Image, error) // FetchLocalSource retrieves a Docker image manifest from a local source. // github.com/GoogleContainerTools/kaniko/cache.LocalSource can be used as @@ -118,11 +115,7 @@ func (w *Warmer) Warm(image string, opts *config.WarmerOptions) (v1.Hash, error) return v1.Hash{}, errors.Wrapf(err, "Failed to verify image name: %s", image) } - transport := http.DefaultTransport.(*http.Transport) - platform := currentPlatform() - - rOpts := []remote.Option{remote.WithTransport(transport), remote.WithAuthFromKeychain(creds.GetKeychain()), remote.WithPlatform(platform)} - img, err := w.Remote(cacheRef, rOpts...) + img, err := w.Remote(image, opts.RegistryOptions, opts.CustomPlatform) if err != nil || img == nil { return v1.Hash{}, errors.Wrapf(err, "Failed to retrieve image: %s", image) } @@ -155,11 +148,3 @@ func (w *Warmer) Warm(image string, opts *config.WarmerOptions) (v1.Hash, error) return digest, nil } - -// CurrentPlatform returns the v1.Platform on which the code runs. -func currentPlatform() v1.Platform { - return v1.Platform{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - } -} diff --git a/pkg/cache/warm_test.go b/pkg/cache/warm_test.go index 554210b4e0..aa879a2d4b 100644 --- a/pkg/cache/warm_test.go +++ b/pkg/cache/warm_test.go @@ -22,9 +22,7 @@ import ( "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/GoogleContainerTools/kaniko/pkg/fakes" - "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" ) const ( @@ -36,7 +34,7 @@ func Test_Warmer_Warm_not_in_cache(t *testing.T) { manifestBuf := new(bytes.Buffer) cw := &Warmer{ - Remote: func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { + Remote: func(_ string, _ config.RegistryOptions, _ string) (v1.Image, error) { return fakes.FakeImage{}, nil }, Local: func(_ *config.CacheOptions, _ string) (v1.Image, error) { @@ -64,7 +62,7 @@ func Test_Warmer_Warm_in_cache_not_expired(t *testing.T) { manifestBuf := new(bytes.Buffer) cw := &Warmer{ - Remote: func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { + Remote: func(_ string, _ config.RegistryOptions, _ string) (v1.Image, error) { return fakes.FakeImage{}, nil }, Local: func(_ *config.CacheOptions, _ string) (v1.Image, error) { @@ -92,7 +90,7 @@ func Test_Warmer_Warm_in_cache_expired(t *testing.T) { manifestBuf := new(bytes.Buffer) cw := &Warmer{ - Remote: func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { + Remote: func(_ string, _ config.RegistryOptions, _ string) (v1.Image, error) { return fakes.FakeImage{}, nil }, Local: func(_ *config.CacheOptions, _ string) (v1.Image, error) { diff --git a/pkg/config/options.go b/pkg/config/options.go index 7651c75226..ca36a435e2 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -30,40 +30,45 @@ type CacheOptions struct { CacheTTL time.Duration } -// KanikoOptions are options that are set by command line arguments -type KanikoOptions struct { - CacheOptions - DockerfilePath string - SrcContext string - SnapshotMode string - CustomPlatform string - Bucket string - TarPath string - Target string - CacheRepo string - DigestFile string - ImageNameDigestFile string - OCILayoutPath string +// RegistryOptions are all the options related to the registries, set by command line arguments. +type RegistryOptions struct { RegistryMirrors multiArg - Destinations multiArg - BuildArgs multiArg InsecureRegistries multiArg - Labels multiArg SkipTLSVerifyRegistries multiArg RegistriesCertificates keyValueArg Insecure bool SkipTLSVerify bool InsecurePull bool SkipTLSVerifyPull bool - SingleSnapshot bool - Reproducible bool - NoPush bool - Cache bool - Cleanup bool - IgnoreVarRun bool - SkipUnusedStages bool - RunV2 bool - Git KanikoGitOptions +} + +// KanikoOptions are options that are set by command line arguments +type KanikoOptions struct { + CacheOptions + RegistryOptions + DockerfilePath string + SrcContext string + SnapshotMode string + CustomPlatform string + Bucket string + TarPath string + Target string + CacheRepo string + DigestFile string + ImageNameDigestFile string + OCILayoutPath string + Destinations multiArg + BuildArgs multiArg + Labels multiArg + SingleSnapshot bool + Reproducible bool + NoPush bool + Cache bool + Cleanup bool + IgnoreVarRun bool + SkipUnusedStages bool + RunV2 bool + Git KanikoGitOptions } type KanikoGitOptions struct { @@ -109,6 +114,8 @@ func (k *KanikoGitOptions) Set(s string) error { // WarmerOptions are options that are set by command line arguments to the cache warmer. type WarmerOptions struct { CacheOptions - Images multiArg - Force bool + RegistryOptions + CustomPlatform string + Images multiArg + Force bool } diff --git a/pkg/executor/build.go b/pkg/executor/build.go index c443065704..ca2b038917 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -25,19 +25,15 @@ import ( "strings" "time" - "github.com/google/go-containerregistry/pkg/v1/partial" - - "github.com/moby/buildkit/frontend/dockerfile/instructions" - - "golang.org/x/sync/errgroup" - "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" "github.com/GoogleContainerTools/kaniko/pkg/cache" "github.com/GoogleContainerTools/kaniko/pkg/commands" @@ -45,9 +41,11 @@ import ( "github.com/GoogleContainerTools/kaniko/pkg/constants" "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" image_util "github.com/GoogleContainerTools/kaniko/pkg/image" + "github.com/GoogleContainerTools/kaniko/pkg/image/remote" "github.com/GoogleContainerTools/kaniko/pkg/snapshot" "github.com/GoogleContainerTools/kaniko/pkg/timing" "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/google/go-containerregistry/pkg/v1/partial" ) // This is the size of an empty tar in Go @@ -745,7 +743,7 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e // This must be an image name, fetch it. logrus.Debugf("Found extra base image stage %s", c.From) - sourceImage, err := image_util.RetrieveRemoteImage(c.From, opts) + sourceImage, err := remote.RetrieveRemoteImage(c.From, opts.RegistryOptions, opts.CustomPlatform) if err != nil { return err } diff --git a/pkg/executor/push.go b/pkg/executor/push.go index f4bda69799..874c724d8f 100644 --- a/pkg/executor/push.go +++ b/pkg/executor/push.go @@ -147,7 +147,7 @@ func CheckPushPermissions(opts *config.KanikoOptions) error { } destRef.Repository.Registry = newReg } - tr := newRetry(util.MakeTransport(opts, registryName)) + tr := newRetry(util.MakeTransport(opts.RegistryOptions, registryName)) if err := checkRemotePushPermission(destRef, creds.GetKeychain(), tr); err != nil { return errors.Wrapf(err, "checking push permission for %q", destRef) } @@ -244,7 +244,7 @@ func DoPush(image v1.Image, opts *config.KanikoOptions) error { return errors.Wrap(err, "resolving pushAuth") } - tr := newRetry(util.MakeTransport(opts, registryName)) + tr := newRetry(util.MakeTransport(opts.RegistryOptions, registryName)) rt := &withUserAgent{t: tr} if err := remote.Write(destRef, image, remote.WithAuth(pushAuth), remote.WithTransport(rt)); err != nil { diff --git a/pkg/image/image_util.go b/pkg/image/image_util.go index 6e8b25abd7..269e2b40d3 100644 --- a/pkg/image/image_util.go +++ b/pkg/image/image_util.go @@ -19,21 +19,18 @@ package image import ( "fmt" "path/filepath" - "runtime" "strconv" - "strings" "github.com/GoogleContainerTools/kaniko/pkg/cache" "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/GoogleContainerTools/kaniko/pkg/constants" - "github.com/GoogleContainerTools/kaniko/pkg/creds" + "github.com/GoogleContainerTools/kaniko/pkg/image/remote" "github.com/GoogleContainerTools/kaniko/pkg/timing" "github.com/GoogleContainerTools/kaniko/pkg/util" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" - "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/sirupsen/logrus" @@ -41,7 +38,7 @@ import ( var ( // RetrieveRemoteImage downloads an image from a remote location - RetrieveRemoteImage = remoteImage + RetrieveRemoteImage = remote.RetrieveRemoteImage retrieveTarImage = tarballImage ) @@ -89,7 +86,7 @@ func RetrieveSourceImage(stage config.KanikoStage, opts *config.KanikoOptions) ( } // Otherwise, initialize image as usual - return RetrieveRemoteImage(currentBaseName, opts) + return RetrieveRemoteImage(currentBaseName, opts.RegistryOptions, opts.CustomPlatform) } func tarballImage(index int) (v1.Image, error) { @@ -98,89 +95,6 @@ func tarballImage(index int) (v1.Image, error) { return tarball.ImageFromPath(tarPath, nil) } -// Retrieves the manifest for the specified image from the specified registry -func remoteImage(image string, opts *config.KanikoOptions) (v1.Image, error) { - logrus.Infof("Retrieving image manifest %s", image) - ref, err := name.ParseReference(image, name.WeakValidation) - if err != nil { - return nil, err - } - - if ref.Context().RegistryStr() == name.DefaultRegistry { - ref, err := normalizeReference(ref, image) - if err != nil { - return nil, err - } - - for _, registryMirror := range opts.RegistryMirrors { - var newReg name.Registry - if opts.InsecurePull || opts.InsecureRegistries.Contains(registryMirror) { - newReg, err = name.NewRegistry(registryMirror, name.WeakValidation, name.Insecure) - } else { - newReg, err = name.NewRegistry(registryMirror, name.StrictValidation) - } - if err != nil { - return nil, err - } - ref := setNewRegistry(ref, newReg) - - logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror) - remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts)...) - if err != nil { - logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err) - continue - } - return remoteImage, nil - } - } - - registryName := ref.Context().RegistryStr() - if opts.InsecurePull || opts.InsecureRegistries.Contains(registryName) { - newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure) - if err != nil { - return nil, err - } - ref = setNewRegistry(ref, newReg) - } - - logrus.Infof("Retrieving image %s from registry %s", ref, registryName) - return remote.Image(ref, remoteOptions(registryName, opts)...) -} - -// normalizeReference adds the library/ prefix to images without it. -// -// It is mostly useful when using a registry mirror that is not able to perform -// this fix automatically. -func normalizeReference(ref name.Reference, image string) (name.Reference, error) { - if !strings.ContainsRune(image, '/') { - return name.ParseReference("library/"+image, name.WeakValidation) - } - - return ref, nil -} - -func setNewRegistry(ref name.Reference, newReg name.Registry) name.Reference { - switch r := ref.(type) { - case name.Tag: - r.Repository.Registry = newReg - return r - case name.Digest: - r.Repository.Registry = newReg - return r - default: - return ref - } -} - -func remoteOptions(registryName string, opts *config.KanikoOptions) []remote.Option { - tr := util.MakeTransport(opts, registryName) - - // on which v1.Platform is this currently running? - platform := currentPlatform(opts) - - return []remote.Option{remote.WithTransport(tr), remote.WithAuthFromKeychain(creds.GetKeychain()), remote.WithPlatform(platform)} -} - func cachedImage(opts *config.KanikoOptions, image string) (v1.Image, error) { ref, err := name.ParseReference(image, name.WeakValidation) if err != nil { @@ -191,7 +105,7 @@ func cachedImage(opts *config.KanikoOptions, image string) (v1.Image, error) { if d, ok := ref.(name.Digest); ok { cacheKey = d.DigestStr() } else { - image, err := remoteImage(image, opts) + image, err := remote.RetrieveRemoteImage(image, opts.RegistryOptions, opts.CustomPlatform) if err != nil { return nil, err } @@ -204,17 +118,3 @@ func cachedImage(opts *config.KanikoOptions, image string) (v1.Image, error) { } return cache.LocalSource(&opts.CacheOptions, cacheKey) } - -// CurrentPlatform returns the v1.Platform on which the code runs -func currentPlatform(opts *config.KanikoOptions) v1.Platform { - if opts.CustomPlatform != "" { - return v1.Platform{ - OS: strings.Split(opts.CustomPlatform, "/")[0], - Architecture: strings.Split(opts.CustomPlatform, "/")[1], - } - } - return v1.Platform{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - } -} diff --git a/pkg/image/image_util_test.go b/pkg/image/image_util_test.go index 2c1937e1bf..3debab63f5 100644 --- a/pkg/image/image_util_test.go +++ b/pkg/image/image_util_test.go @@ -20,7 +20,6 @@ import ( "bytes" "testing" - "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/moby/buildkit/frontend/dockerfile/instructions" @@ -53,7 +52,7 @@ func Test_StandardImage(t *testing.T) { defer func() { RetrieveRemoteImage = original }() - mock := func(image string, opts *config.KanikoOptions) (v1.Image, error) { + mock := func(image string, opts config.RegistryOptions, _ string) (v1.Image, error) { return nil, nil } RetrieveRemoteImage = mock @@ -104,31 +103,14 @@ func Test_ScratchImageFromMirror(t *testing.T) { actual, err := RetrieveSourceImage(config.KanikoStage{ Stage: stages[1], }, &config.KanikoOptions{ - RegistryMirrors: []string{"mirror.gcr.io"}, + RegistryOptions: config.RegistryOptions{ + RegistryMirrors: []string{"mirror.gcr.io"}, + }, }) expected := empty.Image testutil.CheckErrorAndDeepEqual(t, false, err, expected, actual) } -func Test_normalizeReference(t *testing.T) { - image := "debian" - expected := "index.docker.io/library/debian:latest" - - ref, err := name.ParseReference(image) - if err != nil { - t.Fatal(err) - } - - ref2, err := normalizeReference(ref, image) - if err != nil { - t.Fatal(err) - } - - if ref2.Name() != ref.Name() || ref2.Name() != expected { - t.Errorf("%s should have been normalized to %s, got %s", ref2.Name(), expected, ref.Name()) - } -} - // parse parses the contents of a Dockerfile and returns a list of commands func parse(s string) ([]instructions.Stage, error) { p, err := parser.Parse(bytes.NewReader([]byte(s))) diff --git a/pkg/image/remote/remote.go b/pkg/image/remote/remote.go new file mode 100644 index 0000000000..97adda3770 --- /dev/null +++ b/pkg/image/remote/remote.go @@ -0,0 +1,129 @@ +/* +Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package remote + +import ( + "runtime" + "strings" + + "github.com/GoogleContainerTools/kaniko/pkg/config" + "github.com/GoogleContainerTools/kaniko/pkg/creds" + "github.com/GoogleContainerTools/kaniko/pkg/util" + + "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/sirupsen/logrus" +) + +// RetrieveRemoteImage retrieves the manifest for the specified image from the specified registry +func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatform string) (v1.Image, error) { + logrus.Infof("Retrieving image manifest %s", image) + ref, err := name.ParseReference(image, name.WeakValidation) + if err != nil { + return nil, err + } + + if ref.Context().RegistryStr() == name.DefaultRegistry { + ref, err := normalizeReference(ref, image) + if err != nil { + return nil, err + } + + for _, registryMirror := range opts.RegistryMirrors { + var newReg name.Registry + if opts.InsecurePull || opts.InsecureRegistries.Contains(registryMirror) { + newReg, err = name.NewRegistry(registryMirror, name.WeakValidation, name.Insecure) + } else { + newReg, err = name.NewRegistry(registryMirror, name.StrictValidation) + } + if err != nil { + return nil, err + } + ref := setNewRegistry(ref, newReg) + + logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror) + remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts, customPlatform)...) + if err != nil { + logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err) + continue + } + return remoteImage, nil + } + } + + registryName := ref.Context().RegistryStr() + if opts.InsecurePull || opts.InsecureRegistries.Contains(registryName) { + newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure) + if err != nil { + return nil, err + } + ref = setNewRegistry(ref, newReg) + } + + logrus.Infof("Retrieving image %s from registry %s", ref, registryName) + return remote.Image(ref, remoteOptions(registryName, opts, customPlatform)...) +} + +// normalizeReference adds the library/ prefix to images without it. +// +// It is mostly useful when using a registry mirror that is not able to perform +// this fix automatically. +func normalizeReference(ref name.Reference, image string) (name.Reference, error) { + if !strings.ContainsRune(image, '/') { + return name.ParseReference("library/"+image, name.WeakValidation) + } + + return ref, nil +} + +func setNewRegistry(ref name.Reference, newReg name.Registry) name.Reference { + switch r := ref.(type) { + case name.Tag: + r.Repository.Registry = newReg + return r + case name.Digest: + r.Repository.Registry = newReg + return r + default: + return ref + } +} + +func remoteOptions(registryName string, opts config.RegistryOptions, customPlatform string) []remote.Option { + tr := util.MakeTransport(opts, registryName) + + // on which v1.Platform is this currently running? + platform := currentPlatform(customPlatform) + + return []remote.Option{remote.WithTransport(tr), remote.WithAuthFromKeychain(creds.GetKeychain()), remote.WithPlatform(platform)} +} + +// CurrentPlatform returns the v1.Platform on which the code runs +func currentPlatform(customPlatform string) v1.Platform { + if customPlatform != "" { + return v1.Platform{ + OS: strings.Split(customPlatform, "/")[0], + Architecture: strings.Split(customPlatform, "/")[1], + } + } + return v1.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + } +} diff --git a/pkg/image/remote/remote_test.go b/pkg/image/remote/remote_test.go new file mode 100644 index 0000000000..74a0fb4ac1 --- /dev/null +++ b/pkg/image/remote/remote_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2018 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package remote + +import ( + "testing" + + "github.com/google/go-containerregistry/pkg/name" +) + +func Test_normalizeReference(t *testing.T) { + image := "debian" + expected := "index.docker.io/library/debian:latest" + + ref, err := name.ParseReference(image) + if err != nil { + t.Fatal(err) + } + + ref2, err := normalizeReference(ref, image) + if err != nil { + t.Fatal(err) + } + + if ref2.Name() != ref.Name() || ref2.Name() != expected { + t.Errorf("%s should have been normalized to %s, got %s", ref2.Name(), expected, ref.Name()) + } +} diff --git a/pkg/util/transport_util.go b/pkg/util/transport_util.go index a8e3f1e3fe..61ca2aeba1 100644 --- a/pkg/util/transport_util.go +++ b/pkg/util/transport_util.go @@ -62,7 +62,7 @@ func init() { } } -func MakeTransport(opts *config.KanikoOptions, registryName string) http.RoundTripper { +func MakeTransport(opts config.RegistryOptions, registryName string) http.RoundTripper { // Create a transport to set our user-agent. var tr http.RoundTripper = http.DefaultTransport.(*http.Transport).Clone() if opts.SkipTLSVerify || opts.SkipTLSVerifyRegistries.Contains(registryName) { diff --git a/pkg/util/transport_util_test.go b/pkg/util/transport_util_test.go index b10be69951..7d7291678c 100644 --- a/pkg/util/transport_util_test.go +++ b/pkg/util/transport_util_test.go @@ -44,12 +44,12 @@ func Test_makeTransport(t *testing.T) { tests := []struct { name string - opts *config.KanikoOptions + opts config.RegistryOptions check func(*tls.Config, *mockedCertPool) }{ { name: "SkipTLSVerify set", - opts: &config.KanikoOptions{SkipTLSVerify: true}, + opts: config.RegistryOptions{SkipTLSVerify: true}, check: func(config *tls.Config, pool *mockedCertPool) { if !config.InsecureSkipVerify { t.Errorf("makeTransport().TLSClientConfig.InsecureSkipVerify not set while SkipTLSVerify set") @@ -58,7 +58,7 @@ func Test_makeTransport(t *testing.T) { }, { name: "SkipTLSVerifyRegistries set with expected registry", - opts: &config.KanikoOptions{SkipTLSVerifyRegistries: []string{registryName}}, + opts: config.RegistryOptions{SkipTLSVerifyRegistries: []string{registryName}}, check: func(config *tls.Config, pool *mockedCertPool) { if !config.InsecureSkipVerify { t.Errorf("makeTransport().TLSClientConfig.InsecureSkipVerify not set while SkipTLSVerifyRegistries set with registry name") @@ -67,7 +67,7 @@ func Test_makeTransport(t *testing.T) { }, { name: "SkipTLSVerifyRegistries set with other registry", - opts: &config.KanikoOptions{SkipTLSVerifyRegistries: []string{fmt.Sprintf("other.%s", registryName)}}, + opts: config.RegistryOptions{SkipTLSVerifyRegistries: []string{fmt.Sprintf("other.%s", registryName)}}, check: func(config *tls.Config, pool *mockedCertPool) { if config.InsecureSkipVerify { t.Errorf("makeTransport().TLSClientConfig.InsecureSkipVerify set while SkipTLSVerifyRegistries not set with registry name") @@ -76,7 +76,7 @@ func Test_makeTransport(t *testing.T) { }, { name: "RegistriesCertificates set for registry", - opts: &config.KanikoOptions{RegistriesCertificates: map[string]string{registryName: "/path/to/the/certificate.cert"}}, + opts: config.RegistryOptions{RegistriesCertificates: map[string]string{registryName: "/path/to/the/certificate.cert"}}, check: func(config *tls.Config, pool *mockedCertPool) { if len(pool.certificatesPath) != 1 || pool.certificatesPath[0] != "/path/to/the/certificate.cert" { t.Errorf("makeTransport().RegistriesCertificates certificate not appended to system certificates") @@ -85,7 +85,7 @@ func Test_makeTransport(t *testing.T) { }, { name: "RegistriesCertificates set for another registry", - opts: &config.KanikoOptions{RegistriesCertificates: map[string]string{fmt.Sprintf("other.%s=", registryName): "/path/to/the/certificate.cert"}}, + opts: config.RegistryOptions{RegistriesCertificates: map[string]string{fmt.Sprintf("other.%s=", registryName): "/path/to/the/certificate.cert"}}, check: func(config *tls.Config, pool *mockedCertPool) { if len(pool.certificatesPath) != 0 { t.Errorf("makeTransport().RegistriesCertificates certificate appended to system certificates while added for other registry")