introduce build.provenance and sbom support

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2025-07-17 18:28:44 +02:00 committed by Guillaume Lours
parent 1d2223fb23
commit 8f91793fb5
7 changed files with 63 additions and 9 deletions

View File

@ -45,7 +45,8 @@ type buildOptions struct {
deps bool deps bool
print bool print bool
check bool check bool
provenance bool sbom string
provenance string
} }
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) { func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
@ -84,6 +85,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
Check: opts.check, Check: opts.check,
SSHs: SSHKeys, SSHs: SSHKeys,
Builder: builderName, Builder: builderName,
SBOM: opts.sbom,
Provenance: opts.provenance, Provenance: opts.provenance,
}, nil }, nil
} }
@ -125,6 +127,8 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)") flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
flags.StringVar(&opts.builder, "builder", "", "Set builder to use") flags.StringVar(&opts.builder, "builder", "", "Set builder to use")
flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)") flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)")
flags.StringVar(&opts.provenance, "provenance", "", `Add a provenance attestation`)
flags.StringVar(&opts.sbom, "sbom", "", `Add a SBOM attestation`)
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED") flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
flags.MarkHidden("parallel") //nolint:errcheck flags.MarkHidden("parallel") //nolint:errcheck
@ -156,7 +160,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
} }
apiBuildOptions, err := opts.toAPIBuildOptions(services) apiBuildOptions, err := opts.toAPIBuildOptions(services)
apiBuildOptions.Provenance = true apiBuildOptions.Attestations = true
if err != nil { if err != nil {
return err return err
} }

View File

@ -22,9 +22,11 @@ run `docker compose build` to rebuild it.
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. | | `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
| `--no-cache` | `bool` | | Do not use cache when building the image | | `--no-cache` | `bool` | | Do not use cache when building the image |
| `--print` | `bool` | | Print equivalent bake file | | `--print` | `bool` | | Print equivalent bake file |
| `--provenance` | `string` | | Add a provenance attestation |
| `--pull` | `bool` | | Always attempt to pull a newer version of the image | | `--pull` | `bool` | | Always attempt to pull a newer version of the image |
| `--push` | `bool` | | Push service images | | `--push` | `bool` | | Push service images |
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT | | `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
| `--sbom` | `string` | | Add a SBOM attestation |
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) | | `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
| `--with-dependencies` | `bool` | | Also build dependencies (transitively) | | `--with-dependencies` | `bool` | | Also build dependencies (transitively) |

View File

@ -125,6 +125,15 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: provenance
value_type: string
description: Add a provenance attestation
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: pull - option: pull
value_type: bool value_type: bool
default_value: "false" default_value: "false"
@ -156,6 +165,15 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: sbom
value_type: string
description: Add a SBOM attestation
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: ssh - option: ssh
value_type: string value_type: string
description: | description: |

3
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/Microsoft/go-winio v0.6.2 github.com/Microsoft/go-winio v0.6.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4 github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.7.1 github.com/compose-spec/compose-go/v2 v2.7.2-0.20250721082312-a42e7579d813
github.com/containerd/containerd/v2 v2.1.3 github.com/containerd/containerd/v2 v2.1.3
github.com/containerd/errdefs v1.0.0 github.com/containerd/errdefs v1.0.0
github.com/containerd/platforms v1.0.0-rc.1 github.com/containerd/platforms v1.0.0-rc.1
@ -181,6 +181,7 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect

6
go.sum
View File

@ -80,8 +80,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.7.1 h1:EUIbuaD0R/J1KA+FbJMNbcS9+jt/CVudbp5iHqUllSs= github.com/compose-spec/compose-go/v2 v2.7.2-0.20250721082312-a42e7579d813 h1:Lmtch++VWP8Oqzff+FJflVW3g6/JFtDc3wq+tvRsagE=
github.com/compose-spec/compose-go/v2 v2.7.1/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= github.com/compose-spec/compose-go/v2 v2.7.2-0.20250721082312-a42e7579d813/go.mod h1:veko/VB7URrg/tKz3vmIAQDaz+CGiXH8vZsW79NmAww=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
@ -539,6 +539,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@ -170,8 +170,12 @@ type BuildOptions struct {
Print bool Print bool
// Check let builder validate build configuration // Check let builder validate build configuration
Check bool Check bool
// Provenance // Attestations allows to enable attestations generation
Provenance bool Attestations bool
// Provenance generate a provenance attestation
Provenance string
// SBOM generate a SBOM attestation
SBOM string
} }
// Apply mutates project according to build options // Apply mutates project according to build options

View File

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -397,6 +398,7 @@ func resolveAndMergeBuildArgs(dockerCli command.Cli, project *types.Project, ser
return result return result
} }
//nolint:gocyclo
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) { func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
plats, err := parsePlatforms(service) plats, err := parsePlatforms(service)
if err != nil { if err != nil {
@ -471,8 +473,19 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
} }
attests := map[string]*string{} attests := map[string]*string{}
if !options.Provenance { if options.Attestations {
attests["provenance"] = nil if service.Build.Provenance != "" {
attests["provenance"] = attestation(service.Build.Provenance, "provenance")
}
if service.Build.SBOM != "" {
attests["sbom"] = attestation(service.Build.SBOM, "sbom")
}
}
if options.Provenance != "" {
attests["provenance"] = attestation(options.Provenance, "provenance")
}
if options.SBOM != "" {
attests["sbom"] = attestation(options.SBOM, "sbom")
} }
return build.Options{ return build.Options{
@ -502,6 +515,16 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
}, nil }, nil
} }
func attestation(attest string, val string) *string {
if b, err := strconv.ParseBool(val); err == nil {
s := fmt.Sprintf("type=%s,disabled=%t", attest, b)
return &s
} else {
s := fmt.Sprintf("type=%s,%s", attest, val)
return &s
}
}
func toUlimitOpt(ulimits map[string]*types.UlimitsConfig) *cliopts.UlimitOpt { func toUlimitOpt(ulimits map[string]*types.UlimitsConfig) *cliopts.UlimitOpt {
ref := map[string]*container.Ulimit{} ref := map[string]*container.Ulimit{}
for _, limit := range toUlimits(ulimits) { for _, limit := range toUlimits(ulimits) {