Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ expect - see [Known Issues](#known-issues).
- [Flag `FF_KANIKO_SECUREJOIN_EXTRACTION`](#flag-ff_kaniko_securejoin_extraction)
- [Flag `FF_KANIKO_RESOLVE_CACHE_KEY`](#flag-ff_kaniko_resolve_cache_key)
- [Flag `FF_KANIKO_UNTAR_SKIP_ROOT`](#flag-ff_kaniko_untar_skip_root)
- [Flag `FF_KANIKO_EXPAND_HEREDOC`](#flag-ff_kaniko_expand_heredoc)
- [Assertion Overrides](#assertion-overrides)
- [Debug Image](#debug-image)
- [Security](#security)
Expand Down Expand Up @@ -1305,6 +1306,13 @@ Set this flag to `true` to skip the root `.` entry when untarring.
Defaults to `false`.
Becomes default in `v1.29.0`.

#### Flag `FF_KANIKO_EXPAND_HEREDOC`

Docker applies Dockerfile word-expansion to a `COPY` or `ADD` heredoc body when the delimiter is unquoted, so `${VAR}` expands and `\${VAR}` keeps the literal text. A quoted delimiter (`<<'EOF'`) leaves the body verbatim. kaniko writes the body verbatim in every case, so the expanded files diverge from Docker.
Set this flag to `true` to expand build args and env in unquoted `COPY` and `ADD` heredoc bodies.
Defaults to `false`.
Becomes default in `v1.29.0`.

### Assertion Overrides

Kaniko checks internal invariants at runtime. If one is violated the build stops with a message like:
Expand Down
19 changes: 19 additions & 0 deletions integration/dockerfiles/Dockerfile_test_issue_mz817
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# mz817: on v1.27.6 COPY heredoc does not expand build variables.
# Docker applies Dockerfile word-expansion to a COPY heredoc body when the
# delimiter is unquoted: ${VAR} expands and \${VAR} keeps the literal text with
# the backslash stripped. A quoted delimiter (<<'EOF') leaves the body verbatim.
# kaniko writes the body verbatim in every case, so the expanded files diverge.
FROM scratch

ENV DO_EXPAND=expanded

# unquoted delimiter expands ${DO_EXPAND}, keeps \${DO_EXPAND} literal
COPY <<EOF /tmp/expand.txt
value=${DO_EXPAND}
escaped=\${DO_EXPAND}
EOF

# quoted delimiter leaves the body untouched
COPY <<'EOF' /tmp/literal.txt
value=${DO_EXPAND}
EOF
1 change: 1 addition & 0 deletions integration/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ var KanikoEnv = []string{
"FF_KANIKO_RESOLVE_CACHE_KEY=1",
"FF_KANIKO_UNTAR_SKIP_ROOT=1",
"FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS=1",
"FF_KANIKO_EXPAND_HEREDOC=1",
"KANIKO_PRINT_PLAN=1",
}

Expand Down
11 changes: 10 additions & 1 deletion pkg/commands/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,16 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
return nil
}

srcFile := strings.NewReader(src.Data)
data := src.Data
if src.Expand && kConfig.EnvBool("FF_KANIKO_EXPAND_HEREDOC") {
expanded, err := util.ResolveEnvironmentReplacementRaw(src.Data, replacementEnvs)
if err != nil {
return fmt.Errorf("expanding heredoc content: %w", err)
}
data = expanded
}

srcFile := strings.NewReader(data)
err = util.CreateFile(destPath, srcFile, chmod.Apply(0o644), uint32(uid), uint32(gid))
if err != nil {
return fmt.Errorf("creating file: %w", err)
Expand Down
10 changes: 10 additions & 0 deletions pkg/util/command_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ func ResolveEnvironmentReplacement(value string, envs []string, isFilepath bool)
return fp, nil
}

// ResolveEnvironmentReplacementRaw resolves env variables in raw content such as a
// heredoc body. Unlike ResolveEnvironmentReplacement it leaves quotes intact
// (SkipProcessQuotes), matching how docker expands COPY/ADD heredoc contents.
func ResolveEnvironmentReplacementRaw(value string, envs []string) (string, error) {
shlex := shell.NewLex(parser.DefaultEscapeToken)
shlex.SkipProcessQuotes = true
fp, _, err := shlex.ProcessWord(value, shell.EnvsFromSlice(envs))
return fp, err
}

func ResolveEnvAndWildcards(sd instructions.SourcesAndDest, fileContext FileContext, envs []string) ([]string, string, error) {
// First, resolve any environment replacement
resolvedEnvs, err := ResolveEnvironmentReplacementList(sd.SourcePaths, envs, true)
Expand Down
Loading