Compare commits

...

9 Commits

Author SHA1 Message Date
Tõnis Tiigi
245093b99a Merge pull request #2945 from tonistiigi/v0.20.1-cherry-picks
[v0.20.1] cherry picks
2025-01-22 13:41:17 -08:00
CrazyMax
e2ed15f0c9 ci: use main branch for docs upstream validation workflow
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
(cherry picked from commit aa1fbc0421)
2025-01-22 13:11:38 -08:00
David Karlsson
fd442f8e10 docs: add docs for bake --allow
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 012df71b63)
2025-01-22 13:10:12 -08:00
David Karlsson
1002e6fb42 docs(bake): improve docs on "call" and "description" in bake file
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit a26bb271ab)
2025-01-22 13:09:58 -08:00
Jonathan A. Sternberg
d5ad869033 buildflags: fix ref only format for command line and bake
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
(cherry picked from commit 11c84973ef)
2025-01-22 13:00:20 -08:00
Tõnis Tiigi
bd7090b981 Merge pull request #2940 from crazy-max/0.20.1-picks
[v0.20] v0.20.1 cherry-picks
2025-01-22 09:03:22 -08:00
Jonathan A. Sternberg
24f3a1df80 buildflags: marshal attestations into json with extra attributes correctly
`MarshalJSON` would not include the extra attributes because it iterated
over the target map rather than the source map.

Also fixes JSON unmarshaling for SSH and secrets. The intention was to
unmarshal into the struct, but `UnmarshalText` takes priority over the
default struct unmarshaling so it didn't work as intended.

Tests have been added for all marshaling and unmarshaling methods.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-01-22 13:43:36 +01:00
CrazyMax
8e30c4669c Merge pull request #2933 from crazy-max/v0.20_backport_buildkit-0.19.0
[v0.20 backport] vendor: update buildkit to v0.19.0
2025-01-20 19:29:35 +01:00
CrazyMax
cf74356afc vendor: update buildkit to v0.19.0
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-01-20 18:56:37 +01:00
19 changed files with 533 additions and 60 deletions

View File

