Skip to content

Commit

Permalink
bake: cache-from/cache-to options no longer print sensitive values
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
jsternberg committed Dec 9, 2024
1 parent d4eca07 commit 45ae61e
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 115 deletions.
75 changes: 37 additions & 38 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,30 +698,30 @@ type Target struct {
// Inherits is the only field that cannot be overridden with --set
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`

Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
CacheFrom []*buildflags.CacheOptionsEntry `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
CacheTo []*buildflags.CacheOptionsEntry `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
Secrets []*buildflags.Secret `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
SSH []*buildflags.SSH `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
Outputs []*buildflags.ExportEntry `json:"output,omitempty" hcl:"output,optional" cty:"output"`
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.

// linked is a private field to mark a target used as a linked one
Expand All @@ -742,8 +742,8 @@ func (t *Target) normalize() {
t.Secrets = removeDupes(t.Secrets)
t.SSH = removeDupes(t.SSH)
t.Platforms = removeDupesStr(t.Platforms)
t.CacheFrom = removeDupes(t.CacheFrom)
t.CacheTo = removeDupes(t.CacheTo)
t.CacheFrom = t.CacheFrom.Normalize()
t.CacheTo = t.CacheTo.Normalize()
t.Outputs = removeDupes(t.Outputs)
t.NoCacheFilter = removeDupesStr(t.NoCacheFilter)
t.Ulimits = removeDupesStr(t.Ulimits)
Expand Down Expand Up @@ -824,7 +824,7 @@ func (t *Target) Merge(t2 *Target) {
t.Platforms = t2.Platforms
}
if t2.CacheFrom != nil { // merge
t.CacheFrom = append(t.CacheFrom, t2.CacheFrom...)
t.CacheFrom = t.CacheFrom.Merge(t2.CacheFrom)
}
if t2.CacheTo != nil { // no merge
t.CacheTo = t2.CacheTo
Expand Down Expand Up @@ -1372,17 +1372,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
}
}

cacheImports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheFrom))
for i, ci := range t.CacheFrom {
cacheImports[i] = ci.ToPB()
if t.CacheFrom != nil {
bo.CacheFrom = controllerapi.CreateCaches(t.CacheFrom.ToPB())
}
bo.CacheFrom = controllerapi.CreateCaches(cacheImports)

cacheExports := make([]*controllerapi.CacheOptionsEntry, len(t.CacheTo))
for i, ce := range t.CacheTo {
cacheExports[i] = ce.ToPB()
if t.CacheTo != nil {
bo.CacheTo = controllerapi.CreateCaches(t.CacheTo.ToPB())
}
bo.CacheTo = controllerapi.CreateCaches(cacheExports)

outputs := make([]*controllerapi.ExportEntry, len(t.Outputs))
for i, output := range t.Outputs {
Expand Down Expand Up @@ -1625,9 +1620,13 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
return outputs, nil
}

func parseCacheArrValues(s []string) ([]*buildflags.CacheOptionsEntry, error) {
outs := make([]*buildflags.CacheOptionsEntry, 0, len(s))
func parseCacheArrValues(s []string) (buildflags.CacheOptions, error) {
var outs buildflags.CacheOptions
for _, in := range s {
if in == "" {
continue
}

if !strings.Contains(in, "=") {
// This is ref only format. Each field in the CSV is its own entry.
fields, err := csvvalue.Fields(in, nil)
Expand Down
4 changes: 2 additions & 2 deletions bake/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,14 +353,14 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
if err != nil {
return err
}
t.CacheFrom = removeDupes(append(t.CacheFrom, cacheFrom...))
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
}
if len(xb.CacheTo) > 0 {
cacheTo, err := parseCacheArrValues(xb.CacheTo)
if err != nil {
return err
}
t.CacheTo = removeDupes(append(t.CacheTo, cacheTo...))
t.CacheTo = t.CacheTo.Merge(cacheTo)
}
if len(xb.Secrets) > 0 {
secrets, err := parseArrValue[buildflags.Secret](xb.Secrets)
Expand Down
6 changes: 3 additions & 3 deletions bake/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
target "app" {
cache-from = [
{ type = "registry", ref = "user/app:cache" },
{ type = "local", src = "path/to/cache" },
"type=local,src=path/to/cache",
]
cache-to = [
Expand Down Expand Up @@ -649,7 +649,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
target "app" {
cache-from = [
{ type = "registry", ref = "user/app:cache" },
{ type = "local", src = "path/to/cache" },
"type=local,src=path/to/cache",
]
cache-to = [ target.app.cache-from[0] ]
Expand All @@ -674,7 +674,7 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
output = [ "type=oci,dest=../${foo}.tar" ]
secret = [
{ id = target.app.output[0].type, src = "/local/secret" },
{ id = target.app.output[0].type, src = "/${target.app.cache-from[1].type}/secret" },
]
}
`)
Expand Down
71 changes: 48 additions & 23 deletions bake/hclparser/type_implied_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,93 @@ import (
"github.com/zclconf/go-cty/cty/gocty"
)

