From a6c479c5c2d2738b81b456d5141135546b012322 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Mon, 29 Jun 2026 17:26:27 +0100 Subject: [PATCH 1/2] consolidate feature flags into a single registry and extract assert package --- cmd/executor/cmd/root.go | 5 +- cmd/warmer/cmd/root.go | 1 + golden/golden_test.go | 4 + pkg/{util => assert}/assert.go | 2 +- pkg/cache/cache.go | 4 +- pkg/commands/add.go | 3 +- pkg/commands/cmd.go | 4 +- pkg/commands/copy.go | 5 +- pkg/commands/env.go | 2 +- pkg/commands/expose.go | 5 +- pkg/commands/label.go | 3 +- pkg/commands/run.go | 13 +-- pkg/commands/volume.go | 5 +- pkg/commands/workdir.go | 3 +- pkg/config/featureflags.go | 147 +++++++++++++++++++++++++++++++++ pkg/dockerfile/buildargs.go | 8 +- pkg/executor/build.go | 106 ++++++++++++------------ pkg/image/image_util.go | 2 +- pkg/image/transform.go | 8 +- pkg/snapshot/layered_map.go | 9 +- pkg/snapshot/snapshot.go | 17 ++-- pkg/util/command_util.go | 5 +- pkg/util/fs_util.go | 23 +++--- pkg/util/fs_util_test.go | 3 +- pkg/util/tar_util.go | 3 +- pkg/util/transport_util.go | 2 +- pkg/warmer/lock.go | 4 +- pkg/warmer/warm.go | 6 +- 28 files changed, 282 insertions(+), 120 deletions(-) rename pkg/{util => assert}/assert.go (99%) create mode 100644 pkg/config/featureflags.go diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 46b4696e2..ac07cb398 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -123,6 +123,7 @@ var RootCmd = &cobra.Command{ if err := logging.Configure(logLevel, logFormat, logTimestamp); err != nil { return err } + config.LogFeatureFlags() ValidateFlags(opts) @@ -212,7 +213,7 @@ var RootCmd = &cobra.Command{ if err := os.Chdir("/"); err != nil { exit(fmt.Errorf("error changing to root dir: %w", err)) } - if opts.Cleanup && config.EnvBoolDefault("FF_KANIKO_CLEAN_KANIKO_DIR", true) { + if opts.Cleanup && config.FF.CleanKanikoDir { defer func() { if err := config.Cleanup(); err != nil { logrus.Warnf("error cleaning kaniko dir: %v", err) @@ -403,7 +404,7 @@ func checkNoDeprecatedFlags() { logrus.Warn("Flag --skip-unused-stages is deprecated. This is the new default behaviour. If you want to build multiple independent stages pass them as --target instead") } - if !config.EnvBool("FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE") { + if !config.FF.DeprecateInterStageRestore { if opts.PreserveContext && !opts.PreCleanup { logrus.Warn("--preserve-context without --pre-cleanup restores the original context between stages; this is deprecated and will be removed. Use --mount=type=secret for secrets. Set FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE=1 to opt into the new behaviour now.") } diff --git a/cmd/warmer/cmd/root.go b/cmd/warmer/cmd/root.go index 7fe54f53b..0882f30e7 100644 --- a/cmd/warmer/cmd/root.go +++ b/cmd/warmer/cmd/root.go @@ -58,6 +58,7 @@ var RootCmd = &cobra.Command{ if err := logging.Configure(logLevel, logFormat, logTimestamp); err != nil { return err } + config.LogFeatureFlags() // Allow setting --registry-maps using an environment variable. // some users use warmer with --regisry-mirror before v1.21.0 diff --git a/golden/golden_test.go b/golden/golden_test.go index 01dbb78a6..76a0934d1 100644 --- a/golden/golden_test.go +++ b/golden/golden_test.go @@ -121,6 +121,10 @@ func TestRun(t *testing.T) { for k, v := range test.Env { t.Setenv(k, v) } + // Feature flags resolve once at startup; re-resolve so + // this subtest's env takes effect, and restore afterwards. + config.InitFeatureFlags() + t.Cleanup(config.InitFeatureFlags) opts := config.KanikoOptions{} origNewLayerCache := executor.NewLayerCache diff --git a/pkg/util/assert.go b/pkg/assert/assert.go similarity index 99% rename from pkg/util/assert.go rename to pkg/assert/assert.go index 655e2216d..ba766a53f 100644 --- a/pkg/util/assert.go +++ b/pkg/assert/assert.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package assert import ( "os" diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 5030ae726..9e9b98f55 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -196,7 +196,7 @@ func LocalSource(opts *config.CacheOptions, cacheKey string) (v1.Image, error) { } logrus.Infof("Found %s in local cache", cacheKey) - if config.EnvBoolDefault("FF_KANIKO_OCI_WARMER", true) { + if config.FF.OCIWarmer { return ociCachedImageFromPath(path) } else { return cachedImageFromPath(path) @@ -218,7 +218,7 @@ func (c *cachedImage) Digest() (v1.Hash, error) { } func (c *cachedImage) Manifest() (*v1.Manifest, error) { - if config.EnvBool("FF_KANIKO_IGNORE_CACHED_MANIFEST") || c.mfst == nil { + if config.FF.IgnoreCachedManifest || c.mfst == nil { return c.Image.Manifest() } return c.mfst, nil diff --git a/pkg/commands/add.go b/pkg/commands/add.go index bbe88a6c8..329372a24 100644 --- a/pkg/commands/add.go +++ b/pkg/commands/add.go @@ -22,6 +22,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" kConfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" @@ -246,7 +247,7 @@ func addCmdFilesUsedFromContext(config *v1.Config, buildArgs *dockerfile.BuildAr } // Remote URLs and tar archives are filtered out, so the result cannot exceed the source count. - util.Assert("add.files-count", len(files) <= len(srcs), "addCmdFilesUsedFromContext: result exceeds source count (srcs=%d, files=%d)", len(srcs), len(files)) + assert.Assert("add.files-count", len(files) <= len(srcs), "addCmdFilesUsedFromContext: result exceeds source count (srcs=%d, files=%d)", len(srcs), len(files)) logrus.Infof("Using files from context: %v", files) return files, nil } diff --git a/pkg/commands/cmd.go b/pkg/commands/cmd.go index 9961ef1b6..5fb654a54 100644 --- a/pkg/commands/cmd.go +++ b/pkg/commands/cmd.go @@ -21,8 +21,8 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/dockerfile" - "github.com/osscontainertools/kaniko/pkg/util" ) type CmdCommand struct { @@ -44,7 +44,7 @@ func (c *CmdCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui } newCommand = append(shell, strings.Join(c.cmd.CmdLine, " ")) - util.Assert("cmd.command-nonempty", len(newCommand) > 0, "CMD shell form produced an empty command") + assert.Assert("cmd.command-nonempty", len(newCommand) > 0, "CMD shell form produced an empty command") } else { newCommand = c.cmd.CmdLine } diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index 6f871e49f..85df84c03 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -25,6 +25,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" kConfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" @@ -58,7 +59,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu } } else { user := config.User - if kConfig.EnvBool("FF_KANIKO_COPY_AS_ROOT") { + if kConfig.FF.CopyAsRoot { // According to spec: https://docs.docker.com/reference/dockerfile/#copy---chown---chmod // All files and directories copied from the build context // are created with a default UID and GID of 0. @@ -343,7 +344,7 @@ func copyCmdFilesUsedFromContext( files = append(files, fullPath) } - util.Assert("copy.files-count", len(files) <= len(srcs), "copyCmdFilesUsedFromContext: result cannot exceed source count (srcs=%d, files=%d)", len(srcs), len(files)) + assert.Assert("copy.files-count", len(files) <= len(srcs), "copyCmdFilesUsedFromContext: result cannot exceed source count (srcs=%d, files=%d)", len(srcs), len(files)) logrus.Debugf("Using files from context: %v", files) return files, nil diff --git a/pkg/commands/env.go b/pkg/commands/env.go index b3b14564b..2721ec5a9 100644 --- a/pkg/commands/env.go +++ b/pkg/commands/env.go @@ -37,7 +37,7 @@ func (e *EnvCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui return err } // 3344: An ENV declared after an ARG of the same name overrides it. - if kConfig.EnvBoolDefault("FF_KANIKO_BUILDKIT_ARG_ENV_PRECEDENCE", true) { + if kConfig.FF.BuildkitArgEnvPrecedence { for _, keyVal := range newEnvs { buildArgs.RemoveArg(keyVal.Key) } diff --git a/pkg/commands/expose.go b/pkg/commands/expose.go index f91fc61cf..581462497 100644 --- a/pkg/commands/expose.go +++ b/pkg/commands/expose.go @@ -22,6 +22,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" "github.com/sirupsen/logrus" @@ -53,7 +54,7 @@ func (r *ExposeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile. p = p + "/tcp" } parts := strings.Split(p, "/") - util.Assert("expose.parts-count", len(parts) >= 2, "port string must contain '/' after normalization") + assert.Assert("expose.parts-count", len(parts) >= 2, "port string must contain '/' after normalization") protocol := parts[1] if !validProtocol(protocol) { return fmt.Errorf("invalid protocol: %s", protocol) @@ -62,7 +63,7 @@ func (r *ExposeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile. existingPorts[p] = struct{}{} } config.ExposedPorts = existingPorts - util.Assert("expose.port-count-monotone", len(config.ExposedPorts) >= prevPortCount, "EXPOSE must not remove ports: count went from %d to %d", prevPortCount, len(config.ExposedPorts)) + assert.Assert("expose.port-count-monotone", len(config.ExposedPorts) >= prevPortCount, "EXPOSE must not remove ports: count went from %d to %d", prevPortCount, len(config.ExposedPorts)) return nil } diff --git a/pkg/commands/label.go b/pkg/commands/label.go index 40f276175..56156958d 100644 --- a/pkg/commands/label.go +++ b/pkg/commands/label.go @@ -19,6 +19,7 @@ package commands import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" "github.com/sirupsen/logrus" @@ -61,7 +62,7 @@ func updateLabels(labels []instructions.KeyValuePair, config *v1.Config, buildAr } config.Labels = existingLabels - util.Assert("label.count-monotone", len(config.Labels) >= prevLabelCount, "LABEL must not remove labels: count went from %d to %d", prevLabelCount, len(config.Labels)) + assert.Assert("label.count-monotone", len(config.Labels) >= prevLabelCount, "LABEL must not remove labels: count went from %d to %d", prevLabelCount, len(config.Labels)) return nil } diff --git a/pkg/commands/run.go b/pkg/commands/run.go index bc2638e3e..94abadbb0 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -29,6 +29,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" kConfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/constants" "github.com/osscontainertools/kaniko/pkg/dockerfile" @@ -59,7 +60,7 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui } func runCommandWithFlags(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun *instructions.RunCommand, fileContext util.FileContext, secrets kConfig.SecretOptions) (reterr error) { - ff_bind := kConfig.EnvBoolDefault("FF_KANIKO_RUN_MOUNT_BIND", true) + ff_bind := kConfig.FF.RunMountBind for _, f := range cmdRun.FlagsUsed { if f != "mount" { logrus.Warnf("#969 kaniko does not support '--%s' flags in RUN statements - relying on unsupported flags can lead to invalid builds", f) @@ -320,7 +321,7 @@ func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun continue } // Replacement envs must be in KEY=VALUE form. - util.Assert("run.path-separator", len(entry) == 2, "replacement env matching \"PATH\" has no '=' separator") + assert.Assert("run.path-separator", len(entry) == 2, "replacement env matching \"PATH\" has no '=' separator") oldPath := os.Getenv("PATH") err := os.Setenv("PATH", entry[1]) if err != nil { @@ -334,11 +335,11 @@ func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun } } - util.Assert("run.command-nonempty", len(newCommand) > 0, "runCommandInExec: newCommand is empty") + assert.Assert("run.command-nonempty", len(newCommand) > 0, "runCommandInExec: newCommand is empty") logrus.Infof("Cmd: %s", newCommand[0]) logrus.Infof("Args: %s", newCommand[1:]) - if kConfig.EnvBool("FF_KANIKO_RUN_VIA_TINI") { + if kConfig.FF.RunViaTini { newCommand = append([]string{kConfig.TiniExec, "-s", "--"}, newCommand...) } @@ -376,7 +377,7 @@ func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun if err := cmd.Start(); err != nil { return fmt.Errorf("starting command: %w", err) } - util.Assert("run.process-set", cmd.Process != nil, "cmd.Process must be set after a successful Start()") + assert.Assert("run.process-set", cmd.Process != nil, "cmd.Process must be set after a successful Start()") pgid, err := syscall.Getpgid(cmd.Process.Pid) if err != nil { @@ -529,7 +530,7 @@ func runCmdFilesUsedFromContext( config *v1.Config, buildArgs *dockerfile.BuildArgs, cmd *instructions.RunCommand, fileContext util.FileContext, ) ([]string, error) { - ff_bind := kConfig.EnvBoolDefault("FF_KANIKO_RUN_MOUNT_BIND", true) + ff_bind := kConfig.FF.RunMountBind if !ff_bind || len(cmd.FlagsUsed) == 0 { return []string{}, nil } diff --git a/pkg/commands/volume.go b/pkg/commands/volume.go index 4726f6dea..4d06b7d4a 100644 --- a/pkg/commands/volume.go +++ b/pkg/commands/volume.go @@ -22,6 +22,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" kconfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" @@ -51,7 +52,7 @@ func (v *VolumeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile. existingVolumes[volume] = x util.AddVolumePathToIgnoreList(volume) - if !kconfig.EnvBoolDefault("FF_KANIKO_VOLUME_SKIP_MKDIR", true) { + if !kconfig.FF.VolumeSkipMkdir { // Only create and snapshot the dir if it didn't exist already if _, err := os.Stat(volume); os.IsNotExist(err) { logrus.Infof("Creating directory %s", volume) @@ -62,7 +63,7 @@ func (v *VolumeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile. } } config.Volumes = existingVolumes - util.Assert("volume.count-monotone", len(config.Volumes) >= prevVolumeCount, "VOLUME must not remove volumes: count went from %d to %d", prevVolumeCount, len(config.Volumes)) + assert.Assert("volume.count-monotone", len(config.Volumes) >= prevVolumeCount, "VOLUME must not remove volumes: count went from %d to %d", prevVolumeCount, len(config.Volumes)) return nil } diff --git a/pkg/commands/workdir.go b/pkg/commands/workdir.go index a78593169..2ab82b2b3 100644 --- a/pkg/commands/workdir.go +++ b/pkg/commands/workdir.go @@ -23,6 +23,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" kConfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" @@ -46,7 +47,7 @@ func ToAbsPath(path string, workdir string) string { } result = filepath.Join(workdir, path) } - util.Assert("workdir.abs-path", filepath.IsAbs(result), "ToAbsPath must return an absolute path, got %q (path=%q, workdir=%q)", result, path, workdir) + assert.Assert("workdir.abs-path", filepath.IsAbs(result), "ToAbsPath must return an absolute path, got %q (path=%q, workdir=%q)", result, path, workdir) return result } diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go new file mode 100644 index 000000000..4dd5ff666 --- /dev/null +++ b/pkg/config/featureflags.go @@ -0,0 +1,147 @@ +/* +Copyright 2026 OSS Container Tools + +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 config + +import ( + "os" + "reflect" + "slices" + "strings" + + "github.com/osscontainertools/kaniko/pkg/assert" + "github.com/sirupsen/logrus" +) + +type FeatureFlags struct { + BuildkitArgEnvPrecedence bool + CacheLookahead bool + CacheProbeAfterMiss bool + CleanKanikoDir bool + CopyAsRoot bool + CopyChmodOnImplicitDirs bool + DeprecateInterStageRestore bool + DisableHTTP2 bool + IgnoreCachedManifest bool + InferCrossStageCacheKey bool + NoPropagateAnnotations bool + OCIScratchBase bool + OCIWarmer bool + PreserveHardlinks bool + PreserveMountedPaths bool + ReproduciblePreserveBaseLayers bool + ResolveCacheKey bool + RunMountBind bool + RunViaTini bool + ScopedDockerignore bool + SecurejoinExtraction bool + SkipRelabelRecompress bool + SkipWriteWhiteouts bool + UntarSkipRoot bool + VolumeSkipMkdir bool + WarmerCacheLock bool +} + +var FF FeatureFlags + +var ( + knownFeatureFlags []string + activeFeatureFlags []string + redundantFeatureFlags []string + disabledFeatureFlags []string +) + +func featureFlag(name string, def bool) bool { + value := EnvBoolDefault(name, def) + _, explicit := os.LookupEnv(name) + knownFeatureFlags = append(knownFeatureFlags, name) + if value { + activeFeatureFlags = append(activeFeatureFlags, name) + } + if def && explicit { + if value { + redundantFeatureFlags = append(redundantFeatureFlags, name) + } else { + disabledFeatureFlags = append(disabledFeatureFlags, name) + } + } + return value +} + +func InitFeatureFlags() { + knownFeatureFlags = nil + activeFeatureFlags = nil + redundantFeatureFlags = nil + disabledFeatureFlags = nil + + FF = FeatureFlags{ + BuildkitArgEnvPrecedence: featureFlag("FF_KANIKO_BUILDKIT_ARG_ENV_PRECEDENCE", true), + CacheLookahead: featureFlag("FF_KANIKO_CACHE_LOOKAHEAD", false), + CacheProbeAfterMiss: featureFlag("FF_KANIKO_CACHE_PROBE_AFTER_MISS", false), + CleanKanikoDir: featureFlag("FF_KANIKO_CLEAN_KANIKO_DIR", true), + CopyAsRoot: featureFlag("FF_KANIKO_COPY_AS_ROOT", false), + CopyChmodOnImplicitDirs: featureFlag("FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS", false), + DeprecateInterStageRestore: featureFlag("FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE", true), + DisableHTTP2: featureFlag("FF_KANIKO_DISABLE_HTTP2", false), + IgnoreCachedManifest: featureFlag("FF_KANIKO_IGNORE_CACHED_MANIFEST", false), + InferCrossStageCacheKey: featureFlag("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY", false), + NoPropagateAnnotations: featureFlag("FF_KANIKO_NO_PROPAGATE_ANNOTATIONS", true), + OCIScratchBase: featureFlag("FF_KANIKO_OCI_SCRATCH_BASE", false), + OCIWarmer: featureFlag("FF_KANIKO_OCI_WARMER", true), + PreserveHardlinks: featureFlag("FF_KANIKO_PRESERVE_HARDLINKS", true), + PreserveMountedPaths: featureFlag("FF_KANIKO_PRESERVE_MOUNTED_PATHS", true), + ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", false), + ResolveCacheKey: featureFlag("FF_KANIKO_RESOLVE_CACHE_KEY", false), + RunMountBind: featureFlag("FF_KANIKO_RUN_MOUNT_BIND", true), + RunViaTini: featureFlag("FF_KANIKO_RUN_VIA_TINI", false), + ScopedDockerignore: featureFlag("FF_KANIKO_SCOPED_DOCKERIGNORE", false), + SecurejoinExtraction: featureFlag("FF_KANIKO_SECUREJOIN_EXTRACTION", true), + SkipRelabelRecompress: featureFlag("FF_KANIKO_SKIP_RELABEL_RECOMPRESS", false), + SkipWriteWhiteouts: featureFlag("FF_KANIKO_SKIP_WRITE_WHITEOUTS", false), + UntarSkipRoot: featureFlag("FF_KANIKO_UNTAR_SKIP_ROOT", false), + VolumeSkipMkdir: featureFlag("FF_KANIKO_VOLUME_SKIP_MKDIR", true), + WarmerCacheLock: featureFlag("FF_KANIKO_WARMER_CACHE_LOCK", true), + } + + fields := reflect.TypeFor[FeatureFlags]().NumField() + assert.Assert("config.featureflags.complete", fields == len(knownFeatureFlags), "FeatureFlags has %d fields but %d are initialized; every field must be set via featureFlag", fields, len(knownFeatureFlags)) +} + +func init() { + InitFeatureFlags() +} + +func LogFeatureFlags() { + if len(activeFeatureFlags) > 0 { + logrus.Infof("active feature flags: %s", strings.Join(activeFeatureFlags, ", ")) + } + if len(redundantFeatureFlags) > 0 { + logrus.Infof("feature flags enabled by default, setting them explicitly is no longer necessary: %s", strings.Join(redundantFeatureFlags, ", ")) + } + if len(disabledFeatureFlags) > 0 { + logrus.Warnf("feature flags explicitly disabled, please create an issue for your use-case: %s", strings.Join(disabledFeatureFlags, ", ")) + } + var unknown []string + for _, kv := range os.Environ() { + name, _, _ := strings.Cut(kv, "=") + if strings.HasPrefix(name, "FF_KANIKO_") && !slices.Contains(knownFeatureFlags, name) { + unknown = append(unknown, name) + } + } + if len(unknown) > 0 { + logrus.Warnf("unknown feature flags set but not recognised by this version of kaniko: %s", strings.Join(unknown, ", ")) + } +} diff --git a/pkg/dockerfile/buildargs.go b/pkg/dockerfile/buildargs.go index b73c7f8f4..5973bebdf 100644 --- a/pkg/dockerfile/buildargs.go +++ b/pkg/dockerfile/buildargs.go @@ -23,8 +23,8 @@ import ( "github.com/containerd/platforms" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/config" - "github.com/osscontainertools/kaniko/pkg/util" ) // builtinAllowedBuildArgs is list of built-in allowed build args @@ -93,7 +93,7 @@ func (b *BuildArgs) ReplacementEnvs(envs []string) []string { nenvs := len(merged) args := b.GetAllAllowed() for key, val := range args { - if config.EnvBoolDefault("FF_KANIKO_BUILDKIT_ARG_ENV_PRECEDENCE", true) { + if config.FF.BuildkitArgEnvPrecedence { // 3344: args always override envs merged[key] = &val } else if _, exists := merged[key]; !exists { @@ -110,9 +110,9 @@ func (b *BuildArgs) ReplacementEnvs(envs []string) []string { } } // Args can add keys but must not remove existing env keys. - util.Assert("buildargs.replacement-envs.min-size", nenvs <= len(result), "ReplacementEnvs: result (%d) is smaller than de-duplicated envs (%d)", len(result), nenvs) + assert.Assert("buildargs.replacement-envs.min-size", nenvs <= len(result), "ReplacementEnvs: result (%d) is smaller than de-duplicated envs (%d)", len(result), nenvs) // Result is bounded by the union of unique env keys and arg keys. - util.Assert("buildargs.replacement-envs.max-size", len(result) <= nenvs+len(args), "ReplacementEnvs: result (%d) exceeds de-duplicated envs (%d) + args (%d)", len(result), nenvs, len(args)) + assert.Assert("buildargs.replacement-envs.max-size", len(result) <= nenvs+len(args), "ReplacementEnvs: result (%d) exceeds de-duplicated envs (%d) + args (%d)", len(result), nenvs, len(args)) return result } diff --git a/pkg/executor/build.go b/pkg/executor/build.go index e428535b4..8c1dd6d0d 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -38,6 +38,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/cache" "github.com/osscontainertools/kaniko/pkg/commands" "github.com/osscontainertools/kaniko/pkg/config" @@ -184,7 +185,7 @@ func initConfig(img partial.WithConfigFile, opts *config.KanikoOptions) (*v1.Con } } - util.Assert("executor.initconfig.env-nonnull", imageConfig.Config.Env != nil, "initConfig: Env must be non-nil on return") + assert.Assert("executor.initconfig.env-nonnull", imageConfig.Config.Env != nil, "initConfig: Env must be non-nil on return") return imageConfig, nil } @@ -218,8 +219,8 @@ func crossStageCacheKey(command commands.DockerCommand, stageFinalCacheKeys map[ } func populateCompositeKey(command commands.DockerCommand, files []string, compositeKey CompositeCache, args *dockerfile.BuildArgs, env []string, fileContext util.FileContext, stageFinalCacheKeys map[int]string, externalImageDigests map[string]string) (CompositeCache, error) { - util.Assert("executor.compositekey.mutual-exclusion", files == nil || stageFinalCacheKeys == nil, "populateCompositeKey: files and stageFinalCacheKeys are mutually exclusive") - util.Assert("executor.compositekey.command-nonnull", command != nil, "populateCompositeKey called with nil command") + assert.Assert("executor.compositekey.mutual-exclusion", files == nil || stageFinalCacheKeys == nil, "populateCompositeKey: files and stageFinalCacheKeys are mutually exclusive") + assert.Assert("executor.compositekey.command-nonnull", command != nil, "populateCompositeKey called with nil command") // First replace all the environment variables or args in the command replacementEnvs := args.ReplacementEnvs(env) // The sort order of `replacementEnvs` is basically undefined, sort it @@ -240,7 +241,7 @@ func populateCompositeKey(command commands.DockerCommand, files []string, compos // Add the next command to the cache key. keyString := command.String() resolver, ok := command.(commands.CacheKeyResolver) - if ok && config.EnvBool("FF_KANIKO_RESOLVE_CACHE_KEY") { + if ok && config.FF.ResolveCacheKey { resolved, err := resolver.CacheKey(replacementEnvs) if err != nil { return compositeKey, fmt.Errorf("resolving cache key: %w", err) @@ -266,7 +267,7 @@ func populateCompositeKey(command commands.DockerCommand, files []string, compos return compositeKey, nil } - util.Unreachable("populateCompositeKey: both files and stageFinalCacheKeys are nil") + assert.Unreachable("populateCompositeKey: both files and stageFinalCacheKeys are nil") return compositeKey, nil } @@ -291,7 +292,7 @@ func redirectCacheKey(inferredKey CompositeCache, layerCache cache.LayerCache) ( func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, args *dockerfile.BuildArgs, opts *config.KanikoOptions, fileContext util.FileContext, layerCache cache.LayerCache, stageFinalCacheKeys map[int]string, externalImageDigests map[string]string, hasContext bool) (string, *stageCacheInfo, v1.Config, error) { keyValid := compositeKeyPtr != nil if hasContext { - util.Assert("executor.optimize.keyValid", keyValid, "optimize: key must be valid") + assert.Assert("executor.optimize.keyValid", keyValid, "optimize: key must be valid") } ci := &stageCacheInfo{ redirectKeys: make([]string, len(s.cmds)), @@ -305,14 +306,6 @@ func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, } stopCache := false - // FF_KANIKO_CACHE_PROBE_AFTER_MISS: when set, a regular cache miss no - // longer disables lookups for the remaining layers in the stage. Cached - // tar diffs apply cleanly on top of locally-rebuilt prior layers under - // the same determinism assumption the cache scheme already requires. - // The other stopCache=true site (COPY --from in the precompute pass) is - // untouched — that one signals "key cannot be computed without the file - // context", not a transient miss. - probeAfterMiss := config.EnvBool("FF_KANIKO_CACHE_PROBE_AFTER_MISS") sawCacheMiss := false finalCacheKey := "" if keyValid { @@ -336,7 +329,7 @@ func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, copyCmd, isCopy := commands.CastAbstractCopyCommand(command) if !hasContext && isCopy && copyCmd.From() != "" { inferred := false - if config.EnvBool("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY") && opts.CacheCopyLayers { + if config.FF.InferCrossStageCacheKey && opts.CacheCopyLayers { inferredKey, err := populateCompositeKey(command, nil, compositeKey, args, cfg.Env, fileContext, stageFinalCacheKeys, externalImageDigests) if err == nil { inferredCK, err := inferredKey.Hash() @@ -374,7 +367,7 @@ func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, } // mz334: assert the inferred key pointer resolves to the same content key. - if config.EnvBool("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY") && opts.CacheCopyLayers { + if config.FF.InferCrossStageCacheKey && opts.CacheCopyLayers { inferredKey, err := populateCompositeKey(command, nil, prevCompositeKey, args, cfg.Env, fileContext, stageFinalCacheKeys, externalImageDigests) if err == nil { inferredCK, err := inferredKey.Hash() @@ -395,7 +388,7 @@ func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, if err != nil { return "", ci, v1.Config{}, err } - util.Assert("executor.compositekey.key-match", ick == ck, "pointer inferred content key %v does not match the computed content key %v", ick, ck) + assert.Assert("executor.compositekey.key-match", ick == ck, "pointer inferred content key %v does not match the computed content key %v", ick, ck) // mz334: log when the inferred key produced the hit (integration test observability only). logrus.Infof("Cache hit via inferred cross-stage key for cmd: %s", command.String()) ci.redirectHits[i] = true @@ -421,7 +414,14 @@ func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, logrus.Infof("No cached layer found for cmd %s", command.String()) logrus.Debugf("Key missing was: %s", compositeKey.Key()) sawCacheMiss = true - if !probeAfterMiss { + // FF_KANIKO_CACHE_PROBE_AFTER_MISS: when set, a regular cache miss no + // longer disables lookups for the remaining layers in the stage. Cached + // tar diffs apply cleanly on top of locally-rebuilt prior layers under + // the same determinism assumption the cache scheme already requires. + // The other stopCache=true site (COPY --from in the precompute pass) is + // untouched — that one signals "key cannot be computed without the file + // context", not a transient miss. + if !config.FF.CacheProbeAfterMiss { stopCache = true } continue @@ -447,18 +447,18 @@ func (s *stageBuilder) optimize(compositeKeyPtr *CompositeCache, cfg v1.Config, } // Optimize only swaps commands for cached versions. - util.Assert("executor.optimize.command-count", len(s.cmds) == cmdCountBeforeOptimize, "optimize: command count must not change during optimization (before=%d, after=%d)", cmdCountBeforeOptimize, len(s.cmds)) + assert.Assert("executor.optimize.command-count", len(s.cmds) == cmdCountBeforeOptimize, "optimize: command count must not change during optimization (before=%d, after=%d)", cmdCountBeforeOptimize, len(s.cmds)) if hasContext { - util.Assert("executor.optimize.keyValid", keyValid, "optimize: key must be valid") + assert.Assert("executor.optimize.keyValid", keyValid, "optimize: key must be valid") } if hasContext || keyValid { - util.Assert("executor.optimize.finalcachekey", finalCacheKey != "", "optimize: finalCacheKey can't be empty") + assert.Assert("executor.optimize.finalcachekey", finalCacheKey != "", "optimize: finalCacheKey can't be empty") } return finalCacheKey, ci, cfg, nil } func (s *stageBuilder) build(compositeKey CompositeCache, opts *config.KanikoOptions, fileContext util.FileContext, snapshotter snapShotter, crossStageDeps bool, stageFinalCacheKeys map[int]string, externalImageDigests map[string]string) error { - util.Assert("executor.stagebuilder.config-nonnull", s.cf != nil, "stageBuilder (index %d) has nil config file", s.index) + assert.Assert("executor.stagebuilder.config-nonnull", s.cf != nil, "stageBuilder (index %d) has nil config file", s.index) // Unpack file system to root if we need to. shouldUnpack := false for _, cmd := range s.cmds { @@ -494,7 +494,7 @@ func (s *stageBuilder) build(compositeKey CompositeCache, opts *config.KanikoOpt } timing.DefaultRun.Stop(t) - util.Assert("executor.getfs.volumes-reset", len(util.Volumes()) == 0, "stageBuilder.build: getFSFromImage must reset volumes for stage %d", s.index) + assert.Assert("executor.getfs.volumes-reset", len(util.Volumes()) == 0, "stageBuilder.build: getFSFromImage must reset volumes for stage %d", s.index) } else { logrus.Info("Skipping unpacking as no commands require it.") } @@ -531,7 +531,7 @@ func (s *stageBuilder) build(compositeKey CompositeCache, opts *config.KanikoOpt return err } // mz334: also compute the inferred key so we can push a pointer below. - if config.EnvBool("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY") && opts.CacheCopyLayers { + if config.FF.InferCrossStageCacheKey && opts.CacheCopyLayers { inferredKey, err := populateCompositeKey(command, nil, prevCompositeKey, s.args, s.cf.Config.Env, fileContext, stageFinalCacheKeys, externalImageDigests) if err == nil { inferredCacheKey, err = inferredKey.Hash() @@ -597,13 +597,13 @@ func (s *stageBuilder) build(compositeKey CompositeCache, opts *config.KanikoOpt if !unpacked { // Caching commands go through the isCacheCommand branch above // So the only case where we don't need a filesystem is if all commands are MetadataOnly. - util.Assert("executor.build.metadata-only", command.MetadataOnly(), "build: non-MetadataOnly command %q ran without unpacked filesystem in stage %d", command.String(), s.index) + assert.Assert("executor.build.metadata-only", command.MetadataOnly(), "build: non-MetadataOnly command %q ran without unpacked filesystem in stage %d", command.String(), s.index) } _, isVolume := command.(*commands.VolumeCommand) - volumeCreatesFiles := isVolume && !config.EnvBool("FF_KANIKO_VOLUME_SKIP_MKDIR") + volumeCreatesFiles := isVolume && !config.FF.VolumeSkipMkdir if command.MetadataOnly() && !opts.SingleSnapshot && !volumeCreatesFiles { // MetadataOnly commands must not change or even need the filesystem. - util.Assert("executor.build.without-fs", snapshotted == 0, "build: MetadataOnly command %q snapshotted %d file(s)", command.String(), snapshotted) + assert.Assert("executor.build.without-fs", snapshotted == 0, "build: MetadataOnly command %q snapshotted %d file(s)", command.String(), snapshotted) } if opts.Cache { @@ -629,7 +629,7 @@ func (s *stageBuilder) build(compositeKey CompositeCache, opts *config.KanikoOpt if err != nil { return err } - util.Assert("executor.build.key-hash", h == ck, "rawCompositeKey hash %v does not match ck %v", h, ck) + assert.Assert("executor.build.key-hash", h == ck, "rawCompositeKey hash %v does not match ck %v", h, ck) cacheGroup.Go(func() error { return pushPointer(opts, inferredCacheKey, rawKey) }) @@ -659,7 +659,7 @@ func takeSnapshot(files []string, shdDelete bool, opts *config.KanikoOptions, sn if files == nil || opts.SingleSnapshot { snapshot, snapshotted, err = snapshotter.TakeSnapshotFS() } else { - if !config.EnvBool("FF_KANIKO_VOLUME_SKIP_MKDIR") { + if !config.FF.VolumeSkipMkdir { // Volumes are very weird. They get snapshotted in the next command. files = append(files, util.Volumes()...) } @@ -816,7 +816,7 @@ func convertLayerMediaType(layer v1.Layer, image v1.Image, opts *config.KanikoOp if targetMediaType != "" { srcCompression := layerCompression(layerMediaType) dstCompression := layerCompression(targetMediaType) - if config.EnvBool("FF_KANIKO_SKIP_RELABEL_RECOMPRESS") && srcCompression != "" && srcCompression == dstCompression { + if config.FF.SkipRelabelRecompress && srcCompression != "" && srcCompression == dstCompression { relabeled, err := tarball.LayerFromOpener(layer.Compressed, layerOpts...) if err != nil { return nil, err @@ -829,7 +829,7 @@ func convertLayerMediaType(layer v1.Layer, image v1.Image, opts *config.KanikoOp if err != nil { return nil, err } - util.Assert("executor.convertlayer.relabel-digest", rd == ld, "relabel changed layer digest %s != %s", rd, ld) + assert.Assert("executor.convertlayer.relabel-digest", rd == ld, "relabel changed layer digest %s != %s", rd, ld) return relabeled, nil } return tarball.LayerFromOpener(layer.Uncompressed, layerOpts...) @@ -844,7 +844,7 @@ func convertLayerMediaType(layer v1.Layer, image v1.Image, opts *config.KanikoOp } func saveLayerToImage(image v1.Image, layer v1.Layer, createdBy string, opts *config.KanikoOptions) (v1.Image, error) { - util.Assert("executor.savelayer.layer-nonnull", layer != nil, "saveLayerToImage called with nil layer") + assert.Assert("executor.savelayer.layer-nonnull", layer != nil, "saveLayerToImage called with nil layer") layer, err := convertLayerMediaType(layer, image, opts) if err != nil { return nil, err @@ -887,7 +887,7 @@ func CalculateDependencies(stages []config.KanikoStage, opts *config.KanikoOptio var err error if s.BaseImageStoredLocally { image = images[s.BaseImageIndex] - util.Assert("executor.build.local-stage-built", image != nil, "stage %d references local stage %d which has not been built yet", s.Index, s.BaseImageIndex) + assert.Assert("executor.build.local-stage-built", image != nil, "stage %d references local stage %d which has not been built yet", s.Index, s.BaseImageIndex) } else if s.Name == constants.NoBaseImage { image = image_util.EmptyBaseImage } else { @@ -974,7 +974,7 @@ func RenderStages(stages []config.KanikoStage, cacheInfo []*stageCacheInfo, opts printf("UNPACK %s\n", s.BaseName) } for jdx, c := range s.Commands { - if opts.Cache && opts.CacheCopyLayers && config.EnvBool("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY") && config.EnvBool("FF_KANIKO_CACHE_LOOKAHEAD") { + if opts.Cache && opts.CacheCopyLayers && config.FF.InferCrossStageCacheKey && config.FF.CacheLookahead { if copyCmd, ok := c.(*instructions.CopyCommand); ok && copyCmd.From != "" { ci := cacheInfo[s.Index] if ck := ci.redirectKeys[jdx]; ck != "" { @@ -986,7 +986,7 @@ func RenderStages(stages []config.KanikoStage, cacheInfo []*stageCacheInfo, opts } } } - if opts.Cache && config.EnvBool("FF_KANIKO_CACHE_LOOKAHEAD") { + if opts.Cache && config.FF.CacheLookahead { ci := cacheInfo[s.Index] if ck := ci.cacheKeys[jdx]; ck != "" { if ci.cacheHits[jdx] { @@ -1022,13 +1022,13 @@ func RenderStages(stages []config.KanikoStage, cacheInfo []*stageCacheInfo, opts printf("SAVE FILES %v %s%d\n", filesToSave, config.KanikoInterStageDepsDir, s.Index) } printf("CLEAN\n\n") - if !config.EnvBoolDefault("FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE", true) { + if !config.FF.DeprecateInterStageRestore { if opts.PreserveContext && !opts.PreCleanup { printf("RESTORE CONTEXT\n\n") } } } - util.Unreachable("we should always have a final stage") + assert.Unreachable("we should always have a final stage") return retErr } @@ -1058,7 +1058,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { } logrus.Infof("Built cross stage deps: %v", crossStageDependencies) - util.Assert("executor.build.stages-nonempty", len(kanikoStages) > 0, "no stages to build") + assert.Assert("executor.build.stages-nonempty", len(kanikoStages) > 0, "no stages to build") // Some stages may refer to other random images, not previous stages externalImageDigests, extraStageImages, err := resolveExtraStageDigests(kanikoStages, opts) @@ -1067,7 +1067,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { } lastStage := kanikoStages[len(kanikoStages)-1] - util.Assert("executor.build.last-stage-final", lastStage.Final, "last stage (index %d, name %q) must be the final stage", lastStage.Index, lastStage.Name) + assert.Assert("executor.build.last-stage-final", lastStage.Final, "last stage (index %d, name %q) must be the final stage", lastStage.Index, lastStage.Name) baseArgs := dockerfile.NewBuildArgs(opts.BuildArgs) err = baseArgs.InitPredefinedArgs(opts.CustomPlatform, lastStage.Name) if err != nil { @@ -1076,7 +1076,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { stageArgs := make([]*dockerfile.BuildArgs, lastStage.Index+1) cacheInfo := make([]*stageCacheInfo, lastStage.Index+1) - if opts.Cache && config.EnvBool("FF_KANIKO_CACHE_LOOKAHEAD") { + if opts.Cache && config.FF.CacheLookahead { images := make([]v1.Image, lastStage.Index+1) stageConfigs := make([]v1.Config, lastStage.Index+1) for _, stage := range kanikoStages { @@ -1089,14 +1089,14 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { return nil, fmt.Errorf("precompute: failed to get baseImage: %w", err) } } - if config.EnvBoolDefault("FF_KANIKO_NO_PROPAGATE_ANNOTATIONS", true) { + if config.FF.NoPropagateAnnotations { baseImage = image_util.WithoutAnnotations(baseImage) } args := baseArgs if stage.BaseImageStoredLocally { args = stageArgs[stage.BaseImageIndex] } - util.Assert("executor.build.stage-order", args != nil, "stages must be processed in order: base stage %d not yet in stageArgs", stage.BaseImageIndex) + assert.Assert("executor.build.stage-order", args != nil, "stages must be processed in order: base stage %d not yet in stageArgs", stage.BaseImageIndex) sb, err := newStageBuilder(baseImage, args, opts, stage, fileContext) if err != nil { @@ -1195,7 +1195,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if err != nil { return nil, fmt.Errorf("failed to get baseImage: %w", err) } - if config.EnvBoolDefault("FF_KANIKO_NO_PROPAGATE_ANNOTATIONS", true) { + if config.FF.NoPropagateAnnotations { baseImage = image_util.WithoutAnnotations(baseImage) } @@ -1203,7 +1203,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if stage.BaseImageStoredLocally { args = stageArgs[stage.BaseImageIndex] } - util.Assert("executor.build.stage-order", args != nil, "stages must be processed in order: base stage %d not yet in stageArgs", stage.BaseImageIndex) + assert.Assert("executor.build.stage-order", args != nil, "stages must be processed in order: base stage %d not yet in stageArgs", stage.BaseImageIndex) // args is a pointer but is cloned inside newStageBuilder, so sharing it is safe. sb, err := newStageBuilder( baseImage, args, opts, stage, @@ -1232,17 +1232,17 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { return nil, fmt.Errorf("failed to optimize instructions: %w", err) } if opts.Cache && precomputedKey != "" { - util.Assert("executor.build.cache-lookahead", precomputedKey == finalCacheKey, "precomputed finalCacheKey %q != built finalCacheKey %q for stage %d", precomputedKey, finalCacheKey, stage.Index) + assert.Assert("executor.build.cache-lookahead", precomputedKey == finalCacheKey, "precomputed finalCacheKey %q != built finalCacheKey %q for stage %d", precomputedKey, finalCacheKey, stage.Index) } if opts.Cache && cacheInfo[stage.Index] != nil { precompute := cacheInfo[stage.Index] - util.Assert("executor.build.cache-lookahead.length", len(precompute.cacheKeys) == len(buildCi.cacheKeys), "stage %d: precompute cacheKeys length %d != build %d", stage.Index, len(precompute.cacheKeys), len(buildCi.cacheKeys)) + assert.Assert("executor.build.cache-lookahead.length", len(precompute.cacheKeys) == len(buildCi.cacheKeys), "stage %d: precompute cacheKeys length %d != build %d", stage.Index, len(precompute.cacheKeys), len(buildCi.cacheKeys)) for i := range precompute.cacheKeys { if precompute.cacheKeys[i] != "" { - util.Assert("executor.build.cache-lookahead.cache-key", precompute.cacheKeys[i] == buildCi.cacheKeys[i], "stage %d cmd %d: precompute cacheKey %q != build %q", stage.Index, i, precompute.cacheKeys[i], buildCi.cacheKeys[i]) + assert.Assert("executor.build.cache-lookahead.cache-key", precompute.cacheKeys[i] == buildCi.cacheKeys[i], "stage %d cmd %d: precompute cacheKey %q != build %q", stage.Index, i, precompute.cacheKeys[i], buildCi.cacheKeys[i]) } if precompute.redirectKeys[i] != "" { - util.Assert("executor.build.cache-lookahead.redirect-key", precompute.redirectKeys[i] == buildCi.redirectKeys[i], "stage %d cmd %d: precompute redirectKey %q != build %q", stage.Index, i, precompute.redirectKeys[i], buildCi.redirectKeys[i]) + assert.Assert("executor.build.cache-lookahead.redirect-key", precompute.redirectKeys[i] == buildCi.redirectKeys[i], "stage %d cmd %d: precompute redirectKey %q != build %q", stage.Index, i, precompute.redirectKeys[i], buildCi.redirectKeys[i]) } } } @@ -1296,7 +1296,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if err != nil { return nil, err } - if config.EnvBool("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS") { + if config.FF.ReproduciblePreserveBaseLayers { sourceImage, err = image_util.ReplaceBase(sourceImage, baseImage) if err != nil { return nil, err @@ -1311,7 +1311,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if stage.Final { timing.DefaultRun.Stop(t) // Final stage must be last, so by definition after Push stage. - util.Assert("executor.build.push-image-nonnull", pushImage != nil, "pushImage is nil") + assert.Assert("executor.build.push-image-nonnull", pushImage != nil, "pushImage is nil") return pushImage, nil } if stage.SaveStage { @@ -1341,7 +1341,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if err := util.DeleteFilesystem(); err != nil { return nil, fmt.Errorf("deleting file system after stage %d: %w", stage.Index, err) } - if !config.EnvBoolDefault("FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE", true) { + if !config.FF.DeprecateInterStageRestore { if opts.PreserveContext && !opts.PreCleanup { if tarball == "" { return nil, errors.New("context snapshot is missing") @@ -1355,7 +1355,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { } } - util.Unreachable("we should always have a final stage") + assert.Unreachable("we should always have a final stage") return nil, nil } @@ -1435,7 +1435,7 @@ func deduplicatePaths(paths []string) []string { traverse(root, "") // Deduplication can only compress. - util.Assert("executor.dedup.size", len(deduped) <= len(paths), "deduplicatePaths: result must not exceed input size (got %d from %d)", len(deduped), len(paths)) + assert.Assert("executor.dedup.size", len(deduped) <= len(paths), "deduplicatePaths: result must not exceed input size (got %d from %d)", len(deduped), len(paths)) return deduped } diff --git a/pkg/image/image_util.go b/pkg/image/image_util.go index e0175806c..6cf66f3bb 100644 --- a/pkg/image/image_util.go +++ b/pkg/image/image_util.go @@ -44,7 +44,7 @@ var ( retrieveOciImage = ociImage EmptyBaseImage v1.Image = func() v1.Image { image := empty.Image - if config.EnvBool("FF_KANIKO_OCI_SCRATCH_BASE") { + if config.FF.OCIScratchBase { image = mutate.ConfigMediaType( mutate.MediaType(image, types.OCIManifestSchema1), types.OCIConfigJSON, diff --git a/pkg/image/transform.go b/pkg/image/transform.go index ea1724696..2d932aa75 100644 --- a/pkg/image/transform.go +++ b/pkg/image/transform.go @@ -23,7 +23,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/partial" - "github.com/osscontainertools/kaniko/pkg/util" + "github.com/osscontainertools/kaniko/pkg/assert" ) // ReplaceBase returns img with its first len(base.Layers()) layers and first @@ -53,16 +53,16 @@ func ReplaceBase(img, base v1.Image) (v1.Image, error) { } // img must have at least as many layers as base; otherwise base can't be a prefix of img. - util.Assert("image.replacebase.base-layer-fit", len(baseLayers) <= len(imgLayers), + assert.Assert("image.replacebase.base-layer-fit", len(baseLayers) <= len(imgLayers), "base has %d layers, img has %d", len(baseLayers), len(imgLayers)) // Same invariant in history terms — base's history must fit within img's prefix. - util.Assert("image.replacebase.base-history-fit", len(baseCfg.History) <= len(imgCfg.History), + assert.Assert("image.replacebase.base-history-fit", len(baseCfg.History) <= len(imgCfg.History), "base has %d history entries, img has %d", len(baseCfg.History), len(imgCfg.History)) // EmptyLayer flags must agree across the base prefix; if they diverge, the splice would // either attach a layer to an EmptyLayer history entry or leave a non-empty entry without // a layer, producing a corrupt manifest. for i := range baseCfg.History { - util.Assert("image.replacebase.base-history-alignment", + assert.Assert("image.replacebase.base-history-alignment", imgCfg.History[i].EmptyLayer == baseCfg.History[i].EmptyLayer, "history[%d] EmptyLayer differs: img=%v base=%v", i, imgCfg.History[i].EmptyLayer, baseCfg.History[i].EmptyLayer) diff --git a/pkg/snapshot/layered_map.go b/pkg/snapshot/layered_map.go index 25cf947f0..edbf6d565 100644 --- a/pkg/snapshot/layered_map.go +++ b/pkg/snapshot/layered_map.go @@ -22,6 +22,7 @@ import ( "fmt" "maps" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/timing" "github.com/osscontainertools/kaniko/pkg/util" ) @@ -63,7 +64,7 @@ func (l *LayeredMap) Key() (string, error) { var adds map[string]string var deletes map[string]struct{} - util.Assert("layeredmap.slices-sync", len(l.adds) == len(l.deletes), "LayeredMap adds/deletes slices are out of sync (adds=%d, deletes=%d)", len(l.adds), len(l.deletes)) + assert.Assert("layeredmap.slices-sync", len(l.adds) == len(l.deletes), "LayeredMap adds/deletes slices are out of sync (adds=%d, deletes=%d)", len(l.adds), len(l.deletes)) if len(l.adds) != 0 { adds = l.adds[len(l.adds)-1] deletes = l.deletes[len(l.deletes)-1] @@ -96,7 +97,7 @@ func (l *LayeredMap) getCurrentImage() map[string]string { maps.Copy(current, l.currentImage) // Add the last layer on top. - util.Assert("layeredmap.slices-sync", len(l.adds) == len(l.deletes), "LayeredMap adds/deletes slices are out of sync (adds=%d, deletes=%d)", len(l.adds), len(l.deletes)) + assert.Assert("layeredmap.slices-sync", len(l.adds) == len(l.deletes), "LayeredMap adds/deletes slices are out of sync (adds=%d, deletes=%d)", len(l.adds), len(l.deletes)) addedFiles := l.adds[len(l.adds)-1] deletedFiles := l.deletes[len(l.deletes)-1] @@ -141,7 +142,7 @@ func (l *LayeredMap) GetCurrentPaths() map[string]struct{} { // AddDelete will delete the specific files in the current layer. func (l *LayeredMap) AddDelete(s string) error { // A layer must exist before files can be deleted. - util.Assert("layeredmap.delete-before-snapshot", len(l.deletes) > 0, "LayeredMap.AddDelete called before Snapshot()") + assert.Assert("layeredmap.delete-before-snapshot", len(l.deletes) > 0, "LayeredMap.AddDelete called before Snapshot()") l.isCurrentImageValid = false l.deletes[len(l.deletes)-1][s] = struct{}{} @@ -151,7 +152,7 @@ func (l *LayeredMap) AddDelete(s string) error { // Add will add the specified file s to the current layer. func (l *LayeredMap) Add(s string) error { // A layer must exist before files can be added. - util.Assert("layeredmap.add-before-snapshot", len(l.adds) > 0, "LayeredMap.Add called before Snapshot()") + assert.Assert("layeredmap.add-before-snapshot", len(l.adds) > 0, "LayeredMap.Add called before Snapshot()") l.isCurrentImageValid = false // Use hash function and add to layers diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index 64317bede..406afafdb 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -24,6 +24,7 @@ import ( "sort" "syscall" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/filesystem" "github.com/osscontainertools/kaniko/pkg/timing" @@ -49,8 +50,8 @@ func NewSnapshotter(l *LayeredMap, d string) *Snapshotter { // Init initializes a new snapshotter func (s *Snapshotter) Init() error { - util.Assert("snapshot.init.layeredmap-set", s.l != nil, "Snapshotter.Init: layered map must be set") - util.Assert("snapshot.init.directory-set", s.directory != "", "Snapshotter.Init: directory must be non-empty") + assert.Assert("snapshot.init.layeredmap-set", s.l != nil, "Snapshotter.Init: layered map must be set") + assert.Assert("snapshot.init.directory-set", s.directory != "", "Snapshotter.Init: directory must be non-empty") logrus.Info("Initializing snapshotter ...") _, _, err := s.scanFullFilesystem() return err @@ -64,8 +65,8 @@ func (s *Snapshotter) Key() (string, error) { // TakeSnapshot takes a snapshot of the specified files, avoiding directories in the ignorelist, and creates // a tarball of the changed files. Returns the tarball path and the number of files snapshotted. func (s *Snapshotter) TakeSnapshot(files []string, shdCheckDelete bool) (string, int, error) { - util.Assert("snapshot.takesnapshot.layeredmap-set", s.l != nil, "Snapshotter.TakeSnapshot: layered map must be set") - util.Assert("snapshot.takesnapshot.directory-set", s.directory != "", "Snapshotter.TakeSnapshot: directory must be non-empty") + assert.Assert("snapshot.takesnapshot.layeredmap-set", s.l != nil, "Snapshotter.TakeSnapshot: layered map must be set") + assert.Assert("snapshot.takesnapshot.directory-set", s.directory != "", "Snapshotter.TakeSnapshot: directory must be non-empty") err := os.MkdirAll(config.KanikoLayersDir, 0o755) if err != nil { return "", 0, err @@ -128,8 +129,8 @@ func (s *Snapshotter) TakeSnapshot(files []string, shdCheckDelete bool) (string, // TakeSnapshotFS takes a snapshot of the filesystem, avoiding directories in the ignorelist, and creates // a tarball of the changed files. Returns the tarball path and the number of files snapshotted. func (s *Snapshotter) TakeSnapshotFS() (string, int, error) { - util.Assert("snapshot.takesnapshotfs.layeredmap-set", s.l != nil, "Snapshotter.TakeSnapshotFS: layered map must be set") - util.Assert("snapshot.takesnapshotfs.directory-set", s.directory != "", "Snapshotter.TakeSnapshotFS: directory must be non-empty") + assert.Assert("snapshot.takesnapshotfs.layeredmap-set", s.l != nil, "Snapshotter.TakeSnapshotFS: layered map must be set") + assert.Assert("snapshot.takesnapshotfs.directory-set", s.directory != "", "Snapshotter.TakeSnapshotFS: directory must be non-empty") err := os.MkdirAll(config.KanikoLayersDir, 0o755) if err != nil { return "", 0, err @@ -212,7 +213,7 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) { filesToAdd = append(filesToAdd, path) } // Ignore-list filtering can only reduce the file set. - util.Assert("snapshot.scan.files-count", len(filesToAdd) <= len(resolvedFiles), "scanFullFilesystem: filesToAdd (%d) exceeds resolvedFiles (%d)", len(resolvedFiles), len(filesToAdd)) + assert.Assert("snapshot.scan.files-count", len(filesToAdd) <= len(resolvedFiles), "scanFullFilesystem: filesToAdd (%d) exceeds resolvedFiles (%d)", len(resolvedFiles), len(filesToAdd)) logrus.Debugf("Adding to layer: %v", filesToAdd) logrus.Debugf("Deleting in layer: %v", deletedPaths) @@ -250,7 +251,7 @@ func removeObsoleteWhiteouts(deletedFiles map[string]struct{}) (filesToWhiteout } // Whiteouts are a subset of deleted files filtered by parent presence. - util.Assert("snapshot.whiteouts.count", len(filesToWhiteout) <= len(deletedFiles), "removeObsoleteWhiteouts: result (%d) exceeds input (%d)", len(filesToWhiteout), len(deletedFiles)) + assert.Assert("snapshot.whiteouts.count", len(filesToWhiteout) <= len(deletedFiles), "removeObsoleteWhiteouts: result (%d) exceeds input (%d)", len(filesToWhiteout), len(deletedFiles)) return filesToWhiteout } diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 84157ff27..08ae86e7e 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -30,6 +30,7 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/moby/buildkit/frontend/dockerfile/shell" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/config" "github.com/sirupsen/logrus" mode "github.com/tonistiigi/dchapes-mode" @@ -55,7 +56,7 @@ func ResolveEnvironmentReplacementList(values, envs []string, isFilepath bool) ( } resolvedValues = append(resolvedValues, resolved) } - Assert("util.resolve-list.length", len(resolvedValues) == len(values), "ResolveEnvironmentReplacementList: output length %d must equal input length %d; each value must resolve to exactly one result", len(resolvedValues), len(values)) + assert.Assert("util.resolve-list.length", len(resolvedValues) == len(values), "ResolveEnvironmentReplacementList: output length %d must equal input length %d; each value must resolve to exactly one result", len(resolvedValues), len(values)) return resolvedValues, nil } @@ -107,7 +108,7 @@ func ResolveEnvAndWildcards(sd instructions.SourcesAndDest, fileContext FileCont if err != nil { return nil, "", fmt.Errorf("failed to resolve environment for dest path: %w", err) } - Assert("util.resolve-dest.single", len(dests) == 1, "ResolveEnvironmentReplacementList returned %d results for a single dest path input", len(dests)) + assert.Assert("util.resolve-dest.single", len(dests) == 1, "ResolveEnvironmentReplacementList returned %d results for a single dest path input", len(dests)) dest := dests[0] sd.DestPath = dest // Resolve wildcards and get a list of resolved sources diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 24a5d0446..166681f71 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -88,8 +88,6 @@ var ignorelist = append([]IgnoreListEntry{}, defaultIgnoreList...) var volumes = []string{} -var secureExtraction = config.EnvBoolDefault("FF_KANIKO_SECUREJOIN_EXTRACTION", true) - type FileContext struct { Root string ExcludedFiles []string @@ -210,7 +208,7 @@ func extractLayer(i int, l v1.Layer, root string, cfg *FSConfig) ([]string, erro if strings.HasPrefix(base, archive.WhiteoutPrefix) { dir := filepath.Dir(path) - if secureExtraction { + if config.FF.SecurejoinExtraction { securePath, err := securejoin.SecureJoin(root, cleanedName) if err != nil { return nil, fmt.Errorf("resolving whiteout path for %q: %w", hdr.Name, err) @@ -236,7 +234,7 @@ func extractLayer(i int, l v1.Layer, root string, cfg *FSConfig) ([]string, erro } // mz774: whiteout deletion is already applied, the marker is not needed in the filesystem - if !cfg.includeWhiteout || config.EnvBool("FF_KANIKO_SKIP_WRITE_WHITEOUTS") { + if !cfg.includeWhiteout || config.FF.SkipWriteWhiteouts { logrus.Trace("Not including whiteout files") continue } @@ -302,7 +300,7 @@ func childDirInIgnoreList(path string) bool { } func removeAllSkipIgnored(path string) (skip bool, err error) { - if !config.EnvBoolDefault("FF_KANIKO_PRESERVE_MOUNTED_PATHS", true) || !childDirInIgnoreList(path) { + if !config.FF.PreserveMountedPaths || !childDirInIgnoreList(path) { return false, os.RemoveAll(path) } logrus.Debugf("Not removing %s, as it contains an ignored path", path) @@ -347,7 +345,7 @@ func UnTar(r io.Reader, dest string) ([]string, error) { return nil, err } cleanedName := filepath.Clean(hdr.Name) - if cleanedName == "." && config.EnvBool("FF_KANIKO_UNTAR_SKIP_ROOT") { + if cleanedName == "." && config.FF.UntarSkipRoot { continue } if err := ExtractFile(dest, hdr, cleanedName, tr); err != nil { @@ -364,7 +362,7 @@ func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) } var path string - if secureExtraction { + if config.FF.SecurejoinExtraction { // cg330: SecureJoin the parent only, then append the basename lexically. Joining // the full name would resolve the final component when it is a symlink we // mean to overwrite, writing through it and creating loops like @@ -450,7 +448,7 @@ func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) } case tar.TypeDir: logrus.Tracef("Creating dir %s", path) - if secureExtraction { + if config.FF.SecurejoinExtraction { fi, lerr := os.Lstat(path) if lerr == nil && fi.Mode()&os.ModeSymlink != 0 { if err := os.Remove(path); err != nil { @@ -497,7 +495,7 @@ func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) return fmt.Errorf("hardlink target %q is not allowed: references parent directory", hdr.Linkname) } var link string - if secureExtraction { + if config.FF.SecurejoinExtraction { resolved, err := securejoin.SecureJoin(dest, hdr.Linkname) if err != nil { return fmt.Errorf("invalid hardlink target %q: %w", hdr.Linkname, err) @@ -775,7 +773,6 @@ func CopyDir(src, dest string, context FileContext, uid, gid int64, chmod mode.S var copiedFiles []string var updates []timestampUpdate hardlinksSeen := make(map[uint64]string) - preserveHardlinks := config.EnvBoolDefault("FF_KANIKO_PRESERVE_HARDLINKS", true) for _, file := range files { fullPath := filepath.Join(src, file) if context.ExcludesFile(fullPath) { @@ -796,7 +793,7 @@ func CopyDir(src, dest string, context FileContext, uid, gid int64, chmod mode.S logrus.Tracef("Creating directory %s", destPath) perm := os.FileMode(0o755) - if !useDefaultChmod && config.EnvBool("FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS") { + if !useDefaultChmod && config.FF.CopyChmodOnImplicitDirs { // mz507: Here we want to implement chmod on implicit dirs to be // compatible with buildkit. buildkit's mkdir does not whiteout the umask. // As ours does we have to apply the umask here beforehand. @@ -829,7 +826,7 @@ func CopyDir(src, dest string, context FileContext, uid, gid int64, chmod mode.S if _, err := CopySymlink(fullPath, destPath, context); err != nil { return nil, err } - } else if linkDst, ok := checkCopyHardlink(fi, destPath, hardlinksSeen); ok && preserveHardlinks { + } else if linkDst, ok := checkCopyHardlink(fi, destPath, hardlinksSeen); ok && config.FF.PreserveHardlinks { // #2594: inode already copied — create a hardlink instead of duplicating content. logrus.Tracef("Creating hardlink %s -> %s", destPath, linkDst) if err := os.Link(linkDst, destPath); err != nil { @@ -1033,7 +1030,7 @@ func (c FileContext) ExcludesFile(path string) bool { logrus.Errorf("Unable to get relative path, including %s in build: %v", path, err) return false } - } else if filepath.IsAbs(path) && config.EnvBool("FF_KANIKO_SCOPED_DOCKERIGNORE") { + } else if filepath.IsAbs(path) && config.FF.ScopedDockerignore { return false } match, err := patternmatcher.Matches(path, c.ExcludedFiles) diff --git a/pkg/util/fs_util_test.go b/pkg/util/fs_util_test.go index 08ed5c255..65a27da8c 100644 --- a/pkg/util/fs_util_test.go +++ b/pkg/util/fs_util_test.go @@ -1363,7 +1363,8 @@ func Test_correctDockerignoreFileIsUsed(t *testing.T) { } func Test_ExcludesFile_AbsoluteOutsideBuildContext(t *testing.T) { - t.Setenv("FF_KANIKO_SCOPED_DOCKERIGNORE", "1") + config.FF.ScopedDockerignore = true + t.Cleanup(func() { config.FF.ScopedDockerignore = false }) tempDir := t.TempDir() // dockerignore, allowlist style diff --git a/pkg/util/tar_util.go b/pkg/util/tar_util.go index b80c458e4..832706896 100644 --- a/pkg/util/tar_util.go +++ b/pkg/util/tar_util.go @@ -30,6 +30,7 @@ import ( "github.com/moby/go-archive" "github.com/moby/go-archive/compression" + "github.com/osscontainertools/kaniko/pkg/assert" "github.com/osscontainertools/kaniko/pkg/config" "github.com/sirupsen/logrus" ) @@ -81,7 +82,7 @@ func (t *Tar) AddFileToTar(p string) error { return err } - Assert("tar.root-path-excluded", p != config.RootDir, "snapshot must not include root path '/'") + assert.Assert("tar.root-path-excluded", p != config.RootDir, "snapshot must not include root path '/'") // Docker uses no leading / in the tarball hdr.Name = strings.TrimPrefix(p, config.RootDir) diff --git a/pkg/util/transport_util.go b/pkg/util/transport_util.go index 84acf3562..22f495e44 100644 --- a/pkg/util/transport_util.go +++ b/pkg/util/transport_util.go @@ -105,7 +105,7 @@ func MakeTransport(opts config.RegistryOptions, registryName string) (http.Round tr.(*http.Transport).TLSClientConfig.Certificates = []tls.Certificate{cert} } - if config.EnvBool("FF_KANIKO_DISABLE_HTTP2") { + if config.FF.DisableHTTP2 { tr.(*http.Transport).ForceAttemptHTTP2 = false tr.(*http.Transport).TLSClientConfig.NextProtos = []string{"http/1.1"} } diff --git a/pkg/warmer/lock.go b/pkg/warmer/lock.go index 2fe459baa..ee05a93c4 100644 --- a/pkg/warmer/lock.go +++ b/pkg/warmer/lock.go @@ -21,7 +21,7 @@ import ( "os" "path/filepath" - "github.com/osscontainertools/kaniko/pkg/util" + "github.com/osscontainertools/kaniko/pkg/assert" "golang.org/x/sys/unix" ) @@ -64,7 +64,7 @@ func acquireCacheLock(cacheDir, key string) (*cacheLock, error) { // Release unlocks the flock and closes the underlying fd. It must be called // exactly once per successful acquireCacheLock. func (l *cacheLock) Release() { - util.Assert("warmer.cache-lock.single-release", !l.released, "Release() must not be called twice") + assert.Assert("warmer.cache-lock.single-release", !l.released, "Release() must not be called twice") l.released = true _ = unix.Flock(int(l.f.Fd()), unix.LOCK_UN) _ = l.f.Close() diff --git a/pkg/warmer/warm.go b/pkg/warmer/warm.go index 243b8738b..5db44c537 100644 --- a/pkg/warmer/warm.go +++ b/pkg/warmer/warm.go @@ -59,7 +59,7 @@ func WarmCache(opts *config.WarmerOptions) error { logrus.Debugf("%s\n", images) errs := 0 - if config.EnvBoolDefault("FF_KANIKO_OCI_WARMER", true) { + if config.FF.OCIWarmer { for _, img := range images { err := ociWarmToFile(cacheDir, img, opts) if err != nil { @@ -121,7 +121,7 @@ func warmToFile(cacheDir, img string, opts *config.WarmerOptions) error { finalCachePath := path.Join(cacheDir, digest.String()) finalMfstPath := finalCachePath + ".json" - if config.EnvBoolDefault("FF_KANIKO_WARMER_CACHE_LOCK", true) { + if config.FF.WarmerCacheLock { lock, err := acquireCacheLock(cacheDir, digest.String()) if err != nil { return fmt.Errorf("failed to acquire cache lock: %w", err) @@ -182,7 +182,7 @@ func ociWarmToFile(cacheDir, img string, opts *config.WarmerOptions) error { finalCachePath := path.Join(cacheDir, digest.String()) - if config.EnvBoolDefault("FF_KANIKO_WARMER_CACHE_LOCK", true) { + if config.FF.WarmerCacheLock { lock, err := acquireCacheLock(cacheDir, digest.String()) if err != nil { return fmt.Errorf("failed to acquire cache lock: %w", err) From 87ec0e281137dd681e44d8570c02073f1c2091a4 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Tue, 30 Jun 2026 23:21:50 +0100 Subject: [PATCH 2/2] register expected feature-flag warning for mz793 integration test --- integration/images.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/images.go b/integration/images.go index d562468ee..93f639b77 100644 --- a/integration/images.go +++ b/integration/images.go @@ -347,6 +347,8 @@ var warmerOutputChecks = map[string]func(string, []byte) error{ var expectedWarnings = map[string]string{ // mz640: COPY to /kaniko (ignored path) must warn rather than silently skip. "Dockerfile_test_issue_mz560": "Skipping copy targeting kaniko directory", + // mz793: the test disables FF_KANIKO_VOLUME_SKIP_MKDIR, which the flag registry warns about. + "Dockerfile_test_issue_mz793": "feature flags explicitly disabled, please create an issue for your use-case: FF_KANIKO_VOLUME_SKIP_MKDIR", } func checkNoWarnings(dockerfile string, out []byte) error {