From 1bb425a8825fb5ef14ccd31c0be34760f5a12dbb Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 12 Jul 2021 17:53:04 -0700 Subject: [PATCH] bake: allow BAKE_CMD_CONTEXT builtin var Allows accessing the main context for bake command from bake file that has been imported remotely. Signed-off-by: Tonis Tiigi --- bake/bake.go | 24 +++++++++++++++----- bake/bake_test.go | 45 ++++++++++++++++++++++++++++--------- bake/hcl_test.go | 35 +++++++++++++++++++++++------ bake/hclparser/hclparser.go | 15 +++++++++++-- commands/bake.go | 24 +++++++++++++++----- 5 files changed, 113 insertions(+), 30 deletions(-) diff --git a/bake/bake.go b/bake/bake.go index bb0d4578..e10bb014 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -61,8 +61,8 @@ func ReadLocalFiles(names []string) ([]File, error) { return out, nil } -func ReadTargets(ctx context.Context, files []File, targets, overrides []string) (map[string]*Target, error) { - c, err := ParseFiles(files) +func ReadTargets(ctx context.Context, files []File, targets, overrides []string, defaults map[string]string) (map[string]*Target, error) { + c, err := ParseFiles(files, defaults) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func ReadTargets(ctx context.Context, files []File, targets, overrides []string) return m, nil } -func ParseFiles(files []File) (_ *Config, err error) { +func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error) { defer func() { err = formatHCLError(err, files) }() @@ -120,6 +120,7 @@ func ParseFiles(files []File) (_ *Config, err error) { if len(fs) > 0 { if err := hclparser.Parse(hcl.MergeFiles(fs), hclparser.Opt{ LookupVar: os.LookupEnv, + Vars: defaults, }, &c); err.HasErrors() { return nil, err } @@ -143,7 +144,7 @@ func dedupeConfig(c Config) Config { } func ParseFile(dt []byte, fn string) (*Config, error) { - return ParseFiles([]File{{Data: dt, Name: fn}}) + return ParseFiles([]File{{Data: dt, Name: fn}}, nil) } func ParseComposeFile(dt []byte, fn string) (*Config, bool, error) { @@ -526,6 +527,12 @@ func updateContext(t *build.Inputs, inp *Input) { t.ContextPath = inp.URL return } + if strings.HasPrefix(t.ContextPath, "cwd://") { + return + } + if IsRemoteURL(t.ContextPath) { + return + } st := llb.Scratch().File(llb.Copy(*inp.State, t.ContextPath, "/"), llb.WithCustomNamef("set context to %s", t.ContextPath)) t.ContextState = &st } @@ -542,7 +549,9 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { if t.Context != nil { contextPath = *t.Context } - contextPath = path.Clean(contextPath) + if !strings.HasPrefix(contextPath, "cwd://") && !IsRemoteURL(contextPath) { + contextPath = path.Clean(contextPath) + } dockerfilePath := "Dockerfile" if t.Dockerfile != nil { dockerfilePath = *t.Dockerfile @@ -569,6 +578,11 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { bi.DockerfileInline = *t.DockerfileInline } updateContext(&bi, inp) + if strings.HasPrefix(bi.ContextPath, "cwd://") { + bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://")) + } + + t.Context = &bi.ContextPath bo := &build.Options{ Inputs: bi, diff --git a/bake/bake_test.go b/bake/bake_test.go index cf8b081c..f68f1580 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -34,7 +34,7 @@ target "webapp" { ctx := context.TODO() t.Run("NoOverrides", func(t *testing.T) { - m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, nil) + m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, nil, nil) require.NoError(t, err) require.Equal(t, 1, len(m)) @@ -46,7 +46,7 @@ target "webapp" { }) t.Run("InvalidTargetOverrides", func(t *testing.T) { - _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"}) + _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"}, nil) require.NotNil(t, err) require.Equal(t, err.Error(), "could not find any target matching 'nosuchtarget'") }) @@ -63,7 +63,7 @@ target "webapp" { "webapp.args.VAR_FROMENV" + t.Name(), "webapp.args.VAR_INHERITED=override", // not overriding VAR_BOTH on purpose - }) + }, nil) require.NoError(t, err) require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile) @@ -88,7 +88,7 @@ target "webapp" { m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{ "webDEP.args.VAR_INHERITED=override", "webDEP.args.VAR_BOTH=override", - }) + }, nil) require.NoError(t, err) require.Equal(t, m["webapp"].Args["VAR_INHERITED"], "override") require.Equal(t, m["webapp"].Args["VAR_BOTH"], "webapp") @@ -96,23 +96,23 @@ target "webapp" { }) t.Run("ContextOverride", func(t *testing.T) { - _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"}) + _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"}, nil) require.NotNil(t, err) - m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context=foo"}) + m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context=foo"}, nil) require.NoError(t, err) require.Equal(t, "foo", *m["webapp"].Context) }) t.Run("NoCacheOverride", func(t *testing.T) { - m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.no-cache=false"}) + m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.no-cache=false"}, nil) require.NoError(t, err) require.Equal(t, false, *m["webapp"].NoCache) }) t.Run("PullOverride", func(t *testing.T) { - m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}) + m, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil) require.NoError(t, err) require.Equal(t, false, *m["webapp"].Pull) }) @@ -172,7 +172,7 @@ target "webapp" { } for _, test := range cases { t.Run(test.name, func(t *testing.T) { - m, err := ReadTargets(ctx, []File{fp}, test.targets, test.overrides) + m, err := ReadTargets(ctx, []File{fp}, test.targets, test.overrides, nil) test.check(t, m, err) }) } @@ -215,7 +215,7 @@ services: ctx := context.TODO() - m, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil) + m, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil) require.NoError(t, err) require.Equal(t, 3, len(m)) @@ -226,3 +226,28 @@ services: require.Equal(t, "1", m["webapp"].Args["buildno"]) require.Equal(t, "12", m["webapp"].Args["buildno2"]) } + +func TestHCLCwdPrefix(t *testing.T) { + + fp := File{ + Name: "docker-bake.hc", + Data: []byte( + `target "app" { + context = "cwd://foo" + dockerfile = "test" + }`), + } + ctx := context.TODO() + m, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil) + require.NoError(t, err) + + require.Equal(t, 1, len(m)) + _, ok := m["app"] + require.True(t, ok) + + _, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + + require.Equal(t, "test", *m["app"].Dockerfile) + require.Equal(t, "foo", *m["app"].Context) +} diff --git a/bake/hcl_test.go b/bake/hcl_test.go index f053d24a..892f6bdd 100644 --- a/bake/hcl_test.go +++ b/bake/hcl_test.go @@ -276,7 +276,7 @@ func TestHCLMultiFileSharedVariables(t *testing.T) { c, err := ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.hcl"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) require.Equal(t, c.Targets[0].Name, "app") @@ -288,7 +288,7 @@ func TestHCLMultiFileSharedVariables(t *testing.T) { c, err = ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.hcl"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) @@ -326,7 +326,7 @@ func TestHCLVarsWithVars(t *testing.T) { c, err := ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.hcl"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) require.Equal(t, c.Targets[0].Name, "app") @@ -338,7 +338,7 @@ func TestHCLVarsWithVars(t *testing.T) { c, err = ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.hcl"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) @@ -483,7 +483,7 @@ func TestHCLMultiFileAttrs(t *testing.T) { c, err := ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.hcl"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) require.Equal(t, c.Targets[0].Name, "app") @@ -494,7 +494,7 @@ func TestHCLMultiFileAttrs(t *testing.T) { c, err = ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.hcl"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) @@ -589,7 +589,7 @@ services: c, err := ParseFiles([]File{ {Data: dt, Name: "c1.hcl"}, {Data: dt2, Name: "c2.yml"}, - }) + }, nil) require.NoError(t, err) require.Equal(t, 1, len(c.Targets)) @@ -599,3 +599,24 @@ services: require.Equal(t, "dir", *c.Targets[0].Context) require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile) } + +func TestHCLBuiltinVars(t *testing.T) { + dt := []byte(` + target "app" { + context = BAKE_CMD_CONTEXT + dockerfile = "test" + } + `) + + c, err := ParseFiles([]File{ + {Data: dt, Name: "c1.hcl"}, + }, map[string]string{ + "BAKE_CMD_CONTEXT": "foo", + }) + require.NoError(t, err) + + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, "foo", *c.Targets[0].Context) + require.Equal(t, "test", *c.Targets[0].Dockerfile) +} diff --git a/bake/hclparser/hclparser.go b/bake/hclparser/hclparser.go index 46c2316f..6438c7eb 100644 --- a/bake/hclparser/hclparser.go +++ b/bake/hclparser/hclparser.go @@ -17,6 +17,7 @@ import ( type Opt struct { LookupVar func(string) (string, bool) + Vars map[string]string } type variable struct { @@ -178,7 +179,7 @@ func (p *parser) resolveValue(name string) (err error) { }() def, ok := p.attrs[name] - if !ok { + if _, builtin := p.opt.Vars[name]; !ok && !builtin { vr, ok := p.vars[name] if !ok { return errors.Errorf("undefined variable %q", name) @@ -187,7 +188,10 @@ func (p *parser) resolveValue(name string) (err error) { } if def == nil { - val, _ := p.opt.LookupVar(name) + val, ok := p.opt.Vars[name] + if !ok { + val, _ = p.opt.LookupVar(name) + } vv := cty.StringVal(val) v = &vv return @@ -243,6 +247,9 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { for _, bs := range schema.Blocks { reserved[bs.Type] = struct{}{} } + for k := range opt.Vars { + reserved[k] = struct{}{} + } var defs inputs if err := gohcl.DecodeBody(b, nil, &defs); err != nil { @@ -303,6 +310,10 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } delete(p.attrs, "function") + for k := range p.opt.Vars { + _ = p.resolveValue(k) + } + for k := range p.attrs { if err := p.resolveValue(k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { diff --git a/commands/bake.go b/commands/bake.go index dffba509..3318a401 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -36,11 +36,19 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error }() var url string + cmdContext := "cwd://" if len(targets) > 0 { if bake.IsRemoteURL(targets[0]) { url = targets[0] targets = targets[1:] + if len(targets) > 0 { + if bake.IsRemoteURL(targets[0]) { + cmdContext = targets[0] + targets = targets[1:] + + } + } } } @@ -85,6 +93,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error var files []bake.File var inp *bake.Input + if url != "" { files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer) } else { @@ -94,7 +103,15 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error return err } - m, err := bake.ReadTargets(ctx, files, targets, overrides) + m, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{ + "BAKE_CMD_CONTEXT": cmdContext, + }) + if err != nil { + return err + } + + // this function can update target context string from the input so call before printOnly check + bo, err := bake.TargetsToBuildOpt(m, inp) if err != nil { return err } @@ -113,11 +130,6 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error return nil } - bo, err := bake.TargetsToBuildOpt(m, inp) - if err != nil { - return err - } - resp, err := build.Build(ctx, dis, bo, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer) if err != nil { return err