type CapsuleValue interface {
// FromCtyValue will initialize this value using a cty.Value.
FromCtyValue(in cty.Value, path cty.Path) error

// ToCtyValue will convert this capsule value into a native
type ToCtyValueConverter interface {
// ToNativeValueConverter will convert this capsule value into a native
// cty.Value. This should not return a capsule type.
ToCtyValue() cty.Value
}

type FromCtyValueConverter interface {
// FromCtyValue will initialize this value using a cty.Value.
FromCtyValue(in cty.Value, path cty.Path) error
}

type extensionType int

const (
nativeTypeExtension extensionType = iota
)

func impliedTypeExt(rt reflect.Type, _ cty.Path) (cty.Type, error) {
if rt.AssignableTo(capsuleValueType) {
if rt.Kind() != reflect.Pointer {
rt = reflect.PointerTo(rt)
}

if isCapsuleType(rt) {
return capsuleValueCapsuleType(rt), nil
}
return cty.NilType, errdefs.ErrNotImplemented
}

var (
capsuleValueType = reflect.TypeFor[CapsuleValue]()
capsuleValueTypes sync.Map
)
func isCapsuleType(rt reflect.Type) bool {
fromNativeType := reflect.TypeFor[FromCtyValueConverter]()
toNativeType := reflect.TypeFor[ToCtyValueConverter]()
return rt.Implements(fromNativeType) && rt.Implements(toNativeType)
}

var capsuleValueTypes sync.Map

func capsuleValueCapsuleType(rt reflect.Type) cty.Type {
if val, loaded := capsuleValueTypes.Load(rt); loaded {
if rt.Kind() != reflect.Pointer {
panic("capsule value must be a pointer")
}

elem := rt.Elem()
if val, loaded := capsuleValueTypes.Load(elem); loaded {
return val.(cty.Type)
}

// First time used.
ety := cty.CapsuleWithOps(rt.Name(), rt.Elem(), &cty.CapsuleOps{
toNativeType := reflect.TypeFor[ToCtyValueConverter]()

// First time used. Initialize new capsule ops.
ops := &cty.CapsuleOps{
ConversionTo: func(_ cty.Type) func(cty.Value, cty.Path) (any, error) {
return func(in cty.Value, p cty.Path) (any, error) {
rv := reflect.New(rt.Elem()).Interface()
if err := rv.(CapsuleValue).FromCtyValue(in, p); err != nil {
rv := reflect.New(elem).Interface()
if err := rv.(FromCtyValueConverter).FromCtyValue(in, p); err != nil {
return nil, err
}
return rv, nil
}
},
ConversionFrom: func(want cty.Type) func(any, cty.Path) (cty.Value, error) {
return func(in any, _ cty.Path) (cty.Value, error) {
v := in.(CapsuleValue).ToCtyValue()
rv := reflect.ValueOf(in).Convert(toNativeType)
v := rv.Interface().(ToCtyValueConverter).ToCtyValue()
return convert.Convert(v, want)
}
},
ExtensionData: func(key any) any {
switch key {
case nativeTypeExtension:
zero := reflect.Zero(rt).Interface()
return zero.(CapsuleValue).ToCtyValue().Type()
default:
return nil
zero := reflect.Zero(elem).Interface()
if conv, ok := zero.(ToCtyValueConverter); ok {
return conv.ToCtyValue().Type()
}

zero = reflect.Zero(rt).Interface()
if conv, ok := zero.(ToCtyValueConverter); ok {
return conv.ToCtyValue().Type()
}
}
return nil
},
})
}

// Attempt to store the new type. Use whichever was loaded first in the case of a race condition.
val, _ := capsuleValueTypes.LoadOrStore(rt, ety)
// Attempt to store the new type. Use whichever was loaded first in the case
// of a race condition.
ety := cty.CapsuleWithOps(elem.Name(), elem, ops)
val, _ := capsuleValueTypes.LoadOrStore(elem, ety)
return val.(cty.Type)
}

Expand Down
Loading

0 comments on commit 45ae61e

Please sign in to comment.