Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buildkit support with buildx #43

Merged
merged 2 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions cmd/bashbrew/cmd-build.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,27 @@ func cmdBuild(c *cli.Context) error {

// TODO use "meta.StageNames" to do "docker build --target" so we can tag intermediate stages too for cache (streaming "git archive" directly to "docker build" makes that a little hard to accomplish without re-streaming)

var extraEnv []string = nil
if fromScratch {
// https://github.com/docker/cli/blob/v20.10.7/cli/command/image/build.go#L163
extraEnv = []string{"DOCKER_DEFAULT_PLATFORM=" + ociArch.String()}
// ideally, we would set this via an explicit "--platform" flag on "docker build", but it's not supported without buildkit until 20.10+ and this is a trivial way to get Docker to do the right thing in both cases without explicitly trying to detect whether we're on 20.10+
}

err = dockerBuild(cacheTag, entry.ArchFile(arch), archive, extraEnv)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed building %q (tags %q)`, r.RepoName, entry.TagsString()), err)
switch builder := entry.ArchBuilder(arch); builder {
case "classic", "":
var platform string
if fromScratch {
platform = ociArch.String()
}
err = dockerBuild(cacheTag, entry.ArchFile(arch), archive, platform)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed building %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
case "buildkit":
var platform string
if fromScratch {
platform = ociArch.String()
}
err = dockerBuildxBuild(cacheTag, entry.ArchFile(arch), archive, platform)
if err != nil {
return cli.NewMultiError(fmt.Errorf(`failed building %q (tags %q)`, r.RepoName, entry.TagsString()), err)
}
default:
return cli.NewMultiError(fmt.Errorf(`unknown builder %q`, builder))
}
archive.Close() // be sure this happens sooner rather than later (defer might take a while, and we want to reap zombies more aggressively)
}
Expand Down
61 changes: 56 additions & 5 deletions cmd/bashbrew/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
"strconv"
"strings"

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

type dockerfileMetadata struct {
Expand Down Expand Up @@ -218,6 +218,11 @@ func (r Repo) dockerBuildUniqueBits(entry *manifest.Manifest2822Entry) ([]string
entry.ArchDirectory(arch),
entry.ArchFile(arch),
}
if builder := entry.ArchBuilder(arch); builder != "" {
// NOTE: preserve long-term unique id by only attaching builder if
// explicitly specified
uniqueBits = append(uniqueBits, entry.ArchBuilder(arch))
}
meta, err := r.dockerfileMetadata(entry)
if err != nil {
return nil, err
Expand All @@ -237,14 +242,20 @@ func (r Repo) dockerBuildUniqueBits(entry *manifest.Manifest2822Entry) ([]string
return uniqueBits, nil
}

func dockerBuild(tag string, file string, context io.Reader, extraEnv []string) error {
func dockerBuild(tag string, file string, context io.Reader, platform string) error {
args := []string{"build", "--tag", tag, "--file", file, "--rm", "--force-rm"}
args = append(args, "-")
cmd := exec.Command("docker", args...)
if extraEnv != nil {
cmd.Env = append(os.Environ(), extraEnv...)
cmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=0")
if debugFlag {
fmt.Println("$ export DOCKER_BUILDKIT=0")
}
if platform != "" {
// ideally, we would set this via an explicit "--platform" flag on "docker build", but it's not supported without buildkit until 20.10+ and this is a trivial way to get Docker to do the right thing in both cases without explicitly trying to detect whether we're on 20.10+
// https://github.com/docker/cli/blob/v20.10.7/cli/command/image/build.go#L163
cmd.Env = append(cmd.Env, "DOCKER_DEFAULT_PLATFORM="+platform)
if debugFlag {
fmt.Printf("$ export %q\n", extraEnv)
fmt.Printf("$ export DOCKER_DEFAULT_PLATFORM=%q\n", platform)
}
}
cmd.Stdin = context
Expand All @@ -265,6 +276,46 @@ func dockerBuild(tag string, file string, context io.Reader, extraEnv []string)
}
}

const dockerfileSyntaxEnv = "BASHBREW_BUILDKIT_SYNTAX"

func dockerBuildxBuild(tag string, file string, context io.Reader, platform string) error {
dockerfileSyntax, ok := os.LookupEnv(dockerfileSyntaxEnv)
if !ok {
return fmt.Errorf("missing %q", dockerfileSyntaxEnv)
}

args := []string{
"buildx",
"build",
"--progress", "plain",
"--build-arg", "BUILDKIT_SYNTAX=" + dockerfileSyntax,
"--tag", tag,
"--file", file,
}
if platform != "" {
args = append(args, "--platform", platform)
jedevc marked this conversation as resolved.
Show resolved Hide resolved
}
args = append(args, "-")

cmd := exec.Command("docker", args...)
cmd.Stdin = context
if debugFlag {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("$ docker %q\n", args)
return cmd.Run()
} else {
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = buf
err := cmd.Run()
if err != nil {
err = cli.NewMultiError(err, fmt.Errorf(`docker %q output:%s`, args, "\n"+buf.String()))
}
return err
}
}

func dockerTag(tag1 string, tag2 string) error {
if debugFlag {
fmt.Printf("$ docker tag %q %q\n", tag1, tag2)
Expand Down
18 changes: 16 additions & 2 deletions manifest/rfc2822.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Manifest2822Entry struct {
GitCommit string
Directory string
File string
Builder string

// architecture-specific versions of the above fields
ArchValues map[string]string
Expand Down Expand Up @@ -93,7 +94,7 @@ func (entry Manifest2822Entry) Clone() Manifest2822Entry {

func (entry *Manifest2822Entry) SeedArchValues() {
for field, val := range entry.Paragraph.Values {
if strings.HasSuffix(field, "-GitRepo") || strings.HasSuffix(field, "-GitFetch") || strings.HasSuffix(field, "-GitCommit") || strings.HasSuffix(field, "-Directory") || strings.HasSuffix(field, "-File") {
if strings.HasSuffix(field, "-GitRepo") || strings.HasSuffix(field, "-GitFetch") || strings.HasSuffix(field, "-GitCommit") || strings.HasSuffix(field, "-Directory") || strings.HasSuffix(field, "-File") || strings.HasSuffix(field, "-Builder") {
entry.ArchValues[field] = val
}
}
Expand Down Expand Up @@ -142,7 +143,7 @@ func (a Manifest2822Entry) SameBuildArtifacts(b Manifest2822Entry) bool {
}
}

return a.ArchitecturesString() == b.ArchitecturesString() && a.GitRepo == b.GitRepo && a.GitFetch == b.GitFetch && a.GitCommit == b.GitCommit && a.Directory == b.Directory && a.File == b.File && a.ConstraintsString() == b.ConstraintsString()
return a.ArchitecturesString() == b.ArchitecturesString() && a.GitRepo == b.GitRepo && a.GitFetch == b.GitFetch && a.GitCommit == b.GitCommit && a.Directory == b.Directory && a.File == b.File && a.Builder == b.Builder && a.ConstraintsString() == b.ConstraintsString()
}

// returns a list of architecture-specific fields in an Entry
Expand Down Expand Up @@ -187,6 +188,9 @@ func (entry Manifest2822Entry) ClearDefaults(defaults Manifest2822Entry) Manifes
if entry.File == defaults.File {
entry.File = ""
}
if entry.Builder == defaults.Builder {
entry.Builder = ""
}
for _, key := range defaults.archFields() {
if defaults.ArchValues[key] == entry.ArchValues[key] {
delete(entry.ArchValues, key)
Expand Down Expand Up @@ -227,6 +231,9 @@ func (entry Manifest2822Entry) String() string {
if str := entry.File; str != "" {
ret = append(ret, "File: "+str)
}
if str := entry.Builder; str != "" {
ret = append(ret, "Builder: "+str)
}
for _, key := range entry.archFields() {
ret = append(ret, key+": "+entry.ArchValues[key])
}
Expand Down Expand Up @@ -300,6 +307,13 @@ func (entry Manifest2822Entry) ArchFile(arch string) string {
return entry.File
}

func (entry Manifest2822Entry) ArchBuilder(arch string) string {
if val, ok := entry.ArchValues[arch+"-Builder"]; ok && val != "" {
return val
}
return entry.Builder
}

func (entry Manifest2822Entry) HasTag(tag string) bool {
for _, existingTag := range entry.Tags {
if tag == existingTag {
Expand Down