Skip to content

Commit ba0d1a9

Browse files
authored
Merge pull request #2786 from yankay/support-build-attest
Support `nerdctl build` args: `--attest`,` --sbom`,`--provenance`
2 parents 19f947a + 9ae613b commit ba0d1a9

File tree

5 files changed

+108
-0
lines changed

5 files changed

+108
-0
lines changed

cmd/nerdctl/builder_build.go

+36
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"os"
23+
"strconv"
2324
"strings"
2425

2526
"github.com/containerd/nerdctl/v2/pkg/api/types"
@@ -50,13 +51,16 @@ If Dockerfile is not present and -f is not specified, it will look for Container
5051
buildCommand.Flags().Bool("no-cache", false, "Do not use cache when building the image")
5152
buildCommand.Flags().StringP("output", "o", "", "Output destination (format: type=local,dest=path)")
5253
buildCommand.Flags().String("progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
54+
buildCommand.Flags().String("provenance", "", "Shorthand for \"--attest=type=provenance\"")
5355
buildCommand.Flags().StringArray("secret", nil, "Secret file to expose to the build: id=mysecret,src=/local/secret")
5456
buildCommand.Flags().StringArray("allow", nil, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
5557
buildCommand.RegisterFlagCompletionFunc("allow", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
5658
return []string{"network.host", "security.insecure"}, cobra.ShellCompDirectiveNoFileComp
5759
})
60+
buildCommand.Flags().StringArray("attest", nil, "Attestation parameters (format: \"type=sbom,generator=image\")")
5861
buildCommand.Flags().StringArray("ssh", nil, "SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])")
5962
buildCommand.Flags().BoolP("quiet", "q", false, "Suppress the build output and print image ID on success")
63+
buildCommand.Flags().String("sbom", "", "Shorthand for \"--attest=type=sbom\"")
6064
buildCommand.Flags().StringArray("cache-from", nil, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
6165
buildCommand.Flags().StringArray("cache-to", nil, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
6266
buildCommand.Flags().Bool("rm", true, "Remove intermediate containers after a successful build")
@@ -165,6 +169,26 @@ func processBuildCommandFlag(cmd *cobra.Command, args []string) (types.BuilderBu
165169
if err != nil {
166170
return types.BuilderBuildOptions{}, err
167171
}
172+
173+
attest, err := cmd.Flags().GetStringArray("attest")
174+
if err != nil {
175+
return types.BuilderBuildOptions{}, err
176+
}
177+
sbom, err := cmd.Flags().GetString("sbom")
178+
if err != nil {
179+
return types.BuilderBuildOptions{}, err
180+
}
181+
if sbom != "" {
182+
attest = append(attest, canonicalizeAttest("sbom", sbom))
183+
}
184+
provenance, err := cmd.Flags().GetString("provenance")
185+
if err != nil {
186+
return types.BuilderBuildOptions{}, err
187+
}
188+
if provenance != "" {
189+
attest = append(attest, canonicalizeAttest("provenance", provenance))
190+
}
191+
168192
return types.BuilderBuildOptions{
169193
GOptions: globalOptions,
170194
BuildKitHost: buildKitHost,
@@ -179,6 +203,7 @@ func processBuildCommandFlag(cmd *cobra.Command, args []string) (types.BuilderBu
179203
NoCache: noCache,
180204
Secret: secret,
181205
Allow: allow,
206+
Attest: attest,
182207
SSH: ssh,
183208
CacheFrom: cacheFrom,
184209
CacheTo: cacheTo,
@@ -222,3 +247,14 @@ func buildAction(cmd *cobra.Command, args []string) error {
222247

223248
return builder.Build(ctx, client, options)
224249
}
250+
251+
// canonicalizeAttest is from https://github.com/docker/buildx/blob/v0.12/util/buildflags/attests.go##L13-L21
252+
func canonicalizeAttest(attestType string, in string) string {
253+
if in == "" {
254+
return ""
255+
}
256+
if b, err := strconv.ParseBool(in); err == nil {
257+
return fmt.Sprintf("type=%s,disabled=%t", attestType, !b)
258+
}
259+
return fmt.Sprintf("type=%s,%s", attestType, in)
260+
}

cmd/nerdctl/builder_build_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -498,3 +498,60 @@ func TestBuildNetworkShellCompletion(t *testing.T) {
498498
networkName := "default"
499499
base.Cmd(gsc, "build", "--network", "").AssertOutContains(networkName)
500500
}
501+
502+
func buildWithNamedBuilder(base *testutil.Base, builderName string, args ...string) *testutil.Cmd {
503+
buildArgs := []string{"build"}
504+
if testutil.GetTarget() == testutil.Docker {
505+
buildArgs = append(buildArgs, "--builder", builderName)
506+
}
507+
buildArgs = append(buildArgs, args...)
508+
return base.Cmd(buildArgs...)
509+
}
510+
511+
func TestBuildAttestation(t *testing.T) {
512+
t.Parallel()
513+
testutil.RequiresBuild(t)
514+
base := testutil.NewBase(t)
515+
builderName := testutil.Identifier(t)
516+
if testutil.GetTarget() == testutil.Docker {
517+
// create named builder for docker
518+
defer base.Cmd("buildx", "rm", builderName).AssertOK()
519+
base.Cmd("buildx", "create", "--name", builderName, "--bootstrap", "--use").AssertOK()
520+
}
521+
defer base.Cmd("builder", "prune").Run()
522+
523+
dockerfile := "FROM " + testutil.NginxAlpineImage
524+
buildCtx, err := createBuildContext(dockerfile)
525+
assert.NilError(t, err)
526+
defer os.RemoveAll(buildCtx)
527+
528+
// Test sbom
529+
outputSBOMDir := t.TempDir()
530+
buildWithNamedBuilder(base, builderName, "--sbom=true", "-o", fmt.Sprintf("type=local,dest=%s", outputSBOMDir), buildCtx).AssertOK()
531+
const testSBOMFileName = "sbom.spdx.json"
532+
testSBOMFilePath := filepath.Join(outputSBOMDir, testSBOMFileName)
533+
if _, err := os.Stat(testSBOMFilePath); err != nil {
534+
t.Fatal(err)
535+
}
536+
537+
// Test provenance
538+
outputProvenanceDir := t.TempDir()
539+
buildWithNamedBuilder(base, builderName, "--provenance=mode=min", "-o", fmt.Sprintf("type=local,dest=%s", outputProvenanceDir), buildCtx).AssertOK()
540+
const testProvenanceFileName = "provenance.json"
541+
testProvenanceFilePath := filepath.Join(outputProvenanceDir, testProvenanceFileName)
542+
if _, err := os.Stat(testProvenanceFilePath); err != nil {
543+
t.Fatal(err)
544+
}
545+
546+
// Test attestation
547+
outputAttestationDir := t.TempDir()
548+
buildWithNamedBuilder(base, builderName, "--attest=type=provenance,mode=min", "--attest=type=sbom", "-o", fmt.Sprintf("type=local,dest=%s", outputAttestationDir), buildCtx).AssertOK()
549+
testSBOMFilePath = filepath.Join(outputAttestationDir, testSBOMFileName)
550+
testProvenanceFilePath = filepath.Join(outputAttestationDir, testProvenanceFileName)
551+
if _, err := os.Stat(testSBOMFilePath); err != nil {
552+
t.Fatal(err)
553+
}
554+
if _, err := os.Stat(testProvenanceFilePath); err != nil {
555+
t.Fatal(err)
556+
}
557+
}

docs/command-reference.md

+3
Original file line numberDiff line numberDiff line change
@@ -680,10 +680,13 @@ Flags:
680680
- :whale: `type=tar[,dest=path/to/output.tar]`: Raw tar ball
681681
- :whale: `type=image,name=example.com/image,push=true`: Push to a registry (see [`buildctl build`](https://github.com/moby/buildkit/tree/v0.9.0#imageregistry) documentation)
682682
- :whale: `--progress=(auto|plain|tty)`: Set type of progress output (auto, plain, tty). Use plain to show container output
683+
- :whale: `--provenance`: Shorthand for \"--attest=type=provenance\", see [`buildx_build.md`](https://github.com/docker/buildx/blob/v0.12.1/docs/reference/buildx_build.md#provenance) documentation
683684
- :whale: `--secret`: Secret file to expose to the build: id=mysecret,src=/local/secret
684685
- :whale: `--allow`: Allow extra privileged entitlement, e.g. network.host, security.insecure (It’s required to configure the buildkitd to enable the feature, see [`buildkitd.toml`](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) documentation)
686+
- :whale: `--attest`: Attestation parameters (format: "type=sbom,generator=image"), see [`buildx_build.md`](https://github.com/docker/buildx/blob/v0.12.1/docs/reference/buildx_build.md#attest) documentation
685687
- :whale: `--ssh`: SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)
686688
- :whale: `-q, --quiet`: Suppress the build output and print image ID on success
689+
- :whale: `--sbom`: Shorthand for \"--attest=type=sbom\", see [`buildx_build.md`](https://github.com/docker/buildx/blob/v0.12.1/docs/reference/buildx_build.md#sbom) documentation
687690
- :whale: `--cache-from=CACHE`: External cache sources (eg. user/app:cache, type=local,src=path/to/dir) (compatible with `docker buildx build`)
688691
- :whale: `--cache-to=CACHE`: Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir) (compatible with `docker buildx build`)
689692
- :whale: `--platform=(amd64|arm64|...)`: Set target platform for build (compatible with `docker buildx build`)

pkg/api/types/builder_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type BuilderBuildOptions struct {
4545
Secret []string
4646
// Allow extra privileged entitlement, e.g. network.host, security.insecure
4747
Allow []string
48+
// Attestation parameters (format: "type=sbom,generator=image")"
49+
Attest []string
4850
// SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])
4951
SSH []string
5052
// Quiet suppress the build output and print image ID on success

pkg/cmd/builder/build.go

+10
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,16 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
338338
buildctlArgs = append(buildctlArgs, "--allow="+s)
339339
}
340340

341+
for _, s := range strutil.DedupeStrSlice(options.Attest) {
342+
optAttestType, optAttestAttrs, _ := strings.Cut(s, ",")
343+
if strings.HasPrefix(optAttestType, "type=") {
344+
optAttestType := strings.TrimPrefix(optAttestType, "type=")
345+
buildctlArgs = append(buildctlArgs, fmt.Sprintf("--opt=attest:%s=%s", optAttestType, optAttestAttrs))
346+
} else {
347+
return "", nil, false, "", nil, nil, fmt.Errorf("attestation type not specified")
348+
}
349+
}
350+
341351
for _, s := range strutil.DedupeStrSlice(options.SSH) {
342352
buildctlArgs = append(buildctlArgs, "--ssh="+s)
343353
}

0 commit comments

Comments
 (0)