Skip to content

Commit

Permalink
Add support for BASHBREW_BUILDKIT_SBOM_GENERATOR and provenance
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tianon committed Mar 2, 2023
1 parent d7cd73a commit eeaf85b
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 66 deletions.
30 changes: 21 additions & 9 deletions cmd/bashbrew/cmd-build.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ func cmdBuild(c *cli.Context) error {
tags := append([]string{cacheTag}, imageTags...)

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

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

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

if !dryRun {
// https://github.com/docker-library/bashbrew/pull/61/files#r1044926620
// abusing "docker build" for "tag something a lot of times, but efficiently" 👀
err := dockerBuild(imageTags, "", strings.NewReader("FROM "+cacheTag), "")
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed tagging %q: %q`, cacheTag, strings.Join(imageTags, ", ")), err)
if cachedDesc == nil {
// https://github.com/docker-library/bashbrew/pull/61#discussion_r1044926620
// abusing "docker build" for "tag something a lot of times, but efficiently" 👀
err := dockerBuild(imageTags, "", strings.NewReader("FROM "+cacheTag), "")
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed tagging %q: %q`, cacheTag, strings.Join(imageTags, ", ")), err)
}
} else {
fmt.Printf("Importing %s into Docker\n", cachedDesc.Digest)
err = containerdDockerLoad(*cachedDesc, tags)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed (re-)import into Docker of %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/bashbrew/cmd-list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"fmt"
"path"

"github.com/urfave/cli"
"github.com/docker-library/bashbrew/manifest"
"github.com/urfave/cli"
)

