mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-18 09:45:53 +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:
|
||||
- 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
|
||||
area/tests:
|
||||
- changed-files:
|
||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -54,9 +54,9 @@ jobs:
|
||||
- master
|
||||
- latest
|
||||
- buildx-stable-1
|
||||
- v0.19.0-rc2
|
||||
- v0.18.2
|
||||
- v0.17.2
|
||||
- v0.16.0
|
||||
worker:
|
||||
- docker-container
|
||||
- remote
|
||||
|
2
.github/workflows/docs-upstream.yml
vendored
2
.github/workflows/docs-upstream.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
validate:
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@6b73b05acb21edf7995cc5b3c6672d8e314cee7a # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@main
|
||||
needs:
|
||||
- docs-yaml
|
||||
with:
|
||||
|
@@ -5,12 +5,12 @@ ARG ALPINE_VERSION=3.21
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
# for testing
|
||||
ARG DOCKER_VERSION=27.4.1
|
||||
ARG DOCKER_VERSION=27.5.0
|
||||
ARG DOCKER_VERSION_ALT_26=26.1.3
|
||||
ARG DOCKER_CLI_VERSION=${DOCKER_VERSION}
|
||||
ARG GOTESTSUM_VERSION=v1.12.0
|
||||
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
|
||||
|
||||
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/util/entitlements"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/go-csvvalue"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"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]]
|
||||
|
||||
// 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] {
|
||||
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements", "network":
|
||||
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 {
|
||||
// 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 {
|
||||
value := o.Value
|
||||
keys := strings.SplitN(key, ".", 2)
|
||||
@@ -896,7 +899,7 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
||||
case "tags":
|
||||
t.Tags = o.ArrValue
|
||||
case "cache-from":
|
||||
cacheFrom, err := parseCacheArrValues(o.ArrValue)
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(o.ArrValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -909,7 +912,7 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
||||
}
|
||||
}
|
||||
case "cache-to":
|
||||
cacheTo, err := parseCacheArrValues(o.ArrValue)
|
||||
cacheTo, err := buildflags.ParseCacheEntry(o.ArrValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1581,37 +1584,3 @@ func parseArrValue[T any, PT arrValue[T]](s []string) ([]*T, error) {
|
||||
}
|
||||
return outputs, nil
|
||||
}
|
||||
|
||||
func parseCacheArrValues(s []string) (buildflags.CacheOptions, error) {
|
||||
var outs buildflags.CacheOptions
|
||||
for _, in := range s {
|
||||
if in == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(in, "=") {
|
||||
// This is ref only format. Each field in the CSV is its own entry.
|
||||
fields, err := csvvalue.Fields(in, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
out := buildflags.CacheOptionsEntry{}
|
||||
if err := out.UnmarshalText([]byte(field)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outs = append(outs, &out)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Normal entry.
|
||||
out := buildflags.CacheOptionsEntry{}
|
||||
if err := out.UnmarshalText([]byte(in)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outs = append(outs, &out)
|
||||
}
|
||||
return outs, nil
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -1759,6 +1760,27 @@ func TestAnnotations(t *testing.T) {
|
||||
require.Equal(t, "bar", bo["app"].Exports[0].Attrs["annotation-manifest[linux/amd64].foo"])
|
||||
}
|
||||
|
||||
func TestRefOnlyCacheOptions(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=image,name=foo"]
|
||||
cache-from = ["ref1,ref2"]
|
||||
}`),
|
||||
}
|
||||
ctx := context.TODO()
|
||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil, &EntitlementConf{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, m, 1)
|
||||
require.Contains(t, m, "app")
|
||||
require.Equal(t, buildflags.CacheOptions{
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "ref1"}},
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "ref2"}},
|
||||
}, m["app"].CacheFrom)
|
||||
}
|
||||
|
||||
func TestHCLEntitlements(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
|
@@ -145,12 +145,12 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
|
||||
labels[k] = &v
|
||||
}
|
||||
|
||||
cacheFrom, err := parseCacheArrValues(s.Build.CacheFrom)
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(s.Build.CacheFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheTo, err := parseCacheArrValues(s.Build.CacheTo)
|
||||
cacheTo, err := buildflags.ParseCacheEntry(s.Build.CacheTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -349,14 +349,14 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
||||
t.Tags = dedupSlice(append(t.Tags, xb.Tags...))
|
||||
}
|
||||
if len(xb.CacheFrom) > 0 {
|
||||
cacheFrom, err := parseCacheArrValues(xb.CacheFrom)
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(xb.CacheFrom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.CacheFrom = t.CacheFrom.Merge(cacheFrom)
|
||||
}
|
||||
if len(xb.CacheTo) > 0 {
|
||||
cacheTo, err := parseCacheArrValues(xb.CacheTo)
|
||||
cacheTo, err := buildflags.ParseCacheEntry(xb.CacheTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -183,14 +183,17 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error
|
||||
}
|
||||
}
|
||||
|
||||
opts.CacheFrom, err = buildflags.ParseCacheEntry(o.cacheFrom)
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(o.cacheFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.CacheTo, err = buildflags.ParseCacheEntry(o.cacheTo)
|
||||
opts.CacheFrom = cacheFrom.ToPB()
|
||||
|
||||
cacheTo, err := buildflags.ParseCacheEntry(o.cacheTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.CacheTo = cacheTo.ToPB()
|
||||
|
||||
opts.Secrets, err = buildflags.ParseSecretSpecs(o.secrets)
|
||||
if err != nil {
|
||||
|
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"
|
||||
|
||||
debugcmd "github.com/docker/buildx/commands/debug"
|
||||
historycmd "github.com/docker/buildx/commands/history"
|
||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||
"github.com/docker/buildx/controller/remote"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
@@ -106,6 +107,7 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) {
|
||||
pruneCmd(dockerCli, opts),
|
||||
duCmd(dockerCli, opts),
|
||||
imagetoolscmd.RootCmd(cmd, dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||
historycmd.RootCmd(cmd, dockerCli, historycmd.RootOptions{Builder: &opts.builder}),
|
||||
)
|
||||
if confutil.IsExperimental() {
|
||||
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 |
|
||||
| [`cache-from`](#targetcache-from) | List | External cache sources |
|
||||
| [`cache-to`](#targetcache-to) | List | External cache destinations |
|
||||
| [`call`](#targetcall) | String | Specify the frontend method to call for the target. |
|
||||
| [`context`](#targetcontext) | String | Set of files located in the specified path or URL |
|
||||
| [`contexts`](#targetcontexts) | Map | Additional build contexts |
|
||||
| [`description`](#targetdescription) | String | Description of a target |
|
||||
| [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string |
|
||||
| [`dockerfile`](#targetdockerfile) | String | Dockerfile location |
|
||||
| [`inherits`](#targetinherits) | List | Inherit attributes from other targets |
|
||||
@@ -371,6 +373,13 @@ target "app" {
|
||||
}
|
||||
```
|
||||
|
||||
Supported values are:
|
||||
|
||||
- `build` builds the target (default)
|
||||
- `check`: evaluates [build checks](https://docs.docker.com/build/checks/) for the target
|
||||
- `outline`: displays the target's build arguments and their default values if available
|
||||
- `targets`: lists all Bake targets in the loaded definition, along with its [description](#targetdescription).
|
||||
|
||||
For more information about frontend methods, refer to the CLI reference for
|
||||
[`docker buildx build --call`](https://docs.docker.com/reference/cli/docker/buildx/build/#call).
|
||||
|
||||
@@ -481,6 +490,25 @@ FROM baseapp
|
||||
RUN echo "Hello world"
|
||||
```
|
||||
|
||||
### `target.description`
|
||||
|
||||
Defines a human-readable description for the target, clarifying its purpose or
|
||||
functionality.
|
||||
|
||||
```hcl
|
||||
target "lint" {
|
||||
description = "Runs golangci-lint to detect style errors"
|
||||
args = {
|
||||
GOLANGCI_LINT_VERSION = null
|
||||
}
|
||||
dockerfile = "lint.Dockerfile"
|
||||
}
|
||||
```
|
||||
|
||||
This attribute is useful when combined with the `docker buildx bake --list=targets`
|
||||
option, providing a more informative output when listing the available build
|
||||
targets in a Bake file.
|
||||
|
||||
### `target.dockerfile-inline`
|
||||
|
||||
Uses the string value as an inline Dockerfile for the build target.
|
||||
|
@@ -17,6 +17,7 @@ Extended build capabilities with BuildKit
|
||||
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
||||
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
||||
| [`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 |
|
||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||
| [`ls`](buildx_ls.md) | List builder instances |
|
||||
|
@@ -15,7 +15,7 @@ Build from a file
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------|
|
||||
| `--allow` | `stringArray` | | Allow build to access specified resources |
|
||||
| [`--allow`](#allow) | `stringArray` | | Allow build to access specified resources |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--call`](#call) | `string` | `build` | Set method for evaluating build (`check`, `outline`, `targets`) |
|
||||
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
||||
@@ -51,6 +51,80 @@ guide for introduction to writing bake files.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
||||
|
||||
```text
|
||||
--allow=ENTITLEMENT[=VALUE]
|
||||
```
|
||||
|
||||
Entitlements are designed to provide controlled access to privileged
|
||||
operations. By default, Buildx and BuildKit operates with restricted
|
||||
permissions to protect users and their systems from unintended side effects or
|
||||
security risks. The `--allow` flag explicitly grants access to additional
|
||||
entitlements, making it clear when a build or bake operation requires elevated
|
||||
privileges.
|
||||
|
||||
In addition to BuildKit's `network.host` and `security.insecure` entitlements
|
||||
(see [`docker buildx build --allow`](https://docs.docker.com/reference/cli/docker/buildx/build/#allow),
|
||||
Bake supports file system entitlements that grant granular control over file
|
||||
system access. These are particularly useful when working with builds that need
|
||||
access to files outside the default working directory.
|
||||
|
||||
Bake supports the following filesystem entitlements:
|
||||
|
||||
- `--allow fs=<path|*>` - Grant read and write access to files outside of the
|
||||
working directory.
|
||||
- `--allow fs.read=<path|*>` - Grant read access to files outside of the
|
||||
working directory.
|
||||
- `--allow fs.write=<path|*>` - Grant write access to files outside of the
|
||||
working directory.
|
||||
|
||||
The `fs` entitlements take a path value (relative or absolute) to a directory
|
||||
on the filesystem. Alternatively, you can pass a wildcard (`*`) to allow Bake
|
||||
to access the entire filesystem.
|
||||
|
||||
### Example: fs.read
|
||||
|
||||
Given the following Bake configuration, Bake would need to access the parent
|
||||
directory, relative to the Bake file.
|
||||
|
||||
```hcl
|
||||
target "app" {
|
||||
context = "../src"
|
||||
}
|
||||
```
|
||||
|
||||
Assuming `docker buildx bake app` is executed in the same directory as the
|
||||
`docker-bake.hcl` file, you would need to explicitly allow Bake to read from
|
||||
the `../src` directory. In this case, the following invocations all work:
|
||||
|
||||
```console
|
||||
$ docker buildx bake --allow fs.read=* app
|
||||
$ docker buildx bake --allow fs.read=../src app
|
||||
$ docker buildx bake --allow fs=* app
|
||||
```
|
||||
|
||||
### Example: fs.write
|
||||
|
||||
The following `docker-bake.hcl` file requires write access to the `/tmp`
|
||||
directory.
|
||||
|
||||
```hcl
|
||||
target "app" {
|
||||
output = "/tmp"
|
||||
}
|
||||
```
|
||||
|
||||
Assuming `docker buildx bake app` is executed outside of the `/tmp` directory,
|
||||
you would need to allow the `fs.write` entitlement, either by specifying the
|
||||
path or using a wildcard:
|
||||
|
||||
```console
|
||||
$ docker buildx bake --allow fs=/tmp app
|
||||
$ docker buildx bake --allow fs.write=/tmp app
|
||||
$ docker buildx bake --allow fs.write=* app
|
||||
```
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
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/typeurl/v2 v2.2.3
|
||||
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/docker/cli v27.5.0+incompatible
|
||||
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/uuid v1.6.0
|
||||
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/in-toto/in-toto-golang v0.5.0
|
||||
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/signal v0.7.1
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
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/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||
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/sdk 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/sync v0.10.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/term v0.27.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/cmd/protoc-gen-go-grpc v1.5.1
|
||||
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/hashicorp/errwrap v1.1.0 // 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/inconshreveable/mousetrap v1.1.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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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_model v0.6.1 // 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/proto/otlp v1.3.1 // 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/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/time v0.6.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/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // 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/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/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.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.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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
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 v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/buildkit v0.19.0-rc2 h1:7sAuQ5bDNIbdfmc7UDbrWJ2UPOR5w9rNWgnrEoC5aoo=
|
||||
github.com/moby/buildkit v0.19.0-rc2/go.mod h1:4WYJLet/NI2p1o2rPQ6CIFpyyyvwvPz/TVISmwqqpHI=
|
||||
github.com/moby/buildkit v0.19.0 h1:w9G1p7sArvCGNkpWstAqJfRQTXBKukMyMK1bsah1HNo=
|
||||
github.com/moby/buildkit v0.19.0/go.mod h1:WiHBFTgWV8eB1AmPxIWsAlKjUACAwm3X/14xOV4VWew=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
@@ -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/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/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.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/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.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.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
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) {
|
||||
m := make(map[string]interface{}, len(a.Attrs)+2)
|
||||
for k, v := range m {
|
||||
for k, v := range a.Attrs {
|
||||
m[k] = v
|
||||
}
|
||||
m["type"] = a.Type
|
||||
|
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
|
||||
}
|
||||
|
||||
func ParseCacheEntry(in []string) ([]*controllerapi.CacheOptionsEntry, error) {
|
||||
func ParseCacheEntry(in []string) (CacheOptions, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
opts := make(CacheOptions, 0, len(in))
|
||||
for _, in := range in {
|
||||
if !strings.Contains(in, "=") {
|
||||
// This is ref only format. Each field in the CSV is its own entry.
|
||||
fields, err := csvvalue.Fields(in, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
opt := CacheOptionsEntry{}
|
||||
if err := opt.UnmarshalText([]byte(field)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, &opt)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var out CacheOptionsEntry
|
||||
if err := out.UnmarshalText([]byte(in)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, &out)
|
||||
}
|
||||
return opts.ToPB(), nil
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func addGithubToken(ci *controllerapi.CacheOptionsEntry) {
|
||||
|
@@ -30,6 +30,16 @@ func (o *CacheOptions) fromCtyValue(in cty.Value, p cty.Path) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Special handling for a string type to handle ref only format.
|
||||
if value.Type() == cty.String {
|
||||
entries, err := ParseCacheEntry([]string{value.AsString()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*o = append(*o, entries...)
|
||||
continue
|
||||
}
|
||||
|
||||
entry := &CacheOptionsEntry{}
|
||||
if err := entry.FromCtyValue(value, p); err != nil {
|
||||
return err
|
||||
@@ -52,13 +62,6 @@ func (o CacheOptions) ToCtyValue() cty.Value {
|
||||
}
|
||||
|
||||
func (o *CacheOptionsEntry) FromCtyValue(in cty.Value, p cty.Path) error {
|
||||
if in.Type() == cty.String {
|
||||
if err := o.UnmarshalText([]byte(in.AsString())); err != nil {
|
||||
return p.NewError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
conv, err := convert.Convert(in, cty.Map(cty.String))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/controller/pb"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestCacheOptions_DerivedVars(t *testing.T) {
|
||||
@@ -35,5 +37,84 @@ func TestCacheOptions_DerivedVars(t *testing.T) {
|
||||
"session_token": "not_a_mitm_attack",
|
||||
},
|
||||
},
|
||||
}, cacheFrom)
|
||||
}, cacheFrom.ToPB())
|
||||
}
|
||||
|
||||
func TestCacheOptions(t *testing.T) {
|
||||
t.Run("MarshalJSON", func(t *testing.T) {
|
||||
cache := CacheOptions{
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
|
||||
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
|
||||
}
|
||||
|
||||
expected := `[{"type":"registry","ref":"user/app:cache"},{"type":"local","src":"path/to/cache"}]`
|
||||
actual, err := json.Marshal(cache)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, expected, string(actual))
|
||||
})
|
||||
|
||||
t.Run("UnmarshalJSON", func(t *testing.T) {
|
||||
in := `[{"type":"registry","ref":"user/app:cache"},{"type":"local","src":"path/to/cache"}]`
|
||||
|
||||
var actual CacheOptions
|
||||
err := json.Unmarshal([]byte(in), &actual)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := CacheOptions{
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
|
||||
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
|
||||
}
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
t.Run("FromCtyValue", func(t *testing.T) {
|
||||
in := cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"type": cty.StringVal("registry"),
|
||||
"ref": cty.StringVal("user/app:cache"),
|
||||
}),
|
||||
cty.StringVal("type=local,src=path/to/cache"),
|
||||
})
|
||||
|
||||
var actual CacheOptions
|
||||
err := actual.FromCtyValue(in, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := CacheOptions{
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
|
||||
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
|
||||
}
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
t.Run("ToCtyValue", func(t *testing.T) {
|
||||
attests := CacheOptions{
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "user/app:cache"}},
|
||||
{Type: "local", Attrs: map[string]string{"src": "path/to/cache"}},
|
||||
}
|
||||
|
||||
actual := attests.ToCtyValue()
|
||||
expected := cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"type": cty.StringVal("registry"),
|
||||
"ref": cty.StringVal("user/app:cache"),
|
||||
}),
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"type": cty.StringVal("local"),
|
||||
"src": cty.StringVal("path/to/cache"),
|
||||
}),
|
||||
})
|
||||
|
||||
result := actual.Equals(expected)
|
||||
require.True(t, result.True())
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheOptions_RefOnlyFormat(t *testing.T) {
|
||||
opts, err := ParseCacheEntry([]string{"ref1", "ref2"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CacheOptions{
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "ref1"}},
|
||||
{Type: "registry", Attrs: map[string]string{"ref": "ref2"}},
|
||||
}, opts)
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
@@ -73,6 +74,22 @@ func (s *Secret) ToPB() *controllerapi.Secret {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Secret) UnmarshalJSON(data []byte) error {
|
||||
var v struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
FilePath string `json:"src,omitempty"`
|
||||
Env string `json:"env,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.ID = v.ID
|
||||
s.FilePath = v.FilePath
|
||||
s.Env = v.Env
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Secret) UnmarshalText(text []byte) error {
|
||||
value := string(text)
|
||||
fields, err := csvvalue.Fields(value, nil)
|
||||
|
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 (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -76,6 +77,20 @@ func (s *SSH) ToPB() *controllerapi.SSH {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SSH) UnmarshalJSON(data []byte) error {
|
||||
var v struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Paths []string `json:"paths,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.ID = v.ID
|
||||
s.Paths = v.Paths
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SSH) UnmarshalText(text []byte) error {
|
||||
parts := strings.SplitN(string(text), "=", 2)
|
||||
|
||||
|
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
|
||||
}
|
||||
|
||||
func BuildURL(ref string) string {
|
||||
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
||||
}
|
||||
|
||||
func BuildDetailsOutput(refs map[string]string, term bool) string {
|
||||
if len(refs) == 0 {
|
||||
return ""
|
||||
}
|
||||
refURL := func(ref string) string {
|
||||
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
|
||||
}
|
||||
var out bytes.Buffer
|
||||
out.WriteString("View build details: ")
|
||||
multiTargets := len(refs) > 1
|
||||
@@ -43,9 +44,10 @@ func BuildDetailsOutput(refs map[string]string, term bool) string {
|
||||
out.WriteString(fmt.Sprintf("\n %s: ", target))
|
||||
}
|
||||
if term {
|
||||
out.WriteString(hyperlink(refURL(ref)))
|
||||
url := BuildURL(ref)
|
||||
out.WriteString(ANSIHyperlink(url, url))
|
||||
} else {
|
||||
out.WriteString(refURL(ref))
|
||||
out.WriteString(BuildURL(ref))
|
||||
}
|
||||
}
|
||||
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
|
||||
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 {
|
||||
|
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 != "" {
|
||||
gitBinary = cli.git
|
||||
}
|
||||
proxyEnvVars := [...]string{
|
||||
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "ALL_PROXY",
|
||||
"http_proxy", "https_proxy", "no_proxy", "all_proxy",
|
||||
}
|
||||
|
||||
for {
|
||||
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.
|
||||
"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 != "" {
|
||||
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
|
||||
## explicit; go 1.18
|
||||
github.com/creack/pty
|
||||
# github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
## explicit
|
||||
github.com/davecgh/go-spew/spew
|
||||
# github.com/distribution/reference v0.6.0
|
||||
@@ -493,7 +493,7 @@ github.com/mitchellh/go-wordwrap
|
||||
github.com/mitchellh/hashstructure/v2
|
||||
# github.com/mitchellh/mapstructure v1.5.0
|
||||
## explicit; go 1.14
|
||||
# github.com/moby/buildkit v0.19.0-rc2
|
||||
# github.com/moby/buildkit v0.19.0
|
||||
## explicit; go 1.22.0
|
||||
github.com/moby/buildkit/api/services/control
|
||||
github.com/moby/buildkit/api/types
|
||||
@@ -636,6 +636,9 @@ github.com/opencontainers/image-spec/specs-go/v1
|
||||
# github.com/pelletier/go-toml v1.9.5
|
||||
## explicit; go 1.12
|
||||
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
|
||||
## explicit
|
||||
github.com/pkg/errors
|
||||
@@ -654,7 +657,7 @@ github.com/planetscale/vtprotobuf/generator/pattern
|
||||
github.com/planetscale/vtprotobuf/protohelpers
|
||||
github.com/planetscale/vtprotobuf/types/known/timestamppb
|
||||
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
|
||||
github.com/pmezard/go-difflib/difflib
|
||||
# github.com/prometheus/client_golang v1.20.5
|
||||
|
Reference in New Issue
Block a user