controller: strongly type the controller api

Strongly typing the API allows us to perform all command line parsing
fully on the client-side, where we have access to the client local
directory and all the client environment variables, which may not be
available on the remote server.

Additionally, the controller api starts to look a lot like
build.Options, so at some point in the future there may be an
oppportunity to merge the two, which would allow both build and bake to
execute through the controller, instead of needing to maintain multiple
code paths.

Signed-off-by: Justin Chadwell <me@jedevc.com>
This commit is contained in:
Justin Chadwell
2023-02-09 12:03:58 +00:00
parent c4ad930e2a
commit 90d7fb5e77
15 changed files with 842 additions and 470 deletions

View File

@ -26,7 +26,6 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
dockeropts "github.com/docker/cli/opts"
"github.com/docker/distribution/reference"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/go-units"
"github.com/moby/buildkit/client"
@ -53,9 +52,9 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
progressMode = "quiet"
}
contexts, err := parseContextNames(in.Contexts)
if err != nil {
return nil, err
contexts := map[string]build.NamedContext{}
for name, path := range in.NamedContexts {
contexts[name] = build.NamedContext{Path: path}
}
printFunc, err := parsePrintFunc(in.PrintFunc)
@ -70,10 +69,10 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
InStream: inStream,
NamedContexts: contexts,
},
BuildArgs: listToMap(in.BuildArgs, true),
BuildArgs: in.BuildArgs,
ExtraHosts: in.ExtraHosts,
ImageIDFile: in.ImageIDFile,
Labels: listToMap(in.Labels, false),
Labels: in.Labels,
NetworkMode: in.NetworkMode,
NoCache: in.Opts.NoCache,
NoCacheFilter: in.NoCacheFilter,
@ -94,7 +93,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig))
secrets, err := buildflags.ParseSecretSpecs(in.Secrets)
secrets, err := controllerapi.CreateSecrets(in.Secrets)
if err != nil {
return nil, err
}
@ -102,15 +101,15 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
sshSpecs := in.SSH
if len(sshSpecs) == 0 && buildflags.IsGitSSH(in.ContextPath) {
sshSpecs = []string{"default"}
sshSpecs = append(sshSpecs, &controllerapi.SSH{ID: "default"})
}
ssh, err := buildflags.ParseSSHSpecs(sshSpecs)
ssh, err := controllerapi.CreateSSH(sshSpecs)
if err != nil {
return nil, err
}
opts.Session = append(opts.Session, ssh)
outputs, err := buildflags.ParseOutputs(in.Outputs)
outputs, err := controllerapi.CreateExports(in.Exports)
if err != nil {
return nil, err
}
@ -162,17 +161,8 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
return nil, err
}
cacheImports, err := buildflags.ParseCacheEntry(in.CacheFrom)
if err != nil {
return nil, err
}
opts.CacheFrom = cacheImports
cacheExports, err := buildflags.ParseCacheEntry(in.CacheTo)
if err != nil {
return nil, err
}
opts.CacheTo = cacheExports
opts.CacheFrom = controllerapi.CreateCaches(in.CacheFrom)
opts.CacheTo = controllerapi.CreateCaches(in.CacheTo)
allow, err := buildflags.ParseEntitlements(in.Allow)
if err != nil {
@ -301,46 +291,6 @@ func printWarnings(w io.Writer, warnings []client.VertexWarning, mode string) {
}
}
func listToMap(values []string, defaultEnv bool) map[string]string {
result := make(map[string]string, len(values))
for _, value := range values {
kv := strings.SplitN(value, "=", 2)
if len(kv) == 1 {
if defaultEnv {
v, ok := os.LookupEnv(kv[0])
if ok {
result[kv[0]] = v
}
} else {
result[kv[0]] = ""
}
} else {
result[kv[0]] = kv[1]
}
}
return result
}
func parseContextNames(values []string) (map[string]build.NamedContext, error) {
if len(values) == 0 {
return nil, nil
}
result := make(map[string]build.NamedContext, len(values))
for _, value := range values {
kv := strings.SplitN(value, "=", 2)
if len(kv) != 2 {
return nil, errors.Errorf("invalid context value: %s, expected key=value", value)
}
named, err := reference.ParseNormalizedNamed(kv[0])
if err != nil {
return nil, errors.Wrapf(err, "invalid context name %s", kv[0])
}
name := strings.TrimSuffix(reference.FamiliarString(named), ":latest")
result[name] = build.NamedContext{Path: kv[1]}
}
return result, nil
}
func parsePrintFunc(str string) (*build.PrintFunc, error) {
if str == "" {
return nil, nil