diff --git a/README.md b/README.md index 33a8f58a5..3a9eb63ec 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ _If you are interested in contributing to kaniko, see - [Flag `--registry-certificate`](#flag---registry-certificate) - [Flag `--registry-client-cert`](#flag---registry-client-cert) - [Flag `--registry-mirror`](#flag---registry-mirror) + - [Flag `--skip-default-registry-fallback`](#flag---skip-default-registry-fallback) - [Flag `--reproducible`](#flag---reproducible) - [Flag `--single-snapshot`](#flag---single-snapshot) - [Flag `--skip-tls-verify`](#flag---skip-tls-verify) @@ -992,6 +993,12 @@ are: - `192.168.0.1:5000` - `mycompany-docker-virtual.jfrog.io` +#### Flag `--skip-default-registry-fallback` + +Set this flag if you want the build process to fail if none of the mirrors listed in flag [registry-mirror](#flag---registry-mirror) can pull some image. This should be used with mirrors that implements a whitelist or some image restrictions. + +If [registry-mirror](#flag---registry-mirror) is not set or is empty, this flag is ignored. + #### Flag `--reproducible` Set this flag to strip timestamps out of the built image and make it diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 1b40f6d90..774c877ba 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -238,6 +238,7 @@ func addKanikoOptionsFlags() { opts.RegistriesClientCertificates = make(map[string]string) RootCmd.PersistentFlags().VarP(&opts.RegistriesClientCertificates, "registry-client-cert", "", "Use the provided client certificate for mutual TLS (mTLS) communication with the given registry. Expected format is 'my.registry.url=/path/to/client/cert,/path/to/client/key'.") 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().BoolVarP(&opts.SkipDefaultRegistryFallback, "skip-default-registry-fallback", "", false, "If an image is not found on any mirrors (defined with registry-mirror) do not fallback to the default registry. If registry-mirror is not defined, this flag is ignored.") RootCmd.PersistentFlags().BoolVarP(&opts.IgnoreVarRun, "ignore-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image.") RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.") RootCmd.PersistentFlags().BoolVarP(&opts.SkipUnusedStages, "skip-unused-stages", "", false, "Build only used stages if defined to true. Otherwise it builds by default all stages, even the unnecessaries ones until it reaches the target stage / end of Dockerfile") diff --git a/cmd/warmer/cmd/root.go b/cmd/warmer/cmd/root.go index 699373260..6ac7f7d18 100644 --- a/cmd/warmer/cmd/root.go +++ b/cmd/warmer/cmd/root.go @@ -98,6 +98,7 @@ func addKanikoOptionsFlags() { opts.RegistriesClientCertificates = make(map[string]string) RootCmd.PersistentFlags().VarP(&opts.RegistriesClientCertificates, "registry-client-cert", "", "Use the provided client certificate for mutual TLS (mTLS) communication with the given registry. Expected format is 'my.registry.url=/path/to/client/cert,/path/to/client/key'.") 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().BoolVarP(&opts.SkipDefaultRegistryFallback, "skip-default-registry-fallback", "", false, "If an image is not found on any mirrors (defined with registry-mirror) do not fallback to the default registry. If registry-mirror is not defined, this flag is ignored.") RootCmd.PersistentFlags().StringVarP(&opts.CustomPlatform, "customPlatform", "", "", "Specify the build platform if different from the current host") RootCmd.PersistentFlags().StringVarP(&opts.DockerfilePath, "dockerfile", "d", "", "Path to the dockerfile to be cached. The kaniko warmer will parse and write out each stage's base image layers to the cache-dir. Using the same dockerfile path as what you plan to build in the kaniko executor is the expected usage.") RootCmd.PersistentFlags().VarP(&opts.BuildArgs, "build-arg", "", "This flag should be used in conjunction with the dockerfile flag for scenarios where dynamic replacement of the base image is required.") diff --git a/integration/integration_test.go b/integration/integration_test.go index b4785ea30..ab5977165 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -395,6 +395,29 @@ func TestBuildViaRegistryMirrors(t *testing.T) { checkContainerDiffOutput(t, diff, expected) } +func TestBuildSkipFallback(t *testing.T) { + repo := getGitRepo(false) + dockerfile := fmt.Sprintf("%s/%s/Dockerfile_registry_mirror", integrationPath, dockerfilesPath) + + // Build with kaniko + kanikoImage := GetKanikoImage(config.imageRepo, "Dockerfile_registry_mirror") + dockerRunFlags := []string{"run", "--net=host"} + dockerRunFlags = addServiceAccountFlags(dockerRunFlags, config.serviceAccount) + dockerRunFlags = append(dockerRunFlags, ExecutorImage, + "-f", dockerfile, + "-d", kanikoImage, + "--registry-mirror", "doesnotexist.example.com", + "--skip-default-registry-fallback", + "-c", fmt.Sprintf("git://%s", repo)) + + kanikoCmd := exec.Command("docker", dockerRunFlags...) + + _, err := RunCommandWithoutTest(kanikoCmd) + if err == nil { + t.Errorf("Build should fail after using skip-default-registry-fallback and registry-mirror fail to pull") + } +} + // TestKanikoDir tests that a build that sets --kaniko-dir produces the same output as the equivalent docker build. func TestKanikoDir(t *testing.T) { repo := getGitRepo(false) diff --git a/pkg/config/options.go b/pkg/config/options.go index 37044898c..5ad9a0e54 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -37,6 +37,7 @@ type RegistryOptions struct { SkipTLSVerifyRegistries multiArg RegistriesCertificates keyValueArg RegistriesClientCertificates keyValueArg + SkipDefaultRegistryFallback bool Insecure bool SkipTLSVerify bool InsecurePull bool diff --git a/pkg/image/remote/remote.go b/pkg/image/remote/remote.go index e0ecff97b..b47cbb2f0 100644 --- a/pkg/image/remote/remote.go +++ b/pkg/image/remote/remote.go @@ -17,6 +17,7 @@ limitations under the License. package remote import ( + "errors" "strings" "github.com/GoogleContainerTools/kaniko/pkg/config" @@ -31,7 +32,8 @@ import ( ) var ( - manifestCache = make(map[string]v1.Image) + manifestCache = make(map[string]v1.Image) + remoteImageFunc = remote.Image ) // RetrieveRemoteImage retrieves the manifest for the specified image from the specified registry @@ -68,7 +70,7 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo ref := setNewRegistry(ref, newReg) logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror) - remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts, customPlatform)...) + remoteImage, err := remoteImageFunc(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 @@ -78,6 +80,10 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo return remoteImage, nil } + + if len(opts.RegistryMirrors) > 0 && opts.SkipDefaultRegistryFallback { + return nil, errors.New("image not found on any configured mirror(s)") + } } registryName := ref.Context().RegistryStr() @@ -91,7 +97,7 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo logrus.Infof("Retrieving image %s from registry %s", ref, registryName) - remoteImage, err := remote.Image(ref, remoteOptions(registryName, opts, customPlatform)...) + remoteImage, err := remoteImageFunc(ref, remoteOptions(registryName, opts, customPlatform)...) if remoteImage != nil { manifestCache[image] = remoteImage diff --git a/pkg/image/remote/remote_test.go b/pkg/image/remote/remote_test.go index 3879f017d..0a8103c51 100644 --- a/pkg/image/remote/remote_test.go +++ b/pkg/image/remote/remote_test.go @@ -17,11 +17,13 @@ limitations under the License. package remote import ( + "errors" "testing" "github.com/GoogleContainerTools/kaniko/pkg/config" "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" ) @@ -108,3 +110,33 @@ func Test_RetrieveRemoteImage_manifestCache(t *testing.T) { t.Fatal("Expected call to succeed because there is a manifest for this image in the cache.") } } + +func Test_RetrieveRemoteImage_skipFallback(t *testing.T) { + image := "debian" + registryMirror := "some-registry" + + opts := config.RegistryOptions{ + RegistryMirrors: []string{registryMirror}, + SkipDefaultRegistryFallback: false, + } + + remoteImageFunc = func(ref name.Reference, options ...remote.Option) (v1.Image, error) { + if ref.Context().Registry.Name() == registryMirror { + return nil, errors.New("no image found") + } + + return &mockImage{}, nil + } + + if _, err := RetrieveRemoteImage(image, opts, ""); err != nil { + t.Fatal("Expected call to succeed because fallback to default registry") + } + + opts.SkipDefaultRegistryFallback = true + //clean cached image + manifestCache = make(map[string]v1.Image) + + if _, err := RetrieveRemoteImage(image, opts, ""); err == nil { + t.Fatal("Expected call to fail because fallback to default registry is skipped") + } +}