policy: implement policy support for bake
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+43
-26
@@ -588,7 +588,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
|||||||
// IMPORTANT: if you add more fields here, do not forget to update
|
// IMPORTANT: if you add more fields here, do not forget to update
|
||||||
// docs/reference/buildx_bake.md (--set) and https://docs.docker.com/build/bake/overrides/
|
// docs/reference/buildx_bake.md (--set) and https://docs.docker.com/build/bake/overrides/
|
||||||
switch keys[1] {
|
switch keys[1] {
|
||||||
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements", "network", "annotations":
|
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements", "network", "annotations", "policy":
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
override.Append = appendTo
|
override.Append = appendTo
|
||||||
override.ArrValue = append(override.ArrValue, parts[1])
|
override.ArrValue = append(override.ArrValue, parts[1])
|
||||||
@@ -732,31 +732,32 @@ type Target struct {
|
|||||||
// Inherits is the only field that cannot be overridden with --set
|
// Inherits is the only field that cannot be overridden with --set
|
||||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
||||||
|
|
||||||
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
||||||
Attest buildflags.Attests `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
Attest buildflags.Attests `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||||
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||||
DockerfileInline *string `json:"dockerfile-inline,omitempty" hcl:"dockerfile-inline,optional" cty:"dockerfile-inline"`
|
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"`
|
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
||||||
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
||||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
||||||
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
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"`
|
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
||||||
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
||||||
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
||||||
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
||||||
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
|
Platforms []string `json:"platforms,omitempty" hcl:"platforms,optional" cty:"platforms"`
|
||||||
Outputs buildflags.Exports `json:"output,omitempty" hcl:"output,optional" cty:"output"`
|
Outputs buildflags.Exports `json:"output,omitempty" hcl:"output,optional" cty:"output"`
|
||||||
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
|
Pull *bool `json:"pull,omitempty" hcl:"pull,optional" cty:"pull"`
|
||||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||||
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
|
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"`
|
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" cty:"shm-size"`
|
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional" cty:"shm-size"`
|
||||||
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
|
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
|
||||||
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
|
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
|
||||||
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
|
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
|
||||||
ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"`
|
ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"`
|
||||||
|
Policy buildflags.PolicyConfigs `json:"policy,omitempty" hcl:"policy,optional" cty:"policy"`
|
||||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
|
// 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
|
// linked is a private field to mark a target used as a linked one
|
||||||
@@ -891,6 +892,9 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
if t2.Attest != nil { // merge
|
if t2.Attest != nil { // merge
|
||||||
t.Attest = t.Attest.Merge(t2.Attest)
|
t.Attest = t.Attest.Merge(t2.Attest)
|
||||||
}
|
}
|
||||||
|
if t2.Policy != nil { // merge
|
||||||
|
t.Policy = append(t.Policy, t2.Policy...)
|
||||||
|
}
|
||||||
if t2.Secrets != nil { // merge
|
if t2.Secrets != nil { // merge
|
||||||
t.Secrets = t.Secrets.Merge(t2.Secrets)
|
t.Secrets = t.Secrets.Merge(t2.Secrets)
|
||||||
}
|
}
|
||||||
@@ -986,6 +990,17 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
|||||||
} else {
|
} else {
|
||||||
t.Tags = o.ArrValue
|
t.Tags = o.ArrValue
|
||||||
}
|
}
|
||||||
|
case "policy":
|
||||||
|
if !o.Append {
|
||||||
|
t.Policy = nil
|
||||||
|
}
|
||||||
|
for _, v := range o.ArrValue {
|
||||||
|
cfg, err := buildflags.ParsePolicyConfig(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Policy = append(t.Policy, cfg)
|
||||||
|
}
|
||||||
case "cache-from":
|
case "cache-from":
|
||||||
cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue)
|
cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1548,6 +1563,8 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
|
|
||||||
bo.Attests = t.Attest.ToMap()
|
bo.Attests = t.Attest.ToMap()
|
||||||
|
|
||||||
|
bo.Policy = []buildflags.PolicyConfig(t.Policy)
|
||||||
|
|
||||||
bo.SourcePolicy, err = build.ReadSourcePolicy()
|
bo.SourcePolicy, err = build.ReadSourcePolicy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+3
-11
@@ -97,15 +97,7 @@ type Options struct {
|
|||||||
SourcePolicy *spb.Policy
|
SourcePolicy *spb.Policy
|
||||||
GroupRef string
|
GroupRef string
|
||||||
Annotations map[exptypes.AnnotationKey]string // Not used during build, annotations are already set in Exports. Just used to check for support with drivers.
|
Annotations map[exptypes.AnnotationKey]string // Not used during build, annotations are already set in Exports. Just used to check for support with drivers.
|
||||||
Policy []PolicyConfig
|
Policy []buildflags.PolicyConfig
|
||||||
}
|
|
||||||
|
|
||||||
type PolicyConfig struct {
|
|
||||||
Files []policy.File
|
|
||||||
Reset bool
|
|
||||||
Disabled bool
|
|
||||||
Strict *bool
|
|
||||||
LogLevel *logrus.Level
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallFunc struct {
|
type CallFunc struct {
|
||||||
@@ -135,7 +127,7 @@ type policyOpt struct {
|
|||||||
LogLevel *logrus.Level
|
LogLevel *logrus.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func withPolicyConfig(defaultPolicy policyOpt, configs []PolicyConfig) ([]policyOpt, error) {
|
func withPolicyConfig(defaultPolicy policyOpt, configs []buildflags.PolicyConfig) ([]policyOpt, error) {
|
||||||
if len(configs) == 0 {
|
if len(configs) == 0 {
|
||||||
if len(defaultPolicy.Files) == 0 {
|
if len(defaultPolicy.Files) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -161,7 +153,7 @@ func withPolicyConfig(defaultPolicy policyOpt, configs []PolicyConfig) ([]policy
|
|||||||
out = append(out, defaultPolicy)
|
out = append(out, defaultPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
var last PolicyConfig
|
var last buildflags.PolicyConfig
|
||||||
|
|
||||||
for _, cfg := range configs {
|
for _, cfg := range configs {
|
||||||
if cfg.Reset {
|
if cfg.Reset {
|
||||||
|
|||||||
+11
-10
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/buildx/policy"
|
"github.com/docker/buildx/policy"
|
||||||
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -39,33 +40,33 @@ func TestWithPolicyConfigDefaults(t *testing.T) {
|
|||||||
|
|
||||||
// TestWithPolicyConfigDisabled validates disabled policy behavior across invalid and valid combinations.
|
// TestWithPolicyConfigDisabled validates disabled policy behavior across invalid and valid combinations.
|
||||||
func TestWithPolicyConfigDisabled(t *testing.T) {
|
func TestWithPolicyConfigDisabled(t *testing.T) {
|
||||||
_, err := withPolicyConfig(policyOpt{}, []PolicyConfig{
|
_, err := withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Disabled: true, Files: []policy.File{{Filename: "x.rego"}}},
|
{Disabled: true, Files: []policy.File{{Filename: "x.rego"}}},
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
_, err = withPolicyConfig(policyOpt{}, []PolicyConfig{
|
_, err = withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Disabled: true, Reset: true},
|
{Disabled: true, Reset: true},
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
_, err = withPolicyConfig(policyOpt{}, []PolicyConfig{
|
_, err = withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Disabled: true, Strict: boolPtr(true)},
|
{Disabled: true, Strict: boolPtr(true)},
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
_, err = withPolicyConfig(policyOpt{}, []PolicyConfig{
|
_, err = withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Disabled: true, LogLevel: levelPtr(logrus.WarnLevel)},
|
{Disabled: true, LogLevel: levelPtr(logrus.WarnLevel)},
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
_, err = withPolicyConfig(policyOpt{}, []PolicyConfig{
|
_, err = withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Disabled: true},
|
{Disabled: true},
|
||||||
{},
|
{},
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
out, err := withPolicyConfig(policyOpt{}, []PolicyConfig{
|
out, err := withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Disabled: true},
|
{Disabled: true},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -81,7 +82,7 @@ func TestWithPolicyConfigResetAndFiles(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := withPolicyConfig(defaultPolicy, []PolicyConfig{
|
out, err := withPolicyConfig(defaultPolicy, []buildflags.PolicyConfig{
|
||||||
{Reset: true},
|
{Reset: true},
|
||||||
{Files: []policy.File{{Filename: "a.rego"}}},
|
{Files: []policy.File{{Filename: "a.rego"}}},
|
||||||
})
|
})
|
||||||
@@ -97,7 +98,7 @@ func TestWithPolicyConfigStrictAndLogLevel(t *testing.T) {
|
|||||||
Files: []policy.File{{Filename: "default.rego"}},
|
Files: []policy.File{{Filename: "default.rego"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := withPolicyConfig(defaultPolicy, []PolicyConfig{
|
out, err := withPolicyConfig(defaultPolicy, []buildflags.PolicyConfig{
|
||||||
{Strict: boolPtr(true), LogLevel: levelPtr(logrus.WarnLevel)},
|
{Strict: boolPtr(true), LogLevel: levelPtr(logrus.WarnLevel)},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -109,7 +110,7 @@ func TestWithPolicyConfigStrictAndLogLevel(t *testing.T) {
|
|||||||
|
|
||||||
// TestWithPolicyConfigStrictIgnoredWithoutPolicy ensures strict without any policy produces no entries.
|
// TestWithPolicyConfigStrictIgnoredWithoutPolicy ensures strict without any policy produces no entries.
|
||||||
func TestWithPolicyConfigStrictIgnoredWithoutPolicy(t *testing.T) {
|
func TestWithPolicyConfigStrictIgnoredWithoutPolicy(t *testing.T) {
|
||||||
out, err := withPolicyConfig(policyOpt{}, []PolicyConfig{
|
out, err := withPolicyConfig(policyOpt{}, []buildflags.PolicyConfig{
|
||||||
{Strict: boolPtr(true)},
|
{Strict: boolPtr(true)},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -125,7 +126,7 @@ func TestWithPolicyConfigMultipleFilesAndOverrides(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := withPolicyConfig(defaultPolicy, []PolicyConfig{
|
out, err := withPolicyConfig(defaultPolicy, []buildflags.PolicyConfig{
|
||||||
{Files: []policy.File{{Filename: "a.rego"}}},
|
{Files: []policy.File{{Filename: "a.rego"}}},
|
||||||
{Strict: boolPtr(true), LogLevel: levelPtr(logrus.WarnLevel)},
|
{Strict: boolPtr(true), LogLevel: levelPtr(logrus.WarnLevel)},
|
||||||
{Files: []policy.File{{Filename: "b.rego"}}, Strict: boolPtr(true)},
|
{Files: []policy.File{{Filename: "b.rego"}}, Strict: boolPtr(true)},
|
||||||
|
|||||||
+2
-66
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/policy"
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/buildx/store/storeutil"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
@@ -57,7 +56,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/tonistiigi/go-csvvalue"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -153,7 +151,7 @@ func (o *buildOptions) toOptions() (*BuildOptions, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.Policy, err = parsePolicyConfigs(o.policy)
|
opts.Policy, err = buildflags.ParsePolicyConfigs(o.policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -236,68 +234,6 @@ func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
|
|||||||
return progress, nil
|
return progress, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePolicyConfigs(in []string) ([]build.PolicyConfig, error) {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]build.PolicyConfig, 0, len(in))
|
|
||||||
for _, s := range in {
|
|
||||||
fields, err := csvvalue.Fields(s, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := build.PolicyConfig{}
|
|
||||||
for _, field := range fields {
|
|
||||||
key, value, ok := strings.Cut(field, "=")
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("invalid value %s", field)
|
|
||||||
}
|
|
||||||
key = strings.TrimSpace(strings.ToLower(key))
|
|
||||||
switch key {
|
|
||||||
case "filename":
|
|
||||||
if value == "" {
|
|
||||||
return nil, errors.Errorf("invalid value %s", field)
|
|
||||||
}
|
|
||||||
dt, err := os.ReadFile(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to read policy file %s", value)
|
|
||||||
}
|
|
||||||
cfg.Files = append(cfg.Files, policy.File{Filename: value, Data: dt})
|
|
||||||
case "reset":
|
|
||||||
b, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid value %s", field)
|
|
||||||
}
|
|
||||||
cfg.Reset = b
|
|
||||||
case "disabled":
|
|
||||||
b, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid value %s", field)
|
|
||||||
}
|
|
||||||
cfg.Disabled = b
|
|
||||||
case "strict":
|
|
||||||
b, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid value %s", field)
|
|
||||||
}
|
|
||||||
cfg.Strict = &b
|
|
||||||
case "log-level":
|
|
||||||
lvl, err := logrus.ParseLevel(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "invalid value %s", field)
|
|
||||||
}
|
|
||||||
cfg.LogLevel = &lvl
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("invalid value %s", field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = append(out, cfg)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
commandNameAttribute = attribute.Key("command.name")
|
commandNameAttribute = attribute.Key("command.name")
|
||||||
commandOptionsHash = attribute.Key("command.options.hash")
|
commandOptionsHash = attribute.Key("command.options.hash")
|
||||||
@@ -1048,7 +984,7 @@ type BuildOptions struct {
|
|||||||
GroupRef string
|
GroupRef string
|
||||||
Annotations []string
|
Annotations []string
|
||||||
ProvenanceResponseMode string
|
ProvenanceResponseMode string
|
||||||
Policy []build.PolicyConfig
|
Policy []buildflags.PolicyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunBuild runs the specified build and returns the result.
|
// RunBuild runs the specified build and returns the result.
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ The following table shows the complete list of attributes that you can assign to
|
|||||||
| [`no-cache-filter`](#targetno-cache-filter) | List | Disable build cache for specific stages |
|
| [`no-cache-filter`](#targetno-cache-filter) | List | Disable build cache for specific stages |
|
||||||
| [`no-cache`](#targetno-cache) | Boolean | Disable build cache completely |
|
| [`no-cache`](#targetno-cache) | Boolean | Disable build cache completely |
|
||||||
| [`output`](#targetoutput) | List | Output destinations |
|
| [`output`](#targetoutput) | List | Output destinations |
|
||||||
|
| [`policy`](#targetpolicy) | List | Policies to validate build sources and metadata |
|
||||||
| [`platforms`](#targetplatforms) | List | Target platforms |
|
| [`platforms`](#targetplatforms) | List | Target platforms |
|
||||||
| [`pull`](#targetpull) | Boolean | Always pull images |
|
| [`pull`](#targetpull) | Boolean | Always pull images |
|
||||||
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
||||||
@@ -899,6 +900,21 @@ target "default" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `target.policy`
|
||||||
|
|
||||||
|
Policies to validate build sources and metadata. Each entry uses the same keys
|
||||||
|
as the `--policy` flag for `docker buildx build` (`filename`, `reset`,
|
||||||
|
`disabled`, `strict`, `log-level`). Bake also automatically loads
|
||||||
|
`Dockerfile.rego` alongside the target Dockerfile when present.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `target.platforms`
|
### `target.platforms`
|
||||||
|
|
||||||
Set target platforms for the build target.
|
Set target platforms for the build target.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func TestIntegration(t *testing.T) {
|
|||||||
tests = append(tests, buildTests...)
|
tests = append(tests, buildTests...)
|
||||||
tests = append(tests, policyBuildTests...)
|
tests = append(tests, policyBuildTests...)
|
||||||
tests = append(tests, policyEvalTests...)
|
tests = append(tests, policyEvalTests...)
|
||||||
|
tests = append(tests, policyBakeTests...)
|
||||||
tests = append(tests, bakeTests...)
|
tests = append(tests, bakeTests...)
|
||||||
tests = append(tests, historyTests...)
|
tests = append(tests, historyTests...)
|
||||||
tests = append(tests, inspectTests...)
|
tests = append(tests, inspectTests...)
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs/fstest"
|
||||||
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var policyBakeTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
|
testBakePolicyConfigFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBakePolicyConfigFlags(t *testing.T, sb integration.Sandbox) {
|
||||||
|
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
|
||||||
|
|
||||||
|
dockerfile := []byte("FROM scratch\n")
|
||||||
|
defaultPolicy := []byte(`
|
||||||
|
package docker
|
||||||
|
|
||||||
|
default allow = false
|
||||||
|
|
||||||
|
allow if input.env.args["DEFAULT_OK"] == "1"
|
||||||
|
|
||||||
|
decision := {"allow": allow}
|
||||||
|
`)
|
||||||
|
extraPolicy := []byte(`
|
||||||
|
package docker
|
||||||
|
|
||||||
|
default allow = false
|
||||||
|
|
||||||
|
allow if input.env.labels["com.example.extra"] == "1"
|
||||||
|
|
||||||
|
decision := {"allow": allow}
|
||||||
|
`)
|
||||||
|
bakeFile := []byte(`
|
||||||
|
target "pass-both" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
DEFAULT_OK = "1"
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"com.example.extra" = "1"
|
||||||
|
}
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego" },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "fail-default" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
labels = {
|
||||||
|
"com.example.extra" = "1"
|
||||||
|
}
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego" },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "fail-extra" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
args = {
|
||||||
|
DEFAULT_OK = "1"
|
||||||
|
}
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego" },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "reset-pass" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
labels = {
|
||||||
|
"com.example.extra" = "1"
|
||||||
|
}
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego", reset = true },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "reset-fail" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego", reset = true },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "disabled" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
policy = [
|
||||||
|
{ disabled = true },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "disabled-combined" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
policy = [
|
||||||
|
{ filename = "extra.rego" },
|
||||||
|
{ disabled = true },
|
||||||
|
]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
fstest.CreateFile("Dockerfile.rego", defaultPolicy, 0600),
|
||||||
|
fstest.CreateFile("extra.rego", extraPolicy, 0600),
|
||||||
|
fstest.CreateFile("docker-bake.hcl", bakeFile, 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
target string
|
||||||
|
wantErrContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "additional-policy-requires-default",
|
||||||
|
target: "pass-both",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "additional-policy-missing-default",
|
||||||
|
target: "fail-default",
|
||||||
|
wantErrContains: "not allowed by policy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "additional-policy-missing-extra",
|
||||||
|
target: "fail-extra",
|
||||||
|
wantErrContains: "not allowed by policy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reset-ignores-default",
|
||||||
|
target: "reset-pass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reset-requires-extra",
|
||||||
|
target: "reset-fail",
|
||||||
|
wantErrContains: "not allowed by policy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled-skips-default",
|
||||||
|
target: "disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled-cannot-combine",
|
||||||
|
target: "disabled-combined",
|
||||||
|
wantErrContains: "disabled policy cannot be combined with other policy flags",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cmd := buildxCmd(sb, withDir(dir), withArgs(
|
||||||
|
"bake",
|
||||||
|
"--progress=plain",
|
||||||
|
"--file", "docker-bake.hcl",
|
||||||
|
tc.target,
|
||||||
|
))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if tc.wantErrContains == "" {
|
||||||
|
require.NoError(t, err, string(out))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Error(t, err, string(out))
|
||||||
|
require.Contains(t, string(out), tc.wantErrContains)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -703,9 +703,10 @@ decision := {"allow": allow}
|
|||||||
}
|
}
|
||||||
skipNoCompatBuildKit(t, sb, ">= 0.26.3-0", "http checksum policy input")
|
skipNoCompatBuildKit(t, sb, ">= 0.26.3-0", "http checksum policy input")
|
||||||
}
|
}
|
||||||
|
dockerfile := fmt.Appendf(nil, "FROM busybox:latest\nADD %s /tmp/file\n", tc.addURL)
|
||||||
dir := tmpdir(
|
dir := tmpdir(
|
||||||
t,
|
t,
|
||||||
fstest.CreateFile("Dockerfile", []byte(fmt.Sprintf("FROM busybox:latest\nADD %s /tmp/file\n", tc.addURL)), 0600),
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
fstest.CreateFile("policy.rego", []byte(tc.policy), 0600),
|
fstest.CreateFile("policy.rego", []byte(tc.policy), 0600),
|
||||||
)
|
)
|
||||||
policyPath := filepath.Join(dir, "policy.rego")
|
policyPath := filepath.Join(dir, "policy.rego")
|
||||||
@@ -747,8 +748,7 @@ func testBuildPolicyGit(t *testing.T, sb integration.Sandbox) {
|
|||||||
gittestutil.GitAdd(git, t, "Dockerfile", "a")
|
gittestutil.GitAdd(git, t, "Dockerfile", "a")
|
||||||
gittestutil.GitCommit(git, t, "initial commit")
|
gittestutil.GitCommit(git, t, "initial commit")
|
||||||
|
|
||||||
_, err = git.Run("tag", "-a", "v0.1", "-m", "v0.1release")
|
gittestutil.GitTagAnnotated(git, t, "v0.1", "v0.1release")
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "b"), []byte("b"), 0600))
|
require.NoError(t, os.WriteFile(filepath.Join(gitDir, "b"), []byte("b"), 0600))
|
||||||
gittestutil.GitAdd(git, t, "b")
|
gittestutil.GitAdd(git, t, "b")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
urlpkg "net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containerd/continuity/fs/fstest"
|
"github.com/containerd/continuity/fs/fstest"
|
||||||
@@ -14,7 +15,6 @@ import (
|
|||||||
"github.com/moby/buildkit/util/testutil/integration"
|
"github.com/moby/buildkit/util/testutil/integration"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
urlpkg "net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var policyEvalTests = []func(t *testing.T, sb integration.Sandbox){
|
var policyEvalTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package buildflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/policy"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/tonistiigi/go-csvvalue"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PolicyConfig struct {
|
||||||
|
Files []policy.File
|
||||||
|
Reset bool
|
||||||
|
Disabled bool
|
||||||
|
Strict *bool
|
||||||
|
LogLevel *logrus.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePolicyConfigs(in []string) ([]PolicyConfig, error) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]PolicyConfig, 0, len(in))
|
||||||
|
for _, s := range in {
|
||||||
|
cfg, err := ParsePolicyConfig(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, cfg)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePolicyConfig(value string) (PolicyConfig, error) {
|
||||||
|
fields, err := csvvalue.Fields(value, nil)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, err
|
||||||
|
}
|
||||||
|
return parsePolicyFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePolicyFields(fields []string) (PolicyConfig, error) {
|
||||||
|
cfg := PolicyConfig{}
|
||||||
|
for _, field := range fields {
|
||||||
|
key, value, ok := strings.Cut(field, "=")
|
||||||
|
if !ok {
|
||||||
|
return PolicyConfig{}, errors.Errorf("invalid value %s", field)
|
||||||
|
}
|
||||||
|
key = strings.TrimSpace(strings.ToLower(key))
|
||||||
|
switch key {
|
||||||
|
case "filename":
|
||||||
|
if value == "" {
|
||||||
|
return PolicyConfig{}, errors.Errorf("invalid value %s", field)
|
||||||
|
}
|
||||||
|
dt, err := os.ReadFile(value)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, errors.Wrapf(err, "failed to read policy file %s", value)
|
||||||
|
}
|
||||||
|
cfg.Files = append(cfg.Files, policy.File{Filename: value, Data: dt})
|
||||||
|
case "reset":
|
||||||
|
b, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, errors.Wrapf(err, "invalid value %s", field)
|
||||||
|
}
|
||||||
|
cfg.Reset = b
|
||||||
|
case "disabled":
|
||||||
|
b, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, errors.Wrapf(err, "invalid value %s", field)
|
||||||
|
}
|
||||||
|
cfg.Disabled = b
|
||||||
|
case "strict":
|
||||||
|
b, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, errors.Wrapf(err, "invalid value %s", field)
|
||||||
|
}
|
||||||
|
cfg.Strict = &b
|
||||||
|
case "log-level":
|
||||||
|
lvl, err := logrus.ParseLevel(value)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, errors.Wrapf(err, "invalid value %s", field)
|
||||||
|
}
|
||||||
|
cfg.LogLevel = &lvl
|
||||||
|
default:
|
||||||
|
return PolicyConfig{}, errors.Errorf("invalid value %s", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package buildflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PolicyConfigs []PolicyConfig
|
||||||
|
|
||||||
|
var policyConfigType = sync.OnceValue(func() cty.Type {
|
||||||
|
return cty.Map(cty.String)
|
||||||
|
})
|
||||||
|
|
||||||
|
func (p *PolicyConfigs) FromCtyValue(in cty.Value, path cty.Path) error {
|
||||||
|
got := in.Type()
|
||||||
|
if got.IsTupleType() || got.IsListType() {
|
||||||
|
return p.fromCtyValue(in, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := cty.List(policyConfigType())
|
||||||
|
return path.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PolicyConfigs) fromCtyValue(in cty.Value, path cty.Path) (retErr error) {
|
||||||
|
*p = make([]PolicyConfig, 0, in.LengthInt())
|
||||||
|
|
||||||
|
yield := func(value cty.Value) bool {
|
||||||
|
if value.Type() == cty.String {
|
||||||
|
var cfg PolicyConfig
|
||||||
|
cfg, retErr = ParsePolicyConfig(value.AsString())
|
||||||
|
if retErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*p = append(*p, cfg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Type().IsObjectType() || value.Type().IsMapType() {
|
||||||
|
var cfg PolicyConfig
|
||||||
|
cfg, retErr = policyConfigFromMap(value)
|
||||||
|
if retErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*p = append(*p, cfg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
retErr = path.NewErrorf("%s", convert.MismatchMessage(value.Type(), policyConfigType()))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
eachElement(in)(yield)
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PolicyConfigs) ToCtyValue() cty.Value {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return cty.ListValEmpty(policyConfigType())
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make([]cty.Value, len(p))
|
||||||
|
for i, entry := range p {
|
||||||
|
vals[i] = entry.ToCtyValue()
|
||||||
|
}
|
||||||
|
return cty.ListVal(vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PolicyConfig) FromCtyValue(in cty.Value, path cty.Path) error {
|
||||||
|
if in.Type() == cty.String {
|
||||||
|
cfg, err := ParsePolicyConfig(in.AsString())
|
||||||
|
if err != nil {
|
||||||
|
return path.NewError(err)
|
||||||
|
}
|
||||||
|
*p = cfg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.Type().IsObjectType() || in.Type().IsMapType() {
|
||||||
|
cfg, err := policyConfigFromMap(in)
|
||||||
|
if err != nil {
|
||||||
|
return path.NewError(err)
|
||||||
|
}
|
||||||
|
*p = cfg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.NewErrorf("%s", convert.MismatchMessage(in.Type(), policyConfigType()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PolicyConfig) ToCtyValue() cty.Value {
|
||||||
|
vals := map[string]cty.Value{}
|
||||||
|
if len(p.Files) > 0 {
|
||||||
|
vals["filename"] = cty.StringVal(p.Files[0].Filename)
|
||||||
|
}
|
||||||
|
if p.Reset {
|
||||||
|
vals["reset"] = cty.StringVal(strconv.FormatBool(p.Reset))
|
||||||
|
}
|
||||||
|
if p.Disabled {
|
||||||
|
vals["disabled"] = cty.StringVal(strconv.FormatBool(p.Disabled))
|
||||||
|
}
|
||||||
|
if p.Strict != nil {
|
||||||
|
vals["strict"] = cty.StringVal(strconv.FormatBool(*p.Strict))
|
||||||
|
}
|
||||||
|
if p.LogLevel != nil {
|
||||||
|
vals["log-level"] = cty.StringVal(p.LogLevel.String())
|
||||||
|
}
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return cty.MapValEmpty(cty.String)
|
||||||
|
}
|
||||||
|
return cty.MapVal(vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func policyConfigFromMap(in cty.Value) (PolicyConfig, error) {
|
||||||
|
fields := make([]string, 0)
|
||||||
|
for k, v := range in.AsValueMap() {
|
||||||
|
if v.IsNull() || !v.IsKnown() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v.Type() == cty.String && v.AsString() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field, err := policyField(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return PolicyConfig{}, err
|
||||||
|
}
|
||||||
|
fields = append(fields, field)
|
||||||
|
}
|
||||||
|
return parsePolicyFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func policyField(key string, value cty.Value) (string, error) {
|
||||||
|
switch value.Type() {
|
||||||
|
case cty.String:
|
||||||
|
return fmt.Sprintf("%s=%s", key, value.AsString()), nil
|
||||||
|
case cty.Bool:
|
||||||
|
return fmt.Sprintf("%s=%t", key, value.True()), nil
|
||||||
|
case cty.Number:
|
||||||
|
var f big.Float
|
||||||
|
f.Set(value.AsBigFloat())
|
||||||
|
return fmt.Sprintf("%s=%s", key, f.Text('f', -1)), nil
|
||||||
|
default:
|
||||||
|
return "", errors.Errorf("%s", convert.MismatchMessage(value.Type(), cty.String))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package buildflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/policy"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPolicyConfigs_FromCtyValue(t *testing.T) {
|
||||||
|
policyDir := t.TempDir()
|
||||||
|
policyPath := filepath.Join(policyDir, "policy.rego")
|
||||||
|
policyData := []byte("package docker\n")
|
||||||
|
require.NoError(t, os.WriteFile(policyPath, policyData, 0o600))
|
||||||
|
|
||||||
|
in := cty.TupleVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"filename": cty.StringVal(policyPath),
|
||||||
|
"reset": cty.BoolVal(true),
|
||||||
|
"strict": cty.BoolVal(true),
|
||||||
|
"log-level": cty.StringVal("warn"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("filename=" + policyPath + ",disabled=true"),
|
||||||
|
})
|
||||||
|
|
||||||
|
var actual PolicyConfigs
|
||||||
|
err := actual.FromCtyValue(in, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, actual, 2)
|
||||||
|
|
||||||
|
require.Equal(t, policyPath, actual[0].Files[0].Filename)
|
||||||
|
require.Equal(t, policyData, actual[0].Files[0].Data)
|
||||||
|
require.True(t, actual[0].Reset)
|
||||||
|
require.NotNil(t, actual[0].Strict)
|
||||||
|
require.True(t, *actual[0].Strict)
|
||||||
|
require.NotNil(t, actual[0].LogLevel)
|
||||||
|
require.Equal(t, logrus.WarnLevel, *actual[0].LogLevel)
|
||||||
|
|
||||||
|
require.Equal(t, policyPath, actual[1].Files[0].Filename)
|
||||||
|
require.Equal(t, policyData, actual[1].Files[0].Data)
|
||||||
|
require.True(t, actual[1].Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyConfigs_ToCtyValue(t *testing.T) {
|
||||||
|
lvl := logrus.InfoLevel
|
||||||
|
strict := true
|
||||||
|
in := PolicyConfigs{
|
||||||
|
{
|
||||||
|
Files: []policy.File{{Filename: "a.rego"}},
|
||||||
|
Reset: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Files: []policy.File{{Filename: "b.rego"}},
|
||||||
|
Disabled: true,
|
||||||
|
Strict: &strict,
|
||||||
|
LogLevel: &lvl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := in.ToCtyValue()
|
||||||
|
expected := cty.ListVal([]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"filename": cty.StringVal("a.rego"),
|
||||||
|
"reset": cty.StringVal("true"),
|
||||||
|
}),
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"filename": cty.StringVal("b.rego"),
|
||||||
|
"disabled": cty.StringVal("true"),
|
||||||
|
"strict": cty.StringVal("true"),
|
||||||
|
"log-level": cty.StringVal("info"),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
result := actual.Equals(expected)
|
||||||
|
require.True(t, result.True())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyConfig_FromCtyValue(t *testing.T) {
|
||||||
|
policyDir := t.TempDir()
|
||||||
|
policyPath := filepath.Join(policyDir, "policy.rego")
|
||||||
|
policyData := []byte("package docker\n")
|
||||||
|
require.NoError(t, os.WriteFile(policyPath, policyData, 0o600))
|
||||||
|
|
||||||
|
var actual PolicyConfig
|
||||||
|
err := actual.FromCtyValue(cty.StringVal("filename="+policyPath+",disabled=true"), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, policyPath, actual.Files[0].Filename)
|
||||||
|
require.Equal(t, policyData, actual.Files[0].Data)
|
||||||
|
require.True(t, actual.Disabled)
|
||||||
|
}
|
||||||
@@ -33,6 +33,13 @@ func GitTag(c *gitutil.Git, tb testing.TB, tag string) {
|
|||||||
require.Empty(tb, out)
|
require.Empty(tb, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GitTagAnnotated(c *gitutil.Git, tb testing.TB, tag, message string) {
|
||||||
|
tb.Helper()
|
||||||
|
out, err := fakeGit(c, "tag", "-a", tag, "-m", message)
|
||||||
|
require.NoError(tb, err)
|
||||||
|
require.Empty(tb, out)
|
||||||
|
}
|
||||||
|
|
||||||
func GitCheckoutBranch(c *gitutil.Git, tb testing.TB, name string) {
|
func GitCheckoutBranch(c *gitutil.Git, tb testing.TB, name string) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
out, err := fakeGit(c, "checkout", "-b", name)
|
out, err := fakeGit(c, "checkout", "-b", name)
|
||||||
|
|||||||
Reference in New Issue
Block a user