Skip to content

Commit eeaf85b

Browse files
committed
Add support for BASHBREW_BUILDKIT_SBOM_GENERATOR and provenance
Since Docker's image store can't represent these, we round trip them through our self-managed (or external) containerd image store, which also makes pushing more efficient.
1 parent d7cd73a commit eeaf85b

File tree

10 files changed

+184
-66
lines changed

10 files changed

+184
-66
lines changed

cmd/bashbrew/cmd-build.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ func cmdBuild(c *cli.Context) error {
8181
tags := append([]string{cacheTag}, imageTags...)
8282

8383
// check whether we've already built this artifact
84-
_, err = dockerInspect("{{.Id}}", cacheTag)
84+
cachedDesc, err := containerdImageLookup(cacheTag)
85+
if err != nil {
86+
cachedDesc = nil
87+
_, err = dockerInspect("{{.Id}}", cacheTag)
88+
}
8589
if err != nil {
8690
fmt.Printf("Building %s (%s)\n", cacheTag, r.EntryIdentifier(entry))
8791
if !dryRun {
@@ -116,13 +120,13 @@ func cmdBuild(c *cli.Context) error {
116120
archive.Close() // be sure this happens sooner rather than later (defer might take a while, and we want to reap zombies more aggressively)
117121

118122
case "oci-import":
119-
err := ociImportBuild(tags, commit, entry.ArchDirectory(arch), entry.ArchFile(arch))
123+
desc, err := ociImportBuild(tags, commit, entry.ArchDirectory(arch), entry.ArchFile(arch))
120124
if err != nil {
121125
return cli.NewMultiError(fmt.Errorf(`failed oci-import build of %q (tags %q)`, r.RepoName, entry.TagsString()), err)
122126
}
123127

124-
fmt.Printf("Importing %s into Docker\n", r.EntryIdentifier(entry))
125-
err = ociImportDockerLoad(imageTags)
128+
fmt.Printf("Importing %s (%s) into Docker\n", r.EntryIdentifier(entry), desc.Digest)
129+
err = containerdDockerLoad(*desc, imageTags)
126130
if err != nil {
127131
return cli.NewMultiError(fmt.Errorf(`failed oci-import into Docker of %q (tags %q)`, r.RepoName, entry.TagsString()), err)
128132
}
@@ -135,11 +139,19 @@ func cmdBuild(c *cli.Context) error {
135139
fmt.Printf("Using %s (%s)\n", cacheTag, r.EntryIdentifier(entry))
136140

137141
if !dryRun {
138-
// https://github.com/docker-library/bashbrew/pull/61/files#r1044926620
139-
// abusing "docker build" for "tag something a lot of times, but efficiently" 👀
140-
err := dockerBuild(imageTags, "", strings.NewReader("FROM "+cacheTag), "")
141-
if err != nil {
142-
return cli.NewMultiError(fmt.Errorf(`failed tagging %q: %q`, cacheTag, strings.Join(imageTags, ", ")), err)
142+
if cachedDesc == nil {
143+
// https://github.com/docker-library/bashbrew/pull/61#discussion_r1044926620
144+
// abusing "docker build" for "tag something a lot of times, but efficiently" 👀
145+
err := dockerBuild(imageTags, "", strings.NewReader("FROM "+cacheTag), "")
146+
if err != nil {
147+
return cli.NewMultiError(fmt.Errorf(`failed tagging %q: %q`, cacheTag, strings.Join(imageTags, ", ")), err)
148+
}
149+
} else {
150+
fmt.Printf("Importing %s into Docker\n", cachedDesc.Digest)
151+
err = containerdDockerLoad(*cachedDesc, tags)
152+
if err != nil {
153+
return cli.NewMultiError(fmt.Errorf(`failed (re-)import into Docker of %q (tags %q)`, r.RepoName, entry.TagsString()), err)
154+
}
143155
}
144156
}
145157
}

cmd/bashbrew/cmd-list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"fmt"
55
"path"
66

7-
"github.com/urfave/cli"
87
"github.com/docker-library/bashbrew/manifest"
8+
"github.com/urfave/cli"
99
)
1010

1111
func cmdList(c *cli.Context) error {

cmd/bashbrew/cmd-parents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func cmdParents(c *cli.Context) error {
3333
if err != nil {
3434
var (
3535
manifestNotFoundErr manifest.ManifestNotFoundError
36-
tagNotFoundErr manifest.TagNotFoundError
36+
tagNotFoundErr manifest.TagNotFoundError
3737
)
3838
if d != depth && (errors.As(err, &manifestNotFoundErr) || errors.As(err, &tagNotFoundErr)) {
3939
// if this repo isn't one of the original top-level arguments and our error is just that it's not a supported tag, walk no further ("FROM mcr.microsoft.com/...", etc)

cmd/bashbrew/cmd-push.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,19 @@ func cmdPush(c *cli.Context) error {
4949
tags = append(tags, tag)
5050
}
5151

52-
switch builder := entry.ArchBuilder(arch); builder {
53-
case "oci-import":
54-
cacheTag, err := r.DockerCacheName(entry)
55-
if err != nil {
56-
return cli.NewMultiError(fmt.Errorf(`failed calculating "cache hash" for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
57-
}
58-
desc, err := ociImportLookup(cacheTag)
59-
if err != nil {
60-
return cli.NewMultiError(fmt.Errorf(`failed looking up descriptor for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
52+
// if we can't successfully calculate our "cache hash", we can't possibly have built the image we're trying to push 🙈
53+
cacheTag, err := r.DockerCacheName(entry)
54+
if err != nil {
55+
return cli.NewMultiError(fmt.Errorf(`failed calculating "cache hash" for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
56+
}
57+
58+
// if the appropriate "bashbrew/cache:xxx" image exists in the containerd image store, we should prefer that (the nature of the cache hash should make this assumption safe)
59+
desc, err := containerdImageLookup(cacheTag)
60+
if err == nil {
61+
if debugFlag {
62+
fmt.Printf("Found %s (via %q) in containerd image store\n", desc.Digest, cacheTag)
6163
}
62-
skip, update, err := ociImportPushFilter(*desc, tags)
64+
skip, update, err := containerdPushFilter(*desc, tags)
6365
if err != nil {
6466
return cli.NewMultiError(fmt.Errorf(`failed looking up tags for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
6567
}
@@ -71,20 +73,33 @@ func cmdPush(c *cli.Context) error {
7173
}
7274
fmt.Printf("Pushing %s to %s\n", desc.Digest, strings.Join(update, ", "))
7375
if !dryRun {
74-
err := ociImportPush(*desc, update)
76+
err := containerdPush(*desc, update)
7577
if err != nil {
7678
return cli.NewMultiError(fmt.Errorf(`failed pushing %q`, r.EntryIdentifier(entry)), err)
7779
}
7880
}
81+
return nil
82+
}
83+
84+
switch builder := entry.ArchBuilder(arch); builder {
85+
case "oci-import":
86+
// if after all that checking above, we still didn't push, then we must've failed to lookup
87+
return cli.NewMultiError(fmt.Errorf(`failed looking up descriptor for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
7988

8089
default:
8190
TagsLoop:
8291
for _, tag := range tags {
8392
if !force {
84-
localImageId, _ := dockerInspect("{{.Id}}", tag)
93+
localImageId, err := dockerInspect("{{.Id}}", tag)
94+
if err != nil {
95+
return cli.NewMultiError(fmt.Errorf(`failed looking up local image ID for %q`, tag), err)
96+
}
8597
if debugFlag {
8698
fmt.Printf("DEBUG: docker inspect %q -> %q\n", tag, localImageId)
8799
}
100+
if localImageId == "" {
101+
return fmt.Errorf("local image for %q does not seem to exist (or has an empty ID somehow)", tag)
102+
}
88103
registryImageIds := fetchRegistryImageIds(tag)
89104
if debugFlag {
90105
fmt.Printf("DEBUG: registry inspect %q -> %+v\n", tag, registryImageIds)

cmd/bashbrew/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"os"
99
"strings"
1010

11-
"github.com/urfave/cli"
1211
"github.com/docker-library/bashbrew/pkg/stripper"
12+
"github.com/urfave/cli"
1313
"pault.ag/go/debian/control"
1414
)
1515

cmd/bashbrew/containerd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ var containerdClientCache *containerd.Client = nil
4242

4343
// the returned client is cached, don't Close() it!
4444
func newContainerdClient(ctx context.Context) (context.Context, *containerd.Client, error) {
45-
ns := "bashbrew"
45+
ns := "bashbrew-" + arch
4646
for _, envKey := range []string{
4747
`BASHBREW_CONTAINERD_NAMESPACE`,
4848
`CONTAINERD_NAMESPACE`,

cmd/bashbrew/docker.go

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,11 @@ func dockerBuild(tags []string, file string, context io.Reader, platform string)
291291
}
292292
}
293293

294-
const dockerfileSyntaxEnv = "BASHBREW_BUILDKIT_SYNTAX"
294+
const (
295+
dockerfileSyntaxEnv = "BASHBREW_BUILDKIT_SYNTAX"
296+
sbomGeneratorEnv = "BASHBREW_BUILDKIT_SBOM_GENERATOR"
297+
buildxBuilderEnv = "BUILDX_BUILDER"
298+
)
295299

296300
func dockerBuildxBuild(tags []string, file string, context io.Reader, platform string) error {
297301
dockerfileSyntax, ok := os.LookupEnv(dockerfileSyntaxEnv)
@@ -305,26 +309,88 @@ func dockerBuildxBuild(tags []string, file string, context io.Reader, platform s
305309
"--progress", "plain",
306310
"--build-arg", "BUILDKIT_SYNTAX=" + dockerfileSyntax,
307311
}
312+
buildxBuilder := "" != os.Getenv(buildxBuilderEnv)
313+
if buildxBuilder {
314+
args = append(args, "--provenance", "mode=max")
315+
}
316+
if sbomGenerator, ok := os.LookupEnv(sbomGeneratorEnv); ok {
317+
if buildxBuilder {
318+
args = append(args, "--sbom", "generator="+sbomGenerator)
319+
} else {
320+
return fmt.Errorf("have %q but missing %q", sbomGeneratorEnv, buildxBuilderEnv)
321+
}
322+
}
308323
if platform != "" {
309324
args = append(args, "--platform", platform)
310325
}
311326
for _, tag := range tags {
312327
args = append(args, "--tag", tag)
313328
}
314-
args = append(args, "--file", file, "-")
329+
if file != "" {
330+
args = append(args, "--file", file)
331+
}
332+
args = append(args, "-")
333+
334+
if buildxBuilder {
335+
args = append(args, "--output", "type=oci")
336+
// TODO ,annotation.xyz.tianon.foo=bar,annotation-manifest-descriptor.xyz.tianon.foo=bar (for OCI source annotations, which this function doesn't currently have access to)
337+
}
315338

316339
cmd := exec.Command("docker", args...)
317340
cmd.Stdin = context
341+
342+
run := func() error {
343+
return cmd.Run()
344+
}
345+
if buildxBuilder {
346+
run = func() error {
347+
pipe, err := cmd.StdoutPipe()
348+
if err != nil {
349+
return err
350+
}
351+
defer pipe.Close()
352+
353+
err = cmd.Start()
354+
if err != nil {
355+
return err
356+
}
357+
defer cmd.Process.Kill()
358+
359+
_, err = containerdImageLoad(pipe)
360+
if err != nil {
361+
return err
362+
}
363+
pipe.Close()
364+
365+
err = cmd.Wait()
366+
if err != nil {
367+
return err
368+
}
369+
370+
desc, err := containerdImageLookup(tags[0])
371+
if err != nil {
372+
return err
373+
}
374+
375+
fmt.Printf("Importing %s into Docker\n", desc.Digest)
376+
err = containerdDockerLoad(*desc, tags)
377+
if err != nil {
378+
return err
379+
}
380+
381+
return nil
382+
}
383+
}
384+
385+
// intentionally not touching os.Stdout because "buildx build" does *not* put any build output to stdout and in some cases (see above) we use stdout to capture an OCI tarball and pipe it into containerd
318386
if debugFlag {
319-
cmd.Stdout = os.Stdout
320387
cmd.Stderr = os.Stderr
321388
fmt.Printf("$ docker %q\n", args)
322-
return cmd.Run()
389+
return run()
323390
} else {
324391
buf := &bytes.Buffer{}
325-
cmd.Stdout = buf
326392
cmd.Stderr = buf
327-
err := cmd.Run()
393+
err := run()
328394
if err != nil {
329395
err = cli.NewMultiError(err, fmt.Errorf(`docker %q output:%s`, args, "\n"+buf.String()))
330396
}

0 commit comments

Comments
 (0)