Allow to exclude build-args and env vars from cache key

This commit is contained in:
Nikita 2024-04-23 20:18:40 +03:00 committed by cdayz
parent 28e4e29036
commit e1369aadf7
4 changed files with 82 additions and 18 deletions

View File

@ -257,6 +257,8 @@ func addKanikoOptionsFlags() {
RootCmd.PersistentFlags().VarP(&opts.Compression, "compression", "", "Compression algorithm (gzip, zstd)") RootCmd.PersistentFlags().VarP(&opts.Compression, "compression", "", "Compression algorithm (gzip, zstd)")
RootCmd.PersistentFlags().IntVarP(&opts.CompressionLevel, "compression-level", "", -1, "Compression level") RootCmd.PersistentFlags().IntVarP(&opts.CompressionLevel, "compression-level", "", -1, "Compression level")
RootCmd.PersistentFlags().BoolVarP(&opts.Cache, "cache", "", false, "Use cache when building image") RootCmd.PersistentFlags().BoolVarP(&opts.Cache, "cache", "", false, "Use cache when building image")
RootCmd.PersistentFlags().VarP(&opts.ExcludeBuildArgsFromCacheKey, "exclude-build-arg-from-cache-key", "", "This flag allows you to exclude ARG values from the cache key. Set it repeatedly for multiple values.")
RootCmd.PersistentFlags().VarP(&opts.ExcludeEnvsFromCacheKey, "exclude-env-from-cache-key", "", "This flag allows you to exclude ENV values from the cache key. Set it repeatedly for multiple values.")
RootCmd.PersistentFlags().BoolVarP(&opts.CompressedCaching, "compressed-caching", "", true, "Compress the cached layers. Decreases build time, but increases memory usage.") RootCmd.PersistentFlags().BoolVarP(&opts.CompressedCaching, "compressed-caching", "", true, "Compress the cached layers. Decreases build time, but increases memory usage.")
RootCmd.PersistentFlags().BoolVarP(&opts.Cleanup, "cleanup", "", false, "Clean the filesystem at the end") RootCmd.PersistentFlags().BoolVarP(&opts.Cleanup, "cleanup", "", false, "Clean the filesystem at the end")
RootCmd.PersistentFlags().DurationVarP(&opts.CacheTTL, "cache-ttl", "", time.Hour*336, "Cache timeout, requires value and unit of duration -> ex: 6h. Defaults to two weeks.") RootCmd.PersistentFlags().DurationVarP(&opts.CacheTTL, "cache-ttl", "", time.Hour*336, "Cache timeout, requires value and unit of duration -> ex: 6h. Defaults to two weeks.")

View File

@ -28,6 +28,9 @@ import (
type CacheOptions struct { type CacheOptions struct {
CacheDir string CacheDir string
CacheTTL time.Duration CacheTTL time.Duration
ExcludeBuildArgsFromCacheKey multiArg
ExcludeEnvsFromCacheKey multiArg
} }
// RegistryOptions are all the options related to the registries, set by command line arguments. // RegistryOptions are all the options related to the registries, set by command line arguments.

View File

@ -199,20 +199,39 @@ func isOCILayout(path string) bool {
} }
func (s *stageBuilder) populateCompositeKey(command commands.DockerCommand, files []string, compositeKey CompositeCache, args *dockerfile.BuildArgs, env []string) (CompositeCache, error) { func (s *stageBuilder) populateCompositeKey(command commands.DockerCommand, files []string, compositeKey CompositeCache, args *dockerfile.BuildArgs, env []string) (CompositeCache, error) {
// 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
// so we can ensure a stable cache key.
sort.Strings(replacementEnvs)
// Use the special argument "|#" at the start of the args array. This will
// avoid conflicts with any RUN command since commands can not
// start with | (vertical bar). The "#" (number of build envs) is there to
// help ensure proper cache matches.
if command.IsArgsEnvsRequiredInCache() { if command.IsArgsEnvsRequiredInCache() {
if len(replacementEnvs) > 0 { filteredEnvs := []string{}
compositeKey.AddKey(fmt.Sprintf("|%d", len(replacementEnvs))) for _, e := range env {
compositeKey.AddKey(replacementEnvs...) envName := strings.SplitN(e, "=", 2)[0]
if s.opts.ExcludeEnvsFromCacheKey.Contains(envName) {
continue
}
filteredEnvs = append(filteredEnvs, e)
}
// First replace all the environment variables or args in the command
envsAndBuildArgs := args.ReplacementEnvs(filteredEnvs)
filteredEnvsAndBuildArgs := []string{}
for i, env := range envsAndBuildArgs {
envArgName := strings.SplitN(env, "=", 2)[0]
if s.opts.ExcludeBuildArgsFromCacheKey.Contains(envArgName) {
continue
}
filteredEnvsAndBuildArgs = append(filteredEnvs, envsAndBuildArgs[i])
}
// The sort order of `filteredEnvs` is basically undefined, sort it
// so we can ensure a stable cache key.
sort.Strings(filteredEnvsAndBuildArgs)
if len(filteredEnvsAndBuildArgs) > 0 {
// Use the special argument "|#" at the start of the args array. This will
// avoid conflicts with any RUN command since commands can not
// start with | (vertical bar). The "#" (number of build envs) is there to
// help ensure proper cache matches.
compositeKey.AddKey(fmt.Sprintf("|%d", len(filteredEnvsAndBuildArgs)))
compositeKey.AddKey(filteredEnvsAndBuildArgs...)
} }
} }

View File

@ -655,10 +655,12 @@ func newStageContext(command string, args map[string]string, env []string) stage
func Test_stageBuilder_populateCompositeKey(t *testing.T) { func Test_stageBuilder_populateCompositeKey(t *testing.T) {
type testcase struct { type testcase struct {
description string description string
cmd1 stageContext cmd1 stageContext
cmd2 stageContext cmd2 stageContext
shdEqual bool shdEqual bool
excludeBuildArgsFromCacheKey []string
excludeEnvsFromCacheKey []string
} }
testCases := []testcase{ testCases := []testcase{
{ {
@ -744,6 +746,21 @@ func Test_stageBuilder_populateCompositeKey(t *testing.T) {
[]string{}, []string{},
), ),
}, },
{
description: "cache key for same command [RUN] with a build arg values, with excludeBuildArgsFromCacheKey set",
cmd1: newStageContext(
"RUN echo $ARG > test",
map[string]string{"ARG": "foo"},
[]string{},
),
cmd2: newStageContext(
"RUN echo $ARG > test",
map[string]string{"ARG": "bar"},
[]string{},
),
shdEqual: true,
excludeBuildArgsFromCacheKey: []string{"ARG"},
},
{ {
description: "cache key for same command [RUN] with different env values", description: "cache key for same command [RUN] with different env values",
cmd1: newStageContext( cmd1: newStageContext(
@ -798,6 +815,21 @@ func Test_stageBuilder_populateCompositeKey(t *testing.T) {
), ),
shdEqual: false, shdEqual: false,
}, },
{
description: "cache key for command [RUN] with different env values, when excludeEnvsFromCacheKey is set",
cmd1: newStageContext(
"RUN echo ${APP_VERSION%.*} ${APP_VERSION%-*} > test",
map[string]string{"ARG": "foo"},
[]string{"ENV=1"},
),
cmd2: newStageContext(
"RUN echo ${APP_VERSION%.*} ${APP_VERSION%-*} > test",
map[string]string{"ARG": "foo"},
[]string{"ENV=2"},
),
shdEqual: true,
excludeEnvsFromCacheKey: []string{"ENV"},
},
func() testcase { func() testcase {
dir, files := tempDirAndFile(t) dir, files := tempDirAndFile(t)
file := files[0] file := files[0]
@ -867,7 +899,15 @@ func Test_stageBuilder_populateCompositeKey(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
sb := &stageBuilder{fileContext: util.FileContext{Root: "workspace"}} sb := &stageBuilder{
fileContext: util.FileContext{Root: "workspace"},
opts: &config.KanikoOptions{
CacheOptions: config.CacheOptions{
ExcludeBuildArgsFromCacheKey: tc.excludeBuildArgsFromCacheKey,
ExcludeEnvsFromCacheKey: tc.excludeEnvsFromCacheKey,
},
},
}
ck := CompositeCache{} ck := CompositeCache{}
instructions1, err := dockerfile.ParseCommands([]string{tc.cmd1.command.String()}) instructions1, err := dockerfile.ParseCommands([]string{tc.cmd1.command.String()})