Merge pull request #3242 from rrjjvv/new-bakefile-env-var

Allow bake files to be specified via environment variable
This commit is contained in:
Tõnis Tiigi 2025-06-25 08:54:04 -07:00 committed by GitHub
commit 0bed0b5653
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 219 additions and 5 deletions

View File

@ -40,6 +40,11 @@ import (
"go.opentelemetry.io/otel/attribute"
)
const (
bakeEnvFileSeparator = "BUILDX_BAKE_PATH_SEPARATOR"
bakeEnvFilePath = "BUILDX_BAKE_FILE"
)
type bakeOptions struct {
files []string
overrides []string
@ -62,7 +67,7 @@ type bakeOptions struct {
listVars bool
}
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags, filesFromEnv bool) (err error) {
mp := dockerCli.MeterProvider()
ctx, end, err := tracing.TraceCurrentCommand(ctx, append([]string{"bake"}, targets...),
@ -186,7 +191,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
return err
}
files, inp, err := readBakeFiles(ctx, nodes, url, in.files, dockerCli.In(), printer)
files, inp, err := readBakeFiles(ctx, nodes, url, in.files, dockerCli.In(), printer, filesFromEnv)
if err != nil {
return err
}
@ -458,6 +463,15 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
Aliases: []string{"f"},
Short: "Build from a file",
RunE: func(cmd *cobra.Command, args []string) error {
filesFromEnv := false
if len(options.files) == 0 {
envFiles, err := bakeEnvFiles(os.LookupEnv)
if err != nil {
return err
}
options.files = envFiles
filesFromEnv = true
}
// reset to nil to avoid override is unset
if !cmd.Flags().Lookup("no-cache").Changed {
cFlags.noCache = nil
@ -475,7 +489,7 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
options.builder = rootOpts.builder
options.metadataFile = cFlags.metadataFile
// Other common flags (noCache, pull and progress) are processed in runBake function.
return runBake(cmd.Context(), dockerCli, args, options, cFlags)
return runBake(cmd.Context(), dockerCli, args, options, cFlags, filesFromEnv)
},
ValidArgsFunction: completion.BakeTargets(options.files),
}
@ -510,6 +524,37 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
return cmd
}
func bakeEnvFiles(lookup func(string string) (string, bool)) ([]string, error) {
sep, _ := lookup(bakeEnvFileSeparator)
if sep == "" {
sep = string(os.PathListSeparator)
}
f, ok := lookup(bakeEnvFilePath)
if ok {
return cleanPaths(strings.Split(f, sep))
}
return []string{}, nil
}
func cleanPaths(p []string) ([]string, error) {
var paths []string
for _, f := range p {
f = strings.TrimSpace(f)
if f == "" {
continue
}
if f == "-" {
paths = append(paths, f)
continue
}
if _, err := os.Stat(f); err != nil {
return nil, err
}
paths = append(paths, f)
}
return paths, nil
}
func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string, bo map[string]build.Options) error {
l, err := localstate.New(confutil.NewConfig(dockerCli))
if err != nil {
@ -559,7 +604,7 @@ func bakeArgs(args []string) (url, cmdContext string, targets []string) {
return url, cmdContext, targets
}
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer) (files []bake.File, inp *bake.Input, err error) {
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer, filesFromEnv bool) (files []bake.File, inp *bake.Input, err error) {
var lnames []string // local
var rnames []string // remote
var anames []string // both
@ -584,7 +629,11 @@ func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names
if len(lnames) > 0 || url == "" {
var lfiles []bake.File
progress.Wrap("[internal] load local bake definitions", pw.Write, func(sub progress.SubLogger) error {
where := ""
if filesFromEnv {
where = " from " + bakeEnvFilePath + " env"
}
progress.Wrap("[internal] load local bake definitions"+where, pw.Write, func(sub progress.SubLogger) error {
if url != "" {
lfiles, err = bake.ReadLocalFiles(lnames, stdin, sub)
} else {

View File

@ -143,6 +143,11 @@ Use the `-f` / `--file` option to specify the build definition file to use.
The file can be an HCL, JSON or Compose file. If multiple files are specified,
all are read and the build configurations are combined.
Alternatively, the environment variable `BUILDX_BAKE_FILE` can be used to specify the build definition to use.
This is mutually exclusive with `-f` / `--file`; if both are specified, the environment variable is ignored.
Multiple definitions can be specified by separating them with the system's path separator
(typically `;` on Windows and `:` elsewhere), but can be changed with `BUILDX_BAKE_PATH_SEPARATOR`.
You can pass the names of the targets to build, to build only specific target(s).
The following example builds the `db` and `webapp-release` targets that are
defined in the `docker-bake.dev.hcl` file:

View File

@ -81,6 +81,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeMultiPlatform,
testBakeCheckCallOutput,
testBakeExtraHosts,
testBakeFileFromEnvironment,
}
func testBakePrint(t *testing.T, sb integration.Sandbox) {
@ -2191,6 +2192,165 @@ target "default" {
require.NoError(t, err, out)
}
func testBakeFileFromEnvironment(t *testing.T, sb integration.Sandbox) {
bakeFileFirst := []byte(`
target "first" {
dockerfile-inline = "FROM scratch\nCOPY first /"
}
`)
bakeFileSecond := []byte(`
target "second" {
dockerfile-inline = "FROM scratch\nCOPY second /"
}
`)
t.Run("single file", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
})
t.Run("single file, default ignored if present", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("docker-bake.hcl", []byte("invalid bake file"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.NotContains(t, string(dt), "docker-bake.hcl")
})
t.Run("multiple files", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second.hcl", bakeFileSecond, 0600),
fstest.CreateFile("second", []byte("second"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "second", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"+string(os.PathListSeparator)+"second.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.Contains(t, string(dt), `#1 reading second.hcl`)
})
t.Run("multiple files, custom separator", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second.hcl", bakeFileSecond, 0600),
fstest.CreateFile("second", []byte("second"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "second", "first"),
withEnv("BUILDX_BAKE_PATH_SEPARATOR=@", "BUILDX_BAKE_FILE=first.hcl@second.hcl"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.Contains(t, string(dt), `#1 reading second.hcl`)
})
t.Run("multiple files, one STDIN", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second", []byte("second"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "second", "first"),
withEnv("BUILDX_BAKE_FILE=first.hcl"+string(os.PathListSeparator)+"-"))
w, err := cmd.StdinPipe()
require.NoError(t, err)
go func() {
defer w.Close()
w.Write(bakeFileSecond)
}()
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))
require.Contains(t, string(dt), `#1 [internal] load local bake definitions from BUILDX_BAKE_FILE env`)
require.Contains(t, string(dt), `#1 reading first.hcl`)
require.Contains(t, string(dt), `#1 reading from stdin`)
})
t.Run("env ignored if file arg passed", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
fstest.CreateFile("second.hcl", bakeFileSecond, 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "-f", "first.hcl", "first", "second"),
withEnv("BUILDX_BAKE_FILE=second.hcl"))
dt, err := cmd.CombinedOutput()
require.Error(t, err, string(dt))
require.Contains(t, string(dt), "failed to find target second")
})
t.Run("file does not exist", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv("BUILDX_BAKE_FILE=wrong.hcl"))
dt, err := cmd.CombinedOutput()
require.Error(t, err, string(dt))
require.Contains(t, string(dt), "wrong.hcl: no such file or directory")
})
for kind, val := range map[string]string{"missing": "", "whitespace": " "} {
t.Run(kind+" value ignored", func(t *testing.T) {
dir := tmpdir(t,
fstest.CreateFile("first.hcl", bakeFileFirst, 0600),
fstest.CreateFile("first", []byte("first"), 0600),
)
cmd := buildxCmd(sb,
withDir(dir),
withArgs("bake", "--progress=plain", "first"),
withEnv(fmt.Sprintf("BUILDX_BAKE_FILE=%s", val)))
dt, err := cmd.CombinedOutput()
require.Error(t, err, string(dt))
require.Contains(t, string(dt), "couldn't find a bake definition")
})
}
}
func writeTempPrivateKey(fp string) error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {