mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-15 08:15:55 +08:00
Compare commits
113 Commits
v0.20.0-rc
...
v0.21
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7b5fecbd7a | ||
![]() |
05f75a5bd5 | ||
![]() |
0982070af8 | ||
![]() |
1360a9e8d2 | ||
![]() |
6019a2b32f | ||
![]() |
6da88e1555 | ||
![]() |
41f8e5c85c | ||
![]() |
7c2359c6bf | ||
![]() |
65a52b5272 | ||
![]() |
34ed52e4ae | ||
![]() |
a05bbb9e3d | ||
![]() |
d9f8c73375 | ||
![]() |
af5d0d4ab5 | ||
![]() |
20256b6999 | ||
![]() |
c09d38af8a | ||
![]() |
9430ed6752 | ||
![]() |
3b31a33d59 | ||
![]() |
5e5568f3cd | ||
![]() |
9ac9a4170b | ||
![]() |
8d1ba91dcb | ||
![]() |
0df7a75961 | ||
![]() |
de5fbc38b8 | ||
![]() |
ef73c64d2c | ||
![]() |
1784f84561 | ||
![]() |
6a6fa4f422 | ||
![]() |
2389d457a4 | ||
![]() |
3f82aadc6e | ||
![]() |
79e3f12305 | ||
![]() |
1dc5f0751b | ||
![]() |
7ba4da0800 | ||
![]() |
a64e628774 | ||
![]() |
1c4b1a376c | ||
![]() |
e1f690abfc | ||
![]() |
03569c2188 | ||
![]() |
350d3f0f4b | ||
![]() |
dc27815236 | ||
![]() |
1089ff7341 | ||
![]() |
7433d37183 | ||
![]() |
f9a76355b5 | ||
![]() |
cfeea34b2d | ||
![]() |
ba2d3692a6 | ||
![]() |
853b593a4d | ||
![]() |
efb300e613 | ||
![]() |
cee7b344da | ||
![]() |
67dbde6970 | ||
![]() |
295653dabb | ||
![]() |
f5802119c5 | ||
![]() |
40b9ac1ec5 | ||
![]() |
f11496448a | ||
![]() |
c8c9c72ca6 | ||
![]() |
9fe8139022 | ||
![]() |
b3e8c62635 | ||
![]() |
b8e9c28315 | ||
![]() |
3ae9970da5 | ||
![]() |
1d219100fc | ||
![]() |
464f9278d1 | ||
![]() |
7216086b8c | ||
![]() |
b195b80ddf | ||
![]() |
70a5e266d1 | ||
![]() |
689bea7963 | ||
![]() |
5176c38115 | ||
![]() |
ec440c4574 | ||
![]() |
0a4eb7ec76 | ||
![]() |
f710c93157 | ||
![]() |
d1a0a1497c | ||
![]() |
c880ecd513 | ||
![]() |
d557da1935 | ||
![]() |
417af36abc | ||
![]() |
e236b86297 | ||
![]() |
633e8a0881 | ||
![]() |
5e1ea62f92 | ||
![]() |
4b90b84995 | ||
![]() |
abc85c38f8 | ||
![]() |
ccca7c795a | ||
![]() |
04aab6958c | ||
![]() |
9d640f0e33 | ||
![]() |
b76fdcaf8d | ||
![]() |
d693e18c04 | ||
![]() |
b066ee1110 | ||
![]() |
cf8bf9e104 | ||
![]() |
3bd54b19aa | ||
![]() |
934841f329 | ||
![]() |
b2ababc7b6 | ||
![]() |
0ccdb7e248 | ||
![]() |
cacb4fb9b3 | ||
![]() |
df80bd72c6 | ||
![]() |
bb4bef2f04 | ||
![]() |
a11507344a | ||
![]() |
17af006857 | ||
![]() |
11c84973ef | ||
![]() |
cc4a291f6a | ||
![]() |
aa1fbc0421 | ||
![]() |
b2bbb337e4 | ||
![]() |
012df71b63 | ||
![]() |
a26bb271ab | ||
![]() |
3e0682f039 | ||
![]() |
3aed658dc4 | ||
![]() |
b4a0dee723 | ||
![]() |
6904512813 | ||
![]() |
d41e335466 | ||
![]() |
0954dcb5fd | ||
![]() |
38f64bf709 | ||
![]() |
c1d3955fbe | ||
![]() |
d0b63e60e2 | ||
![]() |
e141c8fa71 | ||
![]() |
2ee156236b | ||
![]() |
1335264c9d | ||
![]() |
e74185aa6d | ||
![]() |
0224773102 | ||
![]() |
8c27b5c545 | ||
![]() |
f7594d484b | ||
![]() |
f118749cdc | ||
![]() |
0d92ad713c |
5
.github/labeler.yml
vendored
5
.github/labeler.yml
vendored
@@ -96,6 +96,11 @@ area/hack:
|
|||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'hack/**'
|
- any-glob-to-any-file: 'hack/**'
|
||||||
|
|
||||||
|
# Add 'area/history' label to changes in history command
|
||||||
|
area/history:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'commands/history/**'
|
||||||
|
|
||||||
# Add 'area/tests' label to changes in test files
|
# Add 'area/tests' label to changes in test files
|
||||||
area/tests:
|
area/tests:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
- master
|
- master
|
||||||
- latest
|
- latest
|
||||||
- buildx-stable-1
|
- buildx-stable-1
|
||||||
- v0.19.0-rc2
|
- v0.19.0
|
||||||
- v0.18.2
|
- v0.18.2
|
||||||
- v0.17.2
|
- v0.17.2
|
||||||
worker:
|
worker:
|
||||||
@@ -174,6 +174,11 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SKIP_INTEGRATION_TESTS: 1
|
SKIP_INTEGRATION_TESTS: 1
|
||||||
steps:
|
steps:
|
||||||
|
-
|
||||||
|
name: Setup Git config
|
||||||
|
run: |
|
||||||
|
git config --global core.autocrlf false
|
||||||
|
git config --global core.eol lf
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -264,8 +269,10 @@ jobs:
|
|||||||
name: Install vagrant
|
name: Install vagrant
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
|
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libvirt-daemon libvirt-daemon-system vagrant vagrant-libvirt ruby-libvirt
|
sudo apt-get install -y libvirt-dev libvirt-daemon libvirt-daemon-system vagrant vagrant-libvirt ruby-libvirt
|
||||||
sudo systemctl enable --now libvirtd
|
sudo systemctl enable --now libvirtd
|
||||||
sudo chmod a+rw /var/run/libvirt/libvirt-sock
|
sudo chmod a+rw /var/run/libvirt/libvirt-sock
|
||||||
vagrant plugin install vagrant-libvirt
|
vagrant plugin install vagrant-libvirt
|
||||||
|
2
.github/workflows/docs-upstream.yml
vendored
2
.github/workflows/docs-upstream.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
validate:
|
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:
|
needs:
|
||||||
- docs-yaml
|
- docs-yaml
|
||||||
with:
|
with:
|
||||||
|
3
.github/workflows/e2e.yml
vendored
3
.github/workflows/e2e.yml
vendored
@@ -215,6 +215,9 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Expose GitHub Runtime
|
||||||
|
uses: crazy-max/ghaction-github-runtime@v3
|
||||||
-
|
-
|
||||||
name: Environment variables
|
name: Environment variables
|
||||||
if: matrix.envs != ''
|
if: matrix.envs != ''
|
||||||
|
@@ -43,6 +43,9 @@ linters-settings:
|
|||||||
# buildkit errdefs package (or vice-versa).
|
# buildkit errdefs package (or vice-versa).
|
||||||
- pkg: "github.com/containerd/errdefs"
|
- pkg: "github.com/containerd/errdefs"
|
||||||
alias: "cerrdefs"
|
alias: "cerrdefs"
|
||||||
|
# Use a consistent alias to prevent confusion with "github.com/moby/buildkit/client"
|
||||||
|
- pkg: "github.com/docker/docker/client"
|
||||||
|
alias: "dockerclient"
|
||||||
- pkg: "github.com/opencontainers/image-spec/specs-go/v1"
|
- pkg: "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
alias: "ocispecs"
|
alias: "ocispecs"
|
||||||
- pkg: "github.com/opencontainers/go-digest"
|
- pkg: "github.com/opencontainers/go-digest"
|
||||||
|
@@ -5,12 +5,12 @@ ARG ALPINE_VERSION=3.21
|
|||||||
ARG XX_VERSION=1.6.1
|
ARG XX_VERSION=1.6.1
|
||||||
|
|
||||||
# for testing
|
# for testing
|
||||||
ARG DOCKER_VERSION=27.5.0
|
ARG DOCKER_VERSION=28.0.0-rc.1
|
||||||
ARG DOCKER_VERSION_ALT_26=26.1.3
|
ARG DOCKER_VERSION_ALT_26=26.1.3
|
||||||
ARG DOCKER_CLI_VERSION=${DOCKER_VERSION}
|
ARG DOCKER_CLI_VERSION=${DOCKER_VERSION}
|
||||||
ARG GOTESTSUM_VERSION=v1.12.0
|
ARG GOTESTSUM_VERSION=v1.12.0
|
||||||
ARG REGISTRY_VERSION=2.8.3
|
ARG REGISTRY_VERSION=2.8.3
|
||||||
ARG BUILDKIT_VERSION=v0.19.0-rc2
|
ARG BUILDKIT_VERSION=v0.19.0
|
||||||
ARG UNDOCK_VERSION=0.9.0
|
ARG UNDOCK_VERSION=0.9.0
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
52
bake/bake.go
52
bake/bake.go
@@ -27,9 +27,7 @@ import (
|
|||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
"github.com/moby/buildkit/util/entitlements"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tonistiigi/go-csvvalue"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
)
|
)
|
||||||
@@ -556,6 +554,8 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
|||||||
|
|
||||||
o := t[kk[1]]
|
o := t[kk[1]]
|
||||||
|
|
||||||
|
// IMPORTANT: if you add more fields here, do not forget to update
|
||||||
|
// docs/bake-reference.md 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":
|
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements", "network":
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
@@ -861,6 +861,8 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementConf) error {
|
func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementConf) error {
|
||||||
|
// IMPORTANT: if you add more fields here, do not forget to update
|
||||||
|
// docs/bake-reference.md and https://docs.docker.com/build/bake/overrides/
|
||||||
for key, o := range overrides {
|
for key, o := range overrides {
|
||||||
value := o.Value
|
value := o.Value
|
||||||
keys := strings.SplitN(key, ".", 2)
|
keys := strings.SplitN(key, ".", 2)
|
||||||
@@ -896,7 +898,7 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
|||||||
case "tags":
|
case "tags":
|
||||||
t.Tags = o.ArrValue
|
t.Tags = o.ArrValue
|
||||||
case "cache-from":
|
case "cache-from":
|
||||||
cacheFrom, err := parseCacheArrValues(o.ArrValue)
|
cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -909,7 +911,7 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "cache-to":
|
case "cache-to":
|
||||||
cacheTo, err := parseCacheArrValues(o.ArrValue)
|
cacheTo, err := buildflags.ParseCacheEntry(o.ArrValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1127,7 +1129,9 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(
|
|||||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||||
// make sure local credentials are loaded multiple times for different targets
|
// make sure local credentials are loaded multiple times for different targets
|
||||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||||
authProvider := authprovider.NewDockerAuthProvider(dockerConfig, nil)
|
authProvider := authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
||||||
|
ConfigFile: dockerConfig,
|
||||||
|
})
|
||||||
|
|
||||||
m2 := make(map[string]build.Options, len(m))
|
m2 := make(map[string]build.Options, len(m))
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
@@ -1429,9 +1433,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
bo.Ulimits = ulimits
|
bo.Ulimits = ulimits
|
||||||
|
|
||||||
for _, ent := range t.Entitlements {
|
bo.Allow = append(bo.Allow, t.Entitlements...)
|
||||||
bo.Allow = append(bo.Allow, entitlements.Entitlement(ent))
|
|
||||||
}
|
|
||||||
|
|
||||||
return bo, nil
|
return bo, nil
|
||||||
}
|
}
|
||||||
@@ -1581,37 +1583,3 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
|
|||||||
}
|
}
|
||||||
return outputs, nil
|
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
|
|
||||||
}
|
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/moby/buildkit/util/entitlements"
|
"github.com/moby/buildkit/util/entitlements"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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"])
|
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) {
|
func TestHCLEntitlements(t *testing.T) {
|
||||||
fp := File{
|
fp := File{
|
||||||
Name: "docker-bake.hcl",
|
Name: "docker-bake.hcl",
|
||||||
@@ -1784,8 +1806,8 @@ func TestHCLEntitlements(t *testing.T) {
|
|||||||
require.Equal(t, "network.host", m["app"].Entitlements[1])
|
require.Equal(t, "network.host", m["app"].Entitlements[1])
|
||||||
|
|
||||||
require.Len(t, bo["app"].Allow, 2)
|
require.Len(t, bo["app"].Allow, 2)
|
||||||
require.Equal(t, entitlements.EntitlementSecurityInsecure, bo["app"].Allow[0])
|
require.Equal(t, entitlements.EntitlementSecurityInsecure.String(), bo["app"].Allow[0])
|
||||||
require.Equal(t, entitlements.EntitlementNetworkHost, bo["app"].Allow[1])
|
require.Equal(t, entitlements.EntitlementNetworkHost.String(), bo["app"].Allow[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEntitlementsForNetHostCompose(t *testing.T) {
|
func TestEntitlementsForNetHostCompose(t *testing.T) {
|
||||||
@@ -1824,7 +1846,7 @@ func TestEntitlementsForNetHostCompose(t *testing.T) {
|
|||||||
require.Equal(t, "host", *m["app"].NetworkMode)
|
require.Equal(t, "host", *m["app"].NetworkMode)
|
||||||
|
|
||||||
require.Len(t, bo["app"].Allow, 1)
|
require.Len(t, bo["app"].Allow, 1)
|
||||||
require.Equal(t, entitlements.EntitlementNetworkHost, bo["app"].Allow[0])
|
require.Equal(t, entitlements.EntitlementNetworkHost.String(), bo["app"].Allow[0])
|
||||||
require.Equal(t, "host", bo["app"].NetworkMode)
|
require.Equal(t, "host", bo["app"].NetworkMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1855,7 +1877,7 @@ func TestEntitlementsForNetHost(t *testing.T) {
|
|||||||
require.Equal(t, "host", *m["app"].NetworkMode)
|
require.Equal(t, "host", *m["app"].NetworkMode)
|
||||||
|
|
||||||
require.Len(t, bo["app"].Allow, 1)
|
require.Len(t, bo["app"].Allow, 1)
|
||||||
require.Equal(t, entitlements.EntitlementNetworkHost, bo["app"].Allow[0])
|
require.Equal(t, entitlements.EntitlementNetworkHost.String(), bo["app"].Allow[0])
|
||||||
require.Equal(t, "host", bo["app"].NetworkMode)
|
require.Equal(t, "host", bo["app"].NetworkMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -145,12 +145,12 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
|
|||||||
labels[k] = &v
|
labels[k] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheFrom, err := parseCacheArrValues(s.Build.CacheFrom)
|
cacheFrom, err := buildflags.ParseCacheEntry(s.Build.CacheFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheTo, err := parseCacheArrValues(s.Build.CacheTo)
|
cacheTo, err := buildflags.ParseCacheEntry(s.Build.CacheTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -349,14 +349,14 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
|||||||
t.Tags = dedupSlice(append(t.Tags, xb.Tags...))
|
t.Tags = dedupSlice(append(t.Tags, xb.Tags...))
|
||||||
}
|
}
|
||||||
if len(xb.CacheFrom) > 0 {
|
if len(xb.CacheFrom) > 0 {
|
||||||
cacheFrom, err := parseCacheArrValues(xb.CacheFrom)
|
cacheFrom, err := buildflags.ParseCacheEntry(xb.CacheFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
|
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
|
||||||
}
|
}
|
||||||
if len(xb.CacheTo) > 0 {
|
if len(xb.CacheTo) > 0 {
|
||||||
cacheTo, err := parseCacheArrValues(xb.CacheTo)
|
cacheTo, err := buildflags.ParseCacheEntry(xb.CacheTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/moby/buildkit/util/entitlements"
|
"github.com/moby/buildkit/util/entitlements"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/tonistiigi/go-csvvalue"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EntitlementKey string
|
type EntitlementKey string
|
||||||
@@ -27,6 +28,7 @@ type EntitlementKey string
|
|||||||
const (
|
const (
|
||||||
EntitlementKeyNetworkHost EntitlementKey = "network.host"
|
EntitlementKeyNetworkHost EntitlementKey = "network.host"
|
||||||
EntitlementKeySecurityInsecure EntitlementKey = "security.insecure"
|
EntitlementKeySecurityInsecure EntitlementKey = "security.insecure"
|
||||||
|
EntitlementKeyDevice EntitlementKey = "device"
|
||||||
EntitlementKeyFSRead EntitlementKey = "fs.read"
|
EntitlementKeyFSRead EntitlementKey = "fs.read"
|
||||||
EntitlementKeyFSWrite EntitlementKey = "fs.write"
|
EntitlementKeyFSWrite EntitlementKey = "fs.write"
|
||||||
EntitlementKeyFS EntitlementKey = "fs"
|
EntitlementKeyFS EntitlementKey = "fs"
|
||||||
@@ -39,6 +41,7 @@ const (
|
|||||||
type EntitlementConf struct {
|
type EntitlementConf struct {
|
||||||
NetworkHost bool
|
NetworkHost bool
|
||||||
SecurityInsecure bool
|
SecurityInsecure bool
|
||||||
|
Devices *EntitlementsDevicesConf
|
||||||
FSRead []string
|
FSRead []string
|
||||||
FSWrite []string
|
FSWrite []string
|
||||||
ImagePush []string
|
ImagePush []string
|
||||||
@@ -46,6 +49,11 @@ type EntitlementConf struct {
|
|||||||
SSH bool
|
SSH bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EntitlementsDevicesConf struct {
|
||||||
|
All bool
|
||||||
|
Devices map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
func ParseEntitlements(in []string) (EntitlementConf, error) {
|
func ParseEntitlements(in []string) (EntitlementConf, error) {
|
||||||
var conf EntitlementConf
|
var conf EntitlementConf
|
||||||
for _, e := range in {
|
for _, e := range in {
|
||||||
@@ -59,6 +67,22 @@ func ParseEntitlements(in []string) (EntitlementConf, error) {
|
|||||||
default:
|
default:
|
||||||
k, v, _ := strings.Cut(e, "=")
|
k, v, _ := strings.Cut(e, "=")
|
||||||
switch k {
|
switch k {
|
||||||
|
case string(EntitlementKeyDevice):
|
||||||
|
if v == "" {
|
||||||
|
conf.Devices = &EntitlementsDevicesConf{All: true}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields, err := csvvalue.Fields(v, nil)
|
||||||
|
if err != nil {
|
||||||
|
return EntitlementConf{}, errors.Wrapf(err, "failed to parse device entitlement %q", v)
|
||||||
|
}
|
||||||
|
if conf.Devices == nil {
|
||||||
|
conf.Devices = &EntitlementsDevicesConf{}
|
||||||
|
}
|
||||||
|
if conf.Devices.Devices == nil {
|
||||||
|
conf.Devices.Devices = make(map[string]struct{}, 0)
|
||||||
|
}
|
||||||
|
conf.Devices.Devices[fields[0]] = struct{}{}
|
||||||
case string(EntitlementKeyFSRead):
|
case string(EntitlementKeyFSRead):
|
||||||
conf.FSRead = append(conf.FSRead, v)
|
conf.FSRead = append(conf.FSRead, v)
|
||||||
case string(EntitlementKeyFSWrite):
|
case string(EntitlementKeyFSWrite):
|
||||||
@@ -95,12 +119,34 @@ func (c EntitlementConf) Validate(m map[string]build.Options) (EntitlementConf,
|
|||||||
|
|
||||||
func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) error {
|
func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) error {
|
||||||
for _, e := range bo.Allow {
|
for _, e := range bo.Allow {
|
||||||
|
k, rest, _ := strings.Cut(e, "=")
|
||||||
|
switch k {
|
||||||
|
case entitlements.EntitlementDevice.String():
|
||||||
|
if rest == "" {
|
||||||
|
if c.Devices == nil || !c.Devices.All {
|
||||||
|
expected.Devices = &EntitlementsDevicesConf{All: true}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields, err := csvvalue.Fields(rest, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse device entitlement %q", rest)
|
||||||
|
}
|
||||||
|
if expected.Devices == nil {
|
||||||
|
expected.Devices = &EntitlementsDevicesConf{}
|
||||||
|
}
|
||||||
|
if expected.Devices.Devices == nil {
|
||||||
|
expected.Devices.Devices = make(map[string]struct{}, 0)
|
||||||
|
}
|
||||||
|
expected.Devices.Devices[fields[0]] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
switch e {
|
switch e {
|
||||||
case entitlements.EntitlementNetworkHost:
|
case entitlements.EntitlementNetworkHost.String():
|
||||||
if !c.NetworkHost {
|
if !c.NetworkHost {
|
||||||
expected.NetworkHost = true
|
expected.NetworkHost = true
|
||||||
}
|
}
|
||||||
case entitlements.EntitlementSecurityInsecure:
|
case entitlements.EntitlementSecurityInsecure.String():
|
||||||
if !c.SecurityInsecure {
|
if !c.SecurityInsecure {
|
||||||
expected.SecurityInsecure = true
|
expected.SecurityInsecure = true
|
||||||
}
|
}
|
||||||
@@ -187,6 +233,18 @@ func (c EntitlementConf) Prompt(ctx context.Context, isRemote bool, out io.Write
|
|||||||
flags = append(flags, string(EntitlementKeySecurityInsecure))
|
flags = append(flags, string(EntitlementKeySecurityInsecure))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Devices != nil {
|
||||||
|
if c.Devices.All {
|
||||||
|
msgs = append(msgs, " - Access to CDI devices")
|
||||||
|
flags = append(flags, string(EntitlementKeyDevice))
|
||||||
|
} else {
|
||||||
|
for d := range c.Devices.Devices {
|
||||||
|
msgs = append(msgs, fmt.Sprintf(" - Access to device %s", d))
|
||||||
|
flags = append(flags, string(EntitlementKeyDevice)+"="+d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.SSH {
|
if c.SSH {
|
||||||
msgsFS = append(msgsFS, " - Forwarding default SSH agent socket")
|
msgsFS = append(msgsFS, " - Forwarding default SSH agent socket")
|
||||||
flagsFS = append(flagsFS, string(EntitlementKeySSH))
|
flagsFS = append(flagsFS, string(EntitlementKeySSH))
|
||||||
|
@@ -208,8 +208,8 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "NetworkHostMissing",
|
name: "NetworkHostMissing",
|
||||||
opt: build.Options{
|
opt: build.Options{
|
||||||
Allow: []entitlements.Entitlement{
|
Allow: []string{
|
||||||
entitlements.EntitlementNetworkHost,
|
entitlements.EntitlementNetworkHost.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
@@ -223,8 +223,8 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
NetworkHost: true,
|
NetworkHost: true,
|
||||||
},
|
},
|
||||||
opt: build.Options{
|
opt: build.Options{
|
||||||
Allow: []entitlements.Entitlement{
|
Allow: []string{
|
||||||
entitlements.EntitlementNetworkHost,
|
entitlements.EntitlementNetworkHost.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
@@ -234,9 +234,9 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "SecurityAndNetworkHostMissing",
|
name: "SecurityAndNetworkHostMissing",
|
||||||
opt: build.Options{
|
opt: build.Options{
|
||||||
Allow: []entitlements.Entitlement{
|
Allow: []string{
|
||||||
entitlements.EntitlementNetworkHost,
|
entitlements.EntitlementNetworkHost.String(),
|
||||||
entitlements.EntitlementSecurityInsecure,
|
entitlements.EntitlementSecurityInsecure.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
@@ -251,9 +251,9 @@ func TestValidateEntitlements(t *testing.T) {
|
|||||||
NetworkHost: true,
|
NetworkHost: true,
|
||||||
},
|
},
|
||||||
opt: build.Options{
|
opt: build.Options{
|
||||||
Allow: []entitlements.Entitlement{
|
Allow: []string{
|
||||||
entitlements.EntitlementNetworkHost,
|
entitlements.EntitlementNetworkHost.String(),
|
||||||
entitlements.EntitlementSecurityInsecure,
|
entitlements.EntitlementSecurityInsecure.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: EntitlementConf{
|
expected: EntitlementConf{
|
||||||
|
@@ -2,8 +2,10 @@ package bake
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
hcl "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -606,7 +608,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
|
|||||||
target "app" {
|
target "app" {
|
||||||
attest = [
|
attest = [
|
||||||
{ type = "provenance", mode = "max" },
|
{ type = "provenance", mode = "max" },
|
||||||
"type=sbom,disabled=true",
|
"type=sbom,disabled=true,generator=foo,\"ENV1=bar,baz\",ENV2=hello",
|
||||||
]
|
]
|
||||||
|
|
||||||
cache-from = [
|
cache-from = [
|
||||||
@@ -639,7 +641,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, []string{"type=provenance,mode=max", "type=sbom,disabled=true"}, stringify(c.Targets[0].Attest))
|
require.Equal(t, []string{"type=provenance,mode=max", "type=sbom,disabled=true,\"ENV1=bar,baz\",ENV2=hello,generator=foo"}, stringify(c.Targets[0].Attest))
|
||||||
require.Equal(t, []string{"type=local,dest=../out", "type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
|
require.Equal(t, []string{"type=local,dest=../out", "type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
|
||||||
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
|
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
|
||||||
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo))
|
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo))
|
||||||
@@ -647,7 +649,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
|
|||||||
require.Equal(t, []string{"default", "key=path/to/key"}, stringify(c.Targets[0].SSH))
|
require.Equal(t, []string{"default", "key=path/to/key"}, stringify(c.Targets[0].SSH))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
|
func TestHCLAttrsCapsuleType_ObjectVars(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "foo" {
|
variable "foo" {
|
||||||
default = "bar"
|
default = "bar"
|
||||||
@@ -716,6 +718,52 @@ func TestHCLAttrsCapsuleTypeVars(t *testing.T) {
|
|||||||
require.Equal(t, []string{"id=oci,src=/local/secret"}, stringify(web.Secrets))
|
require.Equal(t, []string{"id=oci,src=/local/secret"}, stringify(web.Secrets))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHCLAttrsCapsuleType_MissingVars(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "app" {
|
||||||
|
attest = [
|
||||||
|
"type=sbom,disabled=${SBOM}",
|
||||||
|
]
|
||||||
|
|
||||||
|
cache-from = [
|
||||||
|
{ type = "registry", ref = "user/app:${FOO1}" },
|
||||||
|
"type=local,src=path/to/cache:${FOO2}",
|
||||||
|
]
|
||||||
|
|
||||||
|
cache-to = [
|
||||||
|
{ type = "local", dest = "path/to/${BAR}" },
|
||||||
|
]
|
||||||
|
|
||||||
|
output = [
|
||||||
|
{ type = "oci", dest = "../${OUTPUT}.tar" },
|
||||||
|
]
|
||||||
|
|
||||||
|
secret = [
|
||||||
|
{ id = "mysecret", src = "/local/${SECRET}" },
|
||||||
|
]
|
||||||
|
|
||||||
|
ssh = [
|
||||||
|
{ id = "key", paths = ["path/to/${SSH_KEY}"] },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.ErrorAs(t, err, &diags)
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`There is no variable named "([\w\d_]+)"`)
|
||||||
|
var actual []string
|
||||||
|
for _, diag := range diags {
|
||||||
|
if m := re.FindStringSubmatch(diag.Error()); m != nil {
|
||||||
|
actual = append(actual, m[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.ElementsMatch(t,
|
||||||
|
[]string{"SBOM", "FOO1", "FOO2", "BAR", "OUTPUT", "SECRET", "SSH_KEY"},
|
||||||
|
actual)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHCLMultiFileAttrs(t *testing.T) {
|
func TestHCLMultiFileAttrs(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "FOO" {
|
variable "FOO" {
|
||||||
|
@@ -40,7 +40,6 @@ import (
|
|||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
||||||
"github.com/moby/buildkit/util/entitlements"
|
|
||||||
"github.com/moby/buildkit/util/progress/progresswriter"
|
"github.com/moby/buildkit/util/progress/progresswriter"
|
||||||
"github.com/moby/buildkit/util/tracing"
|
"github.com/moby/buildkit/util/tracing"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
@@ -63,7 +62,7 @@ type Options struct {
|
|||||||
Inputs Inputs
|
Inputs Inputs
|
||||||
|
|
||||||
Ref string
|
Ref string
|
||||||
Allow []entitlements.Entitlement
|
Allow []string
|
||||||
Attests map[string]*string
|
Attests map[string]*string
|
||||||
BuildArgs map[string]string
|
BuildArgs map[string]string
|
||||||
CacheFrom []client.CacheOptionsEntry
|
CacheFrom []client.CacheOptionsEntry
|
||||||
@@ -835,7 +834,7 @@ func remoteDigestWithMoby(ctx context.Context, d *driver.DriverHandle, name stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
img, _, err := api.ImageInspectWithRaw(ctx, name)
|
img, err := api.ImageInspect(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@@ -318,7 +318,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
|
|||||||
switch opt.NetworkMode {
|
switch opt.NetworkMode {
|
||||||
case "host":
|
case "host":
|
||||||
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
||||||
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
|
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost.String())
|
||||||
case "none":
|
case "none":
|
||||||
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
||||||
case "", "default":
|
case "", "default":
|
||||||
|
@@ -32,10 +32,11 @@ type Node struct {
|
|||||||
Err error
|
Err error
|
||||||
|
|
||||||
// worker settings
|
// worker settings
|
||||||
IDs []string
|
IDs []string
|
||||||
Platforms []ocispecs.Platform
|
Platforms []ocispecs.Platform
|
||||||
GCPolicy []client.PruneInfo
|
GCPolicy []client.PruneInfo
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
|
CDIDevices []client.CDIDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes returns nodes for this builder.
|
// Nodes returns nodes for this builder.
|
||||||
@@ -259,6 +260,7 @@ func (n *Node) loadData(ctx context.Context, clientOpt ...client.ClientOpt) erro
|
|||||||
n.GCPolicy = w.GCPolicy
|
n.GCPolicy = w.GCPolicy
|
||||||
n.Labels = w.Labels
|
n.Labels = w.Labels
|
||||||
}
|
}
|
||||||
|
n.CDIDevices = w.CDIDevices
|
||||||
}
|
}
|
||||||
sort.Strings(n.IDs)
|
sort.Strings(n.IDs)
|
||||||
n.Platforms = platformutil.Dedupe(n.Platforms)
|
n.Platforms = platformutil.Dedupe(n.Platforms)
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/buildx/commands"
|
"github.com/docker/buildx/commands"
|
||||||
controllererrors "github.com/docker/buildx/controller/errdefs"
|
controllererrors "github.com/docker/buildx/controller/errdefs"
|
||||||
@@ -41,7 +42,8 @@ func runStandalone(cmd *command.DockerCli) error {
|
|||||||
}
|
}
|
||||||
defer flushMetrics(cmd)
|
defer flushMetrics(cmd)
|
||||||
|
|
||||||
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
executable := os.Args[0]
|
||||||
|
rootCmd := commands.NewRootCmd(filepath.Base(executable), false, cmd)
|
||||||
return rootCmd.Execute()
|
return rootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -66,7 +66,11 @@ type bakeOptions struct {
|
|||||||
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
||||||
mp := dockerCli.MeterProvider()
|
mp := dockerCli.MeterProvider()
|
||||||
|
|
||||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, append([]string{"bake"}, targets...),
|
||||||
|
attribute.String("builder", in.builder),
|
||||||
|
attribute.StringSlice("targets", targets),
|
||||||
|
attribute.StringSlice("files", in.files),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -271,8 +275,10 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := exp.Prompt(ctx, url != "", &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
|
if progressMode != progressui.RawJSONMode {
|
||||||
return err
|
if err := exp.Prompt(ctx, url != "", &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if printer.IsDone() {
|
if printer.IsDone() {
|
||||||
// init new printer as old one was stopped to show the prompt
|
// init new printer as old one was stopped to show the prompt
|
||||||
@@ -281,7 +287,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := saveLocalStateGroup(dockerCli, in, targets, bo, overrides, def); err != nil {
|
if err := saveLocalStateGroup(dockerCli, in, targets, bo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +492,14 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string, bo map[string]build.Options, overrides []string, def any) error {
|
func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string, bo map[string]build.Options) error {
|
||||||
|
l, err := localstate.New(confutil.NewConfig(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer l.MigrateIfNeeded()
|
||||||
|
|
||||||
prm := confutil.MetadataProvenance()
|
prm := confutil.MetadataProvenance()
|
||||||
if len(in.metadataFile) == 0 {
|
if len(in.metadataFile) == 0 {
|
||||||
prm = confutil.MetadataProvenanceModeDisabled
|
prm = confutil.MetadataProvenanceModeDisabled
|
||||||
@@ -506,19 +519,10 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string
|
|||||||
if len(refs) == 0 {
|
if len(refs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
l, err := localstate.New(confutil.NewConfig(dockerCli))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dtdef, err := json.MarshalIndent(def, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return l.SaveGroup(groupRef, localstate.StateGroup{
|
return l.SaveGroup(groupRef, localstate.StateGroup{
|
||||||
Definition: dtdef,
|
Refs: refs,
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
Inputs: overrides,
|
|
||||||
Refs: refs,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,7 +41,7 @@ import (
|
|||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
dockeropts "github.com/docker/cli/opts"
|
dockeropts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/atomicwriter"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||||
"github.com/moby/buildkit/frontend/subrequests"
|
"github.com/moby/buildkit/frontend/subrequests"
|
||||||
@@ -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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts.CacheTo, err = buildflags.ParseCacheEntry(o.cacheTo)
|
opts.CacheFrom = cacheFrom.ToPB()
|
||||||
|
|
||||||
|
cacheTo, err := buildflags.ParseCacheEntry(o.cacheTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
opts.CacheTo = cacheTo.ToPB()
|
||||||
|
|
||||||
opts.Secrets, err = buildflags.ParseSecretSpecs(o.secrets)
|
opts.Secrets, err = buildflags.ParseSecretSpecs(o.secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -282,7 +285,11 @@ func (o *buildOptionsHash) String() string {
|
|||||||
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
|
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
|
||||||
mp := dockerCli.MeterProvider()
|
mp := dockerCli.MeterProvider()
|
||||||
|
|
||||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, []string{"build", options.contextPath},
|
||||||
|
attribute.String("builder", options.builder),
|
||||||
|
attribute.String("context", options.contextPath),
|
||||||
|
attribute.String("dockerfile", options.dockerfileName),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -463,7 +470,7 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var be *controllererrors.BuildError
|
var be *controllererrors.BuildError
|
||||||
if errors.As(err, &be) {
|
if errors.As(err, &be) {
|
||||||
ref = be.Ref
|
ref = be.SessionID
|
||||||
retErr = err
|
retErr = err
|
||||||
// We can proceed to monitor
|
// We can proceed to monitor
|
||||||
} else {
|
} else {
|
||||||
@@ -590,7 +597,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
|
|
||||||
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, `Add a custom host-to-IP mapping (format: "host:ip")`)
|
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, `Add a custom host-to-IP mapping (format: "host:ip")`)
|
||||||
|
|
||||||
flags.StringSliceVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
flags.StringArrayVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
||||||
|
|
||||||
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
||||||
|
|
||||||
@@ -742,7 +749,7 @@ func writeMetadataFile(filename string, dt interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ioutils.AtomicWriteFile(filename, b, 0644)
|
return atomicwriter.WriteFile(filename, b, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} {
|
func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package history
|
package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd/v2/core/content"
|
"github.com/containerd/containerd/v2/core/content"
|
||||||
@@ -24,21 +26,135 @@ import (
|
|||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/cli/debug"
|
||||||
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
||||||
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
||||||
controlapi "github.com/moby/buildkit/api/services/control"
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
|
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/moby/buildkit/util/stack"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tonistiigi/go-csvvalue"
|
"github.com/tonistiigi/go-csvvalue"
|
||||||
|
spb "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
proto "google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statusT string
|
||||||
|
|
||||||
|
const (
|
||||||
|
statusComplete statusT = "completed"
|
||||||
|
statusRunning statusT = "running"
|
||||||
|
statusError statusT = "failed"
|
||||||
|
statusCanceled statusT = "canceled"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
builder string
|
builder string
|
||||||
ref string
|
ref string
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
|
type inspectOutput struct {
|
||||||
|
Name string `json:",omitempty"`
|
||||||
|
Ref string
|
||||||
|
|
||||||
|
Context string `json:",omitempty"`
|
||||||
|
Dockerfile string `json:",omitempty"`
|
||||||
|
VCSRepository string `json:",omitempty"`
|
||||||
|
VCSRevision string `json:",omitempty"`
|
||||||
|
Target string `json:",omitempty"`
|
||||||
|
Platform []string `json:",omitempty"`
|
||||||
|
KeepGitDir bool `json:",omitempty"`
|
||||||
|
|
||||||
|
NamedContexts []keyValueOutput `json:",omitempty"`
|
||||||
|
|
||||||
|
StartedAt *time.Time `json:",omitempty"`
|
||||||
|
CompletedAt *time.Time `json:",omitempty"`
|
||||||
|
Duration time.Duration `json:",omitempty"`
|
||||||
|
Status statusT `json:",omitempty"`
|
||||||
|
Error *errorOutput `json:",omitempty"`
|
||||||
|
|
||||||
|
NumCompletedSteps int32
|
||||||
|
NumTotalSteps int32
|
||||||
|
NumCachedSteps int32
|
||||||
|
|
||||||
|
BuildArgs []keyValueOutput `json:",omitempty"`
|
||||||
|
Labels []keyValueOutput `json:",omitempty"`
|
||||||
|
|
||||||
|
Config configOutput `json:",omitempty"`
|
||||||
|
|
||||||
|
Materials []materialOutput `json:",omitempty"`
|
||||||
|
Attachments []attachmentOutput `json:",omitempty"`
|
||||||
|
|
||||||
|
Errors []string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type configOutput struct {
|
||||||
|
Network string `json:",omitempty"`
|
||||||
|
ExtraHosts []string `json:",omitempty"`
|
||||||
|
Hostname string `json:",omitempty"`
|
||||||
|
CgroupParent string `json:",omitempty"`
|
||||||
|
ImageResolveMode string `json:",omitempty"`
|
||||||
|
MultiPlatform bool `json:",omitempty"`
|
||||||
|
NoCache bool `json:",omitempty"`
|
||||||
|
NoCacheFilter []string `json:",omitempty"`
|
||||||
|
|
||||||
|
ShmSize string `json:",omitempty"`
|
||||||
|
Ulimit string `json:",omitempty"`
|
||||||
|
CacheMountNS string `json:",omitempty"`
|
||||||
|
DockerfileCheckConfig string `json:",omitempty"`
|
||||||
|
SourceDateEpoch string `json:",omitempty"`
|
||||||
|
SandboxHostname string `json:",omitempty"`
|
||||||
|
|
||||||
|
RestRaw []keyValueOutput `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type materialOutput struct {
|
||||||
|
URI string `json:",omitempty"`
|
||||||
|
Digests []string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type attachmentOutput struct {
|
||||||
|
Digest string `json:",omitempty"`
|
||||||
|
Platform string `json:",omitempty"`
|
||||||
|
Type string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorOutput struct {
|
||||||
|
Code int `json:",omitempty"`
|
||||||
|
Message string `json:",omitempty"`
|
||||||
|
Name string `json:",omitempty"`
|
||||||
|
Logs []string `json:",omitempty"`
|
||||||
|
Sources []byte `json:",omitempty"`
|
||||||
|
Stack []byte `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyValueOutput struct {
|
||||||
|
Name string `json:",omitempty"`
|
||||||
|
Value string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAttr[T any](attrs map[string]string, k string, dest *T, f func(v string) (T, bool)) {
|
||||||
|
if sv, ok := attrs[k]; ok {
|
||||||
|
if f != nil {
|
||||||
|
v, ok := f(sv)
|
||||||
|
if ok {
|
||||||
|
*dest = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d, ok := any(dest).(*string); ok {
|
||||||
|
*d = sv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(attrs, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
|
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
|
||||||
@@ -77,28 +193,36 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
|||||||
|
|
||||||
rec := &recs[0]
|
rec := &recs[0]
|
||||||
|
|
||||||
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
store := proxy.NewContentStore(c.ContentClient())
|
||||||
|
|
||||||
|
var defaultPlatform string
|
||||||
|
workers, err := c.ListWorkers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to list workers")
|
||||||
|
}
|
||||||
|
workers0:
|
||||||
|
for _, w := range workers {
|
||||||
|
for _, p := range w.Platforms {
|
||||||
|
defaultPlatform = platforms.FormatAll(platforms.Normalize(p))
|
||||||
|
break workers0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ls, err := localstate.New(confutil.NewConfig(dockerCli))
|
ls, err := localstate.New(confutil.NewConfig(dockerCli))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
|
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
|
||||||
|
|
||||||
tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
|
||||||
|
|
||||||
attrs := rec.FrontendAttrs
|
attrs := rec.FrontendAttrs
|
||||||
delete(attrs, "frontend.caps")
|
delete(attrs, "frontend.caps")
|
||||||
|
|
||||||
writeAttr := func(k, name string, f func(v string) (string, bool)) {
|
var out inspectOutput
|
||||||
if v, ok := attrs[k]; ok {
|
|
||||||
if f != nil {
|
|
||||||
v, ok = f(v)
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
fmt.Fprintf(tw, "%s:\t%s\n", name, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(attrs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
var context string
|
var context string
|
||||||
var dockerfile string
|
var dockerfile string
|
||||||
@@ -137,131 +261,171 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
|||||||
}
|
}
|
||||||
delete(attrs, "filename")
|
delete(attrs, "filename")
|
||||||
|
|
||||||
if context != "" {
|
out.Name = buildName(rec.FrontendAttrs, st)
|
||||||
fmt.Fprintf(tw, "Context:\t%s\n", context)
|
out.Ref = rec.Ref
|
||||||
}
|
|
||||||
if dockerfile != "" {
|
out.Context = context
|
||||||
fmt.Fprintf(tw, "Dockerfile:\t%s\n", dockerfile)
|
out.Dockerfile = dockerfile
|
||||||
}
|
|
||||||
if _, ok := attrs["context"]; !ok {
|
if _, ok := attrs["context"]; !ok {
|
||||||
if src, ok := attrs["vcs:source"]; ok {
|
if src, ok := attrs["vcs:source"]; ok {
|
||||||
fmt.Fprintf(tw, "VCS Repository:\t%s\n", src)
|
out.VCSRepository = src
|
||||||
}
|
}
|
||||||
if rev, ok := attrs["vcs:revision"]; ok {
|
if rev, ok := attrs["vcs:revision"]; ok {
|
||||||
fmt.Fprintf(tw, "VCS Revision:\t%s\n", rev)
|
out.VCSRevision = rev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeAttr("target", "Target", nil)
|
readAttr(attrs, "target", &out.Target, nil)
|
||||||
writeAttr("platform", "Platform", func(v string) (string, bool) {
|
|
||||||
return tryParseValue(v, func(v string) (string, error) {
|
readAttr(attrs, "platform", &out.Platform, func(v string) ([]string, bool) {
|
||||||
|
return tryParseValue(v, &out.Errors, func(v string) ([]string, error) {
|
||||||
var pp []string
|
var pp []string
|
||||||
for _, v := range strings.Split(v, ",") {
|
for _, v := range strings.Split(v, ",") {
|
||||||
p, err := platforms.Parse(v)
|
p, err := platforms.Parse(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
pp = append(pp, platforms.FormatAll(platforms.Normalize(p)))
|
pp = append(pp, platforms.FormatAll(platforms.Normalize(p)))
|
||||||
}
|
}
|
||||||
return strings.Join(pp, ", "), nil
|
if len(pp) == 0 {
|
||||||
}), true
|
pp = append(pp, defaultPlatform)
|
||||||
})
|
|
||||||
writeAttr("build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR", "Keep Git Dir", func(v string) (string, bool) {
|
|
||||||
return tryParseValue(v, func(v string) (string, error) {
|
|
||||||
b, err := strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
return strconv.FormatBool(b), nil
|
return pp, nil
|
||||||
}), true
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
tw.Flush()
|
readAttr(attrs, "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR", &out.KeepGitDir, func(v string) (bool, bool) {
|
||||||
|
return tryParseValue(v, &out.Errors, strconv.ParseBool)
|
||||||
|
})
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out())
|
out.NamedContexts = readKeyValues(attrs, "context:")
|
||||||
|
|
||||||
printTable(dockerCli.Out(), attrs, "context:", "Named Context")
|
if rec.CreatedAt != nil {
|
||||||
|
tm := rec.CreatedAt.AsTime().Local()
|
||||||
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
out.StartedAt = &tm
|
||||||
|
|
||||||
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Format("2006-01-02 15:04:05"))
|
|
||||||
var duration time.Duration
|
|
||||||
var status string
|
|
||||||
if rec.CompletedAt != nil {
|
|
||||||
duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
|
|
||||||
} else {
|
|
||||||
duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
|
|
||||||
status = " (running)"
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), status)
|
out.Status = statusRunning
|
||||||
if rec.Error != nil {
|
|
||||||
if codes.Code(rec.Error.Code) == codes.Canceled {
|
if rec.CompletedAt != nil {
|
||||||
fmt.Fprintf(tw, "Status:\tCanceled\n")
|
tm := rec.CompletedAt.AsTime().Local()
|
||||||
} else {
|
out.CompletedAt = &tm
|
||||||
fmt.Fprintf(tw, "Error:\t%s %s\n", codes.Code(rec.Error.Code).String(), rec.Error.Message)
|
out.Status = statusComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec.Error != nil || rec.ExternalError != nil {
|
||||||
|
out.Error = &errorOutput{}
|
||||||
|
if rec.Error != nil {
|
||||||
|
if codes.Code(rec.Error.Code) == codes.Canceled {
|
||||||
|
out.Status = statusCanceled
|
||||||
|
} else {
|
||||||
|
out.Status = statusError
|
||||||
|
}
|
||||||
|
out.Error.Code = int(codes.Code(rec.Error.Code))
|
||||||
|
out.Error.Message = rec.Error.Message
|
||||||
|
}
|
||||||
|
if rec.ExternalError != nil {
|
||||||
|
dt, err := content.ReadBlob(ctx, store, ociDesc(rec.ExternalError))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read external error %s", rec.ExternalError.Digest)
|
||||||
|
}
|
||||||
|
var st spb.Status
|
||||||
|
if err := proto.Unmarshal(dt, &st); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to unmarshal external error %s", rec.ExternalError.Digest)
|
||||||
|
}
|
||||||
|
retErr := grpcerrors.FromGRPC(status.ErrorProto(&st))
|
||||||
|
var errsources bytes.Buffer
|
||||||
|
for _, s := range errdefs.Sources(retErr) {
|
||||||
|
s.Print(&errsources)
|
||||||
|
errsources.WriteString("\n")
|
||||||
|
}
|
||||||
|
out.Error.Sources = errsources.Bytes()
|
||||||
|
var ve *errdefs.VertexError
|
||||||
|
if errors.As(retErr, &ve) {
|
||||||
|
dgst, err := digest.Parse(ve.Vertex.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse vertex digest %s", ve.Vertex.Digest)
|
||||||
|
}
|
||||||
|
name, logs, err := loadVertexLogs(ctx, c, rec.Ref, dgst, 16)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to load vertex logs %s", dgst)
|
||||||
|
}
|
||||||
|
out.Error.Name = name
|
||||||
|
out.Error.Logs = logs
|
||||||
|
}
|
||||||
|
out.Error.Stack = []byte(fmt.Sprintf("%+v", stack.Formatter(retErr)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(tw, "Build Steps:\t%d/%d (%.0f%% cached)\n", rec.NumCompletedSteps, rec.NumTotalSteps, float64(rec.NumCachedSteps)/float64(rec.NumTotalSteps)*100)
|
|
||||||
tw.Flush()
|
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out())
|
if out.StartedAt != nil {
|
||||||
|
if out.CompletedAt != nil {
|
||||||
|
out.Duration = out.CompletedAt.Sub(*out.StartedAt)
|
||||||
|
} else {
|
||||||
|
out.Duration = rec.currentTimestamp.Sub(*out.StartedAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
out.NumCompletedSteps = rec.NumCompletedSteps
|
||||||
|
out.NumTotalSteps = rec.NumTotalSteps
|
||||||
|
out.NumCachedSteps = rec.NumCachedSteps
|
||||||
|
|
||||||
writeAttr("force-network-mode", "Network", nil)
|
out.BuildArgs = readKeyValues(attrs, "build-arg:")
|
||||||
writeAttr("hostname", "Hostname", nil)
|
out.Labels = readKeyValues(attrs, "label:")
|
||||||
writeAttr("add-hosts", "Extra Hosts", func(v string) (string, bool) {
|
|
||||||
return tryParseValue(v, func(v string) (string, error) {
|
readAttr(attrs, "force-network-mode", &out.Config.Network, nil)
|
||||||
|
readAttr(attrs, "hostname", &out.Config.Hostname, nil)
|
||||||
|
readAttr(attrs, "cgroup-parent", &out.Config.CgroupParent, nil)
|
||||||
|
readAttr(attrs, "image-resolve-mode", &out.Config.ImageResolveMode, nil)
|
||||||
|
readAttr(attrs, "build-arg:BUILDKIT_MULTI_PLATFORM", &out.Config.MultiPlatform, func(v string) (bool, bool) {
|
||||||
|
return tryParseValue(v, &out.Errors, strconv.ParseBool)
|
||||||
|
})
|
||||||
|
readAttr(attrs, "multi-platform", &out.Config.MultiPlatform, func(v string) (bool, bool) {
|
||||||
|
return tryParseValue(v, &out.Errors, strconv.ParseBool)
|
||||||
|
})
|
||||||
|
readAttr(attrs, "no-cache", &out.Config.NoCache, func(v string) (bool, bool) {
|
||||||
|
if v == "" {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
})
|
||||||
|
readAttr(attrs, "no-cache", &out.Config.NoCacheFilter, func(v string) ([]string, bool) {
|
||||||
|
if v == "" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return strings.Split(v, ","), true
|
||||||
|
})
|
||||||
|
|
||||||
|
readAttr(attrs, "add-hosts", &out.Config.ExtraHosts, func(v string) ([]string, bool) {
|
||||||
|
return tryParseValue(v, &out.Errors, func(v string) ([]string, error) {
|
||||||
fields, err := csvvalue.Fields(v, nil)
|
fields, err := csvvalue.Fields(v, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return strings.Join(fields, ", "), nil
|
return fields, nil
|
||||||
}), true
|
})
|
||||||
})
|
})
|
||||||
writeAttr("cgroup-parent", "Cgroup Parent", nil)
|
|
||||||
writeAttr("image-resolve-mode", "Image Resolve Mode", nil)
|
|
||||||
writeAttr("multi-platform", "Force Multi-Platform", nil)
|
|
||||||
writeAttr("build-arg:BUILDKIT_MULTI_PLATFORM", "Force Multi-Platform", nil)
|
|
||||||
writeAttr("no-cache", "Disable Cache", func(v string) (string, bool) {
|
|
||||||
if v == "" {
|
|
||||||
return "true", true
|
|
||||||
}
|
|
||||||
return v, true
|
|
||||||
})
|
|
||||||
writeAttr("shm-size", "Shm Size", nil)
|
|
||||||
writeAttr("ulimit", "Resource Limits", nil)
|
|
||||||
writeAttr("build-arg:BUILDKIT_CACHE_MOUNT_NS", "Cache Mount Namespace", nil)
|
|
||||||
writeAttr("build-arg:BUILDKIT_DOCKERFILE_CHECK", "Dockerfile Check Config", nil)
|
|
||||||
writeAttr("build-arg:SOURCE_DATE_EPOCH", "Source Date Epoch", nil)
|
|
||||||
writeAttr("build-arg:SANDBOX_HOSTNAME", "Sandbox Hostname", nil)
|
|
||||||
|
|
||||||
var unusedAttrs []string
|
readAttr(attrs, "shm-size", &out.Config.ShmSize, nil)
|
||||||
|
readAttr(attrs, "ulimit", &out.Config.Ulimit, nil)
|
||||||
|
readAttr(attrs, "build-arg:BUILDKIT_CACHE_MOUNT_NS", &out.Config.CacheMountNS, nil)
|
||||||
|
readAttr(attrs, "build-arg:BUILDKIT_DOCKERFILE_CHECK", &out.Config.DockerfileCheckConfig, nil)
|
||||||
|
readAttr(attrs, "build-arg:SOURCE_DATE_EPOCH", &out.Config.SourceDateEpoch, nil)
|
||||||
|
readAttr(attrs, "build-arg:SANDBOX_HOSTNAME", &out.Config.SandboxHostname, nil)
|
||||||
|
|
||||||
|
var unusedAttrs []keyValueOutput
|
||||||
for k := range attrs {
|
for k := range attrs {
|
||||||
if strings.HasPrefix(k, "vcs:") || strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "label:") || strings.HasPrefix(k, "context:") || strings.HasPrefix(k, "attest:") {
|
if strings.HasPrefix(k, "vcs:") || strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "label:") || strings.HasPrefix(k, "context:") || strings.HasPrefix(k, "attest:") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
unusedAttrs = append(unusedAttrs, k)
|
unusedAttrs = append(unusedAttrs, keyValueOutput{
|
||||||
|
Name: k,
|
||||||
|
Value: attrs[k],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
slices.Sort(unusedAttrs)
|
slices.SortFunc(unusedAttrs, func(a, b keyValueOutput) int {
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
for _, k := range unusedAttrs {
|
})
|
||||||
fmt.Fprintf(tw, "%s:\t%s\n", k, attrs[k])
|
out.Config.RestRaw = unusedAttrs
|
||||||
}
|
|
||||||
|
|
||||||
tw.Flush()
|
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out())
|
|
||||||
|
|
||||||
printTable(dockerCli.Out(), attrs, "build-arg:", "Build Arg")
|
|
||||||
printTable(dockerCli.Out(), attrs, "label:", "Label")
|
|
||||||
|
|
||||||
c, err := rec.node.Driver.Client(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
store := proxy.NewContentStore(c.ContentClient())
|
|
||||||
|
|
||||||
attachments, err := allAttachments(ctx, store, *rec)
|
attachments, err := allAttachments(ctx, store, *rec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -273,45 +437,215 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
|||||||
})
|
})
|
||||||
if provIndex != -1 {
|
if provIndex != -1 {
|
||||||
prov := attachments[provIndex]
|
prov := attachments[provIndex]
|
||||||
|
|
||||||
dt, err := content.ReadBlob(ctx, store, prov.descr)
|
dt, err := content.ReadBlob(ctx, store, prov.descr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("failed to read provenance %s: %v", prov.descr.Digest, err)
|
return errors.Errorf("failed to read provenance %s: %v", prov.descr.Digest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pred provenancetypes.ProvenancePredicate
|
var pred provenancetypes.ProvenancePredicate
|
||||||
if err := json.Unmarshal(dt, &pred); err != nil {
|
if err := json.Unmarshal(dt, &pred); err != nil {
|
||||||
return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err)
|
return errors.Errorf("failed to unmarshal provenance %s: %v", prov.descr.Digest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out(), "Materials:")
|
|
||||||
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
|
||||||
fmt.Fprintf(tw, "URI\tDIGEST\n")
|
|
||||||
for _, m := range pred.Materials {
|
for _, m := range pred.Materials {
|
||||||
fmt.Fprintf(tw, "%s\t%s\n", m.URI, strings.Join(digestSetToDigests(m.Digest), ", "))
|
out.Materials = append(out.Materials, materialOutput{
|
||||||
|
URI: m.URI,
|
||||||
|
Digests: digestSetToDigests(m.Digest),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
tw.Flush()
|
|
||||||
fmt.Fprintln(dockerCli.Out())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(attachments) > 0 {
|
if len(attachments) > 0 {
|
||||||
fmt.Fprintf(tw, "Attachments:\n")
|
|
||||||
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
|
||||||
fmt.Fprintf(tw, "DIGEST\tPLATFORM\tTYPE\n")
|
|
||||||
for _, a := range attachments {
|
for _, a := range attachments {
|
||||||
p := ""
|
p := ""
|
||||||
if a.platform != nil {
|
if a.platform != nil {
|
||||||
p = platforms.FormatAll(*a.platform)
|
p = platforms.FormatAll(*a.platform)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(tw, "%s\t%s\t%s\n", a.descr.Digest, p, descrType(a.descr))
|
out.Attachments = append(out.Attachments, attachmentOutput{
|
||||||
|
Digest: a.descr.Digest.String(),
|
||||||
|
Platform: p,
|
||||||
|
Type: descrType(a.descr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.format == formatter.JSONFormatKey {
|
||||||
|
enc := json.NewEncoder(dockerCli.Out())
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
return enc.Encode(out)
|
||||||
|
} else if opts.format != formatter.PrettyFormatKey {
|
||||||
|
tmpl, err := template.New("inspect").Parse(opts.format)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse format template")
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&buf, out); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to execute format template")
|
||||||
|
}
|
||||||
|
fmt.Fprintln(dockerCli.Out(), buf.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
|
if out.Name != "" {
|
||||||
|
fmt.Fprintf(tw, "Name:\t%s\n", out.Name)
|
||||||
|
}
|
||||||
|
if opts.ref == "" && out.Ref != "" {
|
||||||
|
fmt.Fprintf(tw, "Ref:\t%s\n", out.Ref)
|
||||||
|
}
|
||||||
|
if out.Context != "" {
|
||||||
|
fmt.Fprintf(tw, "Context:\t%s\n", out.Context)
|
||||||
|
}
|
||||||
|
if out.Dockerfile != "" {
|
||||||
|
fmt.Fprintf(tw, "Dockerfile:\t%s\n", out.Dockerfile)
|
||||||
|
}
|
||||||
|
if out.VCSRepository != "" {
|
||||||
|
fmt.Fprintf(tw, "VCS Repository:\t%s\n", out.VCSRepository)
|
||||||
|
}
|
||||||
|
if out.VCSRevision != "" {
|
||||||
|
fmt.Fprintf(tw, "VCS Revision:\t%s\n", out.VCSRevision)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Target != "" {
|
||||||
|
fmt.Fprintf(tw, "Target:\t%s\n", out.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out.Platform) > 0 {
|
||||||
|
fmt.Fprintf(tw, "Platforms:\t%s\n", strings.Join(out.Platform, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.KeepGitDir {
|
||||||
|
fmt.Fprintf(tw, "Keep Git Dir:\t%s\n", strconv.FormatBool(out.KeepGitDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.Flush()
|
||||||
|
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
|
||||||
|
printTable(dockerCli.Out(), out.NamedContexts, "Named Context")
|
||||||
|
|
||||||
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "Started:\t%s\n", out.StartedAt.Format("2006-01-02 15:04:05"))
|
||||||
|
var statusStr string
|
||||||
|
if out.Status == statusRunning {
|
||||||
|
statusStr = " (running)"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(out.Duration), statusStr)
|
||||||
|
|
||||||
|
if out.Status == statusError {
|
||||||
|
fmt.Fprintf(tw, "Error:\t%s %s\n", codes.Code(rec.Error.Code).String(), rec.Error.Message)
|
||||||
|
} else if out.Status == statusCanceled {
|
||||||
|
fmt.Fprintf(tw, "Status:\tCanceled\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "Build Steps:\t%d/%d (%.0f%% cached)\n", out.NumCompletedSteps, out.NumTotalSteps, float64(out.NumCachedSteps)/float64(out.NumTotalSteps)*100)
|
||||||
|
tw.Flush()
|
||||||
|
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
|
||||||
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
|
if out.Config.Network != "" {
|
||||||
|
fmt.Fprintf(tw, "Network:\t%s\n", out.Config.Network)
|
||||||
|
}
|
||||||
|
if out.Config.Hostname != "" {
|
||||||
|
fmt.Fprintf(tw, "Hostname:\t%s\n", out.Config.Hostname)
|
||||||
|
}
|
||||||
|
if len(out.Config.ExtraHosts) > 0 {
|
||||||
|
fmt.Fprintf(tw, "Extra Hosts:\t%s\n", strings.Join(out.Config.ExtraHosts, ", "))
|
||||||
|
}
|
||||||
|
if out.Config.CgroupParent != "" {
|
||||||
|
fmt.Fprintf(tw, "Cgroup Parent:\t%s\n", out.Config.CgroupParent)
|
||||||
|
}
|
||||||
|
if out.Config.ImageResolveMode != "" {
|
||||||
|
fmt.Fprintf(tw, "Image Resolve Mode:\t%s\n", out.Config.ImageResolveMode)
|
||||||
|
}
|
||||||
|
if out.Config.MultiPlatform {
|
||||||
|
fmt.Fprintf(tw, "Multi-Platform:\t%s\n", strconv.FormatBool(out.Config.MultiPlatform))
|
||||||
|
}
|
||||||
|
if out.Config.NoCache {
|
||||||
|
fmt.Fprintf(tw, "No Cache:\t%s\n", strconv.FormatBool(out.Config.NoCache))
|
||||||
|
}
|
||||||
|
if len(out.Config.NoCacheFilter) > 0 {
|
||||||
|
fmt.Fprintf(tw, "No Cache Filter:\t%s\n", strings.Join(out.Config.NoCacheFilter, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Config.ShmSize != "" {
|
||||||
|
fmt.Fprintf(tw, "Shm Size:\t%s\n", out.Config.ShmSize)
|
||||||
|
}
|
||||||
|
if out.Config.Ulimit != "" {
|
||||||
|
fmt.Fprintf(tw, "Resource Limits:\t%s\n", out.Config.Ulimit)
|
||||||
|
}
|
||||||
|
if out.Config.CacheMountNS != "" {
|
||||||
|
fmt.Fprintf(tw, "Cache Mount Namespace:\t%s\n", out.Config.CacheMountNS)
|
||||||
|
}
|
||||||
|
if out.Config.DockerfileCheckConfig != "" {
|
||||||
|
fmt.Fprintf(tw, "Dockerfile Check Config:\t%s\n", out.Config.DockerfileCheckConfig)
|
||||||
|
}
|
||||||
|
if out.Config.SourceDateEpoch != "" {
|
||||||
|
fmt.Fprintf(tw, "Source Date Epoch:\t%s\n", out.Config.SourceDateEpoch)
|
||||||
|
}
|
||||||
|
if out.Config.SandboxHostname != "" {
|
||||||
|
fmt.Fprintf(tw, "Sandbox Hostname:\t%s\n", out.Config.SandboxHostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range out.Config.RestRaw {
|
||||||
|
fmt.Fprintf(tw, "%s:\t%s\n", kv.Name, kv.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.Flush()
|
||||||
|
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
|
||||||
|
printTable(dockerCli.Out(), out.BuildArgs, "Build Arg")
|
||||||
|
printTable(dockerCli.Out(), out.Labels, "Label")
|
||||||
|
|
||||||
|
if len(out.Materials) > 0 {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "Materials:")
|
||||||
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
fmt.Fprintf(tw, "URI\tDIGEST\n")
|
||||||
|
for _, m := range out.Materials {
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\n", m.URI, strings.Join(m.Digests, ", "))
|
||||||
}
|
}
|
||||||
tw.Flush()
|
tw.Flush()
|
||||||
fmt.Fprintln(dockerCli.Out())
|
fmt.Fprintln(dockerCli.Out())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(out.Attachments) > 0 {
|
||||||
|
fmt.Fprintf(tw, "Attachments:\n")
|
||||||
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
fmt.Fprintf(tw, "DIGEST\tPLATFORM\tTYPE\n")
|
||||||
|
for _, a := range out.Attachments {
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\t%s\n", a.Digest, a.Platform, a.Type)
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Error != nil {
|
||||||
|
if out.Error.Sources != nil {
|
||||||
|
fmt.Fprint(dockerCli.Out(), string(out.Error.Sources))
|
||||||
|
}
|
||||||
|
if len(out.Error.Logs) > 0 {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "Logs:")
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "> => %s:\n", out.Error.Name)
|
||||||
|
for _, l := range out.Error.Logs {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "> "+l)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
}
|
||||||
|
if len(out.Error.Stack) > 0 {
|
||||||
|
if debug.IsEnabled() {
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "\n%s\n", out.Error.Stack)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "Enable --debug to see stack traces for error\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
|
fmt.Fprintf(dockerCli.Out(), "Print build logs: docker buildx history logs %s\n", rec.Ref)
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "View build in Docker Desktop: %s\n", desktop.BuildURL(rec.Ref))
|
fmt.Fprintf(dockerCli.Out(), "View build in Docker Desktop: %s\n", desktop.BuildURL(fmt.Sprintf("%s/%s/%s", rec.node.Builder, rec.node.Name, rec.Ref)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -337,11 +671,79 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
attachmentCmd(dockerCli, rootOpts),
|
attachmentCmd(dockerCli, rootOpts),
|
||||||
)
|
)
|
||||||
|
|
||||||
// flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.format, "format", formatter.PrettyFormatKey, "Format the output")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadVertexLogs(ctx context.Context, c *client.Client, ref string, dgst digest.Digest, limit int) (string, []string, error) {
|
||||||
|
st, err := c.ControlClient().Status(ctx, &controlapi.StatusRequest{
|
||||||
|
Ref: ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var logs []string
|
||||||
|
lastState := map[int]int{}
|
||||||
|
|
||||||
|
loop0:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
st.CloseSend()
|
||||||
|
return "", nil, context.Cause(ctx)
|
||||||
|
default:
|
||||||
|
ev, err := st.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break loop0
|
||||||
|
}
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
ss := client.NewSolveStatus(ev)
|
||||||
|
for _, v := range ss.Vertexes {
|
||||||
|
if v.Digest == dgst {
|
||||||
|
name = v.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range ss.Logs {
|
||||||
|
if l.Vertex == dgst {
|
||||||
|
parts := bytes.Split(l.Data, []byte("\n"))
|
||||||
|
for i, p := range parts {
|
||||||
|
var wrote bool
|
||||||
|
if i == 0 {
|
||||||
|
idx, ok := lastState[l.Stream]
|
||||||
|
if ok && idx != -1 {
|
||||||
|
logs[idx] = logs[idx] + string(p)
|
||||||
|
wrote = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !wrote {
|
||||||
|
if len(p) > 0 {
|
||||||
|
logs = append(logs, string(p))
|
||||||
|
}
|
||||||
|
lastState[l.Stream] = len(logs) - 1
|
||||||
|
}
|
||||||
|
if i == len(parts)-1 && len(p) == 0 {
|
||||||
|
lastState[l.Stream] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 && len(logs) > limit {
|
||||||
|
logs = logs[len(logs)-limit:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
type attachment struct {
|
type attachment struct {
|
||||||
platform *ocispecs.Platform
|
platform *ocispecs.Platform
|
||||||
descr ocispecs.Descriptor
|
descr ocispecs.Descriptor
|
||||||
@@ -447,36 +849,48 @@ func descrType(desc ocispecs.Descriptor) string {
|
|||||||
return desc.MediaType
|
return desc.MediaType
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryParseValue(s string, f func(string) (string, error)) string {
|
func tryParseValue[T any](s string, errs *[]string, f func(string) (T, error)) (T, bool) {
|
||||||
v, err := f(s)
|
v, err := f(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("%s (%v)", s, err)
|
errStr := fmt.Sprintf("failed to parse %s: (%v)", s, err)
|
||||||
|
*errs = append(*errs, errStr)
|
||||||
}
|
}
|
||||||
return v
|
return v, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTable(w io.Writer, attrs map[string]string, prefix, title string) {
|
func printTable(w io.Writer, kvs []keyValueOutput, title string) {
|
||||||
var keys []string
|
if len(kvs) == 0 {
|
||||||
for k := range attrs {
|
|
||||||
if strings.HasPrefix(k, prefix) {
|
|
||||||
keys = append(keys, strings.TrimPrefix(k, prefix))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slices.Sort(keys)
|
|
||||||
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
||||||
fmt.Fprintf(tw, "%s\tVALUE\n", strings.ToUpper(title))
|
fmt.Fprintf(tw, "%s\tVALUE\n", strings.ToUpper(title))
|
||||||
for _, k := range keys {
|
for _, k := range kvs {
|
||||||
fmt.Fprintf(tw, "%s\t%s\n", k, attrs[prefix+k])
|
fmt.Fprintf(tw, "%s\t%s\n", k.Name, k.Value)
|
||||||
}
|
}
|
||||||
tw.Flush()
|
tw.Flush()
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readKeyValues(attrs map[string]string, prefix string) []keyValueOutput {
|
||||||
|
var out []keyValueOutput
|
||||||
|
for k, v := range attrs {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
out = append(out, keyValueOutput{
|
||||||
|
Name: strings.TrimPrefix(k, prefix),
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slices.SortFunc(out, func(a, b keyValueOutput) int {
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func digestSetToDigests(ds slsa.DigestSet) []string {
|
func digestSetToDigests(ds slsa.DigestSet) []string {
|
||||||
var out []string
|
var out []string
|
||||||
for k, v := range ds {
|
for k, v := range ds {
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
@@ -18,7 +19,6 @@ import (
|
|||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@@ -24,6 +24,7 @@ func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *c
|
|||||||
logsCmd(dockerCli, opts),
|
logsCmd(dockerCli, opts),
|
||||||
inspectCmd(dockerCli, opts),
|
inspectCmd(dockerCli, opts),
|
||||||
openCmd(dockerCli, opts),
|
openCmd(dockerCli, opts),
|
||||||
|
traceCmd(dockerCli, opts),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
260
commands/history/trace.go
Normal file
260
commands/history/trace.go
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
|
"github.com/containerd/containerd/v2/core/content/proxy"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/otelutil"
|
||||||
|
"github.com/docker/buildx/util/otelutil/jaeger"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/browser"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
jaegerui "github.com/tonistiigi/jaeger-ui-rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type traceOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
addr string
|
||||||
|
compare string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTrace(ctx context.Context, ref string, nodes []builder.Node) (string, []byte, error) {
|
||||||
|
var offset *int
|
||||||
|
if strings.HasPrefix(ref, "^") {
|
||||||
|
off, err := strconv.Atoi(ref[1:])
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, errors.Wrapf(err, "invalid offset %q", ref)
|
||||||
|
}
|
||||||
|
offset = &off
|
||||||
|
ref = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rec *historyRecord
|
||||||
|
|
||||||
|
if ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
for _, r := range recs {
|
||||||
|
if r.CompletedAt != nil {
|
||||||
|
if offset != nil {
|
||||||
|
if *offset > 0 {
|
||||||
|
*offset--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rec = &r
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if offset != nil && *offset > 0 {
|
||||||
|
return "", nil, errors.Errorf("no completed build found with offset %d", *offset)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rec = &recs[0]
|
||||||
|
}
|
||||||
|
if rec == nil {
|
||||||
|
if ref == "" {
|
||||||
|
return "", nil, errors.New("no records found")
|
||||||
|
}
|
||||||
|
return "", nil, errors.Errorf("no record found for ref %q", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec.CompletedAt == nil {
|
||||||
|
return "", nil, errors.Errorf("build %q is not completed, only completed builds can be traced", rec.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec.Trace == nil {
|
||||||
|
// build is complete but no trace yet. try to finalize the trace
|
||||||
|
time.Sleep(1 * time.Second) // give some extra time for last parts of trace to be written
|
||||||
|
|
||||||
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
_, err = c.ControlClient().UpdateBuildHistory(ctx, &controlapi.UpdateBuildHistoryRequest{
|
||||||
|
Ref: rec.Ref,
|
||||||
|
Finalize: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, rec.Ref, []builder.Node{*rec.node})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
return "", nil, errors.Errorf("build record %q was deleted", rec.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec = &recs[0]
|
||||||
|
if rec.Trace == nil {
|
||||||
|
return "", nil, errors.Errorf("build record %q is missing a trace", rec.Ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
store := proxy.NewContentStore(c.ContentClient())
|
||||||
|
|
||||||
|
ra, err := store.ReaderAt(ctx, ocispecs.Descriptor{
|
||||||
|
Digest: digest.Digest(rec.Trace.Digest),
|
||||||
|
MediaType: rec.Trace.MediaType,
|
||||||
|
Size: rec.Trace.Size,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
spans, err := otelutil.ParseSpanStubs(io.NewSectionReader(ra, 0, ra.Size()))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := struct {
|
||||||
|
Data []jaeger.Trace `json:"data"`
|
||||||
|
}{
|
||||||
|
Data: spans.JaegerData().Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wrapper.Data) == 0 {
|
||||||
|
return "", nil, errors.New("no trace data")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
if err := enc.Encode(wrapper); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(wrapper.Data[0].TraceID), buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrace(ctx context.Context, dockerCli command.Cli, opts traceOptions) error {
|
||||||
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := b.LoadNodes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Err != nil {
|
||||||
|
return node.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traceID, data, err := loadTrace(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv := jaegerui.NewServer(jaegerui.Config{})
|
||||||
|
if err := srv.AddTrace(traceID, bytes.NewReader(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url := "/trace/" + traceID
|
||||||
|
|
||||||
|
if opts.compare != "" {
|
||||||
|
traceIDcomp, data, err := loadTrace(ctx, opts.compare, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to load trace for %s", opts.compare)
|
||||||
|
}
|
||||||
|
if err := srv.AddTrace(traceIDcomp, bytes.NewReader(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url = "/trace/" + traceIDcomp + "..." + traceID
|
||||||
|
}
|
||||||
|
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !term && opts.compare == "" {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), string(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", opts.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
browser.OpenURL(url)
|
||||||
|
}()
|
||||||
|
|
||||||
|
url = "http://" + ln.Addr().String() + url
|
||||||
|
fmt.Fprintf(dockerCli.Err(), "Trace available at %s\n", url)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
ln.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = srv.Serve(ln)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options traceOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "trace [OPTIONS] [REF]",
|
||||||
|
Short: "Show the OpenTelemetry trace of a build record",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
options.ref = args[0]
|
||||||
|
}
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runTrace(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.addr, "addr", "127.0.0.1:0", "Address to bind the UI server")
|
||||||
|
flags.StringVar(&options.compare, "compare", "", "Compare with another build reference")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
@@ -115,6 +115,25 @@ func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions) e
|
|||||||
fmt.Fprintf(w, "\t%s:\t%s\n", k, v)
|
fmt.Fprintf(w, "\t%s:\t%s\n", k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(nodes[i].CDIDevices) > 0 {
|
||||||
|
fmt.Fprintf(w, "Devices:\n")
|
||||||
|
for _, dev := range nodes[i].CDIDevices {
|
||||||
|
fmt.Fprintf(w, "\tName:\t%s\n", dev.Name)
|
||||||
|
if dev.OnDemand {
|
||||||
|
fmt.Fprintf(w, "\tOn-Demand:\t%v\n", dev.OnDemand)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "\tAutomatically allowed:\t%v\n", dev.AutoAllow)
|
||||||
|
}
|
||||||
|
if len(dev.Annotations) > 0 {
|
||||||
|
fmt.Fprintf(w, "\tAnnotations:\n")
|
||||||
|
for k, v := range dev.Annotations {
|
||||||
|
fmt.Fprintf(w, "\t\t%s:\t%s\n", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for ri, rule := range nodes[i].GCPolicy {
|
for ri, rule := range nodes[i].GCPolicy {
|
||||||
fmt.Fprintf(w, "GC Policy rule#%d:\n", ri)
|
fmt.Fprintf(w, "GC Policy rule#%d:\n", ri)
|
||||||
fmt.Fprintf(w, "\tAll:\t%v\n", rule.All)
|
fmt.Fprintf(w, "\tAll:\t%v\n", rule.All)
|
||||||
|
@@ -159,6 +159,9 @@ func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builde
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if ctx.Format.IsJSON() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, n := range b.Nodes() {
|
for _, n := range b.Nodes() {
|
||||||
if n.Err != nil {
|
if n.Err != nil {
|
||||||
if ctx.Format.IsTable() {
|
if ctx.Format.IsTable() {
|
||||||
|
@@ -75,7 +75,9 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *controllerapi.Buil
|
|||||||
opts.Platforms = platforms
|
opts.Platforms = platforms
|
||||||
|
|
||||||
dockerConfig := dockerCli.ConfigFile()
|
dockerConfig := dockerCli.ConfigFile()
|
||||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig, nil))
|
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
|
||||||
|
ConfigFile: dockerConfig,
|
||||||
|
}))
|
||||||
|
|
||||||
secrets, err := controllerapi.CreateSecrets(in.Secrets)
|
secrets, err := controllerapi.CreateSecrets(in.Secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -221,8 +221,10 @@ The following table shows the complete list of attributes that you can assign to
|
|||||||
| [`attest`](#targetattest) | List | Build attestations |
|
| [`attest`](#targetattest) | List | Build attestations |
|
||||||
| [`cache-from`](#targetcache-from) | List | External cache sources |
|
| [`cache-from`](#targetcache-from) | List | External cache sources |
|
||||||
| [`cache-to`](#targetcache-to) | List | External cache destinations |
|
| [`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 |
|
| [`context`](#targetcontext) | String | Set of files located in the specified path or URL |
|
||||||
| [`contexts`](#targetcontexts) | Map | Additional build contexts |
|
| [`contexts`](#targetcontexts) | Map | Additional build contexts |
|
||||||
|
| [`description`](#targetdescription) | String | Description of a target |
|
||||||
| [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string |
|
| [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string |
|
||||||
| [`dockerfile`](#targetdockerfile) | String | Dockerfile location |
|
| [`dockerfile`](#targetdockerfile) | String | Dockerfile location |
|
||||||
| [`inherits`](#targetinherits) | List | Inherit attributes from other targets |
|
| [`inherits`](#targetinherits) | List | Inherit attributes from other targets |
|
||||||
@@ -283,19 +285,11 @@ The key takes a list of annotations, in the format of `KEY=VALUE`.
|
|||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
target "default" {
|
target "default" {
|
||||||
output = ["type=image,name=foo"]
|
output = [{ type = "image", name = "foo" }]
|
||||||
annotations = ["org.opencontainers.image.authors=dvdksn"]
|
annotations = ["org.opencontainers.image.authors=dvdksn"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
is the same as
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
target "default" {
|
|
||||||
output = ["type=image,name=foo,annotation.org.opencontainers.image.authors=dvdksn"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, the annotation is added to image manifests. You can configure the
|
By default, the annotation is added to image manifests. You can configure the
|
||||||
level of the annotations by adding a prefix to the annotation, containing a
|
level of the annotations by adding a prefix to the annotation, containing a
|
||||||
comma-separated list of all the levels that you want to annotate. The following
|
comma-separated list of all the levels that you want to annotate. The following
|
||||||
@@ -303,7 +297,7 @@ example adds annotations to both the image index and manifests.
|
|||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
target "default" {
|
target "default" {
|
||||||
output = ["type=image,name=foo"]
|
output = [{ type = "image", name = "foo" }]
|
||||||
annotations = ["index,manifest:org.opencontainers.image.authors=dvdksn"]
|
annotations = ["index,manifest:org.opencontainers.image.authors=dvdksn"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -319,8 +313,13 @@ This attribute accepts the long-form CSV version of attestation parameters.
|
|||||||
```hcl
|
```hcl
|
||||||
target "default" {
|
target "default" {
|
||||||
attest = [
|
attest = [
|
||||||
"type=provenance,mode=min",
|
{
|
||||||
"type=sbom"
|
type = "provenance",
|
||||||
|
mode = "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "sbom",
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -336,8 +335,15 @@ This takes a list value, so you can specify multiple cache sources.
|
|||||||
```hcl
|
```hcl
|
||||||
target "app" {
|
target "app" {
|
||||||
cache-from = [
|
cache-from = [
|
||||||
"type=s3,region=eu-west-1,bucket=mybucket",
|
{
|
||||||
"user/repo:cache",
|
type = "s3",
|
||||||
|
region = "eu-west-1",
|
||||||
|
bucket = "mybucket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "registry",
|
||||||
|
ref = "user/repo:cache"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -353,8 +359,14 @@ This takes a list value, so you can specify multiple cache export targets.
|
|||||||
```hcl
|
```hcl
|
||||||
target "app" {
|
target "app" {
|
||||||
cache-to = [
|
cache-to = [
|
||||||
"type=s3,region=eu-west-1,bucket=mybucket",
|
{
|
||||||
"type=inline"
|
type = "s3",
|
||||||
|
region = "eu-west-1",
|
||||||
|
bucket = "mybucket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "inline",
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -371,6 +383,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
|
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).
|
[`docker buildx build --call`](https://docs.docker.com/reference/cli/docker/buildx/build/#call).
|
||||||
|
|
||||||
@@ -481,6 +500,25 @@ FROM baseapp
|
|||||||
RUN echo "Hello world"
|
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`
|
### `target.dockerfile-inline`
|
||||||
|
|
||||||
Uses the string value as an inline Dockerfile for the build target.
|
Uses the string value as an inline Dockerfile for the build target.
|
||||||
@@ -835,7 +873,7 @@ The following example configures the target to use a cache-only output,
|
|||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
target "default" {
|
target "default" {
|
||||||
output = ["type=cacheonly"]
|
output = [{ type = "cacheonly" }]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -875,8 +913,8 @@ variable "HOME" {
|
|||||||
|
|
||||||
target "default" {
|
target "default" {
|
||||||
secret = [
|
secret = [
|
||||||
"type=env,id=KUBECONFIG",
|
{ type = "env", id = "KUBECONFIG" },
|
||||||
"type=file,id=aws,src=${HOME}/.aws/credentials"
|
{ type = "file", id = "aws", src = "${HOME}/.aws/credentials" },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -920,7 +958,7 @@ This can be useful if you need to access private repositories during a build.
|
|||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
target "default" {
|
target "default" {
|
||||||
ssh = ["default"]
|
ssh = [{ id = "default" }]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ Build from a file
|
|||||||
|
|
||||||
| Name | Type | Default | Description |
|
| 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 |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| [`--call`](#call) | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
| [`--call`](#call) | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
||||||
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
||||||
@@ -51,6 +51,80 @@ guide for introduction to writing bake files.
|
|||||||
|
|
||||||
## Examples
|
## 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)
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
Same as [`buildx --builder`](buildx.md#builder).
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
@@ -16,7 +16,7 @@ Start a build
|
|||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------------------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------|
|
|:----------------------------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------|
|
||||||
| [`--add-host`](#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
| [`--add-host`](#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||||
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
| [`--allow`](#allow) | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||||
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
||||||
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||||
|
@@ -12,7 +12,7 @@ Start a build
|
|||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:--------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------|
|
|:--------------------|:--------------|:----------|:-------------------------------------------------------------------------------------------------------------|
|
||||||
| `--add-host` | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
| `--add-host` | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||||
| `--allow` | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
| `--allow` | `stringArray` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||||
| `--annotation` | `stringArray` | | Add annotation to the image |
|
| `--annotation` | `stringArray` | | Add annotation to the image |
|
||||||
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||||
| `--build-arg` | `stringArray` | | Set build-time variables |
|
| `--build-arg` | `stringArray` | | Set build-time variables |
|
||||||
|
@@ -5,13 +5,14 @@ Commands to work on build records
|
|||||||
|
|
||||||
### Subcommands
|
### Subcommands
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
|:---------------------------------------|:-------------------------------|
|
|:---------------------------------------|:-----------------------------------------------|
|
||||||
| [`inspect`](buildx_history_inspect.md) | Inspect a build |
|
| [`inspect`](buildx_history_inspect.md) | Inspect a build |
|
||||||
| [`logs`](buildx_history_logs.md) | Print the logs of a build |
|
| [`logs`](buildx_history_logs.md) | Print the logs of a build |
|
||||||
| [`ls`](buildx_history_ls.md) | List build records |
|
| [`ls`](buildx_history_ls.md) | List build records |
|
||||||
| [`open`](buildx_history_open.md) | Open a build in Docker Desktop |
|
| [`open`](buildx_history_open.md) | Open a build in Docker Desktop |
|
||||||
| [`rm`](buildx_history_rm.md) | Remove build records |
|
| [`rm`](buildx_history_rm.md) | Remove build records |
|
||||||
|
| [`trace`](buildx_history_trace.md) | Show the OpenTelemetry trace of a build record |
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
@@ -12,11 +12,106 @@ Inspect a build
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------|:---------|:--------|:-----------------------------------------|
|
|:----------------------|:---------|:---------|:-----------------------------------------|
|
||||||
| `--builder` | `string` | | Override the configured builder instance |
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| [`--format`](#format) | `string` | `pretty` | Format the output |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="format"></a> Format the output (--format)
|
||||||
|
|
||||||
|
The formatting options (`--format`) pretty-prints the output to `pretty` (default),
|
||||||
|
`json` or using a Go template.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx history inspect
|
||||||
|
Name: buildx (binaries)
|
||||||
|
Context: .
|
||||||
|
Dockerfile: Dockerfile
|
||||||
|
VCS Repository: https://github.com/crazy-max/buildx.git
|
||||||
|
VCS Revision: f15eaa1ee324ffbbab29605600d27a84cab86361
|
||||||
|
Target: binaries
|
||||||
|
Platforms: linux/amd64
|
||||||
|
Keep Git Dir: true
|
||||||
|
|
||||||
|
Started: 2025-02-07 11:56:24
|
||||||
|
Duration: 1m 1s
|
||||||
|
Build Steps: 16/16 (25% cached)
|
||||||
|
|
||||||
|
Image Resolve Mode: local
|
||||||
|
|
||||||
|
Materials:
|
||||||
|
URI DIGEST
|
||||||
|
pkg:docker/docker/dockerfile@1 sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d801e66d25
|
||||||
|
pkg:docker/golang@1.23-alpine3.21?platform=linux%2Famd64 sha256:2c49857f2295e89b23b28386e57e018a86620a8fede5003900f2d138ba9c4037
|
||||||
|
pkg:docker/tonistiigi/xx@1.6.1?platform=linux%2Famd64 sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3
|
||||||
|
|
||||||
|
Attachments:
|
||||||
|
DIGEST PLATFORM TYPE
|
||||||
|
sha256:217329d2af959d4f02e3a96dcbe62bf100cab1feb8006a047ddfe51a5397f7e3 https://slsa.dev/provenance/v0.2
|
||||||
|
|
||||||
|
Print build logs: docker buildx history logs g9808bwrjrlkbhdamxklx660b
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx history inspect --format json
|
||||||
|
{
|
||||||
|
"Name": "buildx (binaries)",
|
||||||
|
"Ref": "5w7vkqfi0rf59hw4hnmn627r9",
|
||||||
|
"Context": ".",
|
||||||
|
"Dockerfile": "Dockerfile",
|
||||||
|
"VCSRepository": "https://github.com/crazy-max/buildx.git",
|
||||||
|
"VCSRevision": "f15eaa1ee324ffbbab29605600d27a84cab86361",
|
||||||
|
"Target": "binaries",
|
||||||
|
"Platform": [
|
||||||
|
"linux/amd64"
|
||||||
|
],
|
||||||
|
"KeepGitDir": true,
|
||||||
|
"StartedAt": "2025-02-07T12:01:05.75807272+01:00",
|
||||||
|
"CompletedAt": "2025-02-07T12:02:07.991778875+01:00",
|
||||||
|
"Duration": 62233706155,
|
||||||
|
"Status": "completed",
|
||||||
|
"NumCompletedSteps": 16,
|
||||||
|
"NumTotalSteps": 16,
|
||||||
|
"NumCachedSteps": 4,
|
||||||
|
"Config": {
|
||||||
|
"ImageResolveMode": "local"
|
||||||
|
},
|
||||||
|
"Materials": [
|
||||||
|
{
|
||||||
|
"URI": "pkg:docker/docker/dockerfile@1",
|
||||||
|
"Digests": [
|
||||||
|
"sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d801e66d25"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"URI": "pkg:docker/golang@1.23-alpine3.21?platform=linux%2Famd64",
|
||||||
|
"Digests": [
|
||||||
|
"sha256:2c49857f2295e89b23b28386e57e018a86620a8fede5003900f2d138ba9c4037"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"URI": "pkg:docker/tonistiigi/xx@1.6.1?platform=linux%2Famd64",
|
||||||
|
"Digests": [
|
||||||
|
"sha256:923441d7c25f1e2eb5789f82d987693c47b8ed987c4ab3b075d6ed2b5d6779a3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Attachments": [
|
||||||
|
{
|
||||||
|
"Digest": "sha256:450fdd2e6b868fecd69e9891c2c404ba461aa38a47663b4805edeb8d2baf80b1",
|
||||||
|
"Type": "https://slsa.dev/provenance/v0.2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx history inspect --format "{{.Name}}: {{.VCSRepository}} ({{.VCSRevision}})"
|
||||||
|
buildx (binaries): https://github.com/crazy-max/buildx.git (f15eaa1ee324ffbbab29605600d27a84cab86361)
|
||||||
|
```
|
||||||
|
17
docs/reference/buildx_history_trace.md
Normal file
17
docs/reference/buildx_history_trace.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# docker buildx history trace
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Show the OpenTelemetry trace of a build record
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------------|:-----------------------------------------|
|
||||||
|
| `--addr` | `string` | `127.0.0.1:0` | Address to bind the UI server |
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `--compare` | `string` | | Compare with another build reference |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
@@ -23,10 +23,10 @@ import (
|
|||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/system"
|
"github.com/docker/docker/api/types/system"
|
||||||
dockerclient "github.com/docker/docker/client"
|
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
dockerarchive "github.com/docker/docker/pkg/archive"
|
dockerarchive "github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -70,7 +70,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
|||||||
return progress.Wrap("[internal] booting buildkit", l, func(sub progress.SubLogger) error {
|
return progress.Wrap("[internal] booting buildkit", l, func(sub progress.SubLogger) error {
|
||||||
_, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
_, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if dockerclient.IsErrNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
return d.create(ctx, sub)
|
return d.create(ctx, sub)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -95,19 +95,20 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, image.CreateOptions{
|
resp, err := d.DockerAPI.ImageCreate(ctx, imageName, image.CreateOptions{
|
||||||
RegistryAuth: ra,
|
RegistryAuth: ra,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = io.Copy(io.Discard, rc)
|
defer resp.Close()
|
||||||
return err
|
return jsonmessage.DisplayJSONMessagesStream(resp, io.Discard, 0, false, nil)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
// image pulling failed, check if it exists in local image store.
|
// image pulling failed, check if it exists in local image store.
|
||||||
// if not, return pulling error. otherwise log it.
|
// if not, return pulling error. otherwise log it.
|
||||||
_, _, errInspect := d.DockerAPI.ImageInspectWithRaw(ctx, imageName)
|
_, errInspect := d.DockerAPI.ImageInspect(ctx, imageName)
|
||||||
if errInspect != nil {
|
found := errInspect == nil
|
||||||
|
if !found {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l.Wrap("pulling failed, using local image "+imageName, func() error { return nil })
|
l.Wrap("pulling failed, using local image "+imageName, func() error { return nil })
|
||||||
@@ -306,7 +307,7 @@ func (d *Driver) start(ctx context.Context) error {
|
|||||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||||
ctn, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
ctn, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if dockerclient.IsErrNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
return &driver.Info{
|
return &driver.Info{
|
||||||
Status: driver.Inactive,
|
Status: driver.Inactive,
|
||||||
}, nil
|
}, nil
|
||||||
|
26
go.mod
26
go.mod
@@ -17,9 +17,9 @@ require (
|
|||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v27.5.0+incompatible
|
github.com/docker/cli v28.0.0-rc.2+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.9.0
|
github.com/docker/cli-docs-tool v0.9.0
|
||||||
github.com/docker/docker v27.5.0+incompatible
|
github.com/docker/docker v28.0.0-rc.2+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/gofrs/flock v0.12.1
|
github.com/gofrs/flock v0.12.1
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
@@ -29,7 +29,7 @@ require (
|
|||||||
github.com/hashicorp/hcl/v2 v2.23.0
|
github.com/hashicorp/hcl/v2 v2.23.0
|
||||||
github.com/in-toto/in-toto-golang v0.5.0
|
github.com/in-toto/in-toto-golang v0.5.0
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/moby/buildkit v0.19.0-rc2
|
github.com/moby/buildkit v0.20.0
|
||||||
github.com/moby/sys/mountinfo v0.7.2
|
github.com/moby/sys/mountinfo v0.7.2
|
||||||
github.com/moby/sys/signal v0.7.1
|
github.com/moby/sys/signal v0.7.1
|
||||||
github.com/morikuni/aec v1.0.0
|
github.com/morikuni/aec v1.0.0
|
||||||
@@ -46,18 +46,20 @@ require (
|
|||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
|
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6
|
||||||
github.com/zclconf/go-cty v1.16.0
|
github.com/zclconf/go-cty v1.16.0
|
||||||
go.opentelemetry.io/otel v1.31.0
|
go.opentelemetry.io/otel v1.31.0
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
|
||||||
go.opentelemetry.io/otel/metric v1.31.0
|
go.opentelemetry.io/otel/metric v1.31.0
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0
|
go.opentelemetry.io/otel/sdk v1.31.0
|
||||||
go.opentelemetry.io/otel/trace v1.31.0
|
go.opentelemetry.io/otel/trace v1.31.0
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
golang.org/x/mod v0.22.0
|
||||||
golang.org/x/mod v0.21.0
|
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.10.0
|
||||||
golang.org/x/sys v0.28.0
|
golang.org/x/sys v0.29.0
|
||||||
golang.org/x/term v0.27.0
|
golang.org/x/term v0.27.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.21.0
|
||||||
google.golang.org/grpc v1.68.1
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38
|
||||||
|
google.golang.org/grpc v1.69.4
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
||||||
google.golang.org/protobuf v1.35.2
|
google.golang.org/protobuf v1.35.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@@ -68,7 +70,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
github.com/apparentlymart/go-cidr v1.0.1 // indirect
|
||||||
@@ -135,7 +137,7 @@ require (
|
|||||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
github.com/moby/sys/user v0.3.0 // indirect
|
github.com/moby/sys/user v0.3.0 // indirect
|
||||||
github.com/moby/sys/userns v0.1.0 // indirect
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/term v0.5.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
@@ -168,12 +170,12 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/oauth2 v0.23.0 // indirect
|
golang.org/x/oauth2 v0.23.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
golang.org/x/tools v0.25.0 // indirect
|
golang.org/x/tools v0.27.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
52
go.sum
52
go.sum
@@ -2,8 +2,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af
|
|||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M=
|
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M=
|
||||||
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw=
|
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||||
@@ -79,8 +79,8 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo
|
|||||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||||
github.com/compose-spec/compose-go/v2 v2.4.7 h1:WNpz5bIbKG+G+w9pfu72B1ZXr+Og9jez8TMEo8ecXPk=
|
github.com/compose-spec/compose-go/v2 v2.4.7 h1:WNpz5bIbKG+G+w9pfu72B1ZXr+Og9jez8TMEo8ecXPk=
|
||||||
github.com/compose-spec/compose-go/v2 v2.4.7/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
github.com/compose-spec/compose-go/v2 v2.4.7/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
||||||
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
|
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
||||||
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
|
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
|
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
|
||||||
@@ -122,15 +122,15 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM=
|
github.com/docker/cli v28.0.0-rc.2+incompatible h1:2N1dpr3qtlJwIQpqXm7oNwWNAUGzpKlsCeJ32ejvpTk=
|
||||||
github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v28.0.0-rc.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
|
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
|
||||||
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
|
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
|
github.com/docker/docker v28.0.0-rc.2+incompatible h1:p+Ri+C0mmbPkhYVD9Sxnp/TnNnZoQWEj/EwOC465Uq4=
|
||||||
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.0.0-rc.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
@@ -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 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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/moby/buildkit v0.19.0-rc2 h1:7sAuQ5bDNIbdfmc7UDbrWJ2UPOR5w9rNWgnrEoC5aoo=
|
github.com/moby/buildkit v0.20.0 h1:aF5RujjQ310Pn6SLL/wQYIrSsPXy0sQ5KvWifwq1h8Y=
|
||||||
github.com/moby/buildkit v0.19.0-rc2/go.mod h1:4WYJLet/NI2p1o2rPQ6CIFpyyyvwvPz/TVISmwqqpHI=
|
github.com/moby/buildkit v0.20.0/go.mod h1:HYFUIK+iGDRxRgdphZ9Nv0y1Fz7mv0HrU7xZoXx217E=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
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/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||||
@@ -317,8 +317,8 @@ github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
|||||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -447,6 +447,8 @@ github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a h1:EfGw4G0x/8qXW
|
|||||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
|
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6 h1:RT/a0RvdX84iwtOrUK45+wjcNpaG+hS7n7XFYqj4axg=
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6/go.mod h1:3Ez1Paeg+0Ghu3KwpEGC1HgZ4CHDlg+Ez/5Baeomk54=
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
|
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
|
||||||
@@ -490,6 +492,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy
|
|||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64=
|
||||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||||
@@ -512,12 +516,12 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@@ -549,8 +553,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
@@ -565,20 +569,20 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
|
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
||||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
||||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
|
@@ -7,7 +7,7 @@ ARG XX_VERSION=1.6.1
|
|||||||
ARG GOLANGCI_LINT_VERSION=1.62.0
|
ARG GOLANGCI_LINT_VERSION=1.62.0
|
||||||
ARG GOPLS_VERSION=v0.26.0
|
ARG GOPLS_VERSION=v0.26.0
|
||||||
# disabled: deprecated unusedvariable simplifyrange
|
# disabled: deprecated unusedvariable simplifyrange
|
||||||
ARG GOPLS_ANALYZERS="embeddirective fillreturns infertypeargs nonewvars norangeoverfunc noresultvalues simplifycompositelit simplifyslice undeclaredname unusedparams useany"
|
ARG GOPLS_ANALYZERS="embeddirective fillreturns infertypeargs nonewvars noresultvalues simplifycompositelit simplifyslice undeclaredname unusedparams useany"
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|
||||||
|
12
hack/test
12
hack/test
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
|
: "${GITHUB_ACTIONS=}"
|
||||||
|
|
||||||
: "${BUILDX_CMD=docker buildx}"
|
: "${BUILDX_CMD=docker buildx}"
|
||||||
|
|
||||||
: "${TEST_COVERAGE=}"
|
: "${TEST_COVERAGE=}"
|
||||||
@@ -37,7 +39,15 @@ if [ "$TEST_COVERAGE" = "1" ]; then
|
|||||||
export GO_TEST_COVERPROFILE="/testreports/coverage-report$TEST_REPORT_SUFFIX.txt"
|
export GO_TEST_COVERPROFILE="/testreports/coverage-report$TEST_REPORT_SUFFIX.txt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cid=$(docker create --rm --privileged \
|
dockerConfigMount=""
|
||||||
|
if [ "$GITHUB_ACTIONS" = "true" ]; then
|
||||||
|
dockerConfigPath="$HOME/.docker/config.json"
|
||||||
|
if [ -f "$dockerConfigPath" ]; then
|
||||||
|
dockerConfigMount="-v $dockerConfigPath:/root/.docker/config.json:ro"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
cid=$(docker create --rm --privileged $dockerConfigMount \
|
||||||
-v /tmp $testReportsVol \
|
-v /tmp $testReportsVol \
|
||||||
--volumes-from=$cacheVolume \
|
--volumes-from=$cacheVolume \
|
||||||
-e GITHUB_REF \
|
-e GITHUB_REF \
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
@@ -14,6 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
version = 2
|
||||||
refsDir = "refs"
|
refsDir = "refs"
|
||||||
groupDir = "__group__"
|
groupDir = "__group__"
|
||||||
)
|
)
|
||||||
@@ -31,12 +33,8 @@ type State struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StateGroup struct {
|
type StateGroup struct {
|
||||||
// Definition is the raw representation of the group (bake definition)
|
|
||||||
Definition []byte
|
|
||||||
// Targets are the targets invoked
|
// Targets are the targets invoked
|
||||||
Targets []string `json:",omitempty"`
|
Targets []string `json:",omitempty"`
|
||||||
// Inputs are the user inputs (bake overrides)
|
|
||||||
Inputs []string `json:",omitempty"`
|
|
||||||
// Refs are used to track all the refs that belong to the same group
|
// Refs are used to track all the refs that belong to the same group
|
||||||
Refs []string
|
Refs []string
|
||||||
}
|
}
|
||||||
@@ -52,9 +50,7 @@ func New(cfg *confutil.Config) (*LocalState, error) {
|
|||||||
if err := cfg.MkdirAll(refsDir, 0700); err != nil {
|
if err := cfg.MkdirAll(refsDir, 0700); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &LocalState{
|
return &LocalState{cfg: cfg}, nil
|
||||||
cfg: cfg,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ls *LocalState) ReadRef(builderName, nodeName, id string) (*State, error) {
|
func (ls *LocalState) ReadRef(builderName, nodeName, id string) (*State, error) {
|
||||||
@@ -87,8 +83,12 @@ func (ls *LocalState) SaveRef(builderName, nodeName, id string, st State) error
|
|||||||
return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0644)
|
return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ls *LocalState) GroupDir() string {
|
||||||
|
return filepath.Join(ls.cfg.Dir(), refsDir, groupDir)
|
||||||
|
}
|
||||||
|
|
||||||
func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
|
func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
|
||||||
dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id))
|
dt, err := os.ReadFile(filepath.Join(ls.GroupDir(), id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ func (ls *LocalState) removeGroup(id string) error {
|
|||||||
if id == "" {
|
if id == "" {
|
||||||
return errors.Errorf("group ref empty")
|
return errors.Errorf("group ref empty")
|
||||||
}
|
}
|
||||||
f := filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id)
|
f := filepath.Join(ls.GroupDir(), id)
|
||||||
if _, err := os.Lstat(f); err != nil {
|
if _, err := os.Lstat(f); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
@@ -230,3 +230,16 @@ func (ls *LocalState) validate(builderName, nodeName, id string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ls *LocalState) readVersion() int {
|
||||||
|
if vdt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, "version")); err == nil {
|
||||||
|
if v, err := strconv.Atoi(string(vdt)); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LocalState) writeVersion(version int) error {
|
||||||
|
return ls.cfg.AtomicWriteFile(filepath.Join(refsDir, "version"), []byte(strconv.Itoa(version)), 0600)
|
||||||
|
}
|
||||||
|
@@ -68,10 +68,8 @@ var (
|
|||||||
|
|
||||||
testStateGroupID = "kvqs0sgly2rmitz84r25u9qd0"
|
testStateGroupID = "kvqs0sgly2rmitz84r25u9qd0"
|
||||||
testStateGroup = StateGroup{
|
testStateGroup = StateGroup{
|
||||||
Definition: []byte(`{"group":{"default":{"targets":["pre-checkin"]},"pre-checkin":{"targets":["vendor-update","format","build"]}},"target":{"build":{"context":".","dockerfile":"dev.Dockerfile","target":"build-update","platforms":["linux/amd64"],"output":["."]},"format":{"context":".","dockerfile":"dev.Dockerfile","target":"format-update","platforms":["linux/amd64"],"output":["."]},"vendor-update":{"context":".","dockerfile":"dev.Dockerfile","target":"vendor-update","platforms":["linux/amd64"],"output":["."]}}}`),
|
Targets: []string{"pre-checkin"},
|
||||||
Targets: []string{"pre-checkin"},
|
Refs: []string{"builder/builder0/hx2qf1w11qvz1x3k471c5i8xw", "builder/builder0/968zj0g03jmlx0s8qslnvh6rl", "builder/builder0/naf44f9i1710lf7y12lv5hb1z"},
|
||||||
Inputs: []string{"*.platform=linux/amd64"},
|
|
||||||
Refs: []string{"builder/builder0/hx2qf1w11qvz1x3k471c5i8xw", "builder/builder0/968zj0g03jmlx0s8qslnvh6rl", "builder/builder0/naf44f9i1710lf7y12lv5hb1z"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testStateGroupRef1ID = "hx2qf1w11qvz1x3k471c5i8xw"
|
testStateGroupRef1ID = "hx2qf1w11qvz1x3k471c5i8xw"
|
||||||
|
56
localstate/migrate.go
Normal file
56
localstate/migrate.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package localstate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ls *LocalState) MigrateIfNeeded() error {
|
||||||
|
currentVersion := ls.readVersion()
|
||||||
|
if currentVersion == version {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
migrations := map[int]func(*LocalState) error{
|
||||||
|
2: (*LocalState).migration2,
|
||||||
|
}
|
||||||
|
for v := currentVersion + 1; v <= version; v++ {
|
||||||
|
migration, found := migrations[v]
|
||||||
|
if !found {
|
||||||
|
return errors.Errorf("localstate migration v%d not found", v)
|
||||||
|
}
|
||||||
|
if err := migration(ls); err != nil {
|
||||||
|
return errors.Wrapf(err, "localstate migration v%d failed", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ls.writeVersion(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LocalState) migration2() error {
|
||||||
|
return filepath.Walk(ls.GroupDir(), func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dt, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var stg StateGroup
|
||||||
|
if err := json.Unmarshal(dt, &stg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mdt, err := json.Marshal(stg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, mdt, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
@@ -66,7 +66,7 @@ func (cm *ReloadCmd) Exec(ctx context.Context, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var be *controllererrors.BuildError
|
var be *controllererrors.BuildError
|
||||||
if errors.As(err, &be) {
|
if errors.As(err, &be) {
|
||||||
ref = be.Ref
|
ref = be.SessionID
|
||||||
resultUpdated = true
|
resultUpdated = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("failed to reload: %v\n", err)
|
fmt.Printf("failed to reload: %v\n", err)
|
||||||
|
@@ -38,6 +38,7 @@ func bakeCmd(sb integration.Sandbox, opts ...cmdOpt) (string, error) {
|
|||||||
var bakeTests = []func(t *testing.T, sb integration.Sandbox){
|
var bakeTests = []func(t *testing.T, sb integration.Sandbox){
|
||||||
testBakePrint,
|
testBakePrint,
|
||||||
testBakePrintSensitive,
|
testBakePrintSensitive,
|
||||||
|
testBakePrintOverrideEmpty,
|
||||||
testBakeLocal,
|
testBakeLocal,
|
||||||
testBakeLocalMulti,
|
testBakeLocalMulti,
|
||||||
testBakeRemote,
|
testBakeRemote,
|
||||||
@@ -286,6 +287,47 @@ RUN echo "Hello ${HELLO}"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBakePrintOverrideEmpty(t *testing.T, sb integration.Sandbox) {
|
||||||
|
dockerfile := []byte(`
|
||||||
|
FROM scratch
|
||||||
|
COPY foo /foo
|
||||||
|
`)
|
||||||
|
bakefile := []byte(`
|
||||||
|
target "default" {
|
||||||
|
cache-to = ["type=gha,mode=min,scope=integration-tests"]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dir := tmpdir(
|
||||||
|
t,
|
||||||
|
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
|
||||||
|
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||||
|
fstest.CreateFile("foo", []byte("foo"), 0600),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd := buildxCmd(sb, withDir(dir), withArgs("bake", "--print", "--set", "*.cache-to="))
|
||||||
|
stdout := bytes.Buffer{}
|
||||||
|
stderr := bytes.Buffer{}
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
require.NoError(t, cmd.Run(), stdout.String(), stderr.String())
|
||||||
|
|
||||||
|
require.JSONEq(t, `{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
func testBakeLocal(t *testing.T, sb integration.Sandbox) {
|
func testBakeLocal(t *testing.T, sb integration.Sandbox) {
|
||||||
dockerfile := []byte(`
|
dockerfile := []byte(`
|
||||||
FROM scratch
|
FROM scratch
|
||||||
@@ -871,6 +913,7 @@ target "default" {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBakeSetNonExistingOutsideNoParallel(t *testing.T, sb integration.Sandbox) {
|
func testBakeSetNonExistingOutsideNoParallel(t *testing.T, sb integration.Sandbox) {
|
||||||
for _, ent := range []bool{true, false} {
|
for _, ent := range []bool{true, false} {
|
||||||
t.Run(fmt.Sprintf("ent=%v", ent), func(t *testing.T) {
|
t.Run(fmt.Sprintf("ent=%v", ent), func(t *testing.T) {
|
||||||
|
@@ -91,7 +91,7 @@ func (a *Attest) ToPB() *controllerapi.Attest {
|
|||||||
|
|
||||||
func (a *Attest) MarshalJSON() ([]byte, error) {
|
func (a *Attest) MarshalJSON() ([]byte, error) {
|
||||||
m := make(map[string]interface{}, len(a.Attrs)+2)
|
m := make(map[string]interface{}, len(a.Attrs)+2)
|
||||||
for k, v := range m {
|
for k, v := range a.Attrs {
|
||||||
m[k] = v
|
m[k] = v
|
||||||
}
|
}
|
||||||
m["type"] = a.Type
|
m["type"] = a.Type
|
||||||
@@ -148,9 +148,8 @@ func (a *Attest) UnmarshalText(text []byte) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return errors.Errorf("invalid value %s", field)
|
return errors.Errorf("invalid value %s", field)
|
||||||
}
|
}
|
||||||
key = strings.TrimSpace(strings.ToLower(key))
|
|
||||||
|
|
||||||
switch key {
|
switch strings.TrimSpace(strings.ToLower(key)) {
|
||||||
case "type":
|
case "type":
|
||||||
a.Type = value
|
a.Type = value
|
||||||
case "disabled":
|
case "disabled":
|
||||||
|
@@ -22,18 +22,19 @@ func (e *Attests) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Attests) fromCtyValue(in cty.Value, p cty.Path) error {
|
func (e *Attests) fromCtyValue(in cty.Value, p cty.Path) (retErr error) {
|
||||||
*e = make([]*Attest, 0, in.LengthInt())
|
*e = make([]*Attest, 0, in.LengthInt())
|
||||||
for elem := in.ElementIterator(); elem.Next(); {
|
|
||||||
_, value := elem.Element()
|
|
||||||
|
|
||||||
|
yield := func(value cty.Value) bool {
|
||||||
entry := &Attest{}
|
entry := &Attest{}
|
||||||
if err := entry.FromCtyValue(value, p); err != nil {
|
if retErr = entry.FromCtyValue(value, p); retErr != nil {
|
||||||
return err
|
return false
|
||||||
}
|
}
|
||||||
*e = append(*e, entry)
|
*e = append(*e, entry)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
eachElement(in)(yield)
|
||||||
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Attests) ToCtyValue() cty.Value {
|
func (e Attests) ToCtyValue() cty.Value {
|
||||||
@@ -64,6 +65,10 @@ func (e *Attest) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
e.Attrs = map[string]string{}
|
e.Attrs = map[string]string{}
|
||||||
for it := conv.ElementIterator(); it.Next(); {
|
for it := conv.ElementIterator(); it.Next(); {
|
||||||
k, v := it.Element()
|
k, v := it.Element()
|
||||||
|
if !v.IsKnown() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch key := k.AsString(); key {
|
switch key := k.AsString(); key {
|
||||||
case "type":
|
case "type":
|
||||||
e.Type = v.AsString()
|
e.Type = v.AsString()
|
||||||
|
117
util/buildflags/attests_test.go
Normal file
117
util/buildflags/attests_test.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
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},
|
||||||
|
{Type: "sbom", Attrs: map[string]string{
|
||||||
|
"generator": "scanner",
|
||||||
|
"ENV1": `"foo,bar"`,
|
||||||
|
"Env2": "hello",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `[{"type":"provenance","mode":"max"},{"type":"sbom","disabled":true},{"ENV1":"\"foo,bar\"","Env2":"hello","generator":"scanner","type":"sbom"}]`
|
||||||
|
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},{"ENV1":"\"foo,bar\"","Env2":"hello","generator":"scanner","type":"sbom"}]`
|
||||||
|
|
||||||
|
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{}},
|
||||||
|
{Type: "sbom", Disabled: false, Attrs: map[string]string{
|
||||||
|
"generator": "scanner",
|
||||||
|
"ENV1": `"foo,bar"`,
|
||||||
|
"Env2": "hello",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
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.ObjectVal(map[string]cty.Value{
|
||||||
|
"type": cty.StringVal("sbom"),
|
||||||
|
"generator": cty.StringVal("scan"),
|
||||||
|
"ENV1": cty.StringVal(`foo,bar`),
|
||||||
|
"Env2": cty.StringVal(`hello`),
|
||||||
|
}),
|
||||||
|
cty.StringVal("type=sbom,disabled=true"),
|
||||||
|
cty.StringVal(`type=sbom,generator=scan,"FOO=bar,baz",Hello=World`),
|
||||||
|
})
|
||||||
|
|
||||||
|
var actual Attests
|
||||||
|
err := actual.FromCtyValue(in, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := Attests{
|
||||||
|
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
|
||||||
|
{Type: "sbom", Attrs: map[string]string{
|
||||||
|
"generator": "scan",
|
||||||
|
"ENV1": "foo,bar",
|
||||||
|
"Env2": "hello",
|
||||||
|
}},
|
||||||
|
{Type: "sbom", Disabled: true, Attrs: map[string]string{}},
|
||||||
|
{Type: "sbom", Attrs: map[string]string{
|
||||||
|
"generator": "scan",
|
||||||
|
"FOO": "bar,baz",
|
||||||
|
"Hello": "World",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
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},
|
||||||
|
{Type: "sbom", Attrs: map[string]string{
|
||||||
|
"generator": "scan",
|
||||||
|
"ENV1": `"foo,bar"`,
|
||||||
|
"Env2": "hello",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
}),
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"type": cty.StringVal("sbom"),
|
||||||
|
"generator": cty.StringVal("scan"),
|
||||||
|
"ENV1": cty.StringVal(`"foo,bar"`),
|
||||||
|
"Env2": cty.StringVal("hello"),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
result := actual.Equals(expected)
|
||||||
|
require.True(t, result.True())
|
||||||
|
})
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||||
@@ -167,34 +168,73 @@ func (e *CacheOptionsEntry) validate(gv interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseCacheEntry(in []string) ([]*controllerapi.CacheOptionsEntry, error) {
|
func ParseCacheEntry(in []string) (CacheOptions, error) {
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := make(CacheOptions, 0, len(in))
|
opts := make(CacheOptions, 0, len(in))
|
||||||
for _, in := range in {
|
for _, in := range in {
|
||||||
|
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 {
|
||||||
|
opt := CacheOptionsEntry{}
|
||||||
|
if err := opt.UnmarshalText([]byte(field)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts = append(opts, &opt)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var out CacheOptionsEntry
|
var out CacheOptionsEntry
|
||||||
if err := out.UnmarshalText([]byte(in)); err != nil {
|
if err := out.UnmarshalText([]byte(in)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts = append(opts, &out)
|
opts = append(opts, &out)
|
||||||
}
|
}
|
||||||
return opts.ToPB(), nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGithubToken(ci *controllerapi.CacheOptionsEntry) {
|
func addGithubToken(ci *controllerapi.CacheOptionsEntry) {
|
||||||
if ci.Type != "gha" {
|
if ci.Type != "gha" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
version, ok := ci.Attrs["version"]
|
||||||
|
if !ok {
|
||||||
|
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L19
|
||||||
|
if v, ok := os.LookupEnv("ACTIONS_CACHE_SERVICE_V2"); ok {
|
||||||
|
if b, err := strconv.ParseBool(v); err == nil && b {
|
||||||
|
version = "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, ok := ci.Attrs["token"]; !ok {
|
if _, ok := ci.Attrs["token"]; !ok {
|
||||||
if v, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN"); ok {
|
if v, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN"); ok {
|
||||||
ci.Attrs["token"] = v
|
ci.Attrs["token"] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := ci.Attrs["url_v2"]; !ok && version == "2" {
|
||||||
|
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L34-L35
|
||||||
|
if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
|
||||||
|
ci.Attrs["url_v2"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, ok := ci.Attrs["url"]; !ok {
|
if _, ok := ci.Attrs["url"]; !ok {
|
||||||
|
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L28-L33
|
||||||
if v, ok := os.LookupEnv("ACTIONS_CACHE_URL"); ok {
|
if v, ok := os.LookupEnv("ACTIONS_CACHE_URL"); ok {
|
||||||
ci.Attrs["url"] = v
|
ci.Attrs["url"] = v
|
||||||
|
} else if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
|
||||||
|
ci.Attrs["url"] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,5 +274,5 @@ func isActive(pb *controllerapi.CacheOptionsEntry) bool {
|
|||||||
if pb.Type != "gha" {
|
if pb.Type != "gha" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return pb.Attrs["token"] != "" && pb.Attrs["url"] != ""
|
return pb.Attrs["token"] != "" && (pb.Attrs["url"] != "" || pb.Attrs["url_v2"] != "")
|
||||||
}
|
}
|
||||||
|
@@ -21,22 +21,30 @@ func (o *CacheOptions) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CacheOptions) fromCtyValue(in cty.Value, p cty.Path) error {
|
func (o *CacheOptions) fromCtyValue(in cty.Value, p cty.Path) (retErr error) {
|
||||||
*o = make([]*CacheOptionsEntry, 0, in.LengthInt())
|
*o = make([]*CacheOptionsEntry, 0, in.LengthInt())
|
||||||
for elem := in.ElementIterator(); elem.Next(); {
|
|
||||||
_, value := elem.Element()
|
|
||||||
|
|
||||||
if isEmpty(value) {
|
yield := func(value cty.Value) bool {
|
||||||
continue
|
// Special handling for a string type to handle ref only format.
|
||||||
|
if value.Type() == cty.String {
|
||||||
|
var entries CacheOptions
|
||||||
|
entries, retErr = ParseCacheEntry([]string{value.AsString()})
|
||||||
|
if retErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*o = append(*o, entries...)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := &CacheOptionsEntry{}
|
entry := &CacheOptionsEntry{}
|
||||||
if err := entry.FromCtyValue(value, p); err != nil {
|
if retErr = entry.FromCtyValue(value, p); retErr != nil {
|
||||||
return err
|
return false
|
||||||
}
|
}
|
||||||
*o = append(*o, entry)
|
*o = append(*o, entry)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
eachElement(in)(yield)
|
||||||
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o CacheOptions) ToCtyValue() cty.Value {
|
func (o CacheOptions) ToCtyValue() cty.Value {
|
||||||
@@ -52,13 +60,6 @@ func (o CacheOptions) ToCtyValue() cty.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *CacheOptionsEntry) FromCtyValue(in cty.Value, p cty.Path) error {
|
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))
|
conv, err := convert.Convert(in, cty.Map(cty.String))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
package buildflags
|
package buildflags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/buildx/controller/pb"
|
"github.com/docker/buildx/controller/pb"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCacheOptions_DerivedVars(t *testing.T) {
|
func TestCacheOptions_DerivedVars(t *testing.T) {
|
||||||
@@ -35,5 +37,84 @@ func TestCacheOptions_DerivedVars(t *testing.T) {
|
|||||||
"session_token": "not_a_mitm_attack",
|
"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)
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,20 @@
|
|||||||
package buildflags
|
package buildflags
|
||||||
|
|
||||||
import "github.com/moby/buildkit/util/entitlements"
|
import (
|
||||||
|
"github.com/moby/buildkit/util/entitlements"
|
||||||
|
)
|
||||||
|
|
||||||
func ParseEntitlements(in []string) ([]entitlements.Entitlement, error) {
|
func ParseEntitlements(in []string) ([]string, error) {
|
||||||
out := make([]entitlements.Entitlement, 0, len(in))
|
out := make([]string, 0, len(in))
|
||||||
for _, v := range in {
|
for _, v := range in {
|
||||||
if v == "" {
|
if v == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
e, err := entitlements.Parse(v)
|
if _, _, err := entitlements.Parse(v); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, e)
|
out = append(out, v)
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package buildflags
|
package buildflags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"maps"
|
"maps"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -259,9 +260,18 @@ func (w *csvBuilder) Write(key, value string) {
|
|||||||
if w.sb.Len() > 0 {
|
if w.sb.Len() > 0 {
|
||||||
w.sb.WriteByte(',')
|
w.sb.WriteByte(',')
|
||||||
}
|
}
|
||||||
w.sb.WriteString(key)
|
|
||||||
w.sb.WriteByte('=')
|
pair := key + "=" + value
|
||||||
w.sb.WriteString(value)
|
if strings.ContainsRune(pair, ',') || strings.ContainsRune(pair, '"') {
|
||||||
|
var attr strings.Builder
|
||||||
|
writer := csv.NewWriter(&attr)
|
||||||
|
writer.Write([]string{pair})
|
||||||
|
writer.Flush()
|
||||||
|
// Strips the extra newline added by the csv writer
|
||||||
|
pair = strings.TrimSpace(attr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
w.sb.WriteString(pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *csvBuilder) WriteAttributes(attrs map[string]string) {
|
func (w *csvBuilder) WriteAttributes(attrs map[string]string) {
|
||||||
|
@@ -21,22 +21,19 @@ func (e *Exports) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exports) fromCtyValue(in cty.Value, p cty.Path) error {
|
func (e *Exports) fromCtyValue(in cty.Value, p cty.Path) (retErr error) {
|
||||||
*e = make([]*ExportEntry, 0, in.LengthInt())
|
*e = make([]*ExportEntry, 0, in.LengthInt())
|
||||||
for elem := in.ElementIterator(); elem.Next(); {
|
|
||||||
_, value := elem.Element()
|
|
||||||
|
|
||||||
if isEmpty(value) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
yield := func(value cty.Value) bool {
|
||||||
entry := &ExportEntry{}
|
entry := &ExportEntry{}
|
||||||
if err := entry.FromCtyValue(value, p); err != nil {
|
if retErr = entry.FromCtyValue(value, p); retErr != nil {
|
||||||
return err
|
return false
|
||||||
}
|
}
|
||||||
*e = append(*e, entry)
|
*e = append(*e, entry)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
eachElement(in)(yield)
|
||||||
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Exports) ToCtyValue() cty.Value {
|
func (e Exports) ToCtyValue() cty.Value {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package buildflags
|
package buildflags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
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 {
|
func (s *Secret) UnmarshalText(text []byte) error {
|
||||||
value := string(text)
|
value := string(text)
|
||||||
fields, err := csvvalue.Fields(value, nil)
|
fields, err := csvvalue.Fields(value, nil)
|
||||||
|
@@ -28,22 +28,19 @@ func (s *Secrets) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Secrets) fromCtyValue(in cty.Value, p cty.Path) error {
|
func (s *Secrets) fromCtyValue(in cty.Value, p cty.Path) (retErr error) {
|
||||||
*s = make([]*Secret, 0, in.LengthInt())
|
*s = make([]*Secret, 0, in.LengthInt())
|
||||||
for elem := in.ElementIterator(); elem.Next(); {
|
|
||||||
_, value := elem.Element()
|
|
||||||
|
|
||||||
if isEmpty(value) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
yield := func(value cty.Value) bool {
|
||||||
entry := &Secret{}
|
entry := &Secret{}
|
||||||
if err := entry.FromCtyValue(value, p); err != nil {
|
if retErr = entry.FromCtyValue(value, p); retErr != nil {
|
||||||
return err
|
return false
|
||||||
}
|
}
|
||||||
*s = append(*s, entry)
|
*s = append(*s, entry)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
eachElement(in)(yield)
|
||||||
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Secrets) ToCtyValue() cty.Value {
|
func (s Secrets) ToCtyValue() cty.Value {
|
||||||
@@ -71,13 +68,13 @@ func (e *Secret) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if id := conv.GetAttr("id"); !id.IsNull() {
|
if id := conv.GetAttr("id"); !id.IsNull() && id.IsKnown() {
|
||||||
e.ID = id.AsString()
|
e.ID = id.AsString()
|
||||||
}
|
}
|
||||||
if src := conv.GetAttr("src"); !src.IsNull() {
|
if src := conv.GetAttr("src"); !src.IsNull() && src.IsKnown() {
|
||||||
e.FilePath = src.AsString()
|
e.FilePath = src.AsString()
|
||||||
}
|
}
|
||||||
if env := conv.GetAttr("env"); !env.IsNull() {
|
if env := conv.GetAttr("env"); !env.IsNull() && env.IsKnown() {
|
||||||
e.Env = env.AsString()
|
e.Env = env.AsString()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
84
util/buildflags/secrets_test.go
Normal file
84
util/buildflags/secrets_test.go
Normal 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())
|
||||||
|
})
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package buildflags
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
|
"encoding/json"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"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 {
|
func (s *SSH) UnmarshalText(text []byte) error {
|
||||||
parts := strings.SplitN(string(text), "=", 2)
|
parts := strings.SplitN(string(text), "=", 2)
|
||||||
|
|
||||||
|
@@ -28,22 +28,19 @@ func (s *SSHKeys) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHKeys) fromCtyValue(in cty.Value, p cty.Path) error {
|
func (s *SSHKeys) fromCtyValue(in cty.Value, p cty.Path) (retErr error) {
|
||||||
*s = make([]*SSH, 0, in.LengthInt())
|
*s = make([]*SSH, 0, in.LengthInt())
|
||||||
for elem := in.ElementIterator(); elem.Next(); {
|
|
||||||
_, value := elem.Element()
|
|
||||||
|
|
||||||
if isEmpty(value) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
yield := func(value cty.Value) bool {
|
||||||
entry := &SSH{}
|
entry := &SSH{}
|
||||||
if err := entry.FromCtyValue(value, p); err != nil {
|
if retErr = entry.FromCtyValue(value, p); retErr != nil {
|
||||||
return err
|
return false
|
||||||
}
|
}
|
||||||
*s = append(*s, entry)
|
*s = append(*s, entry)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
eachElement(in)(yield)
|
||||||
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SSHKeys) ToCtyValue() cty.Value {
|
func (s SSHKeys) ToCtyValue() cty.Value {
|
||||||
@@ -71,10 +68,10 @@ func (e *SSH) FromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if id := conv.GetAttr("id"); !id.IsNull() {
|
if id := conv.GetAttr("id"); !id.IsNull() && id.IsKnown() {
|
||||||
e.ID = id.AsString()
|
e.ID = id.AsString()
|
||||||
}
|
}
|
||||||
if paths := conv.GetAttr("paths"); !paths.IsNull() {
|
if paths := conv.GetAttr("paths"); !paths.IsNull() && paths.IsKnown() {
|
||||||
if err := gocty.FromCtyValue(paths, &e.Paths); err != nil {
|
if err := gocty.FromCtyValue(paths, &e.Paths); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
85
util/buildflags/ssh_test.go
Normal file
85
util/buildflags/ssh_test.go
Normal 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())
|
||||||
|
})
|
||||||
|
}
|
@@ -34,7 +34,7 @@ func removeDupes[E comparable[E]](s []E) []E {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAndDelete(m map[string]cty.Value, attr string, gv interface{}) error {
|
func getAndDelete(m map[string]cty.Value, attr string, gv interface{}) error {
|
||||||
if v, ok := m[attr]; ok {
|
if v, ok := m[attr]; ok && v.IsKnown() {
|
||||||
delete(m, attr)
|
delete(m, attr)
|
||||||
return gocty.FromCtyValue(v, gv)
|
return gocty.FromCtyValue(v, gv)
|
||||||
}
|
}
|
||||||
@@ -44,11 +44,33 @@ func getAndDelete(m map[string]cty.Value, attr string, gv interface{}) error {
|
|||||||
func asMap(m map[string]cty.Value) map[string]string {
|
func asMap(m map[string]cty.Value) map[string]string {
|
||||||
out := make(map[string]string, len(m))
|
out := make(map[string]string, len(m))
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
out[k] = v.AsString()
|
if v.IsKnown() {
|
||||||
|
out[k] = v.AsString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEmpty(v cty.Value) bool {
|
func isEmptyOrUnknown(v cty.Value) bool {
|
||||||
return v.Type() == cty.String && v.AsString() == ""
|
return !v.IsKnown() || (v.Type() == cty.String && v.AsString() == "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seq is a temporary definition of iter.Seq until we are able to migrate
|
||||||
|
// to using go1.23 as our minimum version. This can be removed when go1.24
|
||||||
|
// is released and usages can be changed to use rangefunc.
|
||||||
|
type Seq[V any] func(yield func(V) bool)
|
||||||
|
|
||||||
|
func eachElement(in cty.Value) Seq[cty.Value] {
|
||||||
|
return func(yield func(v cty.Value) bool) {
|
||||||
|
for elem := in.ElementIterator(); elem.Next(); {
|
||||||
|
_, value := elem.Element()
|
||||||
|
if isEmptyOrUnknown(value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/atomicwriter"
|
||||||
"github.com/moby/buildkit/cmd/buildkitd/config"
|
"github.com/moby/buildkit/cmd/buildkitd/config"
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -106,7 +106,7 @@ func (c *Config) MkdirAll(dir string, perm os.FileMode) error {
|
|||||||
// AtomicWriteFile writes data to a file within the config dir atomically
|
// AtomicWriteFile writes data to a file within the config dir atomically
|
||||||
func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||||
f := filepath.Join(c.dir, filename)
|
f := filepath.Join(c.dir, filename)
|
||||||
if err := ioutils.AtomicWriteFile(f, data, perm); err != nil {
|
if err := atomicwriter.WriteFile(f, data, perm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.chowner == nil {
|
if c.chowner == nil {
|
||||||
|
@@ -3,12 +3,12 @@ package dockerutil
|
|||||||
import (
|
import (
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientAPI represents an active docker API object.
|
// ClientAPI represents an active docker API object.
|
||||||
type ClientAPI struct {
|
type ClientAPI struct {
|
||||||
client.APIClient
|
dockerclient.APIClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientAPI(cli command.Cli, ep string) (*ClientAPI, error) {
|
func NewClientAPI(cli command.Cli, ep string) (*ClientAPI, error) {
|
||||||
@@ -36,7 +36,7 @@ func NewClientAPI(cli command.Cli, ep string) (*ClientAPI, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ca.APIClient, err = client.NewClientWithOpts(clientOpts...)
|
ca.APIClient, err = dockerclient.NewClientWithOpts(clientOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client represents an active docker object.
|
// Client represents an active docker object.
|
||||||
@@ -24,7 +24,7 @@ func NewClient(cli command.Cli) *Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// API returns a new docker API client.
|
// API returns a new docker API client.
|
||||||
func (c *Client) API(name string) (client.APIClient, error) {
|
func (c *Client) API(name string) (dockerclient.APIClient, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = c.cli.CurrentContext()
|
name = c.cli.CurrentContext()
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func (c *Client) LoadImage(ctx context.Context, name string, status progress.Wri
|
|||||||
w.mu.Unlock()
|
w.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := dapi.ImageLoad(ctx, pr, false)
|
resp, err := dapi.ImageLoad(ctx, pr)
|
||||||
defer close(done)
|
defer close(done)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
11125
util/otelutil/fixtures/bktraces.json
Normal file
11125
util/otelutil/fixtures/bktraces.json
Normal file
File diff suppressed because it is too large
Load Diff
9542
util/otelutil/fixtures/jaeger.json
Normal file
9542
util/otelutil/fixtures/jaeger.json
Normal file
File diff suppressed because it is too large
Load Diff
11127
util/otelutil/fixtures/otlp.json
Normal file
11127
util/otelutil/fixtures/otlp.json
Normal file
File diff suppressed because it is too large
Load Diff
45
util/otelutil/jaeger.go
Normal file
45
util/otelutil/jaeger.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package otelutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/otelutil/jaeger"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JaegerData struct {
|
||||||
|
Data []jaeger.Trace `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JaegerData return Jaeger data compatible with ui import feature.
|
||||||
|
// https://github.com/jaegertracing/jaeger-ui/issues/381#issuecomment-494150826
|
||||||
|
func (s Spans) JaegerData() JaegerData {
|
||||||
|
roSpans := s.ReadOnlySpans()
|
||||||
|
|
||||||
|
// fetch default service.name from default resource for backup
|
||||||
|
var defaultServiceName string
|
||||||
|
defaultResource := resource.Default()
|
||||||
|
if value, exists := defaultResource.Set().Value(attribute.Key("service.name")); exists {
|
||||||
|
defaultServiceName = value.AsString()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := jaeger.Trace{
|
||||||
|
TraceID: jaeger.TraceID(roSpans[0].SpanContext().TraceID().String()),
|
||||||
|
Processes: make(map[jaeger.ProcessID]jaeger.Process),
|
||||||
|
Spans: []jaeger.Span{},
|
||||||
|
}
|
||||||
|
for i := range roSpans {
|
||||||
|
ss := roSpans[i]
|
||||||
|
pid := jaeger.ProcessID(fmt.Sprintf("p%d", i))
|
||||||
|
data.Processes[pid] = jaeger.ResourceToProcess(ss.Resource(), defaultServiceName)
|
||||||
|
span := jaeger.ConvertSpan(ss)
|
||||||
|
span.Process = nil
|
||||||
|
span.ProcessID = pid
|
||||||
|
data.Spans = append(data.Spans, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
return JaegerData{
|
||||||
|
Data: []jaeger.Trace{data},
|
||||||
|
}
|
||||||
|
}
|
224
util/otelutil/jaeger/convert.go
Normal file
224
util/otelutil/jaeger/convert.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package jaeger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyInstrumentationLibraryName = "otel.library.name"
|
||||||
|
keyInstrumentationLibraryVersion = "otel.library.version"
|
||||||
|
keyError = "error"
|
||||||
|
keySpanKind = "span.kind"
|
||||||
|
keyStatusCode = "otel.status_code"
|
||||||
|
keyStatusMessage = "otel.status_description"
|
||||||
|
keyDroppedAttributeCount = "otel.event.dropped_attributes_count"
|
||||||
|
keyEventName = "event"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ResourceToProcess(res *resource.Resource, defaultServiceName string) Process {
|
||||||
|
var process Process
|
||||||
|
var serviceName attribute.KeyValue
|
||||||
|
if res != nil {
|
||||||
|
for iter := res.Iter(); iter.Next(); {
|
||||||
|
if iter.Attribute().Key == attribute.Key("service.name") {
|
||||||
|
serviceName = iter.Attribute()
|
||||||
|
// Don't convert service.name into tag.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tag := keyValueToJaegerTag(iter.Attribute()); tag != nil {
|
||||||
|
process.Tags = append(process.Tags, *tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no service.name is contained in a Span's Resource,
|
||||||
|
// that field MUST be populated from the default Resource.
|
||||||
|
if serviceName.Value.AsString() == "" {
|
||||||
|
serviceName = attribute.Key("service.version").String(defaultServiceName)
|
||||||
|
}
|
||||||
|
process.ServiceName = serviceName.Value.AsString()
|
||||||
|
|
||||||
|
return process
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertSpan(ss tracesdk.ReadOnlySpan) Span {
|
||||||
|
attr := ss.Attributes()
|
||||||
|
tags := make([]KeyValue, 0, len(attr))
|
||||||
|
for _, kv := range attr {
|
||||||
|
tag := keyValueToJaegerTag(kv)
|
||||||
|
if tag != nil {
|
||||||
|
tags = append(tags, *tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is := ss.InstrumentationScope(); is.Name != "" {
|
||||||
|
tags = append(tags, getStringTag(keyInstrumentationLibraryName, is.Name))
|
||||||
|
if is.Version != "" {
|
||||||
|
tags = append(tags, getStringTag(keyInstrumentationLibraryVersion, is.Version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss.SpanKind() != trace.SpanKindInternal {
|
||||||
|
tags = append(tags,
|
||||||
|
getStringTag(keySpanKind, ss.SpanKind().String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss.Status().Code != codes.Unset {
|
||||||
|
switch ss.Status().Code {
|
||||||
|
case codes.Ok:
|
||||||
|
tags = append(tags, getStringTag(keyStatusCode, "OK"))
|
||||||
|
case codes.Error:
|
||||||
|
tags = append(tags, getBoolTag(keyError, true))
|
||||||
|
tags = append(tags, getStringTag(keyStatusCode, "ERROR"))
|
||||||
|
}
|
||||||
|
if ss.Status().Description != "" {
|
||||||
|
tags = append(tags, getStringTag(keyStatusMessage, ss.Status().Description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var logs []Log
|
||||||
|
for _, a := range ss.Events() {
|
||||||
|
nTags := len(a.Attributes)
|
||||||
|
if a.Name != "" {
|
||||||
|
nTags++
|
||||||
|
}
|
||||||
|
if a.DroppedAttributeCount != 0 {
|
||||||
|
nTags++
|
||||||
|
}
|
||||||
|
fields := make([]KeyValue, 0, nTags)
|
||||||
|
if a.Name != "" {
|
||||||
|
// If an event contains an attribute with the same key, it needs
|
||||||
|
// to be given precedence and overwrite this.
|
||||||
|
fields = append(fields, getStringTag(keyEventName, a.Name))
|
||||||
|
}
|
||||||
|
for _, kv := range a.Attributes {
|
||||||
|
tag := keyValueToJaegerTag(kv)
|
||||||
|
if tag != nil {
|
||||||
|
fields = append(fields, *tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a.DroppedAttributeCount != 0 {
|
||||||
|
fields = append(fields, getInt64Tag(keyDroppedAttributeCount, int64(a.DroppedAttributeCount)))
|
||||||
|
}
|
||||||
|
logs = append(logs, Log{
|
||||||
|
Timestamp: timeAsEpochMicroseconds(a.Time),
|
||||||
|
Fields: fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var refs []Reference
|
||||||
|
for _, link := range ss.Links() {
|
||||||
|
refs = append(refs, Reference{
|
||||||
|
RefType: FollowsFrom,
|
||||||
|
TraceID: TraceID(link.SpanContext.TraceID().String()),
|
||||||
|
SpanID: SpanID(link.SpanContext.SpanID().String()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refs = append(refs, Reference{
|
||||||
|
RefType: ChildOf,
|
||||||
|
TraceID: TraceID(ss.Parent().TraceID().String()),
|
||||||
|
SpanID: SpanID(ss.Parent().SpanID().String()),
|
||||||
|
})
|
||||||
|
|
||||||
|
return Span{
|
||||||
|
TraceID: TraceID(ss.SpanContext().TraceID().String()),
|
||||||
|
SpanID: SpanID(ss.SpanContext().SpanID().String()),
|
||||||
|
Flags: uint32(ss.SpanContext().TraceFlags()),
|
||||||
|
OperationName: ss.Name(),
|
||||||
|
References: refs,
|
||||||
|
StartTime: timeAsEpochMicroseconds(ss.StartTime()),
|
||||||
|
Duration: durationAsMicroseconds(ss.EndTime().Sub(ss.StartTime())),
|
||||||
|
Tags: tags,
|
||||||
|
Logs: logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyValueToJaegerTag(keyValue attribute.KeyValue) *KeyValue {
|
||||||
|
var tag *KeyValue
|
||||||
|
switch keyValue.Value.Type() {
|
||||||
|
case attribute.STRING:
|
||||||
|
s := keyValue.Value.AsString()
|
||||||
|
tag = &KeyValue{
|
||||||
|
Key: string(keyValue.Key),
|
||||||
|
Type: StringType,
|
||||||
|
Value: s,
|
||||||
|
}
|
||||||
|
case attribute.BOOL:
|
||||||
|
b := keyValue.Value.AsBool()
|
||||||
|
tag = &KeyValue{
|
||||||
|
Key: string(keyValue.Key),
|
||||||
|
Type: BoolType,
|
||||||
|
Value: b,
|
||||||
|
}
|
||||||
|
case attribute.INT64:
|
||||||
|
i := keyValue.Value.AsInt64()
|
||||||
|
tag = &KeyValue{
|
||||||
|
Key: string(keyValue.Key),
|
||||||
|
Type: Int64Type,
|
||||||
|
Value: i,
|
||||||
|
}
|
||||||
|
case attribute.FLOAT64:
|
||||||
|
f := keyValue.Value.AsFloat64()
|
||||||
|
tag = &KeyValue{
|
||||||
|
Key: string(keyValue.Key),
|
||||||
|
Type: Float64Type,
|
||||||
|
Value: f,
|
||||||
|
}
|
||||||
|
case attribute.BOOLSLICE,
|
||||||
|
attribute.INT64SLICE,
|
||||||
|
attribute.FLOAT64SLICE,
|
||||||
|
attribute.STRINGSLICE:
|
||||||
|
data, _ := json.Marshal(keyValue.Value.AsInterface())
|
||||||
|
a := (string)(data)
|
||||||
|
tag = &KeyValue{
|
||||||
|
Key: string(keyValue.Key),
|
||||||
|
Type: StringType,
|
||||||
|
Value: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt64Tag(k string, i int64) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Type: Int64Type,
|
||||||
|
Value: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStringTag(k, s string) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Type: StringType,
|
||||||
|
Value: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBoolTag(k string, b bool) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Type: BoolType,
|
||||||
|
Value: b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeAsEpochMicroseconds converts time.Time to microseconds since epoch,
|
||||||
|
// which is the format the StartTime field is stored in the Span.
|
||||||
|
func timeAsEpochMicroseconds(t time.Time) uint64 {
|
||||||
|
return uint64(t.UnixNano() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationAsMicroseconds converts time.Duration to microseconds,
|
||||||
|
// which is the format the Duration field is stored in the Span.
|
||||||
|
func durationAsMicroseconds(d time.Duration) uint64 {
|
||||||
|
return uint64(d.Nanoseconds() / 1000)
|
||||||
|
}
|
102
util/otelutil/jaeger/model.go
Normal file
102
util/otelutil/jaeger/model.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package jaeger
|
||||||
|
|
||||||
|
// ReferenceType is the reference type of one span to another
|
||||||
|
type ReferenceType string
|
||||||
|
|
||||||
|
// TraceID is the shared trace ID of all spans in the trace.
|
||||||
|
type TraceID string
|
||||||
|
|
||||||
|
// SpanID is the id of a span
|
||||||
|
type SpanID string
|
||||||
|
|
||||||
|
// ProcessID is a hashed value of the Process struct that is unique within the trace.
|
||||||
|
type ProcessID string
|
||||||
|
|
||||||
|
// ValueType is the type of a value stored in KeyValue struct.
|
||||||
|
type ValueType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ChildOf means a span is the child of another span
|
||||||
|
ChildOf ReferenceType = "CHILD_OF"
|
||||||
|
// FollowsFrom means a span follows from another span
|
||||||
|
FollowsFrom ReferenceType = "FOLLOWS_FROM"
|
||||||
|
|
||||||
|
// StringType indicates a string value stored in KeyValue
|
||||||
|
StringType ValueType = "string"
|
||||||
|
// BoolType indicates a Boolean value stored in KeyValue
|
||||||
|
BoolType ValueType = "bool"
|
||||||
|
// Int64Type indicates a 64bit signed integer value stored in KeyValue
|
||||||
|
Int64Type ValueType = "int64"
|
||||||
|
// Float64Type indicates a 64bit float value stored in KeyValue
|
||||||
|
Float64Type ValueType = "float64"
|
||||||
|
// BinaryType indicates an arbitrary byte array stored in KeyValue
|
||||||
|
BinaryType ValueType = "binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Trace is a list of spans
|
||||||
|
type Trace struct {
|
||||||
|
TraceID TraceID `json:"traceID"`
|
||||||
|
Spans []Span `json:"spans"`
|
||||||
|
Processes map[ProcessID]Process `json:"processes"`
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span is a span denoting a piece of work in some infrastructure
|
||||||
|
// When converting to UI model, ParentSpanID and Process should be dereferenced into
|
||||||
|
// References and ProcessID, respectively.
|
||||||
|
// When converting to ES model, ProcessID and Warnings should be omitted. Even if
|
||||||
|
// included, ES with dynamic settings off will automatically ignore unneeded fields.
|
||||||
|
type Span struct {
|
||||||
|
TraceID TraceID `json:"traceID"`
|
||||||
|
SpanID SpanID `json:"spanID"`
|
||||||
|
ParentSpanID SpanID `json:"parentSpanID,omitempty"` // deprecated
|
||||||
|
Flags uint32 `json:"flags,omitempty"`
|
||||||
|
OperationName string `json:"operationName"`
|
||||||
|
References []Reference `json:"references"`
|
||||||
|
StartTime uint64 `json:"startTime"` // microseconds since Unix epoch
|
||||||
|
Duration uint64 `json:"duration"` // microseconds
|
||||||
|
Tags []KeyValue `json:"tags"`
|
||||||
|
Logs []Log `json:"logs"`
|
||||||
|
ProcessID ProcessID `json:"processID,omitempty"`
|
||||||
|
Process *Process `json:"process,omitempty"`
|
||||||
|
Warnings []string `json:"warnings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference is a reference from one span to another
|
||||||
|
type Reference struct {
|
||||||
|
RefType ReferenceType `json:"refType"`
|
||||||
|
TraceID TraceID `json:"traceID"`
|
||||||
|
SpanID SpanID `json:"spanID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process is the process emitting a set of spans
|
||||||
|
type Process struct {
|
||||||
|
ServiceName string `json:"serviceName"`
|
||||||
|
Tags []KeyValue `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log is a log emitted in a span
|
||||||
|
type Log struct {
|
||||||
|
Timestamp uint64 `json:"timestamp"`
|
||||||
|
Fields []KeyValue `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyValue is a key-value pair with typed value.
|
||||||
|
type KeyValue struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Type ValueType `json:"type,omitempty"`
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DependencyLink shows dependencies between services
|
||||||
|
type DependencyLink struct {
|
||||||
|
Parent string `json:"parent"`
|
||||||
|
Child string `json:"child"`
|
||||||
|
CallCount uint64 `json:"callCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation defines the data in the operation response when query operation by service and span kind
|
||||||
|
type Operation struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
SpanKind string `json:"spanKind"`
|
||||||
|
}
|
27
util/otelutil/jaeger_test.go
Normal file
27
util/otelutil/jaeger_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package otelutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const jaegerFixture = "./fixtures/jaeger.json"
|
||||||
|
|
||||||
|
func TestJaegerData(t *testing.T) {
|
||||||
|
dt, err := os.ReadFile(bktracesFixture)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
spanStubs, err := ParseSpanStubs(bytes.NewReader(dt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
trace := spanStubs.JaegerData()
|
||||||
|
dtJaegerTrace, err := json.MarshalIndent(trace, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
dtJaeger, err := os.ReadFile(jaegerFixture)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(dtJaeger), string(dtJaegerTrace))
|
||||||
|
}
|
491
util/otelutil/span.go
Normal file
491
util/otelutil/span.go
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
package otelutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Span is a type similar to otel's SpanStub, but with the correct types needed
|
||||||
|
// for handle marshaling and unmarshalling.
|
||||||
|
type Span struct {
|
||||||
|
// Name is the name of a specific span
|
||||||
|
Name string
|
||||||
|
// SpanContext is the unique SpanContext that identifies the span
|
||||||
|
SpanContext trace.SpanContext
|
||||||
|
// Parten is the unique SpanContext that identifies the parent of the span.
|
||||||
|
// If the span has no parent, this span context will be invalid.
|
||||||
|
Parent trace.SpanContext
|
||||||
|
// SpanKind is the role the span plays in a Trace
|
||||||
|
SpanKind trace.SpanKind
|
||||||
|
// StartTime is the time the span started recording
|
||||||
|
StartTime time.Time
|
||||||
|
// EndTime returns the time the span stopped recording
|
||||||
|
EndTime time.Time
|
||||||
|
// Attributes are the defining attributes of a span
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
// Events are all the events that occurred within the span
|
||||||
|
Events []tracesdk.Event
|
||||||
|
// Links are all the links the span has to other spans
|
||||||
|
Links []tracesdk.Link
|
||||||
|
// Status is that span status
|
||||||
|
Status tracesdk.Status
|
||||||
|
// DroppedAttributes is the number of attributes dropped by the span due to
|
||||||
|
// a limit being reached
|
||||||
|
DroppedAttributes int
|
||||||
|
// DroppedEvents is the number of attributes dropped by the span due to a
|
||||||
|
// limit being reached
|
||||||
|
DroppedEvents int
|
||||||
|
// DroppedLinks is the number of links dropped by the span due to a limit
|
||||||
|
// being reached
|
||||||
|
DroppedLinks int
|
||||||
|
// ChildSpanCount is the count of spans that consider the span a direct
|
||||||
|
// parent
|
||||||
|
ChildSpanCount int
|
||||||
|
// Resource is the information about the entity that produced the span
|
||||||
|
// We have to change this type from the otel type to make this struct
|
||||||
|
// marshallable
|
||||||
|
Resource []attribute.KeyValue
|
||||||
|
// InstrumentationLibrary is information about the library that produced
|
||||||
|
// the span
|
||||||
|
//nolint:staticcheck
|
||||||
|
InstrumentationLibrary instrumentation.Library
|
||||||
|
}
|
||||||
|
|
||||||
|
type Spans []Span
|
||||||
|
|
||||||
|
// Len return the length of the Spans.
|
||||||
|
func (s Spans) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOnlySpans return a list of tracesdk.ReadOnlySpan from span stubs.
|
||||||
|
func (s Spans) ReadOnlySpans() []tracesdk.ReadOnlySpan {
|
||||||
|
roSpans := make([]tracesdk.ReadOnlySpan, len(s))
|
||||||
|
for i := range s {
|
||||||
|
roSpans[i] = s[i].Snapshot()
|
||||||
|
}
|
||||||
|
return roSpans
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSpanStubs parses BuildKit trace data into a list of SpanStubs.
|
||||||
|
func ParseSpanStubs(rdr io.Reader) (Spans, error) {
|
||||||
|
var spanStubs []Span
|
||||||
|
decoder := json.NewDecoder(rdr)
|
||||||
|
for {
|
||||||
|
var span Span
|
||||||
|
if err := decoder.Decode(&span); err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error decoding JSON")
|
||||||
|
}
|
||||||
|
spanStubs = append(spanStubs, span)
|
||||||
|
}
|
||||||
|
return spanStubs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// spanData is data that we need to unmarshal in custom ways.
|
||||||
|
type spanData struct {
|
||||||
|
Name string
|
||||||
|
SpanContext spanContext
|
||||||
|
Parent spanContext
|
||||||
|
SpanKind trace.SpanKind
|
||||||
|
StartTime time.Time
|
||||||
|
EndTime time.Time
|
||||||
|
Attributes []keyValue
|
||||||
|
Events []event
|
||||||
|
Links []link
|
||||||
|
Status tracesdk.Status
|
||||||
|
DroppedAttributes int
|
||||||
|
DroppedEvents int
|
||||||
|
DroppedLinks int
|
||||||
|
ChildSpanCount int
|
||||||
|
Resource []keyValue // change this type from the otel type to make this struct marshallable
|
||||||
|
//nolint:staticcheck
|
||||||
|
InstrumentationLibrary instrumentation.Library
|
||||||
|
}
|
||||||
|
|
||||||
|
// spanContext is a custom type used to unmarshal otel SpanContext correctly.
|
||||||
|
type spanContext struct {
|
||||||
|
TraceID string
|
||||||
|
SpanID string
|
||||||
|
TraceFlags string
|
||||||
|
TraceState string // TODO: implement, currently dropped
|
||||||
|
Remote bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// event is a custom type used to unmarshal otel Event correctly.
|
||||||
|
type event struct {
|
||||||
|
Name string
|
||||||
|
Attributes []keyValue
|
||||||
|
DroppedAttributeCount int
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// link is a custom type used to unmarshal otel Link correctly.
|
||||||
|
type link struct {
|
||||||
|
SpanContext spanContext
|
||||||
|
Attributes []keyValue
|
||||||
|
DroppedAttributeCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValue is a custom type used to unmarshal otel KeyValue correctly.
|
||||||
|
type keyValue struct {
|
||||||
|
Key string
|
||||||
|
Value value
|
||||||
|
}
|
||||||
|
|
||||||
|
// value is a custom type used to unmarshal otel Value correctly.
|
||||||
|
type value struct {
|
||||||
|
Type string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler for Span which allows correctly
|
||||||
|
// retrieving attribute.KeyValue values.
|
||||||
|
func (s *Span) UnmarshalJSON(data []byte) error {
|
||||||
|
var sd spanData
|
||||||
|
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&sd); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to decode to spanData")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Name = sd.Name
|
||||||
|
s.SpanKind = sd.SpanKind
|
||||||
|
s.StartTime = sd.StartTime
|
||||||
|
s.EndTime = sd.EndTime
|
||||||
|
s.Status = sd.Status
|
||||||
|
s.DroppedAttributes = sd.DroppedAttributes
|
||||||
|
s.DroppedEvents = sd.DroppedEvents
|
||||||
|
s.DroppedLinks = sd.DroppedLinks
|
||||||
|
s.ChildSpanCount = sd.ChildSpanCount
|
||||||
|
s.InstrumentationLibrary = sd.InstrumentationLibrary
|
||||||
|
|
||||||
|
spanCtx, err := sd.SpanContext.asTraceSpanContext()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to decode spanCtx")
|
||||||
|
}
|
||||||
|
s.SpanContext = spanCtx
|
||||||
|
|
||||||
|
parent, err := sd.Parent.asTraceSpanContext()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to decode parent")
|
||||||
|
}
|
||||||
|
s.Parent = parent
|
||||||
|
|
||||||
|
var attributes []attribute.KeyValue
|
||||||
|
for _, a := range sd.Attributes {
|
||||||
|
kv, err := a.asAttributeKeyValue()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to decode attribute (%s)", a.Key)
|
||||||
|
}
|
||||||
|
attributes = append(attributes, kv)
|
||||||
|
}
|
||||||
|
s.Attributes = attributes
|
||||||
|
|
||||||
|
var events []tracesdk.Event
|
||||||
|
for _, e := range sd.Events {
|
||||||
|
var eventAttributes []attribute.KeyValue
|
||||||
|
for _, a := range e.Attributes {
|
||||||
|
kv, err := a.asAttributeKeyValue()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to decode event attribute (%s)", a.Key)
|
||||||
|
}
|
||||||
|
eventAttributes = append(eventAttributes, kv)
|
||||||
|
}
|
||||||
|
events = append(events, tracesdk.Event{
|
||||||
|
Name: e.Name,
|
||||||
|
Attributes: eventAttributes,
|
||||||
|
DroppedAttributeCount: e.DroppedAttributeCount,
|
||||||
|
Time: e.Time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s.Events = events
|
||||||
|
|
||||||
|
var links []tracesdk.Link
|
||||||
|
for _, l := range sd.Links {
|
||||||
|
linkSpanCtx, err := l.SpanContext.asTraceSpanContext()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to decode linkSpanCtx")
|
||||||
|
}
|
||||||
|
var linkAttributes []attribute.KeyValue
|
||||||
|
for _, a := range l.Attributes {
|
||||||
|
kv, err := a.asAttributeKeyValue()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to decode link attribute (%s)", a.Key)
|
||||||
|
}
|
||||||
|
linkAttributes = append(linkAttributes, kv)
|
||||||
|
}
|
||||||
|
links = append(links, tracesdk.Link{
|
||||||
|
SpanContext: linkSpanCtx,
|
||||||
|
Attributes: linkAttributes,
|
||||||
|
DroppedAttributeCount: l.DroppedAttributeCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s.Links = links
|
||||||
|
|
||||||
|
var resources []attribute.KeyValue
|
||||||
|
for _, r := range sd.Resource {
|
||||||
|
kv, err := r.asAttributeKeyValue()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to decode resource (%s)", r.Key)
|
||||||
|
}
|
||||||
|
resources = append(resources, kv)
|
||||||
|
}
|
||||||
|
s.Resource = resources
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asTraceSpanContext converts the internal spanContext representation to an
|
||||||
|
// otel one.
|
||||||
|
func (sc *spanContext) asTraceSpanContext() (trace.SpanContext, error) {
|
||||||
|
traceID, err := traceIDFromHex(sc.TraceID)
|
||||||
|
if err != nil {
|
||||||
|
return trace.SpanContext{}, errors.Wrap(err, "unable to parse trace id")
|
||||||
|
}
|
||||||
|
spanID, err := spanIDFromHex(sc.SpanID)
|
||||||
|
if err != nil {
|
||||||
|
return trace.SpanContext{}, errors.Wrap(err, "unable to parse span id")
|
||||||
|
}
|
||||||
|
traceFlags := trace.TraceFlags(0x00)
|
||||||
|
if sc.TraceFlags == "01" {
|
||||||
|
traceFlags = trace.TraceFlags(0x01)
|
||||||
|
}
|
||||||
|
config := trace.SpanContextConfig{
|
||||||
|
TraceID: traceID,
|
||||||
|
SpanID: spanID,
|
||||||
|
TraceFlags: traceFlags,
|
||||||
|
Remote: sc.Remote,
|
||||||
|
}
|
||||||
|
return trace.NewSpanContext(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asAttributeKeyValue converts the internal keyValue representation to an
|
||||||
|
// otel one.
|
||||||
|
func (kv *keyValue) asAttributeKeyValue() (attribute.KeyValue, error) {
|
||||||
|
// value types get encoded as string
|
||||||
|
switch kv.Value.Type {
|
||||||
|
case attribute.INVALID.String():
|
||||||
|
return attribute.KeyValue{}, errors.New("invalid value type")
|
||||||
|
case attribute.BOOL.String():
|
||||||
|
return attribute.Bool(kv.Key, kv.Value.Value.(bool)), nil
|
||||||
|
case attribute.INT64.String():
|
||||||
|
// value could be int64 or float64, so handle both cases (float64 comes
|
||||||
|
// from json unmarshal)
|
||||||
|
var v int64
|
||||||
|
switch i := kv.Value.Value.(type) {
|
||||||
|
case int64:
|
||||||
|
v = i
|
||||||
|
case float64:
|
||||||
|
v = int64(i)
|
||||||
|
}
|
||||||
|
return attribute.Int64(kv.Key, v), nil
|
||||||
|
case attribute.FLOAT64.String():
|
||||||
|
return attribute.Float64(kv.Key, kv.Value.Value.(float64)), nil
|
||||||
|
case attribute.STRING.String():
|
||||||
|
return attribute.String(kv.Key, kv.Value.Value.(string)), nil
|
||||||
|
case attribute.BOOLSLICE.String():
|
||||||
|
return attribute.BoolSlice(kv.Key, kv.Value.Value.([]bool)), nil
|
||||||
|
case attribute.INT64SLICE.String():
|
||||||
|
// handle both float64 and int64 (float64 comes from json unmarshal)
|
||||||
|
var v []int64
|
||||||
|
switch sli := kv.Value.Value.(type) {
|
||||||
|
case []int64:
|
||||||
|
v = sli
|
||||||
|
case []float64:
|
||||||
|
for i := range sli {
|
||||||
|
v = append(v, int64(sli[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attribute.Int64Slice(kv.Key, v), nil
|
||||||
|
case attribute.FLOAT64SLICE.String():
|
||||||
|
return attribute.Float64Slice(kv.Key, kv.Value.Value.([]float64)), nil
|
||||||
|
case attribute.STRINGSLICE.String():
|
||||||
|
var strSli []string
|
||||||
|
// sometimes we can get an []interface{} instead of a []string, so
|
||||||
|
// always cast to []string if that happens.
|
||||||
|
switch sli := kv.Value.Value.(type) {
|
||||||
|
case []string:
|
||||||
|
strSli = sli
|
||||||
|
case []interface{}:
|
||||||
|
for i := range sli {
|
||||||
|
var v string
|
||||||
|
// best case we have a string, otherwise, cast it using
|
||||||
|
// fmt.Sprintf
|
||||||
|
if str, ok := sli[i].(string); ok {
|
||||||
|
v = str
|
||||||
|
} else {
|
||||||
|
v = fmt.Sprintf("%v", sli[i])
|
||||||
|
}
|
||||||
|
// add the string to the slice
|
||||||
|
strSli = append(strSli, v)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return attribute.KeyValue{}, errors.Errorf("got unsupported type %q for %s", reflect.ValueOf(kv.Value.Value).Kind(), attribute.STRINGSLICE.String())
|
||||||
|
}
|
||||||
|
return attribute.StringSlice(kv.Key, strSli), nil
|
||||||
|
default:
|
||||||
|
return attribute.KeyValue{}, errors.Errorf("unknown value type %s", kv.Value.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceIDFromHex returns a TraceID from a hex string if it is compliant with
|
||||||
|
// the W3C trace-context specification and removes the validity check.
|
||||||
|
// https://www.w3.org/TR/trace-context/#trace-id
|
||||||
|
func traceIDFromHex(h string) (trace.TraceID, error) {
|
||||||
|
t := trace.TraceID{}
|
||||||
|
if len(h) != 32 {
|
||||||
|
return t, errors.New("unable to parse trace id")
|
||||||
|
}
|
||||||
|
if err := decodeHex(h, t[:]); err != nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// spanIDFromHex returns a SpanID from a hex string if it is compliant with the
|
||||||
|
// W3C trace-context specification and removes the validity check.
|
||||||
|
// https://www.w3.org/TR/trace-context/#parent-id
|
||||||
|
func spanIDFromHex(h string) (trace.SpanID, error) {
|
||||||
|
s := trace.SpanID{}
|
||||||
|
if len(h) != 16 {
|
||||||
|
return s, errors.New("unable to parse span id of length: %d")
|
||||||
|
}
|
||||||
|
if err := decodeHex(h, s[:]); err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeHex decodes hex in a manner compliant with otel.
|
||||||
|
func decodeHex(h string, b []byte) error {
|
||||||
|
for _, r := range h {
|
||||||
|
switch {
|
||||||
|
case 'a' <= r && r <= 'f':
|
||||||
|
continue
|
||||||
|
case '0' <= r && r <= '9':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return errors.New("unable to parse hex id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoded, err := hex.DecodeString(h)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy(b, decoded)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot turns a Span into a ReadOnlySpan which is exportable by otel.
|
||||||
|
func (s *Span) Snapshot() tracesdk.ReadOnlySpan {
|
||||||
|
return spanSnapshot{
|
||||||
|
name: s.Name,
|
||||||
|
spanContext: s.SpanContext,
|
||||||
|
parent: s.Parent,
|
||||||
|
spanKind: s.SpanKind,
|
||||||
|
startTime: s.StartTime,
|
||||||
|
endTime: s.EndTime,
|
||||||
|
attributes: s.Attributes,
|
||||||
|
events: s.Events,
|
||||||
|
links: s.Links,
|
||||||
|
status: s.Status,
|
||||||
|
droppedAttributes: s.DroppedAttributes,
|
||||||
|
droppedEvents: s.DroppedEvents,
|
||||||
|
droppedLinks: s.DroppedLinks,
|
||||||
|
childSpanCount: s.ChildSpanCount,
|
||||||
|
resource: resource.NewSchemaless(s.Resource...),
|
||||||
|
instrumentationScope: s.InstrumentationLibrary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spanSnapshot is a helper type for transforming a Span into a ReadOnlySpan.
|
||||||
|
type spanSnapshot struct {
|
||||||
|
// Embed the interface to implement the private method.
|
||||||
|
tracesdk.ReadOnlySpan
|
||||||
|
|
||||||
|
name string
|
||||||
|
spanContext trace.SpanContext
|
||||||
|
parent trace.SpanContext
|
||||||
|
spanKind trace.SpanKind
|
||||||
|
startTime time.Time
|
||||||
|
endTime time.Time
|
||||||
|
attributes []attribute.KeyValue
|
||||||
|
events []tracesdk.Event
|
||||||
|
links []tracesdk.Link
|
||||||
|
status tracesdk.Status
|
||||||
|
droppedAttributes int
|
||||||
|
droppedEvents int
|
||||||
|
droppedLinks int
|
||||||
|
childSpanCount int
|
||||||
|
resource *resource.Resource
|
||||||
|
instrumentationScope instrumentation.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the Name of the snapshot
|
||||||
|
func (s spanSnapshot) Name() string { return s.name }
|
||||||
|
|
||||||
|
// SpanContext returns the SpanContext of the snapshot
|
||||||
|
func (s spanSnapshot) SpanContext() trace.SpanContext { return s.spanContext }
|
||||||
|
|
||||||
|
// Parent returns the Parent of the snapshot
|
||||||
|
func (s spanSnapshot) Parent() trace.SpanContext { return s.parent }
|
||||||
|
|
||||||
|
// SpanKind returns the SpanKind of the snapshot
|
||||||
|
func (s spanSnapshot) SpanKind() trace.SpanKind { return s.spanKind }
|
||||||
|
|
||||||
|
// StartTime returns the StartTime of the snapshot
|
||||||
|
func (s spanSnapshot) StartTime() time.Time { return s.startTime }
|
||||||
|
|
||||||
|
// EndTime returns the EndTime of the snapshot
|
||||||
|
func (s spanSnapshot) EndTime() time.Time { return s.endTime }
|
||||||
|
|
||||||
|
// Attributes returns the Attributes of the snapshot
|
||||||
|
func (s spanSnapshot) Attributes() []attribute.KeyValue { return s.attributes }
|
||||||
|
|
||||||
|
// Links returns the Links of the snapshot
|
||||||
|
func (s spanSnapshot) Links() []tracesdk.Link { return s.links }
|
||||||
|
|
||||||
|
// Events return the Events of the snapshot
|
||||||
|
func (s spanSnapshot) Events() []tracesdk.Event { return s.events }
|
||||||
|
|
||||||
|
// Status returns the Status of the snapshot
|
||||||
|
func (s spanSnapshot) Status() tracesdk.Status { return s.status }
|
||||||
|
|
||||||
|
// DroppedAttributes returns the DroppedAttributes of the snapshot
|
||||||
|
func (s spanSnapshot) DroppedAttributes() int { return s.droppedAttributes }
|
||||||
|
|
||||||
|
// DroppedLinks returns the DroppedLinks of the snapshot
|
||||||
|
func (s spanSnapshot) DroppedLinks() int { return s.droppedLinks }
|
||||||
|
|
||||||
|
// DroppedEvents returns the DroppedEvents of the snapshot
|
||||||
|
func (s spanSnapshot) DroppedEvents() int { return s.droppedEvents }
|
||||||
|
|
||||||
|
// ChildSpanCount returns the ChildSpanCount of the snapshot
|
||||||
|
func (s spanSnapshot) ChildSpanCount() int { return s.childSpanCount }
|
||||||
|
|
||||||
|
// Resource returns the Resource of the snapshot
|
||||||
|
func (s spanSnapshot) Resource() *resource.Resource { return s.resource }
|
||||||
|
|
||||||
|
// InstrumentationScope returns the InstrumentationScope of the snapshot
|
||||||
|
func (s spanSnapshot) InstrumentationScope() instrumentation.Scope {
|
||||||
|
return s.instrumentationScope
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentationLibrary returns the InstrumentationLibrary of the snapshot
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
func (s spanSnapshot) InstrumentationLibrary() instrumentation.Library {
|
||||||
|
return s.instrumentationScope
|
||||||
|
}
|
159
util/otelutil/span_test.go
Normal file
159
util/otelutil/span_test.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package otelutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// curl -s --unix-socket /tmp/docker-desktop-build-dev.sock http://localhost/blobs/default/default?digest=sha256:3103104e9fa908087bd47572da6ad9a5a7bf973608f736536d18d635a7da0140 -X GET > ./fixtures/bktraces.json
|
||||||
|
const bktracesFixture = "./fixtures/bktraces.json"
|
||||||
|
|
||||||
|
const otlpFixture = "./fixtures/otlp.json"
|
||||||
|
|
||||||
|
func TestParseSpanStubs(t *testing.T) {
|
||||||
|
dt, err := os.ReadFile(bktracesFixture)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
spanStubs, err := ParseSpanStubs(bytes.NewReader(dt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 73, len(spanStubs))
|
||||||
|
|
||||||
|
dtSpanStubs, err := json.MarshalIndent(spanStubs, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
dtotel, err := os.ReadFile(otlpFixture)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(dtotel), string(dtSpanStubs))
|
||||||
|
|
||||||
|
exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, exp.ExportSpans(context.Background(), spanStubs.ReadOnlySpans()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsAttributeKeyValue(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
Type string
|
||||||
|
value any
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want attribute.KeyValue
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "string",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.STRING.String(),
|
||||||
|
value: "value",
|
||||||
|
},
|
||||||
|
want: attribute.String("key", "value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 (int64)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.INT64.String(),
|
||||||
|
value: int64(1),
|
||||||
|
},
|
||||||
|
want: attribute.Int64("key", 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 (float64)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.INT64.String(),
|
||||||
|
value: float64(1.0),
|
||||||
|
},
|
||||||
|
want: attribute.Int64("key", 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.BOOL.String(),
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
want: attribute.Bool("key", true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.FLOAT64.String(),
|
||||||
|
value: float64(1.0),
|
||||||
|
},
|
||||||
|
want: attribute.Float64("key", 1.0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64slice",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.FLOAT64SLICE.String(),
|
||||||
|
value: []float64{1.0, 2.0},
|
||||||
|
},
|
||||||
|
want: attribute.Float64Slice("key", []float64{1.0, 2.0}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64slice (int64)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.INT64SLICE.String(),
|
||||||
|
value: []int64{1, 2},
|
||||||
|
},
|
||||||
|
want: attribute.Int64Slice("key", []int64{1, 2}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64slice (float64)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.INT64SLICE.String(),
|
||||||
|
value: []float64{1.0, 2.0},
|
||||||
|
},
|
||||||
|
want: attribute.Int64Slice("key", []int64{1, 2}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boolslice",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.BOOLSLICE.String(),
|
||||||
|
value: []bool{true, false},
|
||||||
|
},
|
||||||
|
want: attribute.BoolSlice("key", []bool{true, false}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stringslice (strings)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.STRINGSLICE.String(),
|
||||||
|
value: []string{"value1", "value2"},
|
||||||
|
},
|
||||||
|
want: attribute.StringSlice("key", []string{"value1", "value2"}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stringslice (interface of string)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.STRINGSLICE.String(),
|
||||||
|
value: []interface{}{"value1", "value2"},
|
||||||
|
},
|
||||||
|
want: attribute.StringSlice("key", []string{"value1", "value2"}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stringslice (interface mixed)",
|
||||||
|
args: args{
|
||||||
|
Type: attribute.STRINGSLICE.String(),
|
||||||
|
value: []interface{}{"value1", 2},
|
||||||
|
},
|
||||||
|
want: attribute.StringSlice("key", []string{"value1", "2"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
kv := keyValue{
|
||||||
|
Key: "key",
|
||||||
|
Value: value{Type: tt.args.Type, Value: tt.args.value},
|
||||||
|
}
|
||||||
|
attr, err := kv.asAttributeKeyValue()
|
||||||
|
require.NoError(t, err, "failed to convert key value to attribute key value")
|
||||||
|
assert.Equal(t, tt.want, attr, "attribute key value mismatch")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -122,6 +122,7 @@ func NewPrinter(ctx context.Context, out console.File, mode progressui.DisplayMo
|
|||||||
for {
|
for {
|
||||||
pw.status = make(chan *client.SolveStatus)
|
pw.status = make(chan *client.SolveStatus)
|
||||||
pw.done = make(chan struct{})
|
pw.done = make(chan struct{})
|
||||||
|
pw.closeOnce = sync.Once{}
|
||||||
|
|
||||||
pw.logMu.Lock()
|
pw.logMu.Lock()
|
||||||
pw.logSourceMap = map[digest.Digest]interface{}{}
|
pw.logSourceMap = map[digest.Digest]interface{}{}
|
||||||
|
@@ -2,7 +2,6 @@ package tracing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/moby/buildkit/util/tracing/delegated"
|
"github.com/moby/buildkit/util/tracing/delegated"
|
||||||
@@ -13,7 +12,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TraceCurrentCommand(ctx context.Context, name string) (context.Context, func(error), error) {
|
func TraceCurrentCommand(ctx context.Context, args []string, attrs ...attribute.KeyValue) (context.Context, func(error), error) {
|
||||||
opts := []sdktrace.TracerProviderOption{
|
opts := []sdktrace.TracerProviderOption{
|
||||||
sdktrace.WithResource(detect.Resource()),
|
sdktrace.WithResource(detect.Resource()),
|
||||||
sdktrace.WithBatcher(delegated.DefaultExporter),
|
sdktrace.WithBatcher(delegated.DefaultExporter),
|
||||||
@@ -25,8 +24,8 @@ func TraceCurrentCommand(ctx context.Context, name string) (context.Context, fun
|
|||||||
}
|
}
|
||||||
|
|
||||||
tp := sdktrace.NewTracerProvider(opts...)
|
tp := sdktrace.NewTracerProvider(opts...)
|
||||||
ctx, span := tp.Tracer("").Start(ctx, name, trace.WithAttributes(
|
ctx, span := tp.Tracer("").Start(ctx, strings.Join(args, " "), trace.WithAttributes(
|
||||||
attribute.String("command", strings.Join(os.Args, " ")),
|
attrs...,
|
||||||
))
|
))
|
||||||
|
|
||||||
return ctx, func(err error) {
|
return ctx, func(err error) {
|
||||||
|
41
vendor/github.com/Azure/go-ansiterm/SECURITY.md
generated
vendored
Normal file
41
vendor/github.com/Azure/go-ansiterm/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||||
|
|
||||||
|
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||||
|
|
||||||
|
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||||
|
|
||||||
|
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||||
|
|
||||||
|
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||||
|
|
||||||
|
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||||
|
|
||||||
|
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||||
|
* Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
* Any special configuration required to reproduce the issue
|
||||||
|
* Step-by-step instructions to reproduce the issue
|
||||||
|
* Proof-of-concept or exploit code (if possible)
|
||||||
|
* Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||||
|
|
||||||
|
## Preferred Languages
|
||||||
|
|
||||||
|
We prefer all communications to be in English.
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||||
|
|
||||||
|
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
18
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
18
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
@@ -11,21 +11,13 @@ func (oscState oscStringState) Handle(b byte) (s state, e error) {
|
|||||||
return nextState, err
|
return nextState, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
// There are several control characters and sequences which can
|
||||||
case isOscStringTerminator(b):
|
// terminate an OSC string. Most of them are handled by the baseState
|
||||||
|
// handler. The ANSI_BEL character is a special case which behaves as a
|
||||||
|
// terminator only for an OSC string.
|
||||||
|
if b == ANSI_BEL {
|
||||||
return oscState.parser.ground, nil
|
return oscState.parser.ground, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return oscState, nil
|
return oscState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// See below for OSC string terminators for linux
|
|
||||||
// http://man7.org/linux/man-pages/man4/console_codes.4.html
|
|
||||||
func isOscStringTerminator(b byte) bool {
|
|
||||||
|
|
||||||
if b == ANSI_BEL || b == 0x5C {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
4
vendor/github.com/docker/cli/cli-plugins/hooks/printer.go
generated
vendored
4
vendor/github.com/docker/cli/cli-plugins/hooks/printer.go
generated
vendored
@@ -11,8 +11,8 @@ func PrintNextSteps(out io.Writer, messages []string) {
|
|||||||
if len(messages) == 0 {
|
if len(messages) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
|
_, _ = fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
|
||||||
for _, n := range messages {
|
for _, n := range messages {
|
||||||
_, _ = fmt.Fprintf(out, " %s\n", n)
|
_, _ = fmt.Fprintln(out, " ", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
vendor/github.com/docker/cli/cli-plugins/manager/cobra.go
generated
vendored
3
vendor/github.com/docker/cli/cli-plugins/manager/cobra.go
generated
vendored
@@ -52,7 +52,6 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
p := p
|
|
||||||
vendor := p.Vendor
|
vendor := p.Vendor
|
||||||
if vendor == "" {
|
if vendor == "" {
|
||||||
vendor = "unknown"
|
vendor = "unknown"
|
||||||
@@ -82,7 +81,7 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
|
|||||||
cmd.HelpFunc()(rootCmd, args)
|
cmd.HelpFunc()(rootCmd, args)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", cmd.Name())
|
return fmt.Errorf("docker: unknown command: docker %s\n\nRun 'docker --help' for more information", cmd.Name())
|
||||||
},
|
},
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
// Delegate completion to plugin
|
// Delegate completion to plugin
|
||||||
|
24
vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go
generated
vendored
24
vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go
generated
vendored
@@ -3,6 +3,7 @@ package plugin
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -34,7 +35,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
|||||||
|
|
||||||
var persistentPreRunOnce sync.Once
|
var persistentPreRunOnce sync.Once
|
||||||
PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
|
PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
|
||||||
var err error
|
var retErr error
|
||||||
persistentPreRunOnce.Do(func() {
|
persistentPreRunOnce.Do(func() {
|
||||||
ctx, cancel := context.WithCancel(cmd.Context())
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
cmd.SetContext(ctx)
|
cmd.SetContext(ctx)
|
||||||
@@ -46,7 +47,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
|||||||
opts = append(opts, withPluginClientConn(plugin.Name()))
|
opts = append(opts, withPluginClientConn(plugin.Name()))
|
||||||
}
|
}
|
||||||
opts = append(opts, command.WithEnableGlobalMeterProvider(), command.WithEnableGlobalTracerProvider())
|
opts = append(opts, command.WithEnableGlobalMeterProvider(), command.WithEnableGlobalTracerProvider())
|
||||||
err = tcmd.Initialize(opts...)
|
retErr = tcmd.Initialize(opts...)
|
||||||
ogRunE := cmd.RunE
|
ogRunE := cmd.RunE
|
||||||
if ogRunE == nil {
|
if ogRunE == nil {
|
||||||
ogRun := cmd.Run
|
ogRun := cmd.Run
|
||||||
@@ -66,7 +67,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return err
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, args, err := tcmd.HandleGlobalFlags()
|
cmd, args, err := tcmd.HandleGlobalFlags()
|
||||||
@@ -92,18 +93,17 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta manager.Metadata) {
|
|||||||
plugin := makeCmd(dockerCli)
|
plugin := makeCmd(dockerCli)
|
||||||
|
|
||||||
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
|
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
|
||||||
if sterr, ok := err.(cli.StatusError); ok {
|
var stErr cli.StatusError
|
||||||
if sterr.Status != "" {
|
if errors.As(err, &stErr) {
|
||||||
fmt.Fprintln(dockerCli.Err(), sterr.Status)
|
|
||||||
}
|
|
||||||
// StatusError should only be used for errors, and all errors should
|
// StatusError should only be used for errors, and all errors should
|
||||||
// have a non-zero exit status, so never exit with 0
|
// have a non-zero exit status, so never exit with 0
|
||||||
if sterr.StatusCode == 0 {
|
if stErr.StatusCode == 0 { // FIXME(thaJeztah): this should never be used with a zero status-code. Check if we do this anywhere.
|
||||||
os.Exit(1)
|
stErr.StatusCode = 1
|
||||||
}
|
}
|
||||||
os.Exit(sterr.StatusCode)
|
_, _ = fmt.Fprintln(dockerCli.Err(), stErr)
|
||||||
|
os.Exit(stErr.StatusCode)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(dockerCli.Err(), err)
|
_, _ = fmt.Fprintln(dockerCli.Err(), err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
|
|||||||
CompletionOptions: cobra.CompletionOptions{
|
CompletionOptions: cobra.CompletionOptions{
|
||||||
DisableDefaultCmd: false,
|
DisableDefaultCmd: false,
|
||||||
HiddenDefaultCmd: true,
|
HiddenDefaultCmd: true,
|
||||||
DisableDescriptions: true,
|
DisableDescriptions: os.Getenv("DOCKER_CLI_DISABLE_COMPLETION_DESCRIPTION") != "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
opts, _ := cli.SetupPluginRootCommand(cmd)
|
opts, _ := cli.SetupPluginRootCommand(cmd)
|
||||||
|
8
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
8
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
@@ -92,12 +92,8 @@ func FlagErrorFunc(cmd *cobra.Command, err error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
usage := ""
|
|
||||||
if cmd.HasSubCommands() {
|
|
||||||
usage = "\n\n" + cmd.UsageString()
|
|
||||||
}
|
|
||||||
return StatusError{
|
return StatusError{
|
||||||
Status: fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage),
|
Status: fmt.Sprintf("%s\n\nUsage: %s\n\nRun '%s --help' for more information", err, cmd.UseLine(), cmd.CommandPath()),
|
||||||
StatusCode: 125,
|
StatusCode: 125,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,4 +518,4 @@ Run '{{.CommandPath}} COMMAND --help' for more information on a command.
|
|||||||
`
|
`
|
||||||
|
|
||||||
const helpTemplate = `
|
const helpTemplate = `
|
||||||
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
{{- if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
||||||
|
8
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
8
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
@@ -114,7 +114,7 @@ func (cli *DockerCli) CurrentVersion() string {
|
|||||||
// Client returns the APIClient
|
// Client returns the APIClient
|
||||||
func (cli *DockerCli) Client() client.APIClient {
|
func (cli *DockerCli) Client() client.APIClient {
|
||||||
if err := cli.initialize(); err != nil {
|
if err := cli.initialize(); err != nil {
|
||||||
_, _ = fmt.Fprintf(cli.Err(), "Failed to initialize: %s\n", err)
|
_, _ = fmt.Fprintln(cli.Err(), "Failed to initialize:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return cli.client
|
return cli.client
|
||||||
@@ -272,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
|||||||
debug.Enable()
|
debug.Enable()
|
||||||
}
|
}
|
||||||
if opts.Context != "" && len(opts.Hosts) > 0 {
|
if opts.Context != "" && len(opts.Hosts) > 0 {
|
||||||
return errors.New("conflicting options: either specify --host or --context, not both")
|
return errors.New("conflicting options: cannot specify both --host and --context")
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.options = opts
|
cli.options = opts
|
||||||
@@ -299,7 +299,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
|||||||
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
||||||
func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||||
if opts.Context != "" && len(opts.Hosts) > 0 {
|
if opts.Context != "" && len(opts.Hosts) > 0 {
|
||||||
return nil, errors.New("conflicting options: either specify --host or --context, not both")
|
return nil, errors.New("conflicting options: cannot specify both --host and --context")
|
||||||
}
|
}
|
||||||
|
|
||||||
storeConfig := DefaultContextStoreConfig()
|
storeConfig := DefaultContextStoreConfig()
|
||||||
@@ -475,7 +475,7 @@ func (cli *DockerCli) DockerEndpoint() docker.Endpoint {
|
|||||||
if err := cli.initialize(); err != nil {
|
if err := cli.initialize(); err != nil {
|
||||||
// Note that we're not terminating here, as this function may be used
|
// Note that we're not terminating here, as this function may be used
|
||||||
// in cases where we're able to continue.
|
// in cases where we're able to continue.
|
||||||
_, _ = fmt.Fprintf(cli.Err(), "%v\n", cli.initErr)
|
_, _ = fmt.Fprintln(cli.Err(), cli.initErr)
|
||||||
}
|
}
|
||||||
return cli.dockerEndpoint
|
return cli.dockerEndpoint
|
||||||
}
|
}
|
||||||
|
20
vendor/github.com/docker/cli/cli/command/formatter/container.go
generated
vendored
20
vendor/github.com/docker/cli/cli/command/formatter/container.go
generated
vendored
@@ -12,7 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
@@ -67,10 +67,10 @@ ports: {{- pad .Ports 1 0}}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ContainerWrite renders the context for a list of containers
|
// ContainerWrite renders the context for a list of containers
|
||||||
func ContainerWrite(ctx Context, containers []types.Container) error {
|
func ContainerWrite(ctx Context, containers []container.Summary) error {
|
||||||
render := func(format func(subContext SubContext) error) error {
|
render := func(format func(subContext SubContext) error) error {
|
||||||
for _, container := range containers {
|
for _, ctr := range containers {
|
||||||
err := format(&ContainerContext{trunc: ctx.Trunc, c: container})
|
err := format(&ContainerContext{trunc: ctx.Trunc, c: ctr})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func ContainerWrite(ctx Context, containers []types.Container) error {
|
|||||||
type ContainerContext struct {
|
type ContainerContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
trunc bool
|
trunc bool
|
||||||
c types.Container
|
c container.Summary
|
||||||
|
|
||||||
// FieldsUsed is used in the pre-processing step to detect which fields are
|
// FieldsUsed is used in the pre-processing step to detect which fields are
|
||||||
// used in the template. It's currently only used to detect use of the .Size
|
// used in the template. It's currently only used to detect use of the .Size
|
||||||
@@ -193,7 +193,9 @@ func (c *ContainerContext) Command() string {
|
|||||||
return strconv.Quote(command)
|
return strconv.Quote(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAt returns the "Created" date/time of the container as a unix timestamp.
|
// CreatedAt returns the formatted string representing the container's creation date/time.
|
||||||
|
// The format may include nanoseconds if present.
|
||||||
|
// e.g. "2006-01-02 15:04:05.999999999 -0700 MST" or "2006-01-02 15:04:05 -0700 MST"
|
||||||
func (c *ContainerContext) CreatedAt() string {
|
func (c *ContainerContext) CreatedAt() string {
|
||||||
return time.Unix(c.c.Created, 0).String()
|
return time.Unix(c.c.Created, 0).String()
|
||||||
}
|
}
|
||||||
@@ -314,7 +316,7 @@ func (c *ContainerContext) Networks() string {
|
|||||||
// DisplayablePorts returns formatted string representing open ports of container
|
// DisplayablePorts returns formatted string representing open ports of container
|
||||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
||||||
// it's used by command 'docker ps'
|
// it's used by command 'docker ps'
|
||||||
func DisplayablePorts(ports []types.Port) string {
|
func DisplayablePorts(ports []container.Port) string {
|
||||||
type portGroup struct {
|
type portGroup struct {
|
||||||
first uint16
|
first uint16
|
||||||
last uint16
|
last uint16
|
||||||
@@ -375,12 +377,12 @@ func formGroup(key string, start, last uint16) string {
|
|||||||
group = fmt.Sprintf("%s-%d", group, last)
|
group = fmt.Sprintf("%s-%d", group, last)
|
||||||
}
|
}
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
group = fmt.Sprintf("%s->%s", net.JoinHostPort(ip, group), group)
|
||||||
}
|
}
|
||||||
return group + "/" + groupType
|
return group + "/" + groupType
|
||||||
}
|
}
|
||||||
|
|
||||||
func comparePorts(i, j types.Port) bool {
|
func comparePorts(i, j container.Port) bool {
|
||||||
if i.PrivatePort != j.PrivatePort {
|
if i.PrivatePort != j.PrivatePort {
|
||||||
return i.PrivatePort < j.PrivatePort
|
return i.PrivatePort < j.PrivatePort
|
||||||
}
|
}
|
||||||
|
38
vendor/github.com/docker/cli/cli/command/formatter/disk_usage.go
generated
vendored
38
vendor/github.com/docker/cli/cli/command/formatter/disk_usage.go
generated
vendored
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
@@ -36,7 +37,7 @@ type DiskUsageContext struct {
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
LayersSize int64
|
LayersSize int64
|
||||||
Images []*image.Summary
|
Images []*image.Summary
|
||||||
Containers []*types.Container
|
Containers []*container.Summary
|
||||||
Volumes []*volume.Volume
|
Volumes []*volume.Volume
|
||||||
BuildCache []*types.BuildCache
|
BuildCache []*types.BuildCache
|
||||||
BuilderSize int64
|
BuilderSize int64
|
||||||
@@ -124,7 +125,7 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
|
diskUsageContainersCtx := diskUsageContainersContext{containers: []*container.Summary{}}
|
||||||
diskUsageContainersCtx.Header = SubHeaderContext{
|
diskUsageContainersCtx.Header = SubHeaderContext{
|
||||||
"Type": typeHeader,
|
"Type": typeHeader,
|
||||||
"TotalCount": totalHeader,
|
"TotalCount": totalHeader,
|
||||||
@@ -236,7 +237,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
_, _ = ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
||||||
for _, v := range duc.Volumes {
|
for _, v := range duc.Volumes {
|
||||||
if err := ctx.contextFormat(tmpl, v); err != nil {
|
if err := ctx.contextFormat(tmpl, v); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -248,7 +249,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
||||||
for _, v := range duc.BuildCache {
|
for _, v := range duc.BuildCache {
|
||||||
if err := ctx.contextFormat(tmpl, v); err != nil {
|
if err := ctx.contextFormat(tmpl, v); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -313,7 +314,7 @@ func (c *diskUsageImagesContext) Reclaimable() string {
|
|||||||
|
|
||||||
type diskUsageContainersContext struct {
|
type diskUsageContainersContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
containers []*types.Container
|
containers []*container.Summary
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
|
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
|
||||||
@@ -328,16 +329,16 @@ func (c *diskUsageContainersContext) TotalCount() string {
|
|||||||
return strconv.Itoa(len(c.containers))
|
return strconv.Itoa(len(c.containers))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) isActive(container types.Container) bool {
|
func (c *diskUsageContainersContext) isActive(ctr container.Summary) bool {
|
||||||
return strings.Contains(container.State, "running") ||
|
return strings.Contains(ctr.State, "running") ||
|
||||||
strings.Contains(container.State, "paused") ||
|
strings.Contains(ctr.State, "paused") ||
|
||||||
strings.Contains(container.State, "restarting")
|
strings.Contains(ctr.State, "restarting")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) Active() string {
|
func (c *diskUsageContainersContext) Active() string {
|
||||||
used := 0
|
used := 0
|
||||||
for _, container := range c.containers {
|
for _, ctr := range c.containers {
|
||||||
if c.isActive(*container) {
|
if c.isActive(*ctr) {
|
||||||
used++
|
used++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,22 +349,21 @@ func (c *diskUsageContainersContext) Active() string {
|
|||||||
func (c *diskUsageContainersContext) Size() string {
|
func (c *diskUsageContainersContext) Size() string {
|
||||||
var size int64
|
var size int64
|
||||||
|
|
||||||
for _, container := range c.containers {
|
for _, ctr := range c.containers {
|
||||||
size += container.SizeRw
|
size += ctr.SizeRw
|
||||||
}
|
}
|
||||||
|
|
||||||
return units.HumanSize(float64(size))
|
return units.HumanSize(float64(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diskUsageContainersContext) Reclaimable() string {
|
func (c *diskUsageContainersContext) Reclaimable() string {
|
||||||
var reclaimable int64
|
var reclaimable, totalSize int64
|
||||||
var totalSize int64
|
|
||||||
|
|
||||||
for _, container := range c.containers {
|
for _, ctr := range c.containers {
|
||||||
if !c.isActive(*container) {
|
if !c.isActive(*ctr) {
|
||||||
reclaimable += container.SizeRw
|
reclaimable += ctr.SizeRw
|
||||||
}
|
}
|
||||||
totalSize += container.SizeRw
|
totalSize += ctr.SizeRw
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalSize > 0 {
|
if totalSize > 0 {
|
||||||
|
8
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
8
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
@@ -13,9 +13,10 @@ import (
|
|||||||
configtypes "github.com/docker/cli/cli/config/types"
|
configtypes "github.com/docker/cli/cli/config/types"
|
||||||
"github.com/docker/cli/cli/hints"
|
"github.com/docker/cli/cli/hints"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/cli/internal/tui"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ const (
|
|||||||
|
|
||||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||||
// for the given command.
|
// for the given command.
|
||||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
||||||
return func(ctx context.Context) (string, error) {
|
return func(ctx context.Context) (string, error) {
|
||||||
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
||||||
indexServer := registry.GetAuthConfigKey(index)
|
indexServer := registry.GetAuthConfigKey(index)
|
||||||
@@ -179,6 +180,9 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
out := tui.NewOutput(cli.Err())
|
||||||
|
out.PrintNote("A Personal Access Token (PAT) can be used instead.\n" +
|
||||||
|
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
|
||||||
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return registrytypes.AuthConfig{}, err
|
return registrytypes.AuthConfig{}, err
|
||||||
|
2
vendor/github.com/docker/cli/cli/command/telemetry.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/telemetry.go
generated
vendored
@@ -14,7 +14,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
2
vendor/github.com/docker/cli/cli/command/utils.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/utils.go
generated
vendored
@@ -199,7 +199,7 @@ func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
|
|||||||
// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later.
|
// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later.
|
||||||
func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
|
func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
|
||||||
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
||||||
flags.SetAnnotation("platform", "version", []string{"1.32"})
|
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateOutputPath validates the output paths of the `export` and `save` commands.
|
// ValidateOutputPath validates the output paths of the `export` and `save` commands.
|
||||||
|
2
vendor/github.com/docker/cli/cli/config/config.go
generated
vendored
2
vendor/github.com/docker/cli/cli/config/config.go
generated
vendored
@@ -143,7 +143,7 @@ func load(configDir string) (*configfile.ConfigFile, error) {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
err = configFile.LoadFromReader(file)
|
err = configFile.LoadFromReader(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrapf(err, "loading config file: %s: ", filename)
|
err = errors.Wrapf(err, "parsing config file (%s)", filename)
|
||||||
}
|
}
|
||||||
return configFile, err
|
return configFile, err
|
||||||
}
|
}
|
||||||
|
36
vendor/github.com/docker/cli/cli/config/credentials/file_store.go
generated
vendored
36
vendor/github.com/docker/cli/cli/config/credentials/file_store.go
generated
vendored
@@ -1,9 +1,12 @@
|
|||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/config/types"
|
"github.com/docker/cli/cli/config/types"
|
||||||
)
|
)
|
||||||
@@ -57,6 +60,21 @@ func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
|||||||
return c.file.GetAuthConfigs(), nil
|
return c.file.GetAuthConfigs(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unencryptedWarning warns the user when using an insecure credential storage.
|
||||||
|
// After a deprecation period, user will get prompted if stdin and stderr are a terminal.
|
||||||
|
// Otherwise, we'll assume they want it (sadly), because people may have been scripting
|
||||||
|
// insecure logins and we don't want to break them. Maybe they'll see the warning in their
|
||||||
|
// logs and fix things.
|
||||||
|
const unencryptedWarning = `
|
||||||
|
WARNING! Your credentials are stored unencrypted in '%s'.
|
||||||
|
Configure a credential helper to remove this warning. See
|
||||||
|
https://docs.docker.com/go/credential-store/
|
||||||
|
`
|
||||||
|
|
||||||
|
// alreadyPrinted ensures that we only print the unencryptedWarning once per
|
||||||
|
// CLI invocation (no need to warn the user multiple times per command).
|
||||||
|
var alreadyPrinted atomic.Bool
|
||||||
|
|
||||||
// Store saves the given credentials in the file store. This function is
|
// Store saves the given credentials in the file store. This function is
|
||||||
// idempotent and does not update the file if credentials did not change.
|
// idempotent and does not update the file if credentials did not change.
|
||||||
func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
||||||
@@ -66,15 +84,19 @@ func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
authConfigs[authConfig.ServerAddress] = authConfig
|
authConfigs[authConfig.ServerAddress] = authConfig
|
||||||
return c.file.Save()
|
if err := c.file.Save(); err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *fileStore) GetFilename() string {
|
if !alreadyPrinted.Load() && authConfig.Password != "" {
|
||||||
return c.file.GetFilename()
|
// Display a warning if we're storing the users password (not a token).
|
||||||
}
|
//
|
||||||
|
// FIXME(thaJeztah): make output configurable instead of hardcoding to os.Stderr
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf(unencryptedWarning, c.file.GetFilename()))
|
||||||
|
alreadyPrinted.Store(true)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *fileStore) IsFileStore() bool {
|
return nil
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertToHostname converts a registry url which has http|https prepended
|
// ConvertToHostname converts a registry url which has http|https prepended
|
||||||
|
22
vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go
generated
vendored
22
vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go
generated
vendored
@@ -33,18 +33,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New returns net.Conn
|
// New returns net.Conn
|
||||||
func New(_ context.Context, cmd string, args ...string) (net.Conn, error) {
|
func New(ctx context.Context, cmd string, args ...string) (net.Conn, error) {
|
||||||
var (
|
// Don't kill the ssh process if the context is cancelled. Killing the
|
||||||
c commandConn
|
// ssh process causes an error when go's http.Client tries to reuse the
|
||||||
err error
|
// net.Conn (commandConn).
|
||||||
)
|
//
|
||||||
c.cmd = exec.Command(cmd, args...)
|
// Not passing down the Context might seem counter-intuitive, but in this
|
||||||
|
// case, the lifetime of the process should be managed by the http.Client,
|
||||||
|
// not the caller's Context.
|
||||||
|
//
|
||||||
|
// Further details;;
|
||||||
|
//
|
||||||
|
// - https://github.com/docker/cli/pull/3900
|
||||||
|
// - https://github.com/docker/compose/issues/9448#issuecomment-1264263721
|
||||||
|
ctx = context.WithoutCancel(ctx)
|
||||||
|
c := commandConn{cmd: exec.CommandContext(ctx, cmd, args...)}
|
||||||
// we assume that args never contains sensitive information
|
// we assume that args never contains sensitive information
|
||||||
logrus.Debugf("commandconn: starting %s with %v", cmd, args)
|
logrus.Debugf("commandconn: starting %s with %v", cmd, args)
|
||||||
c.cmd.Env = os.Environ()
|
c.cmd.Env = os.Environ()
|
||||||
c.cmd.SysProcAttr = &syscall.SysProcAttr{}
|
c.cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
setPdeathsig(c.cmd)
|
setPdeathsig(c.cmd)
|
||||||
createSession(c.cmd)
|
createSession(c.cmd)
|
||||||
|
var err error
|
||||||
c.stdin, err = c.cmd.StdinPipe()
|
c.stdin, err = c.cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
4
vendor/github.com/docker/cli/cli/context/store/metadatastore.go
generated
vendored
4
vendor/github.com/docker/cli/cli/context/store/metadatastore.go
generated
vendored
@@ -12,7 +12,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/atomicwriter"
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@@ -40,7 +40,7 @@ func (s *metadataStore) createOrUpdate(meta Metadata) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ioutils.AtomicWriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644)
|
return atomicwriter.WriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) {
|
func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) {
|
||||||
|
4
vendor/github.com/docker/cli/cli/context/store/tlsstore.go
generated
vendored
4
vendor/github.com/docker/cli/cli/context/store/tlsstore.go
generated
vendored
@@ -5,7 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/atomicwriter"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ func (s *tlsStore) createOrUpdate(name, endpointName, filename string, data []by
|
|||||||
if err := os.MkdirAll(endpointDir, 0o700); err != nil {
|
if err := os.MkdirAll(endpointDir, 0o700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ioutils.AtomicWriteFile(filepath.Join(endpointDir, filename), data, 0o600)
|
return atomicwriter.WriteFile(filepath.Join(endpointDir, filename), data, 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) {
|
func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) {
|
||||||
|
38
vendor/github.com/docker/cli/cli/error.go
generated
vendored
38
vendor/github.com/docker/cli/cli/error.go
generated
vendored
@@ -1,35 +1,29 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors is a list of errors.
|
|
||||||
// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
|
|
||||||
// all the errors that happened during the loop.
|
|
||||||
//
|
|
||||||
// Deprecated: use [errors.Join] instead; will be removed in the next release.
|
|
||||||
type Errors []error
|
|
||||||
|
|
||||||
func (errList Errors) Error() string {
|
|
||||||
if len(errList) < 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]string, len(errList))
|
|
||||||
for i := range errList {
|
|
||||||
out[i] = errList[i].Error()
|
|
||||||
}
|
|
||||||
return strings.Join(out, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatusError reports an unsuccessful exit by a command.
|
// StatusError reports an unsuccessful exit by a command.
|
||||||
type StatusError struct {
|
type StatusError struct {
|
||||||
|
Cause error
|
||||||
Status string
|
Status string
|
||||||
StatusCode int
|
StatusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error formats the error for printing. If a custom Status is provided,
|
||||||
|
// it is returned as-is, otherwise it generates a generic error-message
|
||||||
|
// based on the StatusCode.
|
||||||
func (e StatusError) Error() string {
|
func (e StatusError) Error() string {
|
||||||
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
|
if e.Status != "" {
|
||||||
|
return e.Status
|
||||||
|
}
|
||||||
|
if e.Cause != nil {
|
||||||
|
return e.Cause.Error()
|
||||||
|
}
|
||||||
|
return "exit status " + strconv.Itoa(e.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e StatusError) Unwrap() error {
|
||||||
|
return e.Cause
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/docker/cli/cli/flags/options.go
generated
vendored
2
vendor/github.com/docker/cli/cli/flags/options.go
generated
vendored
@@ -138,7 +138,7 @@ func SetLogLevel(logLevel string) {
|
|||||||
if logLevel != "" {
|
if logLevel != "" {
|
||||||
lvl, err := logrus.ParseLevel(logLevel)
|
lvl, err := logrus.ParseLevel(logLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", logLevel)
|
_, _ = fmt.Fprintln(os.Stderr, "Unable to parse logging level:", logLevel)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
logrus.SetLevel(lvl)
|
logrus.SetLevel(lvl)
|
||||||
|
4
vendor/github.com/docker/cli/cli/hints/hints.go
generated
vendored
4
vendor/github.com/docker/cli/cli/hints/hints.go
generated
vendored
@@ -5,7 +5,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enabled returns whether cli hints are enabled or not
|
// Enabled returns whether cli hints are enabled or not. Hints are enabled by
|
||||||
|
// default, but can be disabled through the "DOCKER_CLI_HINTS" environment
|
||||||
|
// variable.
|
||||||
func Enabled() bool {
|
func Enabled() bool {
|
||||||
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
|
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
|
||||||
enabled, err := strconv.ParseBool(v)
|
enabled, err := strconv.ParseBool(v)
|
||||||
|
7
vendor/github.com/docker/cli/cli/registry/client/endpoint.go
generated
vendored
7
vendor/github.com/docker/cli/cli/registry/client/endpoint.go
generated
vendored
@@ -22,12 +22,7 @@ type repositoryEndpoint struct {
|
|||||||
|
|
||||||
// Name returns the repository name
|
// Name returns the repository name
|
||||||
func (r repositoryEndpoint) Name() string {
|
func (r repositoryEndpoint) Name() string {
|
||||||
repoName := r.info.Name.Name()
|
return reference.Path(r.info.Name)
|
||||||
// If endpoint does not support CanonicalName, use the RemoteName instead
|
|
||||||
if r.endpoint.TrimHostname {
|
|
||||||
repoName = reference.Path(r.info.Name)
|
|
||||||
}
|
|
||||||
return repoName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseURL returns the endpoint url
|
// BaseURL returns the endpoint url
|
||||||
|
40
vendor/github.com/docker/cli/cli/required.go
generated
vendored
40
vendor/github.com/docker/cli/cli/required.go
generated
vendored
@@ -1,8 +1,6 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -14,15 +12,20 @@ func NoArgs(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cmd.HasSubCommands() {
|
if cmd.HasSubCommands() {
|
||||||
return errors.New("\n" + strings.TrimRight(cmd.UsageString(), "\n"))
|
return errors.Errorf(
|
||||||
|
"%[1]s: unknown command: %[2]s %[3]s\n\nUsage: %[4]s\n\nRun '%[2]s --help' for more information",
|
||||||
|
binName(cmd),
|
||||||
|
cmd.CommandPath(),
|
||||||
|
args[0],
|
||||||
|
cmd.UseLine(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
"%q accepts no arguments.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
"%[1]s: '%[2]s' accepts no arguments\n\nUsage: %[3]s\n\nRun '%[2]s --help' for more information",
|
||||||
cmd.CommandPath(),
|
binName(cmd),
|
||||||
cmd.CommandPath(),
|
cmd.CommandPath(),
|
||||||
cmd.UseLine(),
|
cmd.UseLine(),
|
||||||
cmd.Short,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,13 +36,12 @@ func RequiresMinArgs(minArgs int) cobra.PositionalArgs {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
"%q requires at least %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
"%[1]s: '%[2]s' requires at least %[3]d %[4]s\n\nUsage: %[5]s\n\nSee '%[2]s --help' for more information",
|
||||||
|
binName(cmd),
|
||||||
cmd.CommandPath(),
|
cmd.CommandPath(),
|
||||||
minArgs,
|
minArgs,
|
||||||
pluralize("argument", minArgs),
|
pluralize("argument", minArgs),
|
||||||
cmd.CommandPath(),
|
|
||||||
cmd.UseLine(),
|
cmd.UseLine(),
|
||||||
cmd.Short,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,13 +53,12 @@ func RequiresMaxArgs(maxArgs int) cobra.PositionalArgs {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
"%q requires at most %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
"%[1]s: '%[2]s' requires at most %[3]d %[4]s\n\nUsage: %[5]s\n\nSRun '%[2]s --help' for more information",
|
||||||
|
binName(cmd),
|
||||||
cmd.CommandPath(),
|
cmd.CommandPath(),
|
||||||
maxArgs,
|
maxArgs,
|
||||||
pluralize("argument", maxArgs),
|
pluralize("argument", maxArgs),
|
||||||
cmd.CommandPath(),
|
|
||||||
cmd.UseLine(),
|
cmd.UseLine(),
|
||||||
cmd.Short,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,14 +70,13 @@ func RequiresRangeArgs(minArgs int, maxArgs int) cobra.PositionalArgs {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
"%q requires at least %d and at most %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
"%[1]s: '%[2]s' requires at least %[3]d and at most %[4]d %[5]s\n\nUsage: %[6]s\n\nRun '%[2]s --help' for more information",
|
||||||
|
binName(cmd),
|
||||||
cmd.CommandPath(),
|
cmd.CommandPath(),
|
||||||
minArgs,
|
minArgs,
|
||||||
maxArgs,
|
maxArgs,
|
||||||
pluralize("argument", maxArgs),
|
pluralize("argument", maxArgs),
|
||||||
cmd.CommandPath(),
|
|
||||||
cmd.UseLine(),
|
cmd.UseLine(),
|
||||||
cmd.Short,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,17 +88,21 @@ func ExactArgs(number int) cobra.PositionalArgs {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
"%q requires exactly %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
"%[1]s: '%[2]s' requires %[3]d %[4]s\n\nUsage: %[5]s\n\nRun '%[2]s --help' for more information",
|
||||||
|
binName(cmd),
|
||||||
cmd.CommandPath(),
|
cmd.CommandPath(),
|
||||||
number,
|
number,
|
||||||
pluralize("argument", number),
|
pluralize("argument", number),
|
||||||
cmd.CommandPath(),
|
|
||||||
cmd.UseLine(),
|
cmd.UseLine(),
|
||||||
cmd.Short,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// binName returns the name of the binary / root command (usually 'docker').
|
||||||
|
func binName(cmd *cobra.Command) string {
|
||||||
|
return cmd.Root().Name()
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:unparam
|
//nolint:unparam
|
||||||
func pluralize(word string, number int) string {
|
func pluralize(word string, number int) string {
|
||||||
if number == 1 {
|
if number == 1 {
|
||||||
|
12
vendor/github.com/docker/cli/internal/tui/chip.go
generated
vendored
Normal file
12
vendor/github.com/docker/cli/internal/tui/chip.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func Chip(fg, bg int, content string) string {
|
||||||
|
fgAnsi := "\x1b[38;5;" + strconv.Itoa(fg) + "m"
|
||||||
|
bgAnsi := "\x1b[48;5;" + strconv.Itoa(bg) + "m"
|
||||||
|
return fgAnsi + bgAnsi + content + "\x1b[0m"
|
||||||
|
}
|
33
vendor/github.com/docker/cli/internal/tui/colors.go
generated
vendored
Normal file
33
vendor/github.com/docker/cli/internal/tui/colors.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/morikuni/aec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ColorTitle = aec.NewBuilder(aec.DefaultF, aec.Bold).ANSI
|
||||||
|
ColorPrimary = aec.NewBuilder(aec.DefaultF, aec.Bold).ANSI
|
||||||
|
ColorSecondary = aec.DefaultF
|
||||||
|
ColorTertiary = aec.NewBuilder(aec.DefaultF, aec.Faint).ANSI
|
||||||
|
ColorLink = aec.NewBuilder(aec.LightCyanF, aec.Underline).ANSI
|
||||||
|
ColorWarning = aec.LightYellowF
|
||||||
|
ColorFlag = aec.NewBuilder(aec.Bold).ANSI
|
||||||
|
ColorNone = aec.ANSI(noColor{})
|
||||||
|
)
|
||||||
|
|
||||||
|
type noColor struct{}
|
||||||
|
|
||||||
|
func (a noColor) With(_ ...aec.ANSI) aec.ANSI {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a noColor) Apply(s string) string {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a noColor) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
70
vendor/github.com/docker/cli/internal/tui/count.go
generated
vendored
Normal file
70
vendor/github.com/docker/cli/internal/tui/count.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cleanANSI(s string) string {
|
||||||
|
for {
|
||||||
|
start := strings.Index(s, "\x1b")
|
||||||
|
if start == -1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
end := strings.Index(s[start:], "m")
|
||||||
|
if end == -1 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s = s[:start] + s[start+end+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width returns the width of the string, ignoring ANSI escape codes.
|
||||||
|
// Not all ANSI escape codes are supported yet.
|
||||||
|
func Width(s string) int {
|
||||||
|
return runewidth.StringWidth(cleanANSI(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ellipsis truncates a string to a given number of runes with an ellipsis at the end.
|
||||||
|
// It tries to persist the ANSI escape sequences.
|
||||||
|
func Ellipsis(s string, length int) string {
|
||||||
|
out := make([]rune, 0, length)
|
||||||
|
ln := 0
|
||||||
|
inEscape := false
|
||||||
|
tooLong := false
|
||||||
|
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '\x1b' {
|
||||||
|
out = append(out, r)
|
||||||
|
inEscape = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inEscape {
|
||||||
|
out = append(out, r)
|
||||||
|
if r == 'm' {
|
||||||
|
inEscape = false
|
||||||
|
if tooLong {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ln += 1
|
||||||
|
if ln == length {
|
||||||
|
tooLong = true
|
||||||
|
}
|
||||||
|
if !tooLong {
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tooLong {
|
||||||
|
return string(out) + "…"
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
39
vendor/github.com/docker/cli/internal/tui/note.go
generated
vendored
Normal file
39
vendor/github.com/docker/cli/internal/tui/note.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/morikuni/aec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InfoHeader = Str{
|
||||||
|
Plain: " Info -> ",
|
||||||
|
Fancy: aec.Bold.Apply(aec.LightCyanB.Apply(aec.BlackF.Apply("i")) + " " + aec.LightCyanF.Apply("Info → ")),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Output) PrintNote(format string, args ...any) {
|
||||||
|
if o.isTerminal {
|
||||||
|
// TODO: Handle all flags
|
||||||
|
format = strings.ReplaceAll(format, "--platform", ColorFlag.Apply("--platform"))
|
||||||
|
}
|
||||||
|
|
||||||
|
header := o.Sprint(InfoHeader)
|
||||||
|
|
||||||
|
_, _ = fmt.Fprint(o, "\n", header)
|
||||||
|
s := fmt.Sprintf(format, args...)
|
||||||
|
for idx, line := range strings.Split(s, "\n") {
|
||||||
|
if idx > 0 {
|
||||||
|
_, _ = fmt.Fprint(o, strings.Repeat(" ", Width(header)))
|
||||||
|
}
|
||||||
|
|
||||||
|
l := line
|
||||||
|
if o.isTerminal {
|
||||||
|
l = aec.Italic.Apply(l)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(o, l)
|
||||||
|
}
|
||||||
|
}
|
62
vendor/github.com/docker/cli/internal/tui/output.go
generated
vendored
Normal file
62
vendor/github.com/docker/cli/internal/tui/output.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/streams"
|
||||||
|
"github.com/morikuni/aec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Output struct {
|
||||||
|
*streams.Out
|
||||||
|
isTerminal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type terminalPrintable interface {
|
||||||
|
String(isTerminal bool) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutput(out *streams.Out) Output {
|
||||||
|
return Output{
|
||||||
|
Out: out,
|
||||||
|
isTerminal: out.IsTerminal(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Output) Color(clr aec.ANSI) aec.ANSI {
|
||||||
|
if o.isTerminal {
|
||||||
|
return clr
|
||||||
|
}
|
||||||
|
return ColorNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Output) Sprint(all ...any) string {
|
||||||
|
var out []any
|
||||||
|
for _, p := range all {
|
||||||
|
if s, ok := p.(terminalPrintable); ok {
|
||||||
|
out = append(out, s.String(o.isTerminal))
|
||||||
|
} else {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprint(out...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Output) PrintlnWithColor(clr aec.ANSI, args ...any) {
|
||||||
|
msg := o.Sprint(args...)
|
||||||
|
if o.isTerminal {
|
||||||
|
msg = clr.Apply(msg)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(o.Out, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Output) Println(p ...any) {
|
||||||
|
_, _ = fmt.Fprintln(o.Out, o.Sprint(p...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Output) Print(p ...any) {
|
||||||
|
_, _ = fmt.Print(o.Out, o.Sprint(p...))
|
||||||
|
}
|
19
vendor/github.com/docker/cli/internal/tui/str.go
generated
vendored
Normal file
19
vendor/github.com/docker/cli/internal/tui/str.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
type Str struct {
|
||||||
|
// Fancy is the fancy string representation of the string.
|
||||||
|
Fancy string
|
||||||
|
|
||||||
|
// Plain is the plain string representation of the string.
|
||||||
|
Plain string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Str) String(isTerminal bool) string {
|
||||||
|
if isTerminal {
|
||||||
|
return p.Fancy
|
||||||
|
}
|
||||||
|
return p.Plain
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user