Skip to content

Commit ea5de4c

Browse files
committed
example: add build-using-dockerfile
This command mimics `docker build` CLI flags so that Docker users can more easily get started with BuildKit. Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
1 parent 2822846 commit ea5de4c

File tree

1 file changed

+157
-0
lines changed
  • examples/build-using-dockerfile

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/containerd/console"
13+
"github.com/moby/buildkit/client"
14+
"github.com/moby/buildkit/util/appcontext"
15+
"github.com/moby/buildkit/util/appdefaults"
16+
"github.com/moby/buildkit/util/progress/progressui"
17+
"github.com/pkg/errors"
18+
"github.com/sirupsen/logrus"
19+
"github.com/urfave/cli"
20+
"golang.org/x/sync/errgroup"
21+
)
22+
23+
func main() {
24+
app := cli.NewApp()
25+
app.Name = "build-using-dockerfile"
26+
app.UsageText = `build-using-dockerfile [OPTIONS] PATH | URL | -`
27+
app.Description = `
28+
build using Dockerfile.
29+
30+
This command mimics behavior of "docker build" command so that people can easily get started with BuildKit.
31+
This command is NOT the replacement of "docker build", and should NOT be used for building production images.
32+
33+
By default, the built image is loaded to Docker.
34+
`
35+
dockerIncompatibleFlags := []cli.Flag{
36+
cli.StringFlag{
37+
Name: "buildkit-addr",
38+
Usage: "buildkit daemon address",
39+
EnvVar: "BUILDKIT_HOST",
40+
Value: appdefaults.Address,
41+
},
42+
}
43+
app.Flags = append([]cli.Flag{
44+
cli.StringSliceFlag{
45+
Name: "build-arg",
46+
Usage: "Set build-time variables",
47+
},
48+
cli.StringFlag{
49+
Name: "file, f",
50+
Usage: "Name of the Dockerfile (Default is 'PATH/Dockerfile')",
51+
},
52+
cli.StringFlag{
53+
Name: "tag, t",
54+
Usage: "Name and optionally a tag in the 'name:tag' format",
55+
},
56+
cli.StringFlag{
57+
Name: "target",
58+
Usage: "Set the target build stage to build.",
59+
},
60+
}, dockerIncompatibleFlags...)
61+
app.Action = action
62+
if err := app.Run(os.Args); err != nil {
63+
fmt.Fprintf(os.Stderr, "error: %v\n", err)
64+
os.Exit(1)
65+
}
66+
}
67+
68+
func action(clicontext *cli.Context) error {
69+
if tag := clicontext.String("tag"); tag == "" {
70+
return errors.New("tag is not specified")
71+
}
72+
c, err := client.New(clicontext.String("buildkit-addr"), client.WithBlock())
73+
if err != nil {
74+
return err
75+
}
76+
tmpTar, err := ioutil.TempFile("", "buldkit-build-using-dockerfile")
77+
if err != nil {
78+
return err
79+
}
80+
defer os.Remove(tmpTar.Name())
81+
solveOpt, err := newSolveOpt(clicontext, tmpTar.Name())
82+
if err != nil {
83+
return err
84+
}
85+
ch := make(chan *client.SolveStatus)
86+
eg, ctx := errgroup.WithContext(appcontext.Context())
87+
eg.Go(func() error {
88+
return c.Solve(ctx, nil, *solveOpt, ch)
89+
})
90+
eg.Go(func() error {
91+
if c, err := console.ConsoleFromFile(os.Stderr); err == nil {
92+
// not using shared context to not disrupt display but let is finish reporting errors
93+
return progressui.DisplaySolveStatus(context.TODO(), c, ch)
94+
}
95+
return nil
96+
})
97+
if err := eg.Wait(); err != nil {
98+
return err
99+
}
100+
logrus.Infof("Loading the image to Docker as %q. This may take a while.", clicontext.String("tag"))
101+
if err := loadDockerTar(tmpTar.Name()); err != nil {
102+
return err
103+
}
104+
logrus.Info("Done")
105+
return nil
106+
}
107+
108+
func newSolveOpt(clicontext *cli.Context, tmpTar string) (*client.SolveOpt, error) {
109+
buildCtx := clicontext.Args().First()
110+
if buildCtx == "" {
111+
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
112+
} else if buildCtx == "-" {
113+
return nil, errors.New("stdin not supported yet")
114+
}
115+
116+
file := clicontext.String("file")
117+
if file == "" {
118+
file = filepath.Join(buildCtx, "Dockerfile")
119+
}
120+
localDirs := map[string]string{
121+
"context": buildCtx,
122+
"dockerfile": filepath.Dir(file),
123+
}
124+
125+
frontendAttrs := map[string]string{
126+
"filename": filepath.Base(file),
127+
}
128+
if target := clicontext.String("target"); target != "" {
129+
frontendAttrs["target"] = target
130+
}
131+
132+
for _, buildArg := range clicontext.StringSlice("build-arg") {
133+
kv := strings.SplitN(buildArg, "=", 2)
134+
if len(kv) != 2 {
135+
return nil, errors.Errorf("invalid build-arg value %s", buildArg)
136+
}
137+
frontendAttrs["build-arg:"+kv[0]] = kv[1]
138+
}
139+
return &client.SolveOpt{
140+
Exporter: "docker", // TODO: use containerd image store when it is integrated to Docker
141+
ExporterAttrs: map[string]string{
142+
"name": clicontext.String("tag"),
143+
"output": tmpTar,
144+
},
145+
LocalDirs: localDirs,
146+
Frontend: "dockerfile.v0", // TODO: use gateway
147+
FrontendAttrs: frontendAttrs,
148+
}, nil
149+
}
150+
151+
func loadDockerTar(tar string) error {
152+
// no need to use moby/moby/client here
153+
cmd := exec.Command("docker", "load", "-i", tar)
154+
cmd.Stdout = os.Stdout
155+
cmd.Stderr = os.Stderr
156+
return cmd.Run()
157+
}

0 commit comments

Comments
 (0)