diff --git a/.github/workflows/docs-upstream.yml b/.github/workflows/docs-upstream.yml index f351233ef460..72cb3d1f64d2 100644 --- a/.github/workflows/docs-upstream.yml +++ b/.github/workflows/docs-upstream.yml @@ -65,7 +65,7 @@ jobs: retention-days: 1 validate: - uses: docker/docs/.github/workflows/validate-upstream.yml@6b73b05acb21edf7995cc5b3c6672d8e314cee7a # pin for artifact v4 support: https://github.com/docker/docs/pull/19220 + uses: docker/docs/.github/workflows/validate-upstream.yml@main needs: - docs-yaml with: diff --git a/bake/bake.go b/bake/bake.go index c7979dcf2b5b..f365b65beb8a 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -29,7 +29,6 @@ import ( "github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/util/entitlements" "github.com/pkg/errors" - "github.com/tonistiigi/go-csvvalue" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" ) @@ -900,7 +899,7 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon case "tags": t.Tags = o.ArrValue case "cache-from": - cacheFrom, err := parseCacheArrValues(o.ArrValue) + cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue) if err != nil { return err } @@ -913,7 +912,7 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon } } case "cache-to": - cacheTo, err := parseCacheArrValues(o.ArrValue) + cacheTo, err := buildflags.ParseCacheEntry(o.ArrValue) if err != nil { return err } @@ -1585,37 +1584,3 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) { } return outputs, nil } - -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) - if err != nil { - return nil, err - } - - for _, field := range fields { - out := buildflags.CacheOptionsEntry{} - if err := out.UnmarshalText([]byte(field)); err != nil { - return nil, err - } - outs = append(outs, &out) - } - continue - } - - // Normal entry. - out := buildflags.CacheOptionsEntry{} - if err := out.UnmarshalText([]byte(in)); err != nil { - return nil, err - } - outs = append(outs, &out) - } - return outs, nil -} diff --git a/bake/bake_test.go b/bake/bake_test.go index 15a3333d2a0d..f1f0a9f28a74 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/docker/buildx/util/buildflags" "github.com/moby/buildkit/util/entitlements" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1759,6 +1760,27 @@ func TestAnnotations(t *testing.T) { require.Equal(t, "bar", bo["app"].Exports[0].Attrs["annotation-manifest[linux/amd64].foo"]) } +func TestRefOnlyCacheOptions(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte( + `target "app" { + output = ["type=image,name=foo"] + cache-from = ["ref1,ref2"] + }`), + } + ctx := context.TODO() + m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil, &EntitlementConf{}) + require.NoError(t, err) + + require.Len(t, m, 1) + require.Contains(t, m, "app") + require.Equal(t, buildflags.CacheOptions{ + {Type: "registry", Attrs: map[string]string{"ref": "ref1"}}, + {Type: "registry", Attrs: map[string]string{"ref": "ref2"}}, + }, m["app"].CacheFrom) +} + func TestHCLEntitlements(t *testing.T) { fp := File{ Name: "docker-bake.hcl", diff --git a/bake/compose.go b/bake/compose.go index 58cfc80c953d..fba1f3de2836 100644 --- a/bake/compose.go +++ b/bake/compose.go @@ -145,12 +145,12 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf labels[k] = &v } - cacheFrom, err := parseCacheArrValues(s.Build.CacheFrom) + cacheFrom, err := buildflags.ParseCacheEntry(s.Build.CacheFrom) if err != nil { return nil, err } - cacheTo, err := parseCacheArrValues(s.Build.CacheTo) + cacheTo, err := buildflags.ParseCacheEntry(s.Build.CacheTo) if err != nil { return nil, err } @@ -349,14 +349,14 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error { t.Tags = dedupSlice(append(t.Tags, xb.Tags...)) } if len(xb.CacheFrom) > 0 { - cacheFrom, err := parseCacheArrValues(xb.CacheFrom) + cacheFrom, err := buildflags.ParseCacheEntry(xb.CacheFrom) if err != nil { return err } t.CacheFrom = t.CacheFrom.Merge(cacheFrom) } if len(xb.CacheTo) > 0 { - cacheTo, err := parseCacheArrValues(xb.CacheTo) + cacheTo, err := buildflags.ParseCacheEntry(xb.CacheTo) if err != nil { return err } diff --git a/commands/build.go b/commands/build.go index 264c13400b78..91a5786a4ac7 100644 --- a/commands/build.go +++ b/commands/build.go @@ -183,14 +183,17 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error } } - opts.CacheFrom, err = buildflags.ParseCacheEntry(o.cacheFrom) + cacheFrom, err := buildflags.ParseCacheEntry(o.cacheFrom) if err != nil { return nil, err } - opts.CacheTo, err = buildflags.ParseCacheEntry(o.cacheTo) + opts.CacheFrom = cacheFrom.ToPB() + + cacheTo, err := buildflags.ParseCacheEntry(o.cacheTo) if err != nil { return nil, err } + opts.CacheTo = cacheTo.ToPB() opts.Secrets, err = buildflags.ParseSecretSpecs(o.secrets) if err != nil { diff --git a/docs/bake-reference.md b/docs/bake-reference.md index 192dded4fe60..d8fd5ecc3015 100644 --- a/docs/bake-reference.md +++ b/docs/bake-reference.md @@ -221,8 +221,10 @@ The following table shows the complete list of attributes that you can assign to | [`attest`](#targetattest) | List | Build attestations | | [`cache-from`](#targetcache-from) | List | External cache sources | | [`cache-to`](#targetcache-to) | List | External cache destinations | +| [`call`](#targetcall) | String | Specify the frontend method to call for the target. | | [`context`](#targetcontext) | String | Set of files located in the specified path or URL | | [`contexts`](#targetcontexts) | Map | Additional build contexts | +| [`description`](#targetdescription) | String | Description of a target | | [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string | | [`dockerfile`](#targetdockerfile) | String | Dockerfile location | | [`inherits`](#targetinherits) | List | Inherit attributes from other targets | @@ -371,6 +373,13 @@ target "app" { } ``` +Supported values are: + +- `build` builds the target (default) +- `check`: evaluates [build checks](https://docs.docker.com/build/checks/) for the target +- `outline`: displays the target's build arguments and their default values if available +- `targets`: lists all Bake targets in the loaded definition, along with its [description](#targetdescription). + For more information about frontend methods, refer to the CLI reference for [`docker buildx build --call`](https://docs.docker.com/reference/cli/docker/buildx/build/#call). @@ -481,6 +490,25 @@ FROM baseapp RUN echo "Hello world" ``` +### `target.description` + +Defines a human-readable description for the target, clarifying its purpose or +functionality. + +```hcl +target "lint" { + description = "Runs golangci-lint to detect style errors" + args = { + GOLANGCI_LINT_VERSION = null + } + dockerfile = "lint.Dockerfile" +} +``` + +This attribute is useful when combined with the `docker buildx bake --list=targets` +option, providing a more informative output when listing the available build +targets in a Bake file. + ### `target.dockerfile-inline` Uses the string value as an inline Dockerfile for the build target. diff --git a/docs/reference/buildx_bake.md b/docs/reference/buildx_bake.md index a2b6ff0c2c14..be7deee44355 100644 --- a/docs/reference/buildx_bake.md +++ b/docs/reference/buildx_bake.md @@ -15,7 +15,7 @@ Build from a file | Name | Type | Default | Description | |:------------------------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------| -| `--allow` | `stringArray` | | Allow build to access specified resources | +| [`--allow`](#allow) | `stringArray` | | Allow build to access specified resources | | [`--builder`](#builder) | `string` | | Override the configured builder instance | | [`--call`](#call) | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) | | [`--check`](#check) | `bool` | | Shorthand for `--call=check` | @@ -51,6 +51,80 @@ guide for introduction to writing bake files. ## Examples +### Allow extra privileged entitlement (--allow) + +```text +--allow=ENTITLEMENT[=VALUE] +``` + +Entitlements are designed to provide controlled access to privileged +operations. By default, Buildx and BuildKit operates with restricted +permissions to protect users and their systems from unintended side effects or +security risks. The `--allow` flag explicitly grants access to additional +entitlements, making it clear when a build or bake operation requires elevated +privileges. + +In addition to BuildKit's `network.host` and `security.insecure` entitlements +(see [`docker buildx build --allow`](https://docs.docker.com/reference/cli/docker/buildx/build/#allow), +Bake supports file system entitlements that grant granular control over file +system access. These are particularly useful when working with builds that need +access to files outside the default working directory. + +Bake supports the following filesystem entitlements: + +- `--allow fs=` - Grant read and write access to files outside of the + working directory. +- `--allow fs.read=` - Grant read access to files outside of the + working directory. +- `--allow fs.write=` - Grant write access to files outside of the + working directory. + +The `fs` entitlements take a path value (relative or absolute) to a directory +on the filesystem. Alternatively, you can pass a wildcard (`*`) to allow Bake +to access the entire filesystem. + +### Example: fs.read + +Given the following Bake configuration, Bake would need to access the parent +directory, relative to the Bake file. + +```hcl +target "app" { + context = "../src" +} +``` + +Assuming `docker buildx bake app` is executed in the same directory as the +`docker-bake.hcl` file, you would need to explicitly allow Bake to read from +the `../src` directory. In this case, the following invocations all work: + +```console +$ docker buildx bake --allow fs.read=* app +$ docker buildx bake --allow fs.read=../src app +$ docker buildx bake --allow fs=* app +``` + +### Example: fs.write + +The following `docker-bake.hcl` file requires write access to the `/tmp` +directory. + +```hcl +target "app" { + output = "/tmp" +} +``` + +Assuming `docker buildx bake app` is executed outside of the `/tmp` directory, +you would need to allow the `fs.write` entitlement, either by specifying the +path or using a wildcard: + +```console +$ docker buildx bake --allow fs=/tmp app +$ docker buildx bake --allow fs.write=/tmp app +$ docker buildx bake --allow fs.write=* app +``` + ### Override the configured builder instance (--builder) Same as [`buildx --builder`](buildx.md#builder). diff --git a/util/buildflags/cache.go b/util/buildflags/cache.go index db982e786c59..9cd5afb03d91 100644 --- a/util/buildflags/cache.go +++ b/util/buildflags/cache.go @@ -167,20 +167,37 @@ func (e *CacheOptionsEntry) validate(gv interface{}) error { return nil } -func ParseCacheEntry(in []string) ([]*controllerapi.CacheOptionsEntry, error) { +func ParseCacheEntry(in []string) (CacheOptions, error) { if len(in) == 0 { return nil, nil } opts := make(CacheOptions, 0, len(in)) for _, in := range in { + if !strings.Contains(in, "=") { + // This is ref only format. Each field in the CSV is its own entry. + fields, err := csvvalue.Fields(in, nil) + if err != nil { + return nil, err + } + + for _, field := range fields { + opt := CacheOptionsEntry{} + if err := opt.UnmarshalText([]byte(field)); err != nil { + return nil, err + } + opts = append(opts, &opt) + } + continue + } + var out CacheOptionsEntry if err := out.UnmarshalText([]byte(in)); err != nil { return nil, err } opts = append(opts, &out) } - return opts.ToPB(), nil + return opts, nil } func addGithubToken(ci *controllerapi.CacheOptionsEntry) { diff --git a/util/buildflags/cache_cty.go b/util/buildflags/cache_cty.go index abfcd008a6a4..05e2e6bcccc3 100644 --- a/util/buildflags/cache_cty.go +++ b/util/buildflags/cache_cty.go @@ -30,6 +30,16 @@ func (o *CacheOptions) fromCtyValue(in cty.Value, p cty.Path) error { continue } + // Special handling for a string type to handle ref only format. + if value.Type() == cty.String { + entries, err := ParseCacheEntry([]string{value.AsString()}) + if err != nil { + return err + } + *o = append(*o, entries...) + continue + } + entry := &CacheOptionsEntry{} if err := entry.FromCtyValue(value, p); err != nil { return err @@ -52,13 +62,6 @@ func (o CacheOptions) ToCtyValue() cty.Value { } func (o *CacheOptionsEntry) FromCtyValue(in cty.Value, p cty.Path) error { - if in.Type() == cty.String { - if err := o.UnmarshalText([]byte(in.AsString())); err != nil { - return p.NewError(err) - } - return nil - } - conv, err := convert.Convert(in, cty.Map(cty.String)) if err != nil { return err diff --git a/util/buildflags/cache_test.go b/util/buildflags/cache_test.go index c4368c0d470c..02d2f8ba431e 100644 --- a/util/buildflags/cache_test.go +++ b/util/buildflags/cache_test.go @@ -37,7 +37,7 @@ func TestCacheOptions_DerivedVars(t *testing.T) { "session_token": "not_a_mitm_attack", }, }, - }, cacheFrom) + }, cacheFrom.ToPB()) } func TestCacheOptions(t *testing.T) { @@ -109,3 +109,12 @@ func TestCacheOptions(t *testing.T) { require.True(t, result.True()) }) } + +func TestCacheOptions_RefOnlyFormat(t *testing.T) { + opts, err := ParseCacheEntry([]string{"ref1", "ref2"}) + require.NoError(t, err) + require.Equal(t, CacheOptions{ + {Type: "registry", Attrs: map[string]string{"ref": "ref1"}}, + {Type: "registry", Attrs: map[string]string{"ref": "ref2"}}, + }, opts) +}