@@ -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:

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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
### <a name="allow"></a> 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=<path|*>` - Grant read and write access to files outside of the
working directory.
- `--allow fs.read=<path|*>` - Grant read access to files outside of the
working directory.
- `--allow fs.write=<path|*>` - 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
```
### <a name="builder"></a> Override the configured builder instance (--builder)
Same as [`buildx --builder`](buildx.md#builder).

2
go.mod
View File

@@ -29,7 +29,7 @@ require (
github.com/hashicorp/hcl/v2 v2.23.0
github.com/in-toto/in-toto-golang v0.5.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/buildkit v0.19.0-rc3
github.com/moby/buildkit v0.19.0
github.com/moby/sys/mountinfo v0.7.2
github.com/moby/sys/signal v0.7.1
github.com/morikuni/aec v1.0.0

4
go.sum
View File

@@ -297,8 +297,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.19.0-rc3 h1:9PJv9iECP3fqy/4v8m5WvdBTYg3diDp/ImsfPBy2xG0=
github.com/moby/buildkit v0.19.0-rc3/go.mod h1:WiHBFTgWV8eB1AmPxIWsAlKjUACAwm3X/14xOV4VWew=
github.com/moby/buildkit v0.19.0 h1:w9G1p7sArvCGNkpWstAqJfRQTXBKukMyMK1bsah1HNo=
github.com/moby/buildkit v0.19.0/go.mod h1:WiHBFTgWV8eB1AmPxIWsAlKjUACAwm3X/14xOV4VWew=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=

View File

@@ -91,7 +91,7 @@ func (a *Attest) ToPB() *controllerapi.Attest {
func (a *Attest) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, len(a.Attrs)+2)
for k, v := range m {
for k, v := range a.Attrs {
m[k] = v
}
m["type"] = a.Type

View File

@@ -0,0 +1,79 @@
package buildflags
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestAttests(t *testing.T) {
t.Run("MarshalJSON", func(t *testing.T) {
attests := Attests{
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
{Type: "sbom", Disabled: true},
}
expected := `[{"type":"provenance","mode":"max"},{"type":"sbom","disabled":true}]`
actual, err := json.Marshal(attests)
require.NoError(t, err)
require.JSONEq(t, expected, string(actual))
})
t.Run("UnmarshalJSON", func(t *testing.T) {
in := `[{"type":"provenance","mode":"max"},{"type":"sbom","disabled":true}]`
var actual Attests
err := json.Unmarshal([]byte(in), &actual)
require.NoError(t, err)
expected := Attests{
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
{Type: "sbom", Disabled: true, Attrs: map[string]string{}},
}
require.Equal(t, expected, actual)
})
t.Run("FromCtyValue", func(t *testing.T) {
in := cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"type": cty.StringVal("provenance"),
"mode": cty.StringVal("max"),
}),
cty.StringVal("type=sbom,disabled=true"),
})
var actual Attests
err := actual.FromCtyValue(in, nil)
require.NoError(t, err)
expected := Attests{
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
{Type: "sbom", Disabled: true, Attrs: map[string]string{}},
}
require.Equal(t, expected, actual)
})
t.Run("ToCtyValue", func(t *testing.T) {
attests := Attests{
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
{Type: "sbom", Disabled: true},
}
actual := attests.ToCtyValue()
expected := cty.ListVal([]cty.Value{
cty.MapVal(map[string]cty.Value{
"type": cty.StringVal("provenance"),
"mode": cty.StringVal("max"),
}),
cty.MapVal(map[string]cty.Value{
"type": cty.StringVal("sbom"),
"disabled": cty.StringVal("true"),
}),
})
result := actual.Equals(expected)
require.True(t, result.True())
})
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -1,10 +1,12 @@
package buildflags
import (
"encoding/json"
"testing"
"github.com/docker/buildx/controller/pb"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestCacheOptions_DerivedVars(t *testing.T) {
@@ -35,5 +37,84 @@ func TestCacheOptions_DerivedVars(t *testing.T) {
"session_token": "not_a_mitm_attack",
},
},
}, cacheFrom)
}, cacheFrom.ToPB())
}
func TestCacheOptions(t *testing.T) {
t.Run("MarshalJSON", func(t *testing.T) {
cache := CacheOptions{
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
}
expected := `[{"type":"registry","ref":"user/app:cache"},{"type":"local","src":"path/to/cache"}]`
actual, err := json.Marshal(cache)
require.NoError(t, err)
require.JSONEq(t, expected, string(actual))
})
t.Run("UnmarshalJSON", func(t *testing.T) {
in := `[{"type":"registry","ref":"user/app:cache"},{"type":"local","src":"path/to/cache"}]`
var actual CacheOptions
err := json.Unmarshal([]byte(in), &actual)
require.NoError(t, err)
expected := CacheOptions{
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
}
require.Equal(t, expected, actual)
})
t.Run("FromCtyValue", func(t *testing.T) {
in := cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"type": cty.StringVal("registry"),
"ref": cty.StringVal("user/app:cache"),
}),
cty.StringVal("type=local,src=path/to/cache"),
})
var actual CacheOptions
err := actual.FromCtyValue(in, nil)
require.NoError(t, err)
expected := CacheOptions{
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
}
require.Equal(t, expected, actual)
})
t.Run("ToCtyValue", func(t *testing.T) {
attests := CacheOptions{
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
}
actual := attests.ToCtyValue()
expected := cty.ListVal([]cty.Value{
cty.MapVal(map[string]cty.Value{
"type": cty.StringVal("registry"),
"ref": cty.StringVal("user/app:cache"),
}),
cty.MapVal(map[string]cty.Value{
"type": cty.StringVal("local"),
"src": cty.StringVal("path/to/cache"),
}),
})
result := actual.Equals(expected)
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)
}

View File

@@ -1,6 +1,7 @@
package buildflags
import (
"encoding/json"
"strings"
controllerapi "github.com/docker/buildx/controller/pb"
@@ -73,6 +74,22 @@ func (s *Secret) ToPB() *controllerapi.Secret {
}
}
func (s *Secret) UnmarshalJSON(data []byte) error {
var v struct {
ID string `json:"id,omitempty"`
FilePath string `json:"src,omitempty"`
Env string `json:"env,omitempty"`
}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
s.ID = v.ID
s.FilePath = v.FilePath
s.Env = v.Env
return nil
}
func (s *Secret) UnmarshalText(text []byte) error {
value := string(text)
fields, err := csvvalue.Fields(value, nil)

View File

@@ -0,0 +1,84 @@
package buildflags
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestSecrets(t *testing.T) {
t.Run("MarshalJSON", func(t *testing.T) {
secrets := Secrets{
{ID: "mysecret", FilePath: "/local/secret"},
{ID: "mysecret2", Env: "TOKEN"},
}
expected := `[{"id":"mysecret","src":"/local/secret"},{"id":"mysecret2","env":"TOKEN"}]`
actual, err := json.Marshal(secrets)
require.NoError(t, err)
require.JSONEq(t, expected, string(actual))
})
t.Run("UnmarshalJSON", func(t *testing.T) {
in := `[{"id":"mysecret","src":"/local/secret"},{"id":"mysecret2","env":"TOKEN"}]`
var actual Secrets
err := json.Unmarshal([]byte(in), &actual)
require.NoError(t, err)
expected := Secrets{
{ID: "mysecret", FilePath: "/local/secret"},
{ID: "mysecret2", Env: "TOKEN"},
}
require.Equal(t, expected, actual)
})
t.Run("FromCtyValue", func(t *testing.T) {
in := cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("mysecret"),
"src": cty.StringVal("/local/secret"),
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("mysecret2"),
"env": cty.StringVal("TOKEN"),
}),
})
var actual Secrets
err := actual.FromCtyValue(in, nil)
require.NoError(t, err)
expected := Secrets{
{ID: "mysecret", FilePath: "/local/secret"},
{ID: "mysecret2", Env: "TOKEN"},
}
require.Equal(t, expected, actual)
})
t.Run("ToCtyValue", func(t *testing.T) {
secrets := Secrets{
{ID: "mysecret", FilePath: "/local/secret"},
{ID: "mysecret2", Env: "TOKEN"},
}
actual := secrets.ToCtyValue()
expected := cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("mysecret"),
"src": cty.StringVal("/local/secret"),
"env": cty.StringVal(""),
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("mysecret2"),
"src": cty.StringVal(""),
"env": cty.StringVal("TOKEN"),
}),
})
result := actual.Equals(expected)
require.True(t, result.True())
})
}

View File

@@ -2,6 +2,7 @@ package buildflags
import (
"cmp"
"encoding/json"
"slices"
"strings"
@@ -76,6 +77,20 @@ func (s *SSH) ToPB() *controllerapi.SSH {
}
}
func (s *SSH) UnmarshalJSON(data []byte) error {
var v struct {
ID string `json:"id,omitempty"`
Paths []string `json:"paths,omitempty"`
}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
s.ID = v.ID
s.Paths = v.Paths
return nil
}
func (s *SSH) UnmarshalText(text []byte) error {
parts := strings.SplitN(string(text), "=", 2)

View File

@@ -0,0 +1,85 @@
package buildflags
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestSSHKeys(t *testing.T) {
t.Run("MarshalJSON", func(t *testing.T) {
sshkeys := SSHKeys{
{ID: "default", Paths: []string{}},
{ID: "key", Paths: []string{"path/to/key"}},
}
expected := `[{"id":"default"},{"id":"key","paths":["path/to/key"]}]`
actual, err := json.Marshal(sshkeys)
require.NoError(t, err)
require.JSONEq(t, expected, string(actual))
})
t.Run("UnmarshalJSON", func(t *testing.T) {
in := `[{"id":"default"},{"id":"key","paths":["path/to/key"]}]`
var actual SSHKeys
err := json.Unmarshal([]byte(in), &actual)
require.NoError(t, err)
expected := SSHKeys{
{ID: "default"},
{ID: "key", Paths: []string{"path/to/key"}},
}
require.Equal(t, expected, actual)
})
t.Run("FromCtyValue", func(t *testing.T) {
in := cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("default"),
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("key"),
"paths": cty.TupleVal([]cty.Value{
cty.StringVal("path/to/key"),
}),
}),
})
var actual SSHKeys
err := actual.FromCtyValue(in, nil)
require.NoError(t, err)
expected := SSHKeys{
{ID: "default"},
{ID: "key", Paths: []string{"path/to/key"}},
}
require.Equal(t, expected, actual)
})
t.Run("ToCtyValue", func(t *testing.T) {
sshkeys := SSHKeys{
{ID: "default", Paths: []string{}},
{ID: "key", Paths: []string{"path/to/key"}},
}
actual := sshkeys.ToCtyValue()
expected := cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("default"),
"paths": cty.ListValEmpty(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("key"),
"paths": cty.ListVal([]cty.Value{
cty.StringVal("path/to/key"),
}),
}),
})
result := actual.Equals(expected)
require.True(t, result.True())
})
}

2
vendor/modules.txt vendored
View File

@@ -493,7 +493,7 @@ github.com/mitchellh/go-wordwrap
github.com/mitchellh/hashstructure/v2
# github.com/mitchellh/mapstructure v1.5.0
## explicit; go 1.14
# github.com/moby/buildkit v0.19.0-rc3
# github.com/moby/buildkit v0.19.0
## explicit; go 1.22.0
github.com/moby/buildkit/api/services/control
github.com/moby/buildkit/api/types