mirror of https://github.com/docker/buildx.git
Merge pull request #1412 from jedevc/attestations-cli
Attestations from buildx
This commit is contained in:
commit
1e72e32ec3
15
bake/bake.go
15
bake/bake.go
|
@ -417,7 +417,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
|
|||
o := t[kk[1]]
|
||||
|
||||
switch keys[1] {
|
||||
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh":
|
||||
case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest":
|
||||
if len(parts) == 2 {
|
||||
o.ArrValue = append(o.ArrValue, parts[1])
|
||||
}
|
||||
|
@ -558,6 +558,7 @@ type Target struct {
|
|||
// Inherits is the only field that cannot be overridden with --set
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
|
||||
|
||||
Attest []string `json:"attest,omitempty" hcl:"attest,optional"`
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional"`
|
||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
|
||||
|
@ -583,6 +584,7 @@ type Target struct {
|
|||
}
|
||||
|
||||
func (t *Target) normalize() {
|
||||
t.Attest = removeDupes(t.Attest)
|
||||
t.Tags = removeDupes(t.Tags)
|
||||
t.Secrets = removeDupes(t.Secrets)
|
||||
t.SSH = removeDupes(t.SSH)
|
||||
|
@ -636,6 +638,9 @@ func (t *Target) Merge(t2 *Target) {
|
|||
if t2.Target != nil {
|
||||
t.Target = t2.Target
|
||||
}
|
||||
if t2.Attest != nil { // merge
|
||||
t.Attest = append(t.Attest, t2.Attest...)
|
||||
}
|
||||
if t2.Secrets != nil { // merge
|
||||
t.Secrets = append(t.Secrets, t2.Secrets...)
|
||||
}
|
||||
|
@ -718,6 +723,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||
t.Platforms = o.ArrValue
|
||||
case "output":
|
||||
t.Outputs = o.ArrValue
|
||||
case "attest":
|
||||
t.Attest = append(t.Attest, o.ArrValue...)
|
||||
case "no-cache":
|
||||
noCache, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
|
@ -971,6 +978,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||
}
|
||||
bo.Exports = outputs
|
||||
|
||||
attests, err := buildflags.ParseAttests(t.Attest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bo.Attests = attests
|
||||
|
||||
return bo, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
"github.com/moby/buildkit/frontend/attestations"
|
||||
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||
|
@ -67,6 +68,7 @@ type Options struct {
|
|||
Inputs Inputs
|
||||
|
||||
Allow []entitlements.Entitlement
|
||||
Attests map[string]*string
|
||||
BuildArgs map[string]string
|
||||
CacheFrom []client.CacheOptionsEntry
|
||||
CacheTo []client.CacheOptionsEntry
|
||||
|
@ -578,6 +580,21 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op
|
|||
}
|
||||
}
|
||||
|
||||
if len(opt.Attests) > 0 {
|
||||
if !bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) {
|
||||
return nil, nil, errors.Errorf("attestations are not supported by the current buildkitd")
|
||||
}
|
||||
for k, v := range opt.Attests {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
so.FrontendAttrs[k] = *v
|
||||
}
|
||||
}
|
||||
if _, ok := opt.Attests["attest:provenance"]; !ok {
|
||||
so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true"
|
||||
}
|
||||
|
||||
// set platforms
|
||||
if len(opt.Platforms) != 0 {
|
||||
pp := make([]string, len(opt.Platforms))
|
||||
|
@ -1109,7 +1126,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||
FrontendInputs: frontendInputs,
|
||||
}
|
||||
so.Frontend = ""
|
||||
so.FrontendAttrs = nil
|
||||
so.FrontendAttrs = attestations.Filter(so.FrontendAttrs)
|
||||
so.FrontendInputs = nil
|
||||
|
||||
ch, done := progress.NewChannel(pw)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/docker/buildx/bake"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
|
@ -73,6 +74,12 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||
if in.pull != nil {
|
||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
||||
}
|
||||
if in.sbom != "" {
|
||||
overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("sbom", in.sbom)))
|
||||
}
|
||||
if in.provenance != "" {
|
||||
overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("provenance", in.provenance)))
|
||||
}
|
||||
contextPathHash, _ := os.Getwd()
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
|
@ -199,6 +206,8 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
|
||||
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
|
||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--set=*.attest=type=sbom"`)
|
||||
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`)
|
||||
flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
|
||||
|
||||
commonBuildFlags(&options.commonOptions, flags)
|
||||
|
|
|
@ -53,6 +53,7 @@ type buildOptions struct {
|
|||
printFunc string
|
||||
|
||||
allow []string
|
||||
attests []string
|
||||
buildArgs []string
|
||||
cacheFrom []string
|
||||
cacheTo []string
|
||||
|
@ -60,6 +61,7 @@ type buildOptions struct {
|
|||
contexts []string
|
||||
extraHosts []string
|
||||
imageIDFile string
|
||||
invoke string
|
||||
labels []string
|
||||
networkMode string
|
||||
noCacheFilter []string
|
||||
|
@ -72,7 +74,6 @@ type buildOptions struct {
|
|||
tags []string
|
||||
target string
|
||||
ulimits *dockeropts.UlimitOpt
|
||||
invoke string
|
||||
commonOptions
|
||||
}
|
||||
|
||||
|
@ -85,6 +86,9 @@ type commonOptions struct {
|
|||
|
||||
exportPush bool
|
||||
exportLoad bool
|
||||
|
||||
sbom string
|
||||
provenance string
|
||||
}
|
||||
|
||||
func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||
|
@ -212,9 +216,20 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.Exports = outputs
|
||||
|
||||
inAttests := append([]string{}, in.attests...)
|
||||
if in.provenance != "" {
|
||||
inAttests = append(inAttests, buildflags.CanonicalizeAttest("provenance", in.provenance))
|
||||
}
|
||||
if in.sbom != "" {
|
||||
inAttests = append(inAttests, buildflags.CanonicalizeAttest("sbom", in.sbom))
|
||||
}
|
||||
opts.Attests, err = buildflags.ParseAttests(inAttests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -504,6 +519,10 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||
|
||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||
|
||||
flags.StringArrayVar(&options.attests, "attest", []string{}, `Attestation parameters (format: "type=sbom,generator=image")`)
|
||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
|
||||
flags.StringVar(&options.provenance, "provenance", "", `Shortand for "--attest=type=provenance"`)
|
||||
|
||||
if isExperimental() {
|
||||
flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build [experimental]")
|
||||
}
|
||||
|
|
|
@ -22,8 +22,10 @@ Build from a file
|
|||
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||
| [`--print`](#print) | | | Print the options without building |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| `--provenance` | `string` | | Shorthand for `--set=*.attest=type=provenance` |
|
||||
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
||||
| `--sbom` | `string` | | Shorthand for `--set=*.attest=type=sbom` |
|
||||
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ Start a build
|
|||
| --- | --- | --- | --- |
|
||||
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
|
@ -36,9 +37,11 @@ Start a build
|
|||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) [experimental] |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| `--provenance` | `string` | | Shortand for `--attest=type=provenance` |
|
||||
| `--pull` | | | Always attempt to pull all referenced images |
|
||||
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Size of `/dev/shm` |
|
||||
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package buildflags
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func CanonicalizeAttest(attestType string, in string) string {
|
||||
if in == "" {
|
||||
return ""
|
||||
}
|
||||
if b, err := strconv.ParseBool(in); err == nil {
|
||||
return fmt.Sprintf("type=%s,enabled=%t", attestType, b)
|
||||
}
|
||||
return fmt.Sprintf("type=%s,%s", attestType, in)
|
||||
}
|
||||
|
||||
func ParseAttests(in []string) (map[string]*string, error) {
|
||||
out := map[string]*string{}
|
||||
for _, in := range in {
|
||||
in := in
|
||||
attestType, enabled, err := parseAttest(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := "attest:" + attestType
|
||||
if enabled {
|
||||
out[k] = &in
|
||||
} else {
|
||||
out[k] = nil
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseAttest(in string) (string, bool, error) {
|
||||
if in == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(strings.NewReader(in))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
attestType := ""
|
||||
enabled := true
|
||||
for _, field := range fields {
|
||||
key, value, ok := strings.Cut(field, "=")
|
||||
if !ok {
|
||||
return "", false, errors.Errorf("invalid value %s", field)
|
||||
}
|
||||
key = strings.TrimSpace(strings.ToLower(key))
|
||||
|
||||
switch key {
|
||||
case "type":
|
||||
attestType = value
|
||||
case "enabled":
|
||||
enabled, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if attestType == "" {
|
||||
return "", false, errors.Errorf("attestation type not specified")
|
||||
}
|
||||
|
||||
return attestType, enabled, nil
|
||||
}
|
Loading…
Reference in New Issue