Skip to content

Conversation

@flouthoc
Copy link
Collaborator

@flouthoc flouthoc commented Aug 9, 2025

Expand user,builtin and heading args before logging build steps.

Closes: #6327

What type of PR is this?

/kind api-change
/kind bug
/kind cleanup
/kind deprecation
/kind design
/kind documentation
/kind failing-test
/kind feature
/kind flake
/kind other

What this PR does / why we need it:

How to verify it

Which issue(s) this PR fixes:

Special notes for your reviewer:

Does this PR introduce a user-facing change?

executor: expand args in build logs

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Aug 9, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: flouthoc

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved label Aug 9, 2025
@flouthoc
Copy link
Collaborator Author

flouthoc commented Aug 9, 2025

@vrothberg @nalind PTAL

Copy link
Member

@vrothberg vrothberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes and tests LGTM.

I didn't check, but maybe there's a similar behavior with ENV?

Thanks so much for the quick PR 🙏

Copy link
Member

@nalind nalind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This produces some weird corner cases, both with this patch and with docker build:

FROM busybox
ARG CMD=pwd
RUN CMD=ls; echo $CMD

argsCopy[i], err = imagebuilder.ProcessWord(args[i].(string), userArgs)
if err != nil {
argsCopy[i] = args[i]
fmt.Printf("while replacing arg variables with values for format in logging %q: %w\n", args[i], err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this error message sent to stdout rather than one of the output destinations that's been specified for logging?

headingArgs := argsMapToSlice(stage.Builder.HeadingArgs)
userArgs := argsMapToSlice(stage.Builder.Args)
userArgs = append(builtinArgs, append(userArgs, headingArgs...)...)
argsCopy := args
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is meant to create a copy of args, use slices.Clone(), otherwise they're both still pointing at the same array.

tests/bud.bats Outdated
_EOF

run_buildah build $WITH_POLICY_JSON -t ${target}-undefined -f $contextdir/Containerfile2 $contextdir
expect_output --substring "RUN echo "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would match whether a value was printed or not.

@flouthoc
Copy link
Collaborator Author

This produces some weird corner cases, both with this patch and with docker build:

FROM busybox
ARG CMD=pwd
RUN CMD=ls; echo $CMD

Similar behavior exists in buildkit as well I think this is bug in both buildah and docker. Should we fix it here ?

Copy link
Member

@Luap99 Luap99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This produces some weird corner cases, both with this patch and with docker build:

FROM busybox
ARG CMD=pwd
RUN CMD=ls; echo $CMD

Similar behavior exists in buildkit as well I think this is bug in both buildah and docker. Should we fix it here ?

You don't event need build args to trigger this:
RUN test=1; echo ${test:-abc}

And worse if you just have an unset non existing build arg buildah just removes from the log (uses empty string) which is also super confusing,

The issue states

I would love Buildah to expand the variables as well. I couldn't wrap my head around an issue where the build succeeded but didn't provide the build artifact I expected. Trying docker build helped me debug the issue and correct the Containerfile.

Given that I would argue this change doesn't really improve the situation, it trades one problem for another. But if docker behaves like that and logs incorrectly then maybe matching its bugs makes sense? I don't personally care much either way.

But I think the correct thing is to only replace the vars that buildah actually replaced when executing the command which ties back to my comment about replacing the RUN string in to many different places.

headingArgs := argsMapToSlice(stage.Builder.HeadingArgs)
userArgs := argsMapToSlice(stage.Builder.Args)
userArgs = append(builtinArgs, append(userArgs, headingArgs...)...)
argsCopy := slices.Clone(args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this cloning the full args array if we throw away all values, that is just causing unnecessary memory copies.
To preallocate use make([]string, 0, len(args)).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

userArgs = append(builtinArgs, append(userArgs, headingArgs...)...)
argsCopy := slices.Clone(args)
for i := range args {
argsCopy[i], err = imagebuilder.ProcessWord(args[i].(string), userArgs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

casting to string seems unsafe, the function signature accepts any, This means all non strings values are discarded basically.
If the log can only deal with string the function signature should be updated to only accept strings.

In principle I am also quite confused in how many different places ProcessWord() gets called, buildah seems to call this and replace values as one off in may palaces. I think it would be much cleaner to replace and and then keep the replaced values but I don't understand enough about the buildah design here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I looking at the right function? ProcessWord() appears to take a string and a slice of strings, so type assertions shouldn't even be necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am talking about the log function itself which defines args as []any type. Everything that gets passed which is not a string is basically lost here due this type cast.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I follow now. The assertion isn't checked, so I think that case would panic. In practice, we only pass it strings.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am processing only args with string type, I avoided changing function signature as it involves making changes in public API of some structs so will do that in a new PR. Practically all args are strings so this check is just preventing from panic.

argsCopy[i], err = imagebuilder.ProcessWord(args[i].(string), userArgs)
if err != nil {
argsCopy[i] = args[i]
fmt.Fprintf(stageExecutor.executor.err, "while replacing arg variables with values for format in logging %q: %w\n", args[i], err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you cannot use %w for normal formatting, only in fmt.Errorf()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@flouthoc
Copy link
Collaborator Author

This produces some weird corner cases, both with this patch and with docker build:

FROM busybox
ARG CMD=pwd
RUN CMD=ls; echo $CMD

Similar behavior exists in buildkit as well I think this is bug in both buildah and docker. Should we fix it here ?

You don't event need build args to trigger this: RUN test=1; echo ${test:-abc}

And worse if you just have an unset non existing build arg buildah just removes from the log (uses empty string) which is also super confusing,

The issue states

I would love Buildah to expand the variables as well. I couldn't wrap my head around an issue where the build succeeded but didn't provide the build artifact I expected. Trying docker build helped me debug the issue and correct the Containerfile.

Given that I would argue this change doesn't really improve the situation, it trades one problem for another. But if docker behaves like that and logs incorrectly then maybe matching its bugs makes sense? I don't personally care much either way.

But I think the correct thing is to only replace the vars that buildah actually replaced when executing the command which ties back to my comment about replacing the RUN string in to many different places.

I am keeping behavior same as buildkit for consistency and created an upstream issue with moby/buildkit#6139 to track it there.

@flouthoc flouthoc requested a review from Luap99 August 18, 2025 20:16
Expand user,builtin and heading args before logging build steps.

Closes: containers#6327

Signed-off-by: flouthoc <[email protected]>
Signed-off-by: flouthoc <[email protected]>
Comment on lines +616 to +627
if arg, ok := args[i].(string); ok {
if containsVariable(arg) {
processedArg, err := imagebuilder.ProcessWord(arg, userArgs)
if err != nil {
processedArg = arg
fmt.Fprintf(stageExecutor.executor.err, "while replacing arg variables with values for format in logging %q: %v\n", arg, err)
}
processedArgs = append(processedArgs, processedArg)
} else {
processedArgs = append(processedArgs, arg)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That still discards any arg which is not a string, the else block must be one lever out because it needs to keep the non string args otherwise the formatting string will be messed up badly.

Comment on lines +525 to +526
re := regexp.MustCompile(`\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?`)
return re.MatchString(arg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That compiles the regex every single time which is quite inefficient. That should use something like regexp.Delayed() in the global scope.

But is the regex really right? At the very least it ignores escaping rules \$var is not a variable. ProcessWord() has already has a parser so this would need to match exactly what it is doing. I think it would be better to reuse the ProcessWord code and expose some function for this instead, yeah sure more complicated but the only sane way to avoid any differences between the var replacement code (ProcessWord) and this custom regex

@github-actions
Copy link

A friendly reminder that this PR had no activity for 30 days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

expand build arguments in log

4 participants