mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-20 10:45:56 +08:00
Compare commits
32 Commits
v0.20.0-rc
...
v0.20
Author | SHA1 | Date | |
---|---|---|---|
![]() |
245093b99a | ||
![]() |
e2ed15f0c9 | ||
![]() |
fd442f8e10 | ||
![]() |
1002e6fb42 | ||
![]() |
d5ad869033 | ||
![]() |
bd7090b981 | ||
![]() |
24f3a1df80 | ||
![]() |
8e30c4669c | ||
![]() |
cf74356afc | ||
![]() |
c1d3955fbe | ||
![]() |
d0b63e60e2 | ||
![]() |
e141c8fa71 | ||
![]() |
2ee156236b | ||
![]() |
1335264c9d | ||
![]() |
e74185aa6d | ||
![]() |
0224773102 | ||
![]() |
8c27b5c545 | ||
![]() |
f7594d484b | ||
![]() |
f118749cdc | ||
![]() |
0d92ad713c | ||
![]() |
a18ff4d5ef | ||
![]() |
b035a04aaa | ||
![]() |
6220e0aae8 | ||
![]() |
d9abc78e8f | ||
![]() |
3313026961 | ||
![]() |
06912aa24c | ||
![]() |
cde0e9814d | ||
![]() |
2e6e146087 | ||
![]() |
af3cbe6cec | ||
![]() |
1ef9e67cbb | ||
![]() |
75204426bd | ||
![]() |
6f5486e718 |
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:
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -54,9 +54,9 @@ jobs:
|
|||||||
- master
|
- master
|
||||||
- latest
|
- latest
|
||||||
- buildx-stable-1
|
- buildx-stable-1
|
||||||
|
- v0.19.0-rc2
|
||||||
- v0.18.2
|
- v0.18.2
|
||||||
- v0.17.2
|
- v0.17.2
|
||||||
- v0.16.0
|
|
||||||
worker:
|
worker:
|
||||||
- docker-container
|
- docker-container
|
||||||
- remote
|
- remote
|
||||||
|
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:
|
||||||
|
@@ -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.4.1
|
ARG DOCKER_VERSION=27.5.0
|
||||||
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.18.2
|
ARG BUILDKIT_VERSION=v0.19.0-rc2
|
||||||
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
|
||||||
|
43
bake/bake.go
43
bake/bake.go
@@ -29,7 +29,6 @@ import (
|
|||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
"github.com/moby/buildkit/util/entitlements"
|
"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 +555,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 +862,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 +899,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 +912,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
|
||||||
}
|
}
|
||||||
@@ -1581,37 +1584,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",
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
604
commands/history/inspect.go
Normal file
604
commands/history/inspect.go
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/v2/core/content"
|
||||||
|
"github.com/containerd/containerd/v2/core/content/proxy"
|
||||||
|
"github.com/containerd/containerd/v2/core/images"
|
||||||
|
"github.com/containerd/platforms"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/debug"
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/moby/buildkit/util/stack"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/tonistiigi/go-csvvalue"
|
||||||
|
spb "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
proto "google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inspectOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
|
||||||
|
ls, err := localstate.New(confutil.NewConfig(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
|
||||||
|
|
||||||
|
tw := tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
|
attrs := rec.FrontendAttrs
|
||||||
|
delete(attrs, "frontend.caps")
|
||||||
|
|
||||||
|
writeAttr := func(k, name string, f func(v string) (string, bool)) {
|
||||||
|
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 dockerfile string
|
||||||
|
if st != nil {
|
||||||
|
context = st.LocalPath
|
||||||
|
dockerfile = st.DockerfilePath
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
|
if dockerfile != "" && dockerfile != "-" {
|
||||||
|
if rel, err := filepath.Rel(context, dockerfile); err == nil {
|
||||||
|
if !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||||
|
dockerfile = rel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if context != "" {
|
||||||
|
if rel, err := filepath.Rel(wd, context); err == nil {
|
||||||
|
if !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||||
|
context = rel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := attrs["context"]; ok && context == "" {
|
||||||
|
delete(attrs, "context")
|
||||||
|
context = v
|
||||||
|
}
|
||||||
|
if dockerfile == "" {
|
||||||
|
if v, ok := attrs["filename"]; ok {
|
||||||
|
dockerfile = v
|
||||||
|
if dfdir, ok := attrs["vcs:localdir:dockerfile"]; ok {
|
||||||
|
dockerfile = filepath.Join(dfdir, dockerfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(attrs, "filename")
|
||||||
|
|
||||||
|
if context != "" {
|
||||||
|
fmt.Fprintf(tw, "Context:\t%s\n", context)
|
||||||
|
}
|
||||||
|
if dockerfile != "" {
|
||||||
|
fmt.Fprintf(tw, "Dockerfile:\t%s\n", dockerfile)
|
||||||
|
}
|
||||||
|
if _, ok := attrs["context"]; !ok {
|
||||||
|
if src, ok := attrs["vcs:source"]; ok {
|
||||||
|
fmt.Fprintf(tw, "VCS Repository:\t%s\n", src)
|
||||||
|
}
|
||||||
|
if rev, ok := attrs["vcs:revision"]; ok {
|
||||||
|
fmt.Fprintf(tw, "VCS Revision:\t%s\n", rev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeAttr("target", "Target", nil)
|
||||||
|
writeAttr("platform", "Platform", func(v string) (string, bool) {
|
||||||
|
return tryParseValue(v, func(v string) (string, error) {
|
||||||
|
var pp []string
|
||||||
|
for _, v := range strings.Split(v, ",") {
|
||||||
|
p, err := platforms.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pp = append(pp, platforms.FormatAll(platforms.Normalize(p)))
|
||||||
|
}
|
||||||
|
return strings.Join(pp, ", "), nil
|
||||||
|
}), true
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}), true
|
||||||
|
})
|
||||||
|
|
||||||
|
tw.Flush()
|
||||||
|
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
|
||||||
|
printTable(dockerCli.Out(), attrs, "context:", "Named Context")
|
||||||
|
|
||||||
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Local().Format("2006-01-02 15:04:05"))
|
||||||
|
var duration time.Duration
|
||||||
|
var statusStr string
|
||||||
|
if rec.CompletedAt != nil {
|
||||||
|
duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
|
||||||
|
} else {
|
||||||
|
duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
|
||||||
|
statusStr = " (running)"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), statusStr)
|
||||||
|
if rec.Error != nil {
|
||||||
|
if codes.Code(rec.Error.Code) == codes.Canceled {
|
||||||
|
fmt.Fprintf(tw, "Status:\tCanceled\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(tw, "Error:\t%s %s\n", codes.Code(rec.Error.Code).String(), rec.Error.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
|
||||||
|
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
|
||||||
|
|
||||||
|
writeAttr("force-network-mode", "Network", nil)
|
||||||
|
writeAttr("hostname", "Hostname", nil)
|
||||||
|
writeAttr("add-hosts", "Extra Hosts", func(v string) (string, bool) {
|
||||||
|
return tryParseValue(v, func(v string) (string, error) {
|
||||||
|
fields, err := csvvalue.Fields(v, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.Join(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
|
||||||
|
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:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unusedAttrs = append(unusedAttrs, k)
|
||||||
|
}
|
||||||
|
slices.Sort(unusedAttrs)
|
||||||
|
|
||||||
|
for _, k := range unusedAttrs {
|
||||||
|
fmt.Fprintf(tw, "%s:\t%s\n", k, attrs[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
provIndex := slices.IndexFunc(attachments, func(a attachment) bool {
|
||||||
|
return descrType(a.descr) == slsa02.PredicateSLSAProvenance
|
||||||
|
})
|
||||||
|
if provIndex != -1 {
|
||||||
|
prov := attachments[provIndex]
|
||||||
|
|
||||||
|
dt, err := content.ReadBlob(ctx, store, prov.descr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("failed to read provenance %s: %v", prov.descr.Digest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pred provenancetypes.ProvenancePredicate
|
||||||
|
if err := json.Unmarshal(dt, &pred); err != nil {
|
||||||
|
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 {
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\n", m.URI, strings.Join(digestSetToDigests(m.Digest), ", "))
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
p := ""
|
||||||
|
if a.platform != nil {
|
||||||
|
p = platforms.FormatAll(*a.platform)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\t%s\n", a.descr.Digest, p, descrType(a.descr))
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
for _, s := range errdefs.Sources(retErr) {
|
||||||
|
s.Print(dockerCli.Out())
|
||||||
|
}
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if len(logs) > 0 {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "Logs:")
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "> => %s:\n", name)
|
||||||
|
for _, l := range logs {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "> "+l)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(dockerCli.Out())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug.IsEnabled() {
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "\n%+v\n", stack.Formatter(retErr))
|
||||||
|
} else if len(stack.Traces(retErr)) > 0 {
|
||||||
|
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(), "View build in Docker Desktop: %s\n", desktop.BuildURL(fmt.Sprintf("%s/%s/%s", rec.node.Builder, rec.node.Name, rec.Ref)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options inspectOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "inspect [OPTIONS] [REF]",
|
||||||
|
Short: "Inspect a build",
|
||||||
|
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 runInspect(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(
|
||||||
|
attachmentCmd(dockerCli, rootOpts),
|
||||||
|
)
|
||||||
|
|
||||||
|
// flags := cmd.Flags()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
platform *ocispecs.Platform
|
||||||
|
descr ocispecs.Descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
func allAttachments(ctx context.Context, store content.Store, rec historyRecord) ([]attachment, error) {
|
||||||
|
var attachments []attachment
|
||||||
|
|
||||||
|
if rec.Result != nil {
|
||||||
|
for _, a := range rec.Result.Attestations {
|
||||||
|
attachments = append(attachments, attachment{
|
||||||
|
descr: ociDesc(a),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, r := range rec.Result.Results {
|
||||||
|
attachments = append(attachments, walkAttachments(ctx, store, ociDesc(r), nil)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, ri := range rec.Results {
|
||||||
|
p, err := platforms.Parse(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, a := range ri.Attestations {
|
||||||
|
attachments = append(attachments, attachment{
|
||||||
|
platform: &p,
|
||||||
|
descr: ociDesc(a),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, r := range ri.Results {
|
||||||
|
attachments = append(attachments, walkAttachments(ctx, store, ociDesc(r), &p)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(attachments, func(a, b attachment) int {
|
||||||
|
pCmp := 0
|
||||||
|
if a.platform == nil && b.platform != nil {
|
||||||
|
return -1
|
||||||
|
} else if a.platform != nil && b.platform == nil {
|
||||||
|
return 1
|
||||||
|
} else if a.platform != nil && b.platform != nil {
|
||||||
|
pCmp = cmp.Compare(platforms.FormatAll(*a.platform), platforms.FormatAll(*b.platform))
|
||||||
|
}
|
||||||
|
return cmp.Or(
|
||||||
|
pCmp,
|
||||||
|
cmp.Compare(descrType(a.descr), descrType(b.descr)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return attachments, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkAttachments(ctx context.Context, store content.Store, desc ocispecs.Descriptor, platform *ocispecs.Platform) []attachment {
|
||||||
|
_, err := store.Info(ctx, desc.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []attachment
|
||||||
|
|
||||||
|
if desc.Annotations["vnd.docker.reference.type"] != "attestation-manifest" {
|
||||||
|
out = append(out, attachment{platform: platform, descr: desc})
|
||||||
|
}
|
||||||
|
|
||||||
|
if desc.MediaType != ocispecs.MediaTypeImageIndex && desc.MediaType != images.MediaTypeDockerSchema2ManifestList {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := content.ReadBlob(ctx, store, desc)
|
||||||
|
if err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx ocispecs.Index
|
||||||
|
if err := json.Unmarshal(dt, &idx); err != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range idx.Manifests {
|
||||||
|
p := platform
|
||||||
|
if d.Platform != nil {
|
||||||
|
p = d.Platform
|
||||||
|
}
|
||||||
|
out = append(out, walkAttachments(ctx, store, d, p)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ociDesc(in *controlapi.Descriptor) ocispecs.Descriptor {
|
||||||
|
return ocispecs.Descriptor{
|
||||||
|
MediaType: in.MediaType,
|
||||||
|
Digest: digest.Digest(in.Digest),
|
||||||
|
Size: in.Size,
|
||||||
|
Annotations: in.Annotations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func descrType(desc ocispecs.Descriptor) string {
|
||||||
|
if typ, ok := desc.Annotations["in-toto.io/predicate-type"]; ok {
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
return desc.MediaType
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryParseValue(s string, f func(string) (string, error)) string {
|
||||||
|
v, err := f(s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("%s (%v)", s, err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTable(w io.Writer, attrs map[string]string, prefix, title string) {
|
||||||
|
var keys []string
|
||||||
|
for k := range attrs {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
keys = append(keys, strings.TrimPrefix(k, prefix))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
|
||||||
|
fmt.Fprintf(tw, "%s\tVALUE\n", strings.ToUpper(title))
|
||||||
|
for _, k := range keys {
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\n", k, attrs[prefix+k])
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func digestSetToDigests(ds slsa.DigestSet) []string {
|
||||||
|
var out []string
|
||||||
|
for k, v := range ds {
|
||||||
|
out = append(out, fmt.Sprintf("%s:%s", k, v))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
152
commands/history/inspect_attachment.go
Normal file
152
commands/history/inspect_attachment.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/v2/core/content/proxy"
|
||||||
|
"github.com/containerd/platforms"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
|
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attachmentOptions struct {
|
||||||
|
builder string
|
||||||
|
typ string
|
||||||
|
platform string
|
||||||
|
ref string
|
||||||
|
digest digest.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
func runAttachment(ctx context.Context, dockerCli command.Cli, opts attachmentOptions) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
|
||||||
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
store := proxy.NewContentStore(c.ContentClient())
|
||||||
|
|
||||||
|
if opts.digest != "" {
|
||||||
|
ra, err := store.ReaderAt(ctx, ocispecs.Descriptor{Digest: opts.digest})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(dockerCli.Out(), io.NewSectionReader(ra, 0, ra.Size()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments, err := allAttachments(ctx, store, *rec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := opts.typ
|
||||||
|
switch typ {
|
||||||
|
case "index":
|
||||||
|
typ = ocispecs.MediaTypeImageIndex
|
||||||
|
case "manifest":
|
||||||
|
typ = ocispecs.MediaTypeImageManifest
|
||||||
|
case "image":
|
||||||
|
typ = ocispecs.MediaTypeImageConfig
|
||||||
|
case "provenance":
|
||||||
|
typ = slsa02.PredicateSLSAProvenance
|
||||||
|
case "sbom":
|
||||||
|
typ = intoto.PredicateSPDX
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range attachments {
|
||||||
|
if opts.platform != "" && (a.platform == nil || platforms.FormatAll(*a.platform) != opts.platform) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if typ != "" && descrType(a.descr) != typ {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ra, err := store.ReaderAt(ctx, a.descr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(dockerCli.Out(), io.NewSectionReader(ra, 0, ra.Size()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Errorf("no matching attachment found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachmentCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options attachmentOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "attachment [OPTIONS] REF [DIGEST]",
|
||||||
|
Short: "Inspect a build attachment",
|
||||||
|
Args: cobra.RangeArgs(1, 2),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
options.ref = args[0]
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
dgst, err := digest.Parse(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "invalid digest %q", args[1])
|
||||||
|
}
|
||||||
|
options.digest = dgst
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.digest == "" && options.platform == "" && options.typ == "" {
|
||||||
|
return errors.New("at least one of --type, --platform or DIGEST must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runAttachment(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.typ, "type", "", "Type of attachment")
|
||||||
|
flags.StringVar(&options.platform, "platform", "", "Platform of attachment")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
124
commands/history/logs.go
Normal file
124
commands/history/logs.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logsOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
progress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLogs(ctx context.Context, dockerCli command.Cli, opts logsOptions) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := c.ControlClient().Status(ctx, &controlapi.StatusRequest{
|
||||||
|
Ref: rec.Ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode progressui.DisplayMode = progressui.DisplayMode(opts.progress)
|
||||||
|
if mode == progressui.AutoMode {
|
||||||
|
mode = progressui.PlainMode
|
||||||
|
}
|
||||||
|
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
loop0:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
cl.CloseSend()
|
||||||
|
return context.Cause(ctx)
|
||||||
|
default:
|
||||||
|
ev, err := cl.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break loop0
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printer.Write(client.NewSolveStatus(ev))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return printer.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func logsCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options logsOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "logs [OPTIONS] [REF]",
|
||||||
|
Short: "Print the logs of a build",
|
||||||
|
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 runLogs(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.progress, "progress", "plain", "Set type of progress output (plain, rawjson, tty)")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
234
commands/history/ls.go
Normal file
234
commands/history/ls.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lsHeaderBuildID = "BUILD ID"
|
||||||
|
lsHeaderName = "NAME"
|
||||||
|
lsHeaderStatus = "STATUS"
|
||||||
|
lsHeaderCreated = "CREATED AT"
|
||||||
|
lsHeaderDuration = "DURATION"
|
||||||
|
lsHeaderLink = ""
|
||||||
|
|
||||||
|
lsDefaultTableFormat = "table {{.Ref}}\t{{.Name}}\t{{.Status}}\t{{.CreatedAt}}\t{{.Duration}}\t{{.Link}}"
|
||||||
|
|
||||||
|
headerKeyTimestamp = "buildkit-current-timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lsOptions struct {
|
||||||
|
builder string
|
||||||
|
format string
|
||||||
|
noTrunc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLs(ctx context.Context, dockerCli command.Cli, opts lsOptions) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := queryRecords(ctx, "", nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ls, err := localstate.New(confutil.NewConfig(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rec := range out {
|
||||||
|
st, _ := ls.ReadRef(rec.node.Builder, rec.node.Name, rec.Ref)
|
||||||
|
rec.name = buildName(rec.FrontendAttrs, st)
|
||||||
|
out[i] = rec
|
||||||
|
}
|
||||||
|
|
||||||
|
return lsPrint(dockerCli, out, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options lsOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "List build records",
|
||||||
|
Args: cli.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runLs(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
||||||
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsPrint(dockerCli command.Cli, records []historyRecord, in lsOptions) error {
|
||||||
|
if in.format == formatter.TableFormatKey {
|
||||||
|
in.format = lsDefaultTableFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := formatter.Context{
|
||||||
|
Output: dockerCli.Out(),
|
||||||
|
Format: formatter.Format(in.format),
|
||||||
|
Trunc: !in.noTrunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(records, func(a, b historyRecord) int {
|
||||||
|
if a.CompletedAt == nil && b.CompletedAt != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.CompletedAt != nil && b.CompletedAt == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
|
for _, r := range records {
|
||||||
|
if err := format(&lsContext{
|
||||||
|
format: formatter.Format(in.format),
|
||||||
|
isTerm: term,
|
||||||
|
trunc: !in.noTrunc,
|
||||||
|
record: &r,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lsCtx := lsContext{
|
||||||
|
isTerm: term,
|
||||||
|
trunc: !in.noTrunc,
|
||||||
|
}
|
||||||
|
lsCtx.Header = formatter.SubHeaderContext{
|
||||||
|
"Ref": lsHeaderBuildID,
|
||||||
|
"Name": lsHeaderName,
|
||||||
|
"Status": lsHeaderStatus,
|
||||||
|
"CreatedAt": lsHeaderCreated,
|
||||||
|
"Duration": lsHeaderDuration,
|
||||||
|
"Link": lsHeaderLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Write(&lsCtx, render)
|
||||||
|
}
|
||||||
|
|
||||||
|
type lsContext struct {
|
||||||
|
formatter.HeaderContext
|
||||||
|
|
||||||
|
isTerm bool
|
||||||
|
trunc bool
|
||||||
|
format formatter.Format
|
||||||
|
record *historyRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) MarshalJSON() ([]byte, error) {
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"ref": c.FullRef(),
|
||||||
|
"name": c.Name(),
|
||||||
|
"status": c.Status(),
|
||||||
|
"created_at": c.record.CreatedAt.AsTime().Format(time.RFC3339Nano),
|
||||||
|
"total_steps": c.record.NumTotalSteps,
|
||||||
|
"completed_steps": c.record.NumCompletedSteps,
|
||||||
|
"cached_steps": c.record.NumCachedSteps,
|
||||||
|
}
|
||||||
|
if c.record.CompletedAt != nil {
|
||||||
|
m["completed_at"] = c.record.CompletedAt.AsTime().Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
return json.Marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Ref() string {
|
||||||
|
return c.record.Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) FullRef() string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s", c.record.node.Builder, c.record.node.Name, c.record.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Name() string {
|
||||||
|
name := c.record.name
|
||||||
|
if c.trunc && c.format.IsTable() {
|
||||||
|
return trimBeginning(name, 36)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Status() string {
|
||||||
|
if c.record.CompletedAt != nil {
|
||||||
|
if c.record.Error != nil {
|
||||||
|
return "Error"
|
||||||
|
}
|
||||||
|
return "Completed"
|
||||||
|
}
|
||||||
|
return "Running"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) CreatedAt() string {
|
||||||
|
return units.HumanDuration(time.Since(c.record.CreatedAt.AsTime())) + " ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Duration() string {
|
||||||
|
lastTime := c.record.currentTimestamp
|
||||||
|
if c.record.CompletedAt != nil {
|
||||||
|
tm := c.record.CompletedAt.AsTime()
|
||||||
|
lastTime = &tm
|
||||||
|
}
|
||||||
|
if lastTime == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v := formatDuration(lastTime.Sub(c.record.CreatedAt.AsTime()))
|
||||||
|
if c.record.CompletedAt == nil {
|
||||||
|
v += "+"
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lsContext) Link() string {
|
||||||
|
url := desktop.BuildURL(c.FullRef())
|
||||||
|
if c.format.IsTable() {
|
||||||
|
if c.isTerm {
|
||||||
|
return desktop.ANSIHyperlink(url, "Open")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
80
commands/history/open.go
Normal file
80
commands/history/open.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/browser"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type openOptions struct {
|
||||||
|
builder string
|
||||||
|
ref string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runOpen(ctx context.Context, dockerCli command.Cli, opts openOptions) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recs, err := queryRecords(ctx, opts.ref, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recs) == 0 {
|
||||||
|
if opts.ref == "" {
|
||||||
|
return errors.New("no records found")
|
||||||
|
}
|
||||||
|
return errors.Errorf("no record found for ref %q", opts.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ref == "" {
|
||||||
|
slices.SortFunc(recs, func(a, b historyRecord) int {
|
||||||
|
return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &recs[0]
|
||||||
|
|
||||||
|
url := desktop.BuildURL(fmt.Sprintf("%s/%s/%s", rec.node.Builder, rec.node.Name, rec.Ref))
|
||||||
|
return browser.OpenURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options openOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "open [OPTIONS] [REF]",
|
||||||
|
Short: "Open a build in Docker Desktop",
|
||||||
|
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 runOpen(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
151
commands/history/rm.go
Normal file
151
commands/history/rm.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rmOptions struct {
|
||||||
|
builder string
|
||||||
|
refs []string
|
||||||
|
all bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runRm(ctx context.Context, dockerCli command.Cli, opts rmOptions) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := make([][]error, len(opts.refs))
|
||||||
|
for i := range errs {
|
||||||
|
errs[i] = make([]error, len(nodes))
|
||||||
|
}
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
for i, node := range nodes {
|
||||||
|
node := node
|
||||||
|
eg.Go(func() error {
|
||||||
|
if node.Driver == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c, err := node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
refs := opts.refs
|
||||||
|
|
||||||
|
if opts.all {
|
||||||
|
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||||
|
EarlyExit: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer serv.CloseSend()
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := serv.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Type == controlapi.BuildHistoryEventType_COMPLETE {
|
||||||
|
refs = append(refs, resp.Record.Ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, ref := range refs {
|
||||||
|
_, err = c.ControlClient().UpdateBuildHistory(ctx, &controlapi.UpdateBuildHistoryRequest{
|
||||||
|
Ref: ref,
|
||||||
|
Delete: true,
|
||||||
|
})
|
||||||
|
if opts.all {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errs[j][i] = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []error
|
||||||
|
loop0:
|
||||||
|
for _, nodeErrs := range errs {
|
||||||
|
var nodeErr error
|
||||||
|
for _, err1 := range nodeErrs {
|
||||||
|
if err1 == nil {
|
||||||
|
continue loop0
|
||||||
|
}
|
||||||
|
if nodeErr == nil {
|
||||||
|
nodeErr = err1
|
||||||
|
} else {
|
||||||
|
nodeErr = multierror.Append(nodeErr, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, nodeErr)
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(out) == 1 {
|
||||||
|
return out[0]
|
||||||
|
}
|
||||||
|
return multierror.Append(out[0], out[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rmCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
|
var options rmOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "rm [OPTIONS] [REF...]",
|
||||||
|
Short: "Remove build records",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 && !options.all {
|
||||||
|
return errors.New("rm requires at least one argument")
|
||||||
|
}
|
||||||
|
if len(args) > 0 && options.all {
|
||||||
|
return errors.New("rm requires either --all or at least one argument")
|
||||||
|
}
|
||||||
|
options.refs = args
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
|
return runRm(cmd.Context(), dockerCli, options)
|
||||||
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVar(&options.all, "all", false, "Remove all build records")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
30
commands/history/root.go
Normal file
30
commands/history/root.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RootOptions struct {
|
||||||
|
Builder *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "history",
|
||||||
|
Short: "Commands to work on build records",
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
|
RunE: rootcmd.RunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(
|
||||||
|
lsCmd(dockerCli, opts),
|
||||||
|
rmCmd(dockerCli, opts),
|
||||||
|
logsCmd(dockerCli, opts),
|
||||||
|
inspectCmd(dockerCli, opts),
|
||||||
|
openCmd(dockerCli, opts),
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
180
commands/history/utils.go
Normal file
180
commands/history/utils.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/localstate"
|
||||||
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildName(fattrs map[string]string, ls *localstate.State) string {
|
||||||
|
var res string
|
||||||
|
|
||||||
|
var target, contextPath, dockerfilePath, vcsSource string
|
||||||
|
if v, ok := fattrs["target"]; ok {
|
||||||
|
target = v
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["context"]; ok {
|
||||||
|
contextPath = filepath.ToSlash(v)
|
||||||
|
} else if v, ok := fattrs["vcs:localdir:context"]; ok && v != "." {
|
||||||
|
contextPath = filepath.ToSlash(v)
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["vcs:source"]; ok {
|
||||||
|
vcsSource = v
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["filename"]; ok && v != "Dockerfile" {
|
||||||
|
dockerfilePath = filepath.ToSlash(v)
|
||||||
|
}
|
||||||
|
if v, ok := fattrs["vcs:localdir:dockerfile"]; ok && v != "." {
|
||||||
|
dockerfilePath = filepath.ToSlash(filepath.Join(v, dockerfilePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
var localPath string
|
||||||
|
if ls != nil && !build.IsRemoteURL(ls.LocalPath) {
|
||||||
|
if ls.LocalPath != "" && ls.LocalPath != "-" {
|
||||||
|
localPath = filepath.ToSlash(ls.LocalPath)
|
||||||
|
}
|
||||||
|
if ls.DockerfilePath != "" && ls.DockerfilePath != "-" && ls.DockerfilePath != "Dockerfile" {
|
||||||
|
dockerfilePath = filepath.ToSlash(ls.DockerfilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove default dockerfile name
|
||||||
|
const defaultFilename = "/Dockerfile"
|
||||||
|
hasDefaultFileName := strings.HasSuffix(dockerfilePath, defaultFilename) || dockerfilePath == ""
|
||||||
|
dockerfilePath = strings.TrimSuffix(dockerfilePath, defaultFilename)
|
||||||
|
|
||||||
|
// dockerfile is a subpath of context
|
||||||
|
if strings.HasPrefix(dockerfilePath, localPath) && len(dockerfilePath) > len(localPath) {
|
||||||
|
res = dockerfilePath[strings.LastIndex(localPath, "/")+1:]
|
||||||
|
} else {
|
||||||
|
// Otherwise, use basename
|
||||||
|
bpath := localPath
|
||||||
|
if len(dockerfilePath) > 0 {
|
||||||
|
bpath = dockerfilePath
|
||||||
|
}
|
||||||
|
if len(bpath) > 0 {
|
||||||
|
lidx := strings.LastIndex(bpath, "/")
|
||||||
|
res = bpath[lidx+1:]
|
||||||
|
if !hasDefaultFileName {
|
||||||
|
if lidx != -1 {
|
||||||
|
res = filepath.ToSlash(filepath.Join(filepath.Base(bpath[:lidx]), res))
|
||||||
|
} else {
|
||||||
|
res = filepath.ToSlash(filepath.Join(filepath.Base(bpath), res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contextPath) > 0 {
|
||||||
|
res = contextPath
|
||||||
|
}
|
||||||
|
if len(target) > 0 {
|
||||||
|
if len(res) > 0 {
|
||||||
|
res = res + " (" + target + ")"
|
||||||
|
} else {
|
||||||
|
res = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res == "" && vcsSource != "" {
|
||||||
|
return vcsSource
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimBeginning(s string, n int) string {
|
||||||
|
if len(s) <= n {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ".." + s[len(s)-n+2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
type historyRecord struct {
|
||||||
|
*controlapi.BuildHistoryRecord
|
||||||
|
currentTimestamp *time.Time
|
||||||
|
node *builder.Node
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryRecords(ctx context.Context, ref string, nodes []builder.Node) ([]historyRecord, error) {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var out []historyRecord
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
for _, node := range nodes {
|
||||||
|
node := node
|
||||||
|
eg.Go(func() error {
|
||||||
|
if node.Driver == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var records []historyRecord
|
||||||
|
c, err := node.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||||
|
EarlyExit: true,
|
||||||
|
Ref: ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md, err := serv.Header()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ts *time.Time
|
||||||
|
if v, ok := md[headerKeyTimestamp]; ok {
|
||||||
|
t, err := time.Parse(time.RFC3339Nano, v[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ts = &t
|
||||||
|
}
|
||||||
|
defer serv.CloseSend()
|
||||||
|
for {
|
||||||
|
he, err := serv.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if he.Type == controlapi.BuildHistoryEventType_DELETED || he.Record == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, historyRecord{
|
||||||
|
BuildHistoryRecord: he.Record,
|
||||||
|
currentTimestamp: ts,
|
||||||
|
node: &node,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
out = append(out, records...)
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
if d < time.Minute {
|
||||||
|
return fmt.Sprintf("%.1fs", d.Seconds())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dm %2ds", int(d.Minutes()), int(d.Seconds())%60)
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
debugcmd "github.com/docker/buildx/commands/debug"
|
debugcmd "github.com/docker/buildx/commands/debug"
|
||||||
|
historycmd "github.com/docker/buildx/commands/history"
|
||||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||||
"github.com/docker/buildx/controller/remote"
|
"github.com/docker/buildx/controller/remote"
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
@@ -106,6 +107,7 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) {
|
|||||||
pruneCmd(dockerCli, opts),
|
pruneCmd(dockerCli, opts),
|
||||||
duCmd(dockerCli, opts),
|
duCmd(dockerCli, opts),
|
||||||
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||||
|
historycmd.RootCmd(cmd, dockerCli, historycmd.RootOptions{Builder: &opts.builder}),
|
||||||
)
|
)
|
||||||
if confutil.IsExperimental() {
|
if confutil.IsExperimental() {
|
||||||
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
||||||
|
@@ -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 |
|
||||||
@@ -371,6 +373,13 @@ target "app" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Supported values are:
|
||||||
|
|
||||||
|
- `build` builds the target (default)
|
||||||
|
- `check`: evaluates [build checks](https://docs.docker.com/build/checks/) for the target
|
||||||
|
- `outline`: displays the target's build arguments and their default values if available
|
||||||
|
- `targets`: lists all Bake targets in the loaded definition, along with its [description](#targetdescription).
|
||||||
|
|
||||||
For more information about frontend methods, refer to the CLI reference for
|
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 +490,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.
|
||||||
|
@@ -17,6 +17,7 @@ Extended build capabilities with BuildKit
|
|||||||
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
||||||
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
||||||
| [`du`](buildx_du.md) | Disk usage |
|
| [`du`](buildx_du.md) | Disk usage |
|
||||||
|
| [`history`](buildx_history.md) | Commands to work on build records |
|
||||||
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||||
| [`ls`](buildx_ls.md) | List builder instances |
|
| [`ls`](buildx_ls.md) | List builder instances |
|
||||||
|
@@ -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).
|
||||||
|
26
docs/reference/buildx_history.md
Normal file
26
docs/reference/buildx_history.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# docker buildx history
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Commands to work on build records
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|:---------------------------------------|:-------------------------------|
|
||||||
|
| [`inspect`](buildx_history_inspect.md) | Inspect a build |
|
||||||
|
| [`logs`](buildx_history_logs.md) | Print the logs of a build |
|
||||||
|
| [`ls`](buildx_history_ls.md) | List build records |
|
||||||
|
| [`open`](buildx_history_open.md) | Open a build in Docker Desktop |
|
||||||
|
| [`rm`](buildx_history_rm.md) | Remove build records |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
22
docs/reference/buildx_history_inspect.md
Normal file
22
docs/reference/buildx_history_inspect.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# docker buildx history inspect
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Inspect a build
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|:-----------------------------------------------------|:---------------------------|
|
||||||
|
| [`attachment`](buildx_history_inspect_attachment.md) | Inspect a build attachment |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
17
docs/reference/buildx_history_inspect_attachment.md
Normal file
17
docs/reference/buildx_history_inspect_attachment.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# docker buildx history inspect attachment
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Inspect a build attachment
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| `--platform` | `string` | | Platform of attachment |
|
||||||
|
| `--type` | `string` | | Type of attachment |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
16
docs/reference/buildx_history_logs.md
Normal file
16
docs/reference/buildx_history_logs.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# docker buildx history logs
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Print the logs of a build
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:--------------------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| `--progress` | `string` | `plain` | Set type of progress output (plain, rawjson, tty) |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
17
docs/reference/buildx_history_ls.md
Normal file
17
docs/reference/buildx_history_ls.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# docker buildx history ls
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List build records
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
| `--format` | `string` | `table` | Format the output |
|
||||||
|
| `--no-trunc` | `bool` | | Don't truncate output |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
15
docs/reference/buildx_history_open.md
Normal file
15
docs/reference/buildx_history_open.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# docker buildx history open
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Open a build in Docker Desktop
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
16
docs/reference/buildx_history_rm.md
Normal file
16
docs/reference/buildx_history_rm.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# docker buildx history rm
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Remove build records
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||||
|
| `--all` | `bool` | | Remove all build records |
|
||||||
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
22
go.mod
22
go.mod
@@ -15,7 +15,7 @@ require (
|
|||||||
github.com/containerd/platforms v1.0.0-rc.1
|
github.com/containerd/platforms v1.0.0-rc.1
|
||||||
github.com/containerd/typeurl/v2 v2.2.3
|
github.com/containerd/typeurl/v2 v2.2.3
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
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 v27.5.0+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.9.0
|
github.com/docker/cli-docs-tool v0.9.0
|
||||||
@@ -25,16 +25,18 @@ require (
|
|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/go-cty-funcs v0.0.0-20241120183456-c51673e0b3dd
|
github.com/hashicorp/go-cty-funcs v0.0.0-20241120183456-c51673e0b3dd
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
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.19.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
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.0
|
github.com/opencontainers/image-spec v1.1.0
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
|
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
|
||||||
@@ -49,11 +51,13 @@ require (
|
|||||||
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.21.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.28.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/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38
|
||||||
google.golang.org/grpc v1.68.1
|
google.golang.org/grpc v1.68.1
|
||||||
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
|
||||||
@@ -114,7 +118,6 @@ require (
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
@@ -138,7 +141,7 @@ require (
|
|||||||
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
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
@@ -166,13 +169,11 @@ 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-20240909161429-701f63a606c0 // 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.25.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // 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
|
||||||
@@ -182,3 +183,12 @@ require (
|
|||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
exclude (
|
||||||
|
// FIXME(thaJeztah): remoove this once kubernetes updated their dependencies to no longer need this.
|
||||||
|
//
|
||||||
|
// For additional details, see this PR and links mentioned in that PR:
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/pull/5830#issuecomment-2569960859
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
|
||||||
|
)
|
||||||
|
12
go.sum
12
go.sum
@@ -117,9 +117,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
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=
|
||||||
@@ -298,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.19.0 h1:w9G1p7sArvCGNkpWstAqJfRQTXBKukMyMK1bsah1HNo=
|
||||||
github.com/moby/buildkit v0.19.0-rc2/go.mod h1:4WYJLet/NI2p1o2rPQ6CIFpyyyvwvPz/TVISmwqqpHI=
|
github.com/moby/buildkit v0.19.0/go.mod h1:WiHBFTgWV8eB1AmPxIWsAlKjUACAwm3X/14xOV4VWew=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 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=
|
||||||
@@ -360,15 +359,16 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq
|
|||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
@@ -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
|
||||||
|
79
util/buildflags/attests_test.go
Normal file
79
util/buildflags/attests_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package buildflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAttests(t *testing.T) {
|
||||||
|
t.Run("MarshalJSON", func(t *testing.T) {
|
||||||
|
attests := Attests{
|
||||||
|
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
|
||||||
|
{Type: "sbom", Disabled: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `[{"type":"provenance","mode":"max"},{"type":"sbom","disabled":true}]`
|
||||||
|
actual, err := json.Marshal(attests)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.JSONEq(t, expected, string(actual))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UnmarshalJSON", func(t *testing.T) {
|
||||||
|
in := `[{"type":"provenance","mode":"max"},{"type":"sbom","disabled":true}]`
|
||||||
|
|
||||||
|
var actual Attests
|
||||||
|
err := json.Unmarshal([]byte(in), &actual)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := Attests{
|
||||||
|
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
|
||||||
|
{Type: "sbom", Disabled: true, Attrs: map[string]string{}},
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FromCtyValue", func(t *testing.T) {
|
||||||
|
in := cty.TupleVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"type": cty.StringVal("provenance"),
|
||||||
|
"mode": cty.StringVal("max"),
|
||||||
|
}),
|
||||||
|
cty.StringVal("type=sbom,disabled=true"),
|
||||||
|
})
|
||||||
|
|
||||||
|
var actual Attests
|
||||||
|
err := actual.FromCtyValue(in, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := Attests{
|
||||||
|
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
|
||||||
|
{Type: "sbom", Disabled: true, Attrs: map[string]string{}},
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ToCtyValue", func(t *testing.T) {
|
||||||
|
attests := Attests{
|
||||||
|
{Type: "provenance", Attrs: map[string]string{"mode": "max"}},
|
||||||
|
{Type: "sbom", Disabled: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := attests.ToCtyValue()
|
||||||
|
expected := cty.ListVal([]cty.Value{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"type": cty.StringVal("provenance"),
|
||||||
|
"mode": cty.StringVal("max"),
|
||||||
|
}),
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"type": cty.StringVal("sbom"),
|
||||||
|
"disabled": cty.StringVal("true"),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
result := actual.Equals(expected)
|
||||||
|
require.True(t, result.True())
|
||||||
|
})
|
||||||
|
}
|
@@ -167,20 +167,37 @@ 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 !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) {
|
||||||
|
@@ -30,6 +30,16 @@ func (o *CacheOptions) fromCtyValue(in cty.Value, p cty.Path) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for a string type to handle ref only format.
|
||||||
|
if value.Type() == cty.String {
|
||||||
|
entries, err := ParseCacheEntry([]string{value.AsString()})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*o = append(*o, entries...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
entry := &CacheOptionsEntry{}
|
entry := &CacheOptionsEntry{}
|
||||||
if err := entry.FromCtyValue(value, p); err != nil {
|
if err := entry.FromCtyValue(value, p); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -52,13 +62,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,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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
}
|
@@ -28,13 +28,14 @@ func BuildBackendEnabled() bool {
|
|||||||
return bbEnabled
|
return bbEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildURL(ref string) string {
|
||||||
|
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
||||||
|
}
|
||||||
|
|
||||||
func BuildDetailsOutput(refs map[string]string, term bool) string {
|
func BuildDetailsOutput(refs map[string]string, term bool) string {
|
||||||
if len(refs) == 0 {
|
if len(refs) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
refURL := func(ref string) string {
|
|
||||||
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
|
||||||
}
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
out.WriteString("View build details: ")
|
out.WriteString("View build details: ")
|
||||||
multiTargets := len(refs) > 1
|
multiTargets := len(refs) > 1
|
||||||
@@ -43,9 +44,10 @@ func BuildDetailsOutput(refs map[string]string, term bool) string {
|
|||||||
out.WriteString(fmt.Sprintf("\n %s: ", target))
|
out.WriteString(fmt.Sprintf("\n %s: ", target))
|
||||||
}
|
}
|
||||||
if term {
|
if term {
|
||||||
out.WriteString(hyperlink(refURL(ref)))
|
url := BuildURL(ref)
|
||||||
|
out.WriteString(ANSIHyperlink(url, url))
|
||||||
} else {
|
} else {
|
||||||
out.WriteString(refURL(ref))
|
out.WriteString(BuildURL(ref))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
@@ -57,9 +59,9 @@ func PrintBuildDetails(w io.Writer, refs map[string]string, term bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hyperlink(url string) string {
|
func ANSIHyperlink(url, text string) string {
|
||||||
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||||
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, url)
|
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorWithBuildRef struct {
|
type ErrorWithBuildRef struct {
|
||||||
|
9
vendor/github.com/moby/buildkit/util/gitutil/git_cli.go
generated
vendored
9
vendor/github.com/moby/buildkit/util/gitutil/git_cli.go
generated
vendored
@@ -134,6 +134,10 @@ func (cli *GitCLI) Run(ctx context.Context, args ...string) (_ []byte, err error
|
|||||||
if cli.git != "" {
|
if cli.git != "" {
|
||||||
gitBinary = cli.git
|
gitBinary = cli.git
|
||||||
}
|
}
|
||||||
|
proxyEnvVars := [...]string{
|
||||||
|
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "ALL_PROXY",
|
||||||
|
"http_proxy", "https_proxy", "no_proxy", "all_proxy",
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
@@ -190,6 +194,11 @@ func (cli *GitCLI) Run(ctx context.Context, args ...string) (_ []byte, err error
|
|||||||
"HOME=/dev/null", // Disable reading from user gitconfig.
|
"HOME=/dev/null", // Disable reading from user gitconfig.
|
||||||
"LC_ALL=C", // Ensure consistent output.
|
"LC_ALL=C", // Ensure consistent output.
|
||||||
}
|
}
|
||||||
|
for _, ev := range proxyEnvVars {
|
||||||
|
if v, ok := os.LookupEnv(ev); ok {
|
||||||
|
cmd.Env = append(cmd.Env, ev+"="+v)
|
||||||
|
}
|
||||||
|
}
|
||||||
if cli.sshAuthSock != "" {
|
if cli.sshAuthSock != "" {
|
||||||
cmd.Env = append(cmd.Env, "SSH_AUTH_SOCK="+cli.sshAuthSock)
|
cmd.Env = append(cmd.Env, "SSH_AUTH_SOCK="+cli.sshAuthSock)
|
||||||
}
|
}
|
||||||
|
23
vendor/github.com/pkg/browser/LICENSE
generated
vendored
Normal file
23
vendor/github.com/pkg/browser/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2014, Dave Cheney <dave@cheney.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
55
vendor/github.com/pkg/browser/README.md
generated
vendored
Normal file
55
vendor/github.com/pkg/browser/README.md
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
# browser
|
||||||
|
import "github.com/pkg/browser"
|
||||||
|
|
||||||
|
Package browser provides helpers to open files, readers, and urls in a browser window.
|
||||||
|
|
||||||
|
The choice of which browser is started is entirely client dependant.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
``` go
|
||||||
|
var Stderr io.Writer = os.Stderr
|
||||||
|
```
|
||||||
|
Stderr is the io.Writer to which executed commands write standard error.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var Stdout io.Writer = os.Stdout
|
||||||
|
```
|
||||||
|
Stdout is the io.Writer to which executed commands write standard output.
|
||||||
|
|
||||||
|
|
||||||
|
## func OpenFile
|
||||||
|
``` go
|
||||||
|
func OpenFile(path string) error
|
||||||
|
```
|
||||||
|
OpenFile opens new browser window for the file path.
|
||||||
|
|
||||||
|
|
||||||
|
## func OpenReader
|
||||||
|
``` go
|
||||||
|
func OpenReader(r io.Reader) error
|
||||||
|
```
|
||||||
|
OpenReader consumes the contents of r and presents the
|
||||||
|
results in a new browser window.
|
||||||
|
|
||||||
|
|
||||||
|
## func OpenURL
|
||||||
|
``` go
|
||||||
|
func OpenURL(url string) error
|
||||||
|
```
|
||||||
|
OpenURL opens a new browser window pointing to url.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- - -
|
||||||
|
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
|
57
vendor/github.com/pkg/browser/browser.go
generated
vendored
Normal file
57
vendor/github.com/pkg/browser/browser.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Package browser provides helpers to open files, readers, and urls in a browser window.
|
||||||
|
//
|
||||||
|
// The choice of which browser is started is entirely client dependant.
|
||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stdout is the io.Writer to which executed commands write standard output.
|
||||||
|
var Stdout io.Writer = os.Stdout
|
||||||
|
|
||||||
|
// Stderr is the io.Writer to which executed commands write standard error.
|
||||||
|
var Stderr io.Writer = os.Stderr
|
||||||
|
|
||||||
|
// OpenFile opens new browser window for the file path.
|
||||||
|
func OpenFile(path string) error {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return OpenURL("file://" + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenReader consumes the contents of r and presents the
|
||||||
|
// results in a new browser window.
|
||||||
|
func OpenReader(r io.Reader) error {
|
||||||
|
f, err := ioutil.TempFile("", "browser.*.html")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("browser: could not create temporary file: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(f, r); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return fmt.Errorf("browser: caching temporary file failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("browser: caching temporary file failed: %v", err)
|
||||||
|
}
|
||||||
|
return OpenFile(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenURL opens a new browser window pointing to url.
|
||||||
|
func OpenURL(url string) error {
|
||||||
|
return openBrowser(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCmd(prog string, args ...string) error {
|
||||||
|
cmd := exec.Command(prog, args...)
|
||||||
|
cmd.Stdout = Stdout
|
||||||
|
cmd.Stderr = Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
5
vendor/github.com/pkg/browser/browser_darwin.go
generated
vendored
Normal file
5
vendor/github.com/pkg/browser/browser_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
return runCmd("open", url)
|
||||||
|
}
|
14
vendor/github.com/pkg/browser/browser_freebsd.go
generated
vendored
Normal file
14
vendor/github.com/pkg/browser/browser_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
err := runCmd("xdg-open", url)
|
||||||
|
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||||
|
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
21
vendor/github.com/pkg/browser/browser_linux.go
generated
vendored
Normal file
21
vendor/github.com/pkg/browser/browser_linux.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
providers := []string{"xdg-open", "x-www-browser", "www-browser"}
|
||||||
|
|
||||||
|
// There are multiple possible providers to open a browser on linux
|
||||||
|
// One of them is xdg-open, another is x-www-browser, then there's www-browser, etc.
|
||||||
|
// Look for one that exists and run it
|
||||||
|
for _, provider := range providers {
|
||||||
|
if _, err := exec.LookPath(provider); err == nil {
|
||||||
|
return runCmd(provider, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &exec.Error{Name: strings.Join(providers, ","), Err: exec.ErrNotFound}
|
||||||
|
}
|
14
vendor/github.com/pkg/browser/browser_netbsd.go
generated
vendored
Normal file
14
vendor/github.com/pkg/browser/browser_netbsd.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
err := runCmd("xdg-open", url)
|
||||||
|
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||||
|
return errors.New("xdg-open: command not found - install xdg-utils from pkgsrc(7)")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
14
vendor/github.com/pkg/browser/browser_openbsd.go
generated
vendored
Normal file
14
vendor/github.com/pkg/browser/browser_openbsd.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
err := runCmd("xdg-open", url)
|
||||||
|
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||||
|
return errors.New("xdg-open: command not found - install xdg-utils from ports(8)")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
12
vendor/github.com/pkg/browser/browser_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/pkg/browser/browser_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// +build !linux,!windows,!darwin,!openbsd,!freebsd,!netbsd
|
||||||
|
|
||||||
|
package browser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS)
|
||||||
|
}
|
7
vendor/github.com/pkg/browser/browser_windows.go
generated
vendored
Normal file
7
vendor/github.com/pkg/browser/browser_windows.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package browser
|
||||||
|
|
||||||
|
import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL)
|
||||||
|
}
|
9
vendor/modules.txt
vendored
9
vendor/modules.txt
vendored
@@ -223,7 +223,7 @@ github.com/cpuguy83/go-md2man/v2/md2man
|
|||||||
# github.com/creack/pty v1.1.24
|
# github.com/creack/pty v1.1.24
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/creack/pty
|
github.com/creack/pty
|
||||||
# github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
# github.com/davecgh/go-spew v1.1.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/davecgh/go-spew/spew
|
github.com/davecgh/go-spew/spew
|
||||||
# github.com/distribution/reference v0.6.0
|
# github.com/distribution/reference v0.6.0
|
||||||
@@ -493,7 +493,7 @@ github.com/mitchellh/go-wordwrap
|
|||||||
github.com/mitchellh/hashstructure/v2
|
github.com/mitchellh/hashstructure/v2
|
||||||
# github.com/mitchellh/mapstructure v1.5.0
|
# github.com/mitchellh/mapstructure v1.5.0
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
# github.com/moby/buildkit v0.19.0-rc2
|
# github.com/moby/buildkit v0.19.0
|
||||||
## explicit; go 1.22.0
|
## explicit; go 1.22.0
|
||||||
github.com/moby/buildkit/api/services/control
|
github.com/moby/buildkit/api/services/control
|
||||||
github.com/moby/buildkit/api/types
|
github.com/moby/buildkit/api/types
|
||||||
@@ -636,6 +636,9 @@ github.com/opencontainers/image-spec/specs-go/v1
|
|||||||
# github.com/pelletier/go-toml v1.9.5
|
# github.com/pelletier/go-toml v1.9.5
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/pelletier/go-toml
|
github.com/pelletier/go-toml
|
||||||
|
# github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
|
## explicit; go 1.14
|
||||||
|
github.com/pkg/browser
|
||||||
# github.com/pkg/errors v0.9.1
|
# github.com/pkg/errors v0.9.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/pkg/errors
|
github.com/pkg/errors
|
||||||
@@ -654,7 +657,7 @@ github.com/planetscale/vtprotobuf/generator/pattern
|
|||||||
github.com/planetscale/vtprotobuf/protohelpers
|
github.com/planetscale/vtprotobuf/protohelpers
|
||||||
github.com/planetscale/vtprotobuf/types/known/timestamppb
|
github.com/planetscale/vtprotobuf/types/known/timestamppb
|
||||||
github.com/planetscale/vtprotobuf/vtproto
|
github.com/planetscale/vtprotobuf/vtproto
|
||||||
# github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
|
# github.com/pmezard/go-difflib v1.0.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/pmezard/go-difflib/difflib
|
github.com/pmezard/go-difflib/difflib
|
||||||
# github.com/prometheus/client_golang v1.20.5
|
# github.com/prometheus/client_golang v1.20.5
|
||||||
|
Reference in New Issue
Block a user