func cmdList(c *cli.Context) error {
Expand Down
2 changes: 1 addition & 1 deletion cmd/bashbrew/cmd-parents.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func cmdParents(c *cli.Context) error {
if err != nil {
var (
manifestNotFoundErr manifest.ManifestNotFoundError
tagNotFoundErr manifest.TagNotFoundError
tagNotFoundErr manifest.TagNotFoundError
)
if d != depth && (errors.As(err, &manifestNotFoundErr) || errors.As(err, &tagNotFoundErr)) {
// 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)
Expand Down
39 changes: 27 additions & 12 deletions cmd/bashbrew/cmd-push.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,19 @@ func cmdPush(c *cli.Context) error {
tags = append(tags, tag)
}

switch builder := entry.ArchBuilder(arch); builder {
case "oci-import":
cacheTag, err := r.DockerCacheName(entry)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed calculating "cache hash" for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
desc, err := ociImportLookup(cacheTag)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed looking up descriptor for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
// if we can't successfully calculate our "cache hash", we can't possibly have built the image we're trying to push 🙈
cacheTag, err := r.DockerCacheName(entry)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed calculating "cache hash" for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}

// 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)
desc, err := containerdImageLookup(cacheTag)
if err == nil {
if debugFlag {
fmt.Printf("Found %s (via %q) in containerd image store\n", desc.Digest, cacheTag)
}
skip, update, err := ociImportPushFilter(*desc, tags)
skip, update, err := containerdPushFilter(*desc, tags)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed looking up tags for %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
Expand All @@ -71,20 +73,33 @@ func cmdPush(c *cli.Context) error {
}
fmt.Printf("Pushing %s to %s\n", desc.Digest, strings.Join(update, ", "))
if !dryRun {
err := ociImportPush(*desc, update)
err := containerdPush(*desc, update)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed pushing %q`, r.EntryIdentifier(entry)), err)
}
}
return nil
}

switch builder := entry.ArchBuilder(arch); builder {
case "oci-import":
// if after all that checking above, we still didn't push, then we must've failed to lookup
return cli.NewMultiError(fmt.Errorf(`failed looking up descriptor for %q (tags %q)`, r.RepoName, entry.TagsString()), err)

default:
TagsLoop:
for _, tag := range tags {
if !force {
localImageId, _ := dockerInspect("{{.Id}}", tag)
localImageId, err := dockerInspect("{{.Id}}", tag)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed looking up local image ID for %q`, tag), err)
}
if debugFlag {
fmt.Printf("DEBUG: docker inspect %q -> %q\n", tag, localImageId)
}
if localImageId == "" {
return fmt.Errorf("local image for %q does not seem to exist (or has an empty ID somehow)", tag)
}
registryImageIds := fetchRegistryImageIds(tag)
if debugFlag {
fmt.Printf("DEBUG: registry inspect %q -> %+v\n", tag, registryImageIds)
Expand Down
2 changes: 1 addition & 1 deletion cmd/bashbrew/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"os"
"strings"

"github.com/urfave/cli"
"github.com/docker-library/bashbrew/pkg/stripper"
"github.com/urfave/cli"
"pault.ag/go/debian/control"
)

Expand Down
2 changes: 1 addition & 1 deletion cmd/bashbrew/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var containerdClientCache *containerd.Client = nil

// the returned client is cached, don't Close() it!
func newContainerdClient(ctx context.Context) (context.Context, *containerd.Client, error) {
ns := "bashbrew"
ns := "bashbrew-" + arch
for _, envKey := range []string{
`BASHBREW_CONTAINERD_NAMESPACE`,
`CONTAINERD_NAMESPACE`,
Expand Down
78 changes: 72 additions & 6 deletions cmd/bashbrew/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,11 @@ func dockerBuild(tags []string, file string, context io.Reader, platform string)
}
}

const dockerfileSyntaxEnv = "BASHBREW_BUILDKIT_SYNTAX"
const (
dockerfileSyntaxEnv = "BASHBREW_BUILDKIT_SYNTAX"
sbomGeneratorEnv = "BASHBREW_BUILDKIT_SBOM_GENERATOR"
buildxBuilderEnv = "BUILDX_BUILDER"
)

func dockerBuildxBuild(tags []string, file string, context io.Reader, platform string) error {
dockerfileSyntax, ok := os.LookupEnv(dockerfileSyntaxEnv)
Expand All @@ -305,26 +309,88 @@ func dockerBuildxBuild(tags []string, file string, context io.Reader, platform s
"--progress", "plain",
"--build-arg", "BUILDKIT_SYNTAX=" + dockerfileSyntax,
}
buildxBuilder := "" != os.Getenv(buildxBuilderEnv)
if buildxBuilder {
args = append(args, "--provenance", "mode=max")
}
if sbomGenerator, ok := os.LookupEnv(sbomGeneratorEnv); ok {
if buildxBuilder {
args = append(args, "--sbom", "generator="+sbomGenerator)
} else {
return fmt.Errorf("have %q but missing %q", sbomGeneratorEnv, buildxBuilderEnv)
}
}
if platform != "" {
args = append(args, "--platform", platform)
}
for _, tag := range tags {
args = append(args, "--tag", tag)
}
args = append(args, "--file", file, "-")
if file != "" {
args = append(args, "--file", file)
}
args = append(args, "-")

if buildxBuilder {
args = append(args, "--output", "type=oci")
// 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)
}

cmd := exec.Command("docker", args...)
cmd.Stdin = context

run := func() error {
return cmd.Run()
}
if buildxBuilder {
run = func() error {
pipe, err := cmd.StdoutPipe()
if err != nil {
return err
}
defer pipe.Close()

err = cmd.Start()
if err != nil {
return err
}
defer cmd.Process.Kill()

_, err = containerdImageLoad(pipe)
if err != nil {
return err
}
pipe.Close()

err = cmd.Wait()
if err != nil {
return err
}

desc, err := containerdImageLookup(tags[0])
if err != nil {
return err
}

fmt.Printf("Importing %s into Docker\n", desc.Digest)
err = containerdDockerLoad(*desc, tags)
if err != nil {
return err
}

return nil
}
}

// 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
if debugFlag {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("$ docker %q\n", args)
return cmd.Run()
return run()
} else {
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = buf
err := cmd.Run()
err := run()
if err != nil {
err = cli.NewMultiError(err, fmt.Errorf(`docker %q output:%s`, args, "\n"+buf.String()))
}
Expand Down
Loading

0 comments on commit eeaf85b

Please sign in to comment.