mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-15 16:07:11 +08:00
Merge pull request #1412 from jedevc/attestations-cli
Attestations from buildx
This commit is contained in:
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]]
|
o := t[kk[1]]
|
||||||
|
|
||||||
switch keys[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 {
|
if len(parts) == 2 {
|
||||||
o.ArrValue = append(o.ArrValue, parts[1])
|
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 is the only field that cannot be overridden with --set
|
||||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
|
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"`
|
||||||
|
|
||||||
|
Attest []string `json:"attest,omitempty" hcl:"attest,optional"`
|
||||||
Context *string `json:"context,omitempty" hcl:"context,optional"`
|
Context *string `json:"context,omitempty" hcl:"context,optional"`
|
||||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional"`
|
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional"`
|
||||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
|
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"`
|
||||||
@ -583,6 +584,7 @@ type Target struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Target) normalize() {
|
func (t *Target) normalize() {
|
||||||
|
t.Attest = removeDupes(t.Attest)
|
||||||
t.Tags = removeDupes(t.Tags)
|
t.Tags = removeDupes(t.Tags)
|
||||||
t.Secrets = removeDupes(t.Secrets)
|
t.Secrets = removeDupes(t.Secrets)
|
||||||
t.SSH = removeDupes(t.SSH)
|
t.SSH = removeDupes(t.SSH)
|
||||||
@ -636,6 +638,9 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
if t2.Target != nil {
|
if t2.Target != nil {
|
||||||
t.Target = t2.Target
|
t.Target = t2.Target
|
||||||
}
|
}
|
||||||
|
if t2.Attest != nil { // merge
|
||||||
|
t.Attest = append(t.Attest, t2.Attest...)
|
||||||
|
}
|
||||||
if t2.Secrets != nil { // merge
|
if t2.Secrets != nil { // merge
|
||||||
t.Secrets = append(t.Secrets, t2.Secrets...)
|
t.Secrets = append(t.Secrets, t2.Secrets...)
|
||||||
}
|
}
|
||||||
@ -718,6 +723,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||||||
t.Platforms = o.ArrValue
|
t.Platforms = o.ArrValue
|
||||||
case "output":
|
case "output":
|
||||||
t.Outputs = o.ArrValue
|
t.Outputs = o.ArrValue
|
||||||
|
case "attest":
|
||||||
|
t.Attest = append(t.Attest, o.ArrValue...)
|
||||||
case "no-cache":
|
case "no-cache":
|
||||||
noCache, err := strconv.ParseBool(value)
|
noCache, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -971,6 +978,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
bo.Exports = outputs
|
bo.Exports = outputs
|
||||||
|
|
||||||
|
attests, err := buildflags.ParseAttests(t.Attest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bo.Attests = attests
|
||||||
|
|
||||||
return bo, nil
|
return bo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||||
|
"github.com/moby/buildkit/frontend/attestations"
|
||||||
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||||
@ -67,6 +68,7 @@ type Options struct {
|
|||||||
Inputs Inputs
|
Inputs Inputs
|
||||||
|
|
||||||
Allow []entitlements.Entitlement
|
Allow []entitlements.Entitlement
|
||||||
|
Attests map[string]*string
|
||||||
BuildArgs map[string]string
|
BuildArgs map[string]string
|
||||||
CacheFrom []client.CacheOptionsEntry
|
CacheFrom []client.CacheOptionsEntry
|
||||||
CacheTo []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
|
// set platforms
|
||||||
if len(opt.Platforms) != 0 {
|
if len(opt.Platforms) != 0 {
|
||||||
pp := make([]string, len(opt.Platforms))
|
pp := make([]string, len(opt.Platforms))
|
||||||
@ -1109,7 +1126,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
|
|||||||
FrontendInputs: frontendInputs,
|
FrontendInputs: frontendInputs,
|
||||||
}
|
}
|
||||||
so.Frontend = ""
|
so.Frontend = ""
|
||||||
so.FrontendAttrs = nil
|
so.FrontendAttrs = attestations.Filter(so.FrontendAttrs)
|
||||||
so.FrontendInputs = nil
|
so.FrontendInputs = nil
|
||||||
|
|
||||||
ch, done := progress.NewChannel(pw)
|
ch, done := progress.NewChannel(pw)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/docker/buildx/bake"
|
"github.com/docker/buildx/bake"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"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 {
|
if in.pull != nil {
|
||||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
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()
|
contextPathHash, _ := os.Getwd()
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
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.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
|
||||||
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
||||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
|
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")`)
|
flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
|
||||||
|
|
||||||
commonBuildFlags(&options.commonOptions, flags)
|
commonBuildFlags(&options.commonOptions, flags)
|
||||||
|
@ -53,6 +53,7 @@ type buildOptions struct {
|
|||||||
printFunc string
|
printFunc string
|
||||||
|
|
||||||
allow []string
|
allow []string
|
||||||
|
attests []string
|
||||||
buildArgs []string
|
buildArgs []string
|
||||||
cacheFrom []string
|
cacheFrom []string
|
||||||
cacheTo []string
|
cacheTo []string
|
||||||
@ -60,6 +61,7 @@ type buildOptions struct {
|
|||||||
contexts []string
|
contexts []string
|
||||||
extraHosts []string
|
extraHosts []string
|
||||||
imageIDFile string
|
imageIDFile string
|
||||||
|
invoke string
|
||||||
labels []string
|
labels []string
|
||||||
networkMode string
|
networkMode string
|
||||||
noCacheFilter []string
|
noCacheFilter []string
|
||||||
@ -72,7 +74,6 @@ type buildOptions struct {
|
|||||||
tags []string
|
tags []string
|
||||||
target string
|
target string
|
||||||
ulimits *dockeropts.UlimitOpt
|
ulimits *dockeropts.UlimitOpt
|
||||||
invoke string
|
|
||||||
commonOptions
|
commonOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +86,9 @@ type commonOptions struct {
|
|||||||
|
|
||||||
exportPush bool
|
exportPush bool
|
||||||
exportLoad bool
|
exportLoad bool
|
||||||
|
|
||||||
|
sbom string
|
||||||
|
provenance string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
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
|
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)
|
cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -504,6 +519,10 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
|
|
||||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
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() {
|
if isExperimental() {
|
||||||
flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build [experimental]")
|
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 |
|
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||||
| [`--print`](#print) | | | Print the options without building |
|
| [`--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 |
|
| [`--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 |
|
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||||
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
| `--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`) |
|
| [`--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`) |
|
| [`--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`) |
|
| [`--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-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
@ -36,9 +37,11 @@ Start a build
|
|||||||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) [experimental] |
|
| `--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 |
|
| [`--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 |
|
| `--pull` | | | Always attempt to pull all referenced images |
|
||||||
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
| `-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]`) |
|
| [`--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` |
|
| [`--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>]]`) |
|
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
|
76
util/buildflags/attests.go
Normal file
76
util/buildflags/attests.go
Normal file
@ -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
|
||||||
|
}
|
Reference in New Issue
Block a user