Skip to content

Commit

Permalink
feat(CLI): Support cache (#648)
Browse files Browse the repository at this point in the history
* feat(CLI): Support cache

Signed-off-by: Ce Gao <cegao@tensorchord.ai>

* fix: Fix test cases

Signed-off-by: Ce Gao <cegao@tensorchord.ai>

* Update pkg/builder/builder.go

Co-authored-by: Keming <kemingy94@gmail.com>

Co-authored-by: Keming <kemingy94@gmail.com>
  • Loading branch information
gaocegege and kemingy committed Jul 22, 2022
1 parent 42e7531 commit 6f05072
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 30 deletions.
16 changes: 15 additions & 1 deletion pkg/app/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,18 @@ To build and push the image to a registry:
Usage: "Force rebuild the image",
Value: false,
},
// https://github.com/urfave/cli/issues/1134#issuecomment-1191407527
&cli.StringFlag{
Name: "export-cache",
Usage: "Export the cache (e.g. type=registry,ref=<image>)",
Aliases: []string{"ec"},
},
&cli.StringFlag{
Name: "import-cache",
Usage: "Import the cache (e.g. type=registry,ref=<image>)",
Aliases: []string{"ic"},
},
},

Action: build,
}

Expand Down Expand Up @@ -122,6 +132,8 @@ func build(clicontext *cli.Context) error {
debug := clicontext.Bool("debug")
output := clicontext.String("output")
force := clicontext.Bool("force")
exportCache := clicontext.String("export-cache")
importCache := clicontext.String("import-cache")

opt := builder.Options{
ManifestFilePath: manifest,
Expand All @@ -132,6 +144,8 @@ func build(clicontext *cli.Context) error {
OutputOpts: output,
PubKeyPath: clicontext.Path("public-key"),
ProgressMode: "auto",
ExportCache: exportCache,
ImportCache: importCache,
}
if debug {
opt.ProgressMode = "plain"
Expand Down
15 changes: 15 additions & 0 deletions pkg/app/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ var CommandUp = &cli.Command{
Usage: "Force rebuild and run the container although the previous container is running",
Value: false,
},
// https://github.com/urfave/cli/issues/1134#issuecomment-1191407527
&cli.StringFlag{
Name: "export-cache",
Usage: "Export the cache (e.g. type=registry,ref=<image>)",
Aliases: []string{"ec"},
},
&cli.StringFlag{
Name: "import-cache",
Usage: "Import the cache (e.g. type=registry,ref=<image>)",
Aliases: []string{"ic"},
},
},

Action: up,
Expand Down Expand Up @@ -140,6 +151,8 @@ func up(clicontext *cli.Context) error {
debug := clicontext.Bool("debug")
force := clicontext.Bool("force")
output := ""
exportCache := clicontext.String("export-cache")
importCache := clicontext.String("import-cache")

opt := builder.Options{
ManifestFilePath: manifest,
Expand All @@ -150,6 +163,8 @@ func up(clicontext *cli.Context) error {
OutputOpts: output,
PubKeyPath: clicontext.Path("public-key"),
ProgressMode: "auto",
ExportCache: exportCache,
ImportCache: importCache,
}
if debug {
opt.ProgressMode = "plain"
Expand Down
12 changes: 10 additions & 2 deletions pkg/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,17 @@ func (b generalBuilder) BuildFunc() func(ctx context.Context, c client.Client) (
return nil, errors.Wrap(err, "failed to compile")
}

res, err := c.Solve(ctx, client.SolveRequest{
sreq := client.SolveRequest{
Definition: def.ToPB(),
})
}
if b.Options.ImportCache != "" {
ci, err := ParseImportCache([]string{b.Options.ImportCache})
if err != nil {
return nil, errors.Wrap(err, "failed to get the import cache")
}
sreq.CacheImports = ci
}
res, err := c.Solve(ctx, sreq)
if err != nil {
return nil, err
}
Expand Down
42 changes: 24 additions & 18 deletions pkg/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ type Options struct {
PubKeyPath string
// OutputOpts is the output options.
OutputOpts string
// ExportCache is the option to export cache.
// e.g. type=registry,ref=docker.io/username/image
ExportCache string
// ImportCache is the option to import cache.
// e.g. type=registry,ref=docker.io/username/image
ImportCache string
}

type generalBuilder struct {
Expand Down Expand Up @@ -188,6 +194,11 @@ func (b generalBuilder) build(ctx context.Context, pw progresswriter.Writer) err
if err != nil {
return errors.Wrap(err, "failed to get labels")
}

ce, err := ParseExportCache([]string{b.ExportCache}, nil)
if err != nil {
return errors.Wrap(err, "failed to parse export cache")
}
// k := platforms.Format(platforms.DefaultSpec())
ctx, cancel := context.WithCancel(ctx)
defer cancel()
Expand All @@ -199,6 +210,9 @@ func (b generalBuilder) build(ctx context.Context, pw progresswriter.Writer) err
for _, entry := range b.entries {
// Set up docker config auth.
attachable := []session.Attachable{authprovider.NewDockerAuthProvider(os.Stderr)}
b.logger.WithFields(logrus.Fields{
"type": entry.Type,
}).Debug("build image with buildkit")
switch entry.Type {
// Create default build.
case client.ExporterDocker:
Expand All @@ -217,20 +231,15 @@ func (b generalBuilder) build(ctx context.Context, pw progresswriter.Writer) err
}
}
defer pipeW.Close()
_, err := b.Client.Build(ctx, client.SolveOpt{
Exports: []client.ExportEntry{entry},
solveOpt := client.SolveOpt{
CacheExports: ce,
Exports: []client.ExportEntry{entry},
LocalDirs: map[string]string{
// TODO(gaocegege): Move it to BuildFunc with the help
// of llb.Local
flag.FlagCacheDir: home.GetManager().CacheDir(),
},
Session: attachable,
// TODO(gaocegege): Use llb.WithProxy to implement it.
FrontendAttrs: map[string]string{
"build-arg:HTTPS_PROXY": os.Getenv("HTTPS_PROXY"),
},
}, "envd", b.BuildFunc(), pw.Status())

}
_, err := b.Client.Build(ctx, solveOpt, "envd", b.BuildFunc(), pw.Status())
if err != nil {
err = errors.Wrap(err, "failed to solve LLB")
return err
Expand All @@ -256,18 +265,15 @@ func (b generalBuilder) build(ctx context.Context, pw progresswriter.Writer) err
})
default:
eg.Go(func() error {
_, err := b.Client.Build(ctx, client.SolveOpt{
Exports: []client.ExportEntry{entry},
solveOpt := client.SolveOpt{
CacheExports: ce,
Exports: []client.ExportEntry{entry},
LocalDirs: map[string]string{
flag.FlagCacheDir: home.GetManager().CacheDir(),
},
Session: attachable,
// TODO(gaocegege): Use llb.WithProxy to implement it.
FrontendAttrs: map[string]string{
"build-arg:HTTPS_PROXY": os.Getenv("HTTPS_PROXY"),
},
}, "envd", b.BuildFunc(), pw.Status())

}
_, err := b.Client.Build(ctx, solveOpt, "envd", b.BuildFunc(), pw.Status())
if err != nil {
err = errors.Wrap(err, "failed to solve LLB")
return err
Expand Down
137 changes: 137 additions & 0 deletions pkg/builder/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/containerd/console"
"github.com/containerd/containerd/platforms"
"github.com/moby/buildkit/client"
gatewayclient "github.com/moby/buildkit/frontend/gateway/client"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -76,6 +77,142 @@ func DefaultPathEnv(os string) string {
return DefaultPathEnvUnix
}

func parseImportCacheCSV(s string) (gatewayclient.CacheOptionsEntry, error) {
im := gatewayclient.CacheOptionsEntry{
Type: "",
Attrs: map[string]string{},
}
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return im, err
}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) != 2 {
return im, errors.Errorf("invalid value %s", field)
}
key := strings.ToLower(parts[0])
value := parts[1]
switch key {
case "type":
im.Type = value
default:
im.Attrs[key] = value
}
}
if im.Type == "" {
return im, errors.New("--import-cache requires type=<type>")
}
return im, nil
}

// ParseImportCache parses --import-cache
func ParseImportCache(importCaches []string) ([]gatewayclient.CacheOptionsEntry, error) {
var imports []gatewayclient.CacheOptionsEntry
for _, importCache := range importCaches {
legacy := !strings.Contains(importCache, "type=")
if legacy {
logrus.Warn("--import-cache <ref> is deprecated. Please use --import-cache type=registry,ref=<ref>,<opt>=<optval>[,<opt>=<optval>] instead.")
imports = append(imports, gatewayclient.CacheOptionsEntry{
Type: "registry",
Attrs: map[string]string{"ref": importCache},
})
} else {
im, err := parseImportCacheCSV(importCache)
if err != nil {
return nil, err
}
imports = append(imports, im)
}
}
return imports, nil
}

// ParseExportCache parses --export-cache (and legacy --export-cache-opt)
// Refer to github.com/moby/buildkit/cmd/buildctl/build/exportcache.go
func ParseExportCache(exportCaches, legacyExportCacheOpts []string) ([]client.CacheOptionsEntry, error) {
var exports []client.CacheOptionsEntry
if len(legacyExportCacheOpts) > 0 {
if len(exportCaches) != 1 {
return nil, errors.New("--export-cache-opt requires exactly single --export-cache")
}
}
for _, exportCache := range exportCaches {
legacy := !strings.Contains(exportCache, "type=")
if legacy {
logrus.Warnf("--export-cache <ref> --export-cache-opt <opt>=<optval> is deprecated. Please use --export-cache type=registry,ref=<ref>,<opt>=<optval>[,<opt>=<optval>] instead")
attrs, err := attrMap(legacyExportCacheOpts)
if err != nil {
return nil, err
}
if _, ok := attrs["mode"]; !ok {
attrs["mode"] = "min"
}
attrs["ref"] = exportCache
exports = append(exports, client.CacheOptionsEntry{
Type: "registry",
Attrs: attrs,
})
} else {
if len(legacyExportCacheOpts) > 0 {
return nil, errors.New("--export-cache-opt is not supported for the specified --export-cache. Please use --export-cache type=<type>,<opt>=<optval>[,<opt>=<optval>] instead")
}
ex, err := parseExportCacheCSV(exportCache)
if err != nil {
return nil, err
}
exports = append(exports, ex)
}
}
return exports, nil
}

func attrMap(sl []string) (map[string]string, error) {
m := map[string]string{}
for _, v := range sl {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
return nil, errors.Errorf("invalid value %s", v)
}
m[parts[0]] = parts[1]
}
return m, nil
}

func parseExportCacheCSV(s string) (client.CacheOptionsEntry, error) {
ex := client.CacheOptionsEntry{
Type: "",
Attrs: map[string]string{},
}
csvReader := csv.NewReader(strings.NewReader(s))
fields, err := csvReader.Read()
if err != nil {
return ex, err
}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) != 2 {
return ex, errors.Errorf("invalid value %s", field)
}
key := strings.ToLower(parts[0])
value := parts[1]
switch key {
case "type":
ex.Type = value
default:
ex.Attrs[key] = value
}
}
if ex.Type == "" {
return ex, errors.New("--export-cache requires type=<type>")
}
if _, ok := ex.Attrs["mode"]; !ok {
ex.Attrs["mode"] = "min"
}
return ex, nil
}

// parseOutput parses --output
// Refer to https://github.com/moby/buildkit/blob/master/cmd/buildctl/build/output.go#L56
func parseOutput(exports string) ([]client.ExportEntry, error) {
Expand Down
Loading

0 comments on commit 6f05072

Please sign in to comment.