Skip to content

Commit 71d0415

Browse files
committed
bake: cache-from/cache-to options no longer print sensitive values
This refactors how the cache-from/cache-to composable attributes work so they no longer print sensitive values that are automatically added. This also expands the available syntax that works with the cache options. It is now possible to interleave the csv syntax with the object syntax without any problems. The canonical form is still the object syntax and variables are resolved according to that syntax. `cache-from` and `cache-to` now correctly ignore empty string inputs so these can be used with variables. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com> now works with a direct wrapper around the slice
1 parent d6d713a commit 71d0415

File tree

10 files changed

+426
-122
lines changed

10 files changed

+426
-122
lines changed

bake/bake.go

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -698,30 +698,30 @@ type Target struct {
698698
// Inherits is the only field that cannot be overridden with --set
699699
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
700700

701-
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
702-
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
703-
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
704-
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
705-
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
706-
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
707-
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
708-
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
709-
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
710-
CacheFrom []*buildflags.CacheOptionsEntry `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
711-
CacheTo []*buildflags.CacheOptionsEntry `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
712-
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
713-
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
714-
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
715-
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
716-
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
717-
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
718-
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
719-
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
720-
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
721-
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
722-
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
723-
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
724-
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
701+
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
702+
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
703+
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
704+
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
705+
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
706+
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
707+
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
708+
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
709+
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
710+
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
711+
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
712+
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
713+
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
714+
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
715+
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
716+
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
717+
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
718+
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
719+
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
720+
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
721+
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
722+
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
723+
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
724+
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
725725
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
726726

727727
// linked is a private field to mark a target used as a linked one
@@ -742,8 +742,8 @@ func (t *Target) normalize() {
742742
t.Secrets = removeDupes(t.Secrets)
743743
t.SSH = removeDupes(t.SSH)
744744
t.Platforms = removeDupesStr(t.Platforms)
745-
t.CacheFrom = removeDupes(t.CacheFrom)
746-
t.CacheTo = removeDupes(t.CacheTo)
745+
t.CacheFrom = t.CacheFrom.Normalize()
746+
t.CacheTo = t.CacheTo.Normalize()
747747
t.Outputs = removeDupes(t.Outputs)
748748
t.NoCacheFilter = removeDupesStr(t.NoCacheFilter)
749749
t.Ulimits = removeDupesStr(t.Ulimits)
@@ -824,7 +824,7 @@ func (t *Target) Merge(t2 *Target) {
824824
t.Platforms = t2.Platforms
825825
}
826826
if t2.CacheFrom != nil { // merge
827-
t.CacheFrom = append(t.CacheFrom, t2.CacheFrom...)
827+
t.CacheFrom = t.CacheFrom.Merge(t2.CacheFrom)
828828
}
829829
if t2.CacheTo != nil { // no merge
830830
t.CacheTo = t2.CacheTo
@@ -1338,17 +1338,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
13381338
}
13391339
}
13401340

1341-
cacheImports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheFrom))
1342-
for i, ci := range t.CacheFrom {
1343-
cacheImports[i] = ci.ToPB()
1341+
if t.CacheFrom != nil {
1342+
bo.CacheFrom = controllerapi.CreateCaches(t.CacheFrom.ToPB())
13441343
}
1345-
bo.CacheFrom = controllerapi.CreateCaches(cacheImports)
1346-
1347-
cacheExports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheTo))
1348-
for i, ce := range t.CacheTo {
1349-
cacheExports[i] = ce.ToPB()
1344+
if t.CacheTo != nil {
1345+
bo.CacheTo = controllerapi.CreateCaches(t.CacheTo.ToPB())
13501346
}
1351-
bo.CacheTo = controllerapi.CreateCaches(cacheExports)
13521347

13531348
outputs := make([]*controllerapi.ExportEntry, len(t.Outputs))
13541349
for i, output := range t.Outputs {
@@ -1591,9 +1586,13 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
15911586
return outputs, nil
15921587
}
15931588

1594-
func parseCacheArrValues(s []string) ([]*buildflags.CacheOptionsEntry, error) {
1595-
outs := make([]*buildflags.CacheOptionsEntry, 0, len(s))
1589+
func parseCacheArrValues(s []string) (buildflags.CacheOptions, error) {
1590+
var outs buildflags.CacheOptions
15961591
for _, in := range s {
1592+
if in == "" {
1593+
continue
1594+
}
1595+
15971596
if !strings.Contains(in, "=") {
15981597
// This is ref only format. Each field in the CSV is its own entry.
15991598
fields, err := csvvalue.Fields(in, nil)

bake/compose.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,14 +353,14 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
353353
if err != nil {
354354
return err
355355
}
356-
t.CacheFrom = removeDupes(append(t.CacheFrom, cacheFrom...))
356+
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
357357
}
358358
if len(xb.CacheTo) > 0 {
359359
cacheTo, err := parseCacheArrValues(xb.CacheTo)
360360
if err != nil {
361361
return err
362362
}
363-
t.CacheTo = removeDupes(append(t.CacheTo, cacheTo...))
363+
t.CacheTo = t.CacheTo.Merge(cacheTo)
364364
}
365365
if len(xb.Secrets) > 0 {
366366
secrets, err := parseArrValue[buildflags.Secret](xb.Secrets)

bake/hcl_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
606606
target "app" {
607607
cache-from = [
608608
{ type = "registry", ref = "user/app:cache" },
609-
{ type = "local", src = "path/to/cache" },
609+
"type=local,src=path/to/cache",
610610
]
611611
612612
cache-to = [
@@ -649,7 +649,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
649649
target "app" {
650650
cache-from = [
651651
{ type = "registry", ref = "user/app:cache" },
652-
{ type = "local", src = "path/to/cache" },
652+
"type=local,src=path/to/cache",
653653
]
654654
655655
cache-to = [ target.app.cache-from[0] ]
@@ -674,7 +674,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
674674
output = [ "type=oci,dest=../${foo}.tar" ]
675675
676676
secret = [
677-
{ id = target.app.output[0].type, src = "/local/secret" },
677+
{ id = target.app.output[0].type, src = "/${target.app.cache-from[1].type}/secret" },
678678
]
679679
}
680680
`)

bake/hclparser/type_implied_ext.go

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import (
1010
"github.com/zclconf/go-cty/cty/gocty"
1111
)
1212

13-
type CapsuleValue interface {
14-
// FromCtyValue will initialize this value using a cty.Value.
15-
FromCtyValue(in cty.Value, path cty.Path) error
16-
17-
// ToCtyValue will convert this capsule value into a native
13+
type ToNativeValueConverter interface {
14+
// ToNativeValueConverter will convert this capsule value into a native
1815
// cty.Value. This should not return a capsule type.
19-
ToCtyValue() cty.Value
16+
ToNativeValue() cty.Value
17+
}
18+
19+
type FromNativeValueConverter interface {
20+
// FromCtyValue will initialize this value using a cty.Value.
21+
FromNativeValue(in cty.Value, path cty.Path) error
2022
}
2123

2224
type extensionType int
@@ -26,52 +28,75 @@ const (
2628
)
2729

2830
func impliedTypeExt(rt reflect.Type, _ cty.Path) (cty.Type, error) {
29-
if rt.AssignableTo(capsuleValueType) {
31+
if rt.Kind() != reflect.Pointer {
32+
rt = reflect.PointerTo(rt)
33+
}
34+
35+
if isCapsuleType(rt) {
3036
return capsuleValueCapsuleType(rt), nil
3137
}
3238
return cty.NilType, errdefs.ErrNotImplemented
3339
}
3440

35-
var (
36-
capsuleValueType = reflect.TypeFor[CapsuleValue]()
37-
capsuleValueTypes sync.Map
38-
)
41+
func isCapsuleType(rt reflect.Type) bool {
42+
fromNativeType := reflect.TypeFor[FromNativeValueConverter]()
43+
toNativeType := reflect.TypeFor[ToNativeValueConverter]()
44+
return rt.Implements(fromNativeType) && rt.Implements(toNativeType)
45+
}
46+
47+
var capsuleValueTypes sync.Map
3948

4049
func capsuleValueCapsuleType(rt reflect.Type) cty.Type {
41-
if val, loaded := capsuleValueTypes.Load(rt); loaded {
50+
if rt.Kind() != reflect.Pointer {
51+
panic("capsule value must be a pointer")
52+
}
53+
54+
elem := rt.Elem()
55+
if val, loaded := capsuleValueTypes.Load(elem); loaded {
4256
return val.(cty.Type)
4357
}
4458

45-
// First time used.
46-
ety := cty.CapsuleWithOps(rt.Name(), rt.Elem(), &cty.CapsuleOps{
59+
toNativeType := reflect.TypeFor[ToNativeValueConverter]()
60+
61+
// First time used. Initialize new capsule ops.
62+
ops := &cty.CapsuleOps{
4763
ConversionTo: func(_ cty.Type) func(cty.Value, cty.Path) (any, error) {
4864
return func(in cty.Value, p cty.Path) (any, error) {
49-
rv := reflect.New(rt.Elem()).Interface()
50-
if err := rv.(CapsuleValue).FromCtyValue(in, p); err != nil {
65+
rv := reflect.New(elem).Interface()
66+
if err := rv.(FromNativeValueConverter).FromNativeValue(in, p); err != nil {
5167
return nil, err
5268
}
5369
return rv, nil
5470
}
5571
},
5672
ConversionFrom: func(want cty.Type) func(any, cty.Path) (cty.Value, error) {
5773
return func(in any, _ cty.Path) (cty.Value, error) {
58-
v := in.(CapsuleValue).ToCtyValue()
74+
rv := reflect.ValueOf(in).Convert(toNativeType)
75+
v := rv.Interface().(ToNativeValueConverter).ToNativeValue()
5976
return convert.Convert(v, want)
6077
}
6178
},
6279
ExtensionData: func(key any) any {
6380
switch key {
6481
case nativeTypeExtension:
65-
zero := reflect.Zero(rt).Interface()
66-
return zero.(CapsuleValue).ToCtyValue().Type()
67-
default:
68-
return nil
82+
zero := reflect.Zero(elem).Interface()
83+
if conv, ok := zero.(ToNativeValueConverter); ok {
84+
return conv.ToNativeValue().Type()
85+
}
86+
87+
zero = reflect.Zero(rt).Interface()
88+
if conv, ok := zero.(ToNativeValueConverter); ok {
89+
return conv.ToNativeValue().Type()
90+
}
6991
}
92+
return nil
7093
},
71-
})
94+
}
7295

73-
// Attempt to store the new type. Use whichever was loaded first in the case of a race condition.
74-
val, _ := capsuleValueTypes.LoadOrStore(rt, ety)
96+
// Attempt to store the new type. Use whichever was loaded first in the case
97+
// of a race condition.
98+
ety := cty.CapsuleWithOps(elem.Name(), elem, ops)
99+
val, _ := capsuleValueTypes.LoadOrStore(elem, ety)
75100
return val.(cty.Type)
76101
}
77102

0 commit comments

Comments
 (0)