diff --git a/Jenkinsfile b/Jenkinsfile index e644e7318..1b42bf51f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -107,7 +107,7 @@ pipeline { sh 'docker load -i coverage-invocation-image.tar' } ansiColor('xterm') { - sh 'make -f docker.Makefile TAG=$TAG-coverage coverage-run || true' + sh 'make -f docker.Makefile TAG=$TAG-coverage coverage-run' sh 'make -f docker.Makefile TAG=$TAG-coverage coverage-results' } archiveArtifacts '_build/ci-cov/all.out' diff --git a/Jenkinsfile.baguette b/Jenkinsfile.baguette index b00e4ae3d..05217094c 100644 --- a/Jenkinsfile.baguette +++ b/Jenkinsfile.baguette @@ -129,7 +129,7 @@ pipeline { sh 'docker load -i coverage-invocation-image.tar' } ansiColor('xterm') { - sh 'make -f docker.Makefile TAG=$TAG-coverage coverage-run || true' + sh 'make -f docker.Makefile TAG=$TAG-coverage coverage-run' sh 'make -f docker.Makefile TAG=$TAG-coverage coverage-results' } archiveArtifacts '_build/ci-cov/all.out' diff --git a/e2e/commands_test.go b/e2e/commands_test.go index 8e313dc0d..0a8b98173 100644 --- a/e2e/commands_test.go +++ b/e2e/commands_test.go @@ -158,11 +158,11 @@ func TestInspectApp(t *testing.T) { fs.WithDir("attachments.dockerapp", fs.FromDir("testdata/attachments.dockerapp"))) defer dir.Remove() - cmd.Command = dockerCli.Command("app", "inspect") + cmd.Command = dockerCli.Command("app", "image", "inspect") cmd.Dir = dir.Path() icmd.RunCmd(cmd).Assert(t, icmd.Expected{ ExitCode: 1, - Err: `"docker app inspect" requires exactly 1 argument.`, + Err: `"docker app image inspect" requires exactly 1 argument.`, }) contextPath := filepath.Join("testdata", "simple") @@ -170,7 +170,7 @@ func TestInspectApp(t *testing.T) { cmd.Dir = "" icmd.RunCmd(cmd).Assert(t, icmd.Success) - cmd.Command = dockerCli.Command("app", "inspect", "simple-app:1.0.0") + cmd.Command = dockerCli.Command("app", "image", "inspect", "simple-app:1.0.0") cmd.Dir = dir.Path() output := icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined() golden.Assert(t, output, "app-inspect.golden") diff --git a/e2e/testdata/plugin-usage.golden b/e2e/testdata/plugin-usage.golden index f10669c91..89c536426 100644 --- a/e2e/testdata/plugin-usage.golden +++ b/e2e/testdata/plugin-usage.golden @@ -12,7 +12,6 @@ Management Commands: Commands: build Build service images for the application init Initialize Docker Application definition - inspect Shows metadata, parameters and a summary of the Compose file for a given application ls List the installations and their last known installation result pull Pull an application package from a registry push Push an application package to a registry diff --git a/internal/cnab/cnab.go b/internal/cnab/cnab.go new file mode 100644 index 000000000..727fc9a12 --- /dev/null +++ b/internal/cnab/cnab.go @@ -0,0 +1,138 @@ +package cnab + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/deislabs/cnab-go/bundle" + "github.com/docker/app/internal" + "github.com/docker/app/internal/log" + "github.com/docker/app/internal/packager" + "github.com/docker/app/internal/store" + appstore "github.com/docker/app/internal/store" + "github.com/docker/cli/cli/command" + "github.com/docker/cnab-to-oci/remotes" + "github.com/docker/distribution/reference" +) + +type nameKind uint + +const ( + _ nameKind = iota + nameKindEmpty + nameKindFile + nameKindDir + nameKindReference +) + +func getAppNameKind(name string) (string, nameKind) { + if name == "" { + return name, nameKindEmpty + } + // name can be a bundle.json or bundle.cnab file, or a dockerapp directory + st, err := os.Stat(name) + if os.IsNotExist(err) { + // try with .dockerapp extension + st, err = os.Stat(name + internal.AppExtension) + if err == nil { + name += internal.AppExtension + } + } + if err != nil { + return name, nameKindReference + } + if st.IsDir() { + return name, nameKindDir + } + return name, nameKindFile +} + +func extractAndLoadAppBasedBundle(dockerCli command.Cli, name string) (*bundle.Bundle, string, error) { + app, err := packager.Extract(name) + if err != nil { + return nil, "", err + } + defer app.Cleanup() + bndl, err := packager.MakeBundleFromApp(dockerCli, app, nil) + return bndl, "", err +} + +func loadBundleFromFile(filename string) (*bundle.Bundle, error) { + b := &bundle.Bundle{} + data, err := ioutil.ReadFile(filename) + if err != nil { + return b, err + } + return bundle.Unmarshal(data) +} + +// ResolveBundle looks for a CNAB bundle which can be in a Docker App Package format or +// a bundle stored locally or in the bundle store. It returns a built or found bundle, +// a reference to the bundle if it is found in the bundlestore, and an error. +func ResolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string) (*bundle.Bundle, string, error) { + // resolution logic: + // - if there is a docker-app package in working directory, or an http:// / https:// prefix, use packager.Extract result + // - the name has a .json or .cnab extension and refers to an existing file or web resource: load the bundle + // - name matches a bundle name:version stored in the bundle store: use it + // - pull the bundle from the registry and add it to the bundle store + name, kind := getAppNameKind(name) + switch kind { + case nameKindFile: + if strings.HasSuffix(name, internal.AppExtension) { + return extractAndLoadAppBasedBundle(dockerCli, name) + } + bndl, err := loadBundleFromFile(name) + return bndl, "", err + case nameKindDir, nameKindEmpty: + return extractAndLoadAppBasedBundle(dockerCli, name) + case nameKindReference: + bndl, tagRef, err := GetBundle(dockerCli, bundleStore, name) + if err != nil { + return nil, "", err + } + return bndl, tagRef.String(), err + } + return nil, "", fmt.Errorf("could not resolve bundle %q", name) +} + +// GetBundle searches for the bundle locally and tries to pull it if not found +func GetBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string) (*bundle.Bundle, reference.Reference, error) { + ref, err := store.StringToRef(name) + if err != nil { + return nil, nil, err + } + bndl, err := bundleStore.Read(ref) + if err != nil { + fmt.Fprintf(dockerCli.Err(), "Unable to find application image %q locally\n", reference.FamiliarString(ref)) + + fmt.Fprintf(dockerCli.Out(), "Pulling from registry...\n") + if named, ok := ref.(reference.Named); ok { + bndl, err = PullBundle(dockerCli, bundleStore, named) + if err != nil { + return nil, nil, err + } + } + } + + return bndl, ref, nil +} + +// PullBundle pulls the bundle and stores it into the bundle store +func PullBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, tagRef reference.Named) (*bundle.Bundle, error) { + insecureRegistries, err := internal.InsecureRegistriesFromEngine(dockerCli) + if err != nil { + return nil, fmt.Errorf("could not retrieve insecure registries: %v", err) + } + + bndl, err := remotes.Pull(log.WithLogContext(context.Background()), reference.TagNameOnly(tagRef), remotes.CreateResolver(dockerCli.ConfigFile(), insecureRegistries...)) + if err != nil { + return nil, err + } + if _, err := bundleStore.Store(tagRef, bndl); err != nil { + return nil, err + } + return bndl, nil +} diff --git a/internal/cnab/driver.go b/internal/cnab/driver.go new file mode 100644 index 000000000..518468e65 --- /dev/null +++ b/internal/cnab/driver.go @@ -0,0 +1,115 @@ +package cnab + +import ( + "bytes" + "io" + "os" + "strings" + + "github.com/deislabs/cnab-go/claim" + "github.com/deislabs/cnab-go/driver" + dockerDriver "github.com/deislabs/cnab-go/driver/docker" + "github.com/docker/app/internal" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/context/docker" + "github.com/docker/cli/cli/context/store" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" +) + +// BindMount +type BindMount struct { + required bool + endpoint string +} + +const defaultSocketPath string = "/var/run/docker.sock" + +func RequiredClaimBindMount(c claim.Claim, targetContextName string, dockerCli command.Cli) (BindMount, error) { + var specifiedOrchestrator string + if rawOrchestrator, ok := c.Parameters[internal.ParameterOrchestratorName]; ok { + specifiedOrchestrator = rawOrchestrator.(string) + } + + return RequiredBindMount(targetContextName, specifiedOrchestrator, dockerCli.ContextStore()) +} + +// RequiredBindMount Returns the path required to bind mount when running +// the invocation image. +func RequiredBindMount(targetContextName string, targetOrchestrator string, s store.Store) (BindMount, error) { + if targetOrchestrator == "kubernetes" { + return BindMount{}, nil + } + + if targetContextName == "" { + targetContextName = "default" + } + + // in case of docker desktop, we want to rewrite the context in cases where it targets the local swarm or Kubernetes + s = &internal.DockerDesktopAwareStore{Store: s} + + ctxMeta, err := s.GetMetadata(targetContextName) + if err != nil { + return BindMount{}, err + } + dockerCtx, err := command.GetDockerContext(ctxMeta) + if err != nil { + return BindMount{}, err + } + if dockerCtx.StackOrchestrator == command.OrchestratorKubernetes { + return BindMount{}, nil + } + dockerEndpoint, err := docker.EndpointFromContext(ctxMeta) + if err != nil { + return BindMount{}, err + } + + host := dockerEndpoint.Host + return BindMount{isDockerHostLocal(host), socketPath(host)}, nil +} + +func socketPath(host string) string { + if strings.HasPrefix(host, "unix://") { + return strings.TrimPrefix(host, "unix://") + } + + return defaultSocketPath +} + +func isDockerHostLocal(host string) bool { + return host == "" || strings.HasPrefix(host, "unix://") || strings.HasPrefix(host, "npipe://") +} + +// PrepareDriver prepares a driver per the user's request. +func PrepareDriver(dockerCli command.Cli, bindMount BindMount, stdout io.Writer) (driver.Driver, *bytes.Buffer) { + d := &dockerDriver.Driver{} + errBuf := bytes.NewBuffer(nil) + d.SetDockerCli(dockerCli) + if stdout != nil { + d.SetContainerOut(stdout) + } + d.SetContainerErr(errBuf) + if bindMount.required { + d.AddConfigurationOptions(func(config *container.Config, hostConfig *container.HostConfig) error { + config.User = "0:0" + mounts := []mount.Mount{ + { + Type: mount.TypeBind, + Source: bindMount.endpoint, + Target: bindMount.endpoint, + }, + } + hostConfig.Mounts = mounts + return nil + }) + } + + // Load any driver-specific config out of the environment. + driverCfg := map[string]string{} + for env := range d.Config() { + driverCfg[env] = os.Getenv(env) + } + d.SetConfig(driverCfg) + + return d, errBuf +} diff --git a/internal/cnab/driver_test.go b/internal/cnab/driver_test.go new file mode 100644 index 000000000..dea58da81 --- /dev/null +++ b/internal/cnab/driver_test.go @@ -0,0 +1,125 @@ +package cnab + +import ( + "testing" + + "github.com/docker/cli/cli/command" + cliflags "github.com/docker/cli/cli/flags" + "gotest.tools/assert" +) + +func TestRequiresBindMount(t *testing.T) { + dockerCli, err := command.NewDockerCli() + assert.NilError(t, err) + err = dockerCli.Initialize(cliflags.NewClientOptions()) + assert.NilError(t, err) + + testCases := []struct { + name string + targetContextName string + targetOrchestrator string + expectedRequired bool + expectedEndpoint string + expectedError string + }{ + { + name: "kubernetes-orchestrator", + targetContextName: "target-context", + targetOrchestrator: "kubernetes", + expectedRequired: false, + expectedEndpoint: "", + expectedError: "", + }, + { + name: "no-context", + targetContextName: "", + targetOrchestrator: "swarm", + expectedRequired: true, + expectedEndpoint: "/var/run/docker.sock", + expectedError: "", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result, err := RequiredBindMount(testCase.targetContextName, testCase.targetOrchestrator, dockerCli.ContextStore()) + if testCase.expectedError == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, testCase.expectedError) + } + assert.Equal(t, testCase.expectedRequired, result.required) + assert.Equal(t, testCase.expectedEndpoint, result.endpoint) + }) + } +} + +func TestIsDockerHostLocal(t *testing.T) { + testCases := []struct { + name string + host string + expected bool + }{ + { + name: "not-local", + host: "tcp://not.local.host", + expected: false, + }, + { + name: "no-endpoint", + host: "", + expected: true, + }, + { + name: "docker-sock", + host: "unix:///var/run/docker.sock", + expected: true, + }, + { + name: "named-pipe", + host: "npipe:////./pipe/docker_engine", + expected: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expected, isDockerHostLocal(testCase.host)) + }) + } +} + +func TestSocketPath(t *testing.T) { + testCases := []struct { + name string + host string + expected string + }{ + { + name: "unixSocket", + host: "unix:///my/socket.sock", + expected: "/my/socket.sock", + }, + { + name: "namedPipe", + host: "npipe:////./docker", + expected: "/var/run/docker.sock", + }, + { + name: "emptyHost", + host: "", + expected: "/var/run/docker.sock", + }, + { + name: "tcpHost", + host: "tcp://my/tcp/endpoint", + expected: "/var/run/docker.sock", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expected, socketPath(testCase.host)) + }) + } +} diff --git a/internal/commands/cnab.go b/internal/commands/cnab.go deleted file mode 100644 index 68271d10e..000000000 --- a/internal/commands/cnab.go +++ /dev/null @@ -1,414 +0,0 @@ -package commands - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "strings" - - "github.com/deislabs/cnab-go/action" - "github.com/deislabs/cnab-go/bundle" - "github.com/deislabs/cnab-go/claim" - "github.com/deislabs/cnab-go/credentials" - "github.com/deislabs/cnab-go/driver" - dockerDriver "github.com/deislabs/cnab-go/driver/docker" - "github.com/docker/app/internal" - "github.com/docker/app/internal/commands/image" - "github.com/docker/app/internal/log" - "github.com/docker/app/internal/packager" - appstore "github.com/docker/app/internal/store" - "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/config" - "github.com/docker/cli/cli/context/docker" - "github.com/docker/cli/cli/context/store" - contextstore "github.com/docker/cli/cli/context/store" - "github.com/docker/cnab-to-oci/remotes" - "github.com/docker/distribution/reference" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/registry" - "github.com/pkg/errors" -) - -type bindMount struct { - required bool - endpoint string -} - -const defaultSocketPath string = "/var/run/docker.sock" - -type credentialSetOpt func(b *bundle.Bundle, creds credentials.Set) error - -func addNamedCredentialSets(credStore appstore.CredentialStore, namedCredentialsets []string) credentialSetOpt { - return func(_ *bundle.Bundle, creds credentials.Set) error { - for _, file := range namedCredentialsets { - var ( - c *credentials.CredentialSet - err error - ) - // Check the credentialset locally first, then try in the credential store - if _, e := os.Stat(file); e == nil { - c, err = credentials.Load(file) - } else { - c, err = credStore.Read(file) - if os.IsNotExist(err) { - err = e - } - } - if err != nil { - return err - } - values, err := c.Resolve() - if err != nil { - return err - } - if err := creds.Merge(values); err != nil { - return err - } - } - return nil - } -} - -func parseCommandlineCredential(c string) (string, string, error) { - split := strings.SplitN(c, "=", 2) - if len(split) != 2 || split[0] == "" { - return "", "", errors.Errorf("failed to parse %q as a credential name=value", c) - } - name := split[0] - value := split[1] - return name, value, nil -} - -func addCredentials(strcreds []string) credentialSetOpt { - return func(_ *bundle.Bundle, creds credentials.Set) error { - for _, c := range strcreds { - name, value, err := parseCommandlineCredential(c) - if err != nil { - return err - } - if err := creds.Merge(credentials.Set{ - name: value, - }); err != nil { - return err - } - } - return nil - } -} - -func addDockerCredentials(contextName string, store contextstore.Store) credentialSetOpt { - // docker desktop contexts require some rewriting for being used within a container - store = dockerDesktopAwareStore{Store: store} - return func(_ *bundle.Bundle, creds credentials.Set) error { - if contextName != "" { - data, err := ioutil.ReadAll(contextstore.Export(contextName, store)) - if err != nil { - return err - } - creds[internal.CredentialDockerContextName] = string(data) - } - return nil - } -} - -func addRegistryCredentials(shouldPopulate bool, dockerCli command.Cli) credentialSetOpt { - return func(b *bundle.Bundle, creds credentials.Set) error { - if _, ok := b.Credentials[internal.CredentialRegistryName]; !ok { - return nil - } - - registryCreds := map[string]types.AuthConfig{} - if shouldPopulate { - for _, img := range b.Images { - named, err := reference.ParseNormalizedNamed(img.Image) - if err != nil { - return err - } - info, err := registry.ParseRepositoryInfo(named) - if err != nil { - return err - } - key := registry.GetAuthConfigKey(info.Index) - if _, ok := registryCreds[key]; !ok { - registryCreds[key] = command.ResolveAuthConfig(context.Background(), dockerCli, info.Index) - } - } - } - registryCredsJSON, err := json.Marshal(registryCreds) - if err != nil { - return err - } - creds[internal.CredentialRegistryName] = string(registryCredsJSON) - return nil - } -} - -func prepareCredentialSet(b *bundle.Bundle, opts ...credentialSetOpt) (map[string]string, error) { - creds := map[string]string{} - for _, op := range opts { - if err := op(b, creds); err != nil { - return nil, err - } - } - - _, requiresDockerContext := b.Credentials[internal.CredentialDockerContextName] - _, hasDockerContext := creds[internal.CredentialDockerContextName] - if requiresDockerContext && !hasDockerContext { - return nil, errors.New("no target context specified. Use --target-context= or DOCKER_TARGET_CONTEXT= to define it") - } - - return creds, nil -} - -func getTargetContext(optstargetContext, currentContext string) string { - var targetContext string - switch { - case optstargetContext != "": - targetContext = optstargetContext - case os.Getenv("DOCKER_TARGET_CONTEXT") != "": - targetContext = os.Getenv("DOCKER_TARGET_CONTEXT") - } - if targetContext == "" { - targetContext = currentContext - } - return targetContext -} - -// prepareDriver prepares a driver per the user's request. -func prepareDriver(dockerCli command.Cli, bindMount bindMount, stdout io.Writer) (driver.Driver, *bytes.Buffer) { - d := &dockerDriver.Driver{} - errBuf := bytes.NewBuffer(nil) - d.SetDockerCli(dockerCli) - if stdout != nil { - d.SetContainerOut(stdout) - } - d.SetContainerErr(errBuf) - if bindMount.required { - d.AddConfigurationOptions(func(config *container.Config, hostConfig *container.HostConfig) error { - config.User = "0:0" - mounts := []mount.Mount{ - { - Type: mount.TypeBind, - Source: bindMount.endpoint, - Target: bindMount.endpoint, - }, - } - hostConfig.Mounts = mounts - return nil - }) - } - - // Load any driver-specific config out of the environment. - driverCfg := map[string]string{} - for env := range d.Config() { - driverCfg[env] = os.Getenv(env) - } - d.SetConfig(driverCfg) - - return d, errBuf -} - -func getAppNameKind(name string) (string, nameKind) { - if name == "" { - return name, nameKindEmpty - } - // name can be a bundle.json or bundle.cnab file, or a dockerapp directory - st, err := os.Stat(name) - if os.IsNotExist(err) { - // try with .dockerapp extension - st, err = os.Stat(name + internal.AppExtension) - if err == nil { - name += internal.AppExtension - } - } - if err != nil { - return name, nameKindReference - } - if st.IsDir() { - return name, nameKindDir - } - return name, nameKindFile -} - -func extractAndLoadAppBasedBundle(dockerCli command.Cli, name string) (*bundle.Bundle, string, error) { - app, err := packager.Extract(name) - if err != nil { - return nil, "", err - } - defer app.Cleanup() - bndl, err := packager.MakeBundleFromApp(dockerCli, app, nil) - return bndl, "", err -} - -func loadBundleFromFile(filename string) (*bundle.Bundle, error) { - b := &bundle.Bundle{} - data, err := ioutil.ReadFile(filename) - if err != nil { - return b, err - } - return bundle.Unmarshal(data) -} - -//resolveBundle looks for a CNAB bundle which can be in a Docker App Package format or -// a bundle stored locally or in the bundle store. It returns a built or found bundle, -// a reference to the bundle if it is found in the bundlestore, and an error. -func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string) (*bundle.Bundle, string, error) { - // resolution logic: - // - if there is a docker-app package in working directory, or an http:// / https:// prefix, use packager.Extract result - // - the name has a .json or .cnab extension and refers to an existing file or web resource: load the bundle - // - name matches a bundle name:version stored in the bundle store: use it - // - pull the bundle from the registry and add it to the bundle store - name, kind := getAppNameKind(name) - switch kind { - case nameKindFile: - if strings.HasSuffix(name, internal.AppExtension) { - return extractAndLoadAppBasedBundle(dockerCli, name) - } - bndl, err := loadBundleFromFile(name) - return bndl, "", err - case nameKindDir, nameKindEmpty: - return extractAndLoadAppBasedBundle(dockerCli, name) - case nameKindReference: - bndl, tagRef, err := getBundle(dockerCli, bundleStore, name) - if err != nil { - return nil, "", err - } - return bndl, tagRef.String(), err - } - return nil, "", fmt.Errorf("could not resolve bundle %q", name) -} - -func getBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string) (*bundle.Bundle, reference.Reference, error) { - ref, err := image.StringToRef(name) - if err != nil { - return nil, nil, err - } - bndl, err := bundleStore.Read(ref) - if err != nil { - fmt.Fprintf(dockerCli.Err(), "Unable to find application image %q locally\n", reference.FamiliarString(ref)) - - fmt.Fprintf(dockerCli.Out(), "Pulling from registry...\n") - if named, ok := ref.(reference.Named); ok { - bndl, err = pullBundle(dockerCli, bundleStore, named) - if err != nil { - return nil, nil, err - } - } - } - - return bndl, ref, nil -} - -func pullBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, tagRef reference.Named) (*bundle.Bundle, error) { - insecureRegistries, err := insecureRegistriesFromEngine(dockerCli) - if err != nil { - return nil, fmt.Errorf("could not retrieve insecure registries: %v", err) - } - - bndl, err := remotes.Pull(log.WithLogContext(context.Background()), reference.TagNameOnly(tagRef), remotes.CreateResolver(dockerCli.ConfigFile(), insecureRegistries...)) - if err != nil { - return nil, err - } - if _, err := bundleStore.Store(tagRef, bndl); err != nil { - return nil, err - } - return bndl, nil -} - -func requiredClaimBindMount(c claim.Claim, targetContextName string, dockerCli command.Cli) (bindMount, error) { - var specifiedOrchestrator string - if rawOrchestrator, ok := c.Parameters[internal.ParameterOrchestratorName]; ok { - specifiedOrchestrator = rawOrchestrator.(string) - } - - return requiredBindMount(targetContextName, specifiedOrchestrator, dockerCli.ContextStore()) -} - -func requiredBindMount(targetContextName string, targetOrchestrator string, s store.Store) (bindMount, error) { - if targetOrchestrator == "kubernetes" { - return bindMount{}, nil - } - - if targetContextName == "" { - targetContextName = "default" - } - - // in case of docker desktop, we want to rewrite the context in cases where it targets the local swarm or Kubernetes - s = &dockerDesktopAwareStore{Store: s} - - ctxMeta, err := s.GetMetadata(targetContextName) - if err != nil { - return bindMount{}, err - } - dockerCtx, err := command.GetDockerContext(ctxMeta) - if err != nil { - return bindMount{}, err - } - if dockerCtx.StackOrchestrator == command.OrchestratorKubernetes { - return bindMount{}, nil - } - dockerEndpoint, err := docker.EndpointFromContext(ctxMeta) - if err != nil { - return bindMount{}, err - } - - host := dockerEndpoint.Host - return bindMount{isDockerHostLocal(host), socketPath(host)}, nil -} - -func socketPath(host string) string { - if strings.HasPrefix(host, "unix://") { - return strings.TrimPrefix(host, "unix://") - } - - return defaultSocketPath -} - -func isDockerHostLocal(host string) bool { - return host == "" || strings.HasPrefix(host, "unix://") || strings.HasPrefix(host, "npipe://") -} - -func prepareCustomAction(actionName string, dockerCli command.Cli, appname string, stdout io.Writer, paramsOpts parametersOptions) (*action.RunCustom, *appstore.Installation, *bytes.Buffer, error) { - s, err := appstore.NewApplicationStore(config.Dir()) - if err != nil { - return nil, nil, nil, err - } - bundleStore, err := s.BundleStore() - if err != nil { - return nil, nil, nil, err - } - bundle, ref, err := resolveBundle(dockerCli, bundleStore, appname) - if err != nil { - return nil, nil, nil, err - } - installation, err := appstore.NewInstallation("custom-action", ref) - if err != nil { - return nil, nil, nil, err - } - installation.Bundle = bundle - - if err := mergeBundleParameters(installation, - withFileParameters(paramsOpts.parametersFiles), - withCommandLineParameters(paramsOpts.overrides), - ); err != nil { - return nil, nil, nil, err - } - - driverImpl, errBuf := prepareDriver(dockerCli, bindMount{}, stdout) - a := &action.RunCustom{ - Action: actionName, - Driver: driverImpl, - } - return a, installation, errBuf, nil -} - -func isInstallationFailed(installation *appstore.Installation) bool { - return installation.Result.Action == claim.ActionInstall && - installation.Result.Status == claim.StatusFailure -} diff --git a/internal/commands/credentials.go b/internal/commands/credentials.go new file mode 100644 index 000000000..6831efe25 --- /dev/null +++ b/internal/commands/credentials.go @@ -0,0 +1,144 @@ +package commands + +import ( + "context" + "encoding/json" + "io/ioutil" + "os" + "strings" + + "github.com/deislabs/cnab-go/bundle" + "github.com/deislabs/cnab-go/credentials" + "github.com/docker/app/internal" + appstore "github.com/docker/app/internal/store" + "github.com/docker/cli/cli/command" + contextstore "github.com/docker/cli/cli/context/store" + "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" + "github.com/docker/docker/registry" + "github.com/pkg/errors" +) + +type credentialSetOpt func(b *bundle.Bundle, creds credentials.Set) error + +func addNamedCredentialSets(credStore appstore.CredentialStore, namedCredentialsets []string) credentialSetOpt { + return func(_ *bundle.Bundle, creds credentials.Set) error { + for _, file := range namedCredentialsets { + var ( + c *credentials.CredentialSet + err error + ) + // Check the credentialset locally first, then try in the credential store + if _, e := os.Stat(file); e == nil { + c, err = credentials.Load(file) + } else { + c, err = credStore.Read(file) + if os.IsNotExist(err) { + err = e + } + } + if err != nil { + return err + } + values, err := c.Resolve() + if err != nil { + return err + } + if err := creds.Merge(values); err != nil { + return err + } + } + return nil + } +} + +func parseCommandlineCredential(c string) (string, string, error) { + split := strings.SplitN(c, "=", 2) + if len(split) != 2 || split[0] == "" { + return "", "", errors.Errorf("failed to parse %q as a credential name=value", c) + } + name := split[0] + value := split[1] + return name, value, nil +} + +func addCredentials(strcreds []string) credentialSetOpt { + return func(_ *bundle.Bundle, creds credentials.Set) error { + for _, c := range strcreds { + name, value, err := parseCommandlineCredential(c) + if err != nil { + return err + } + if err := creds.Merge(credentials.Set{ + name: value, + }); err != nil { + return err + } + } + return nil + } +} + +func addDockerCredentials(contextName string, store contextstore.Store) credentialSetOpt { + // docker desktop contexts require some rewriting for being used within a container + store = internal.DockerDesktopAwareStore{Store: store} + return func(_ *bundle.Bundle, creds credentials.Set) error { + if contextName != "" { + data, err := ioutil.ReadAll(contextstore.Export(contextName, store)) + if err != nil { + return err + } + creds[internal.CredentialDockerContextName] = string(data) + } + return nil + } +} + +func addRegistryCredentials(shouldPopulate bool, dockerCli command.Cli) credentialSetOpt { + return func(b *bundle.Bundle, creds credentials.Set) error { + if _, ok := b.Credentials[internal.CredentialRegistryName]; !ok { + return nil + } + + registryCreds := map[string]types.AuthConfig{} + if shouldPopulate { + for _, img := range b.Images { + named, err := reference.ParseNormalizedNamed(img.Image) + if err != nil { + return err + } + info, err := registry.ParseRepositoryInfo(named) + if err != nil { + return err + } + key := registry.GetAuthConfigKey(info.Index) + if _, ok := registryCreds[key]; !ok { + registryCreds[key] = command.ResolveAuthConfig(context.Background(), dockerCli, info.Index) + } + } + } + registryCredsJSON, err := json.Marshal(registryCreds) + if err != nil { + return err + } + creds[internal.CredentialRegistryName] = string(registryCredsJSON) + return nil + } +} + +func prepareCredentialSet(b *bundle.Bundle, opts ...credentialSetOpt) (map[string]string, error) { + creds := map[string]string{} + for _, op := range opts { + if err := op(b, creds); err != nil { + return nil, err + } + } + + _, requiresDockerContext := b.Credentials[internal.CredentialDockerContextName] + _, hasDockerContext := creds[internal.CredentialDockerContextName] + if requiresDockerContext && !hasDockerContext { + return nil, errors.New("no target context specified. Use --target-context= or DOCKER_TARGET_CONTEXT= to define it") + } + + return creds, nil +} diff --git a/internal/commands/cnab_test.go b/internal/commands/credentials_test.go similarity index 56% rename from internal/commands/cnab_test.go rename to internal/commands/credentials_test.go index 2091956a9..30f970447 100644 --- a/internal/commands/cnab_test.go +++ b/internal/commands/credentials_test.go @@ -9,126 +9,9 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" - cliflags "github.com/docker/cli/cli/flags" "gotest.tools/assert" ) -func TestRequiresBindMount(t *testing.T) { - dockerCli, err := command.NewDockerCli() - assert.NilError(t, err) - err = dockerCli.Initialize(cliflags.NewClientOptions()) - assert.NilError(t, err) - - testCases := []struct { - name string - targetContextName string - targetOrchestrator string - expectedRequired bool - expectedEndpoint string - expectedError string - }{ - { - name: "kubernetes-orchestrator", - targetContextName: "target-context", - targetOrchestrator: "kubernetes", - expectedRequired: false, - expectedEndpoint: "", - expectedError: "", - }, - { - name: "no-context", - targetContextName: "", - targetOrchestrator: "swarm", - expectedRequired: true, - expectedEndpoint: "/var/run/docker.sock", - expectedError: "", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result, err := requiredBindMount(testCase.targetContextName, testCase.targetOrchestrator, dockerCli.ContextStore()) - if testCase.expectedError == "" { - assert.NilError(t, err) - } else { - assert.Error(t, err, testCase.expectedError) - } - assert.Equal(t, testCase.expectedRequired, result.required) - assert.Equal(t, testCase.expectedEndpoint, result.endpoint) - }) - } -} - -func TestIsDockerHostLocal(t *testing.T) { - testCases := []struct { - name string - host string - expected bool - }{ - { - name: "not-local", - host: "tcp://not.local.host", - expected: false, - }, - { - name: "no-endpoint", - host: "", - expected: true, - }, - { - name: "docker-sock", - host: "unix:///var/run/docker.sock", - expected: true, - }, - { - name: "named-pipe", - host: "npipe:////./pipe/docker_engine", - expected: true, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - assert.Equal(t, testCase.expected, isDockerHostLocal(testCase.host)) - }) - } -} - -func TestSocketPath(t *testing.T) { - testCases := []struct { - name string - host string - expected string - }{ - { - name: "unixSocket", - host: "unix:///my/socket.sock", - expected: "/my/socket.sock", - }, - { - name: "namedPipe", - host: "npipe:////./docker", - expected: "/var/run/docker.sock", - }, - { - name: "emptyHost", - host: "", - expected: "/var/run/docker.sock", - }, - { - name: "tcpHost", - host: "tcp://my/tcp/endpoint", - expected: "/var/run/docker.sock", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - assert.Equal(t, testCase.expected, socketPath(testCase.host)) - }) - } -} - type registryConfigMock struct { command.Cli configFile *configfile.ConfigFile diff --git a/internal/commands/image/command.go b/internal/commands/image/command.go index 02a4d18c8..c6e0c30ff 100644 --- a/internal/commands/image/command.go +++ b/internal/commands/image/command.go @@ -16,6 +16,7 @@ func Cmd(dockerCli command.Cli) *cobra.Command { listCmd(dockerCli), rmCmd(), tagCmd(), + inspectCmd(dockerCli), ) return cmd diff --git a/internal/commands/inspect.go b/internal/commands/image/inspect.go similarity index 72% rename from internal/commands/inspect.go rename to internal/commands/image/inspect.go index 3913b20cc..8dd7c4453 100644 --- a/internal/commands/inspect.go +++ b/internal/commands/image/inspect.go @@ -1,10 +1,12 @@ -package commands +package image import ( "fmt" + "io/ioutil" "github.com/deislabs/cnab-go/action" "github.com/docker/app/internal" + "github.com/docker/app/internal/cnab" appstore "github.com/docker/app/internal/store" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -16,6 +18,15 @@ type inspectOptions struct { pretty bool } +func muteDockerCli(dockerCli command.Cli) func() { + stdout := dockerCli.Out() + stderr := dockerCli.Err() + dockerCli.Apply(command.WithCombinedStreams(ioutil.Discard)) //nolint:errcheck // WithCombinedStreams cannot error + return func() { + dockerCli.Apply(command.WithOutputStream(stdout), command.WithErrorStream(stderr)) //nolint:errcheck // as above + } +} + func inspectCmd(dockerCli command.Cli) *cobra.Command { var opts inspectOptions cmd := &cobra.Command{ @@ -25,7 +36,7 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command { $docker app inspect my-app:1.0.0`, Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runInspect(dockerCli, firstOrEmpty(args), opts) + return runInspect(dockerCli, args[0], opts) }, } cmd.Flags().BoolVar(&opts.pretty, "pretty", false, "Pretty print the output") @@ -43,7 +54,7 @@ func runInspect(dockerCli command.Cli, appname string, opts inspectOptions) erro if err != nil { return err } - bndl, ref, err := getBundle(dockerCli, bundleStore, appname) + bndl, ref, err := cnab.GetBundle(dockerCli, bundleStore, appname) if err != nil { return err @@ -54,7 +65,7 @@ func runInspect(dockerCli command.Cli, appname string, opts inspectOptions) erro } installation.Bundle = bndl - driverImpl, errBuf := prepareDriver(dockerCli, bindMount{}, nil) + driverImpl, errBuf := cnab.PrepareDriver(dockerCli, cnab.BindMount{}, nil) a := &action.RunCustom{ Action: internal.ActionInspectName, Driver: driverImpl, diff --git a/internal/commands/image/rm.go b/internal/commands/image/rm.go index 017286b7b..51cf36202 100644 --- a/internal/commands/image/rm.go +++ b/internal/commands/image/rm.go @@ -47,7 +47,7 @@ $ docker app image rm docker.io/library/myapp@sha256:beef...`, } func runRm(bundleStore store.BundleStore, app string) error { - ref, err := StringToRef(app) + ref, err := store.StringToRef(app) if err != nil { return err } diff --git a/internal/commands/image/tag.go b/internal/commands/image/tag.go index f36e82f79..9e4739878 100644 --- a/internal/commands/image/tag.go +++ b/internal/commands/image/tag.go @@ -7,7 +7,6 @@ import ( "github.com/docker/app/internal/store" "github.com/docker/cli/cli" "github.com/docker/cli/cli/config" - "github.com/docker/distribution/reference" "github.com/spf13/cobra" ) @@ -44,7 +43,7 @@ func runTag(bundleStore store.BundleStore, srcAppImage, destAppImage string) err } func readBundle(name string, bundleStore store.BundleStore) (*bundle.Bundle, error) { - cnabRef, err := StringToRef(name) + cnabRef, err := store.StringToRef(name) if err != nil { return nil, err } @@ -57,17 +56,10 @@ func readBundle(name string, bundleStore store.BundleStore) (*bundle.Bundle, err } func storeBundle(bundle *bundle.Bundle, name string, bundleStore store.BundleStore) error { - cnabRef, err := StringToRef(name) + cnabRef, err := store.StringToRef(name) if err != nil { return err } _, err = bundleStore.Store(cnabRef, bundle) return err } - -func StringToRef(s string) (reference.Reference, error) { - if named, err := reference.ParseNormalizedNamed(s); err == nil { - return reference.TagNameOnly(named), nil - } - return store.FromString(s) -} diff --git a/internal/commands/list.go b/internal/commands/list.go index e59e2e4ce..443e8541f 100644 --- a/internal/commands/list.go +++ b/internal/commands/list.go @@ -17,7 +17,7 @@ import ( ) type listOptions struct { - targetContext string + targetContextOptions } var ( @@ -57,8 +57,8 @@ func listCmd(dockerCli command.Cli) *cobra.Command { } func runList(dockerCli command.Cli, opts listOptions) error { - targetContext := getTargetContext(opts.targetContext, dockerCli.CurrentContext()) - installations, err := getInstallations(targetContext, config.Dir()) + opts.SetDefaultTargetContext(dockerCli) + installations, err := getInstallations(opts.targetContext, config.Dir()) if err != nil { return err } diff --git a/internal/commands/pull.go b/internal/commands/pull.go index 179b464f9..9f4a6f3df 100644 --- a/internal/commands/pull.go +++ b/internal/commands/pull.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/docker/app/internal/cnab" "github.com/docker/app/internal/store" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -41,7 +42,7 @@ func runPull(dockerCli command.Cli, name string) error { } tagRef := reference.TagNameOnly(ref) - bndl, err := pullBundle(dockerCli, bundleStore, tagRef) + bndl, err := cnab.PullBundle(dockerCli, bundleStore, tagRef) if err != nil { return errors.Wrap(err, name) } diff --git a/internal/commands/push.go b/internal/commands/push.go index 90fd90d73..21f4221f4 100644 --- a/internal/commands/push.go +++ b/internal/commands/push.go @@ -9,11 +9,12 @@ import ( "os" "strings" - "github.com/docker/app/internal/packager" - "github.com/containerd/containerd/platforms" "github.com/deislabs/cnab-go/bundle" + "github.com/docker/app/internal" + "github.com/docker/app/internal/cnab" "github.com/docker/app/internal/log" + "github.com/docker/app/internal/packager" "github.com/docker/app/types/metadata" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -102,7 +103,7 @@ func resolveReferenceAndBundle(dockerCli command.Cli, name string) (*bundle.Bund return nil, "", err } - bndl, ref, err := resolveBundle(dockerCli, bundleStore, name) + bndl, ref, err := cnab.ResolveBundle(dockerCli, bundleStore, name) if err != nil { return nil, "", err } @@ -136,7 +137,7 @@ func pushInvocationImage(dockerCli command.Cli, retag retagResult) error { } func pushBundle(dockerCli command.Cli, opts pushOptions, bndl *bundle.Bundle, retag retagResult) error { - insecureRegistries, err := insecureRegistriesFromEngine(dockerCli) + insecureRegistries, err := internal.InsecureRegistriesFromEngine(dockerCli) if err != nil { return errors.Wrap(err, "could not retrieve insecure registries") } diff --git a/internal/commands/remove.go b/internal/commands/remove.go index 3c2219091..a9dbc81d0 100644 --- a/internal/commands/remove.go +++ b/internal/commands/remove.go @@ -6,6 +6,7 @@ import ( "github.com/deislabs/cnab-go/action" "github.com/deislabs/cnab-go/credentials" + "github.com/docker/app/internal/cnab" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" @@ -60,11 +61,11 @@ func runRemove(dockerCli command.Cli, installationName string, opts removeOption fmt.Fprintf(os.Stderr, "deletion forced for installation %q\n", installationName) }() } - bind, err := requiredClaimBindMount(installation.Claim, opts.targetContext, dockerCli) + bind, err := cnab.RequiredClaimBindMount(installation.Claim, opts.targetContext, dockerCli) if err != nil { return err } - driverImpl, errBuf := prepareDriver(dockerCli, bind, nil) + driverImpl, errBuf := cnab.PrepareDriver(dockerCli, bind, nil) creds, err := prepareCredentialSet(installation.Bundle, opts.CredentialSetOpts(dockerCli, credentialStore)...) if err != nil { return err diff --git a/internal/commands/render.go b/internal/commands/render.go index 1bb4a10c5..9c094df75 100644 --- a/internal/commands/render.go +++ b/internal/commands/render.go @@ -1,13 +1,18 @@ package commands import ( + "bytes" "fmt" "io" "os" + "github.com/deislabs/cnab-go/action" "github.com/docker/app/internal" + "github.com/docker/app/internal/cnab" + appstore "github.com/docker/app/internal/store" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/config" "github.com/spf13/cobra" ) @@ -60,3 +65,37 @@ func runRender(dockerCli command.Cli, appname string, opts renderOptions) error } return nil } + +func prepareCustomAction(actionName string, dockerCli command.Cli, appname string, stdout io.Writer, paramsOpts parametersOptions) (*action.RunCustom, *appstore.Installation, *bytes.Buffer, error) { + s, err := appstore.NewApplicationStore(config.Dir()) + if err != nil { + return nil, nil, nil, err + } + bundleStore, err := s.BundleStore() + if err != nil { + return nil, nil, nil, err + } + bundle, ref, err := cnab.ResolveBundle(dockerCli, bundleStore, appname) + if err != nil { + return nil, nil, nil, err + } + installation, err := appstore.NewInstallation("custom-action", ref) + if err != nil { + return nil, nil, nil, err + } + installation.Bundle = bundle + + if err := mergeBundleParameters(installation, + withFileParameters(paramsOpts.parametersFiles), + withCommandLineParameters(paramsOpts.overrides), + ); err != nil { + return nil, nil, nil, err + } + + driverImpl, errBuf := cnab.PrepareDriver(dockerCli, cnab.BindMount{}, stdout) + a := &action.RunCustom{ + Action: actionName, + Driver: driverImpl, + } + return a, installation, errBuf, nil +} diff --git a/internal/commands/root.go b/internal/commands/root.go index 91bf9adba..12ed5f459 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -1,18 +1,18 @@ package commands import ( - "context" "fmt" "io/ioutil" "os" + "github.com/deislabs/cnab-go/claim" "github.com/docker/app/internal" "github.com/docker/app/internal/commands/build" "github.com/docker/app/internal/commands/image" "github.com/docker/app/internal/store" + appstore "github.com/docker/app/internal/store" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -54,7 +54,6 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) { removeCmd(dockerCli), listCmd(dockerCli), initCmd(dockerCli), - inspectCmd(dockerCli), renderCmd(dockerCli), validateCmd(), pushCmd(dockerCli), @@ -122,8 +121,30 @@ func (o *parametersOptions) addFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&o.overrides, "set", "s", []string{}, "Override parameter value") } +type targetContextOptions struct { + targetContext string +} + +func (o *targetContextOptions) SetDefaultTargetContext(dockerCli command.Cli) { + o.targetContext = getTargetContext(o.targetContext, dockerCli.CurrentContext()) +} + +func getTargetContext(optstargetContext, currentContext string) string { + var targetContext string + switch { + case optstargetContext != "": + targetContext = optstargetContext + case os.Getenv("DOCKER_TARGET_CONTEXT") != "": + targetContext = os.Getenv("DOCKER_TARGET_CONTEXT") + } + if targetContext == "" { + targetContext = currentContext + } + return targetContext +} + type credentialOptions struct { - targetContext string + targetContextOptions credentialsets []string credentials []string sendRegistryAuth bool @@ -136,10 +157,6 @@ func (o *credentialOptions) addFlags(flags *pflag.FlagSet) { flags.BoolVar(&o.sendRegistryAuth, "with-registry-auth", false, "Sends registry auth") } -func (o *credentialOptions) SetDefaultTargetContext(dockerCli command.Cli) { - o.targetContext = getTargetContext(o.targetContext, dockerCli.CurrentContext()) -} - func (o *credentialOptions) CredentialSetOpts(dockerCli command.Cli, credentialStore store.CredentialStore) []credentialSetOpt { return []credentialSetOpt{ addNamedCredentialSets(credentialStore, o.credentialsets), @@ -149,23 +166,7 @@ func (o *credentialOptions) CredentialSetOpts(dockerCli command.Cli, credentialS } } -// insecureRegistriesFromEngine reads the registry configuration from the daemon and returns -// a list of all insecure ones. -func insecureRegistriesFromEngine(dockerCli command.Cli) ([]string, error) { - registries := []string{} - - info, err := dockerCli.Client().Info(context.Background()) - if err != nil { - return nil, fmt.Errorf("could not get docker info: %v", err) - } - - for _, reg := range info.RegistryConfig.IndexConfigs { - if !reg.Secure { - registries = append(registries, reg.Name) - } - } - - logrus.Debugf("insecure registries: %v", registries) - - return registries, nil +func isInstallationFailed(installation *appstore.Installation) bool { + return installation.Result.Action == claim.ActionInstall && + installation.Result.Status == claim.StatusFailure } diff --git a/internal/commands/run.go b/internal/commands/run.go index 478b2969c..a0d0b7bf0 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -6,6 +6,7 @@ import ( "github.com/deislabs/cnab-go/action" "github.com/deislabs/cnab-go/credentials" + "github.com/docker/app/internal/cnab" "github.com/docker/app/internal/store" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -23,16 +24,6 @@ type runOptions struct { stackName string } -type nameKind uint - -const ( - _ nameKind = iota - nameKindEmpty - nameKindFile - nameKindDir - nameKindReference -) - const longDescription = `Run an application based on a docker app image.` const example = `$ docker app run --name myinstallation --target-context=mycontext myrepo/myapp:mytag` @@ -63,7 +54,7 @@ func runCmd(dockerCli command.Cli) *cobra.Command { func runRun(dockerCli command.Cli, appname string, opts runOptions) error { opts.SetDefaultTargetContext(dockerCli) - bind, err := requiredBindMount(opts.targetContext, opts.orchestrator, dockerCli.ContextStore()) + bind, err := cnab.RequiredBindMount(opts.targetContext, opts.orchestrator, dockerCli.ContextStore()) if err != nil { return err } @@ -72,7 +63,7 @@ func runRun(dockerCli command.Cli, appname string, opts runOptions) error { return err } - bndl, ref, err := resolveBundle(dockerCli, bundleStore, appname) + bndl, ref, err := cnab.ResolveBundle(dockerCli, bundleStore, appname) if err != nil { return errors.Wrapf(err, "Unable to find application %q", appname) } @@ -101,7 +92,7 @@ func runRun(dockerCli command.Cli, appname string, opts runOptions) error { return err } - driverImpl, errBuf := prepareDriver(dockerCli, bind, nil) + driverImpl, errBuf := cnab.PrepareDriver(dockerCli, bind, nil) installation.Bundle = bndl if err := mergeBundleParameters(installation, diff --git a/internal/commands/update.go b/internal/commands/update.go index 228ba522e..dfbbdc041 100644 --- a/internal/commands/update.go +++ b/internal/commands/update.go @@ -6,6 +6,7 @@ import ( "github.com/deislabs/cnab-go/action" "github.com/deislabs/cnab-go/credentials" + "github.com/docker/app/internal/cnab" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" ) @@ -53,7 +54,7 @@ func runUpdate(dockerCli command.Cli, installationName string, opts updateOption } if opts.bundleOrDockerApp != "" { - b, _, err := resolveBundle(dockerCli, bundleStore, opts.bundleOrDockerApp) + b, _, err := cnab.ResolveBundle(dockerCli, bundleStore, opts.bundleOrDockerApp) if err != nil { return err } @@ -67,11 +68,11 @@ func runUpdate(dockerCli command.Cli, installationName string, opts updateOption return err } - bind, err := requiredClaimBindMount(installation.Claim, opts.targetContext, dockerCli) + bind, err := cnab.RequiredClaimBindMount(installation.Claim, opts.targetContext, dockerCli) if err != nil { return err } - driverImpl, errBuf := prepareDriver(dockerCli, bind, nil) + driverImpl, errBuf := cnab.PrepareDriver(dockerCli, bind, nil) creds, err := prepareCredentialSet(installation.Bundle, opts.CredentialSetOpts(dockerCli, credentialStore)...) if err != nil { return err diff --git a/internal/commands/dockerdesktop.go b/internal/dockerdesktop.go similarity index 95% rename from internal/commands/dockerdesktop.go rename to internal/dockerdesktop.go index 74d3ffd0a..c2b031e88 100644 --- a/internal/commands/dockerdesktop.go +++ b/internal/dockerdesktop.go @@ -1,4 +1,4 @@ -package commands +package internal import ( "fmt" @@ -128,11 +128,11 @@ func rewriteContextIfDockerDesktop(meta *store.Metadata, s store.Store) { meta.Endpoints[kubernetes.KubernetesEndpoint] = *kubeEp } -type dockerDesktopAwareStore struct { +type DockerDesktopAwareStore struct { store.Store } -func (s dockerDesktopAwareStore) List() ([]store.Metadata, error) { +func (s DockerDesktopAwareStore) List() ([]store.Metadata, error) { contexts, err := s.Store.List() if err != nil { return nil, err @@ -144,7 +144,7 @@ func (s dockerDesktopAwareStore) List() ([]store.Metadata, error) { return contexts, nil } -func (s dockerDesktopAwareStore) GetMetadata(name string) (store.Metadata, error) { +func (s DockerDesktopAwareStore) GetMetadata(name string) (store.Metadata, error) { context, err := s.Store.GetMetadata(name) if err != nil { return store.Metadata{}, err diff --git a/internal/commands/dockerdesktop_test.go b/internal/dockerdesktop_test.go similarity index 99% rename from internal/commands/dockerdesktop_test.go rename to internal/dockerdesktop_test.go index 3c0508b90..f6a71d0c4 100644 --- a/internal/commands/dockerdesktop_test.go +++ b/internal/dockerdesktop_test.go @@ -1,4 +1,4 @@ -package commands +package internal import ( "testing" diff --git a/internal/registry.go b/internal/registry.go new file mode 100644 index 000000000..9c864dada --- /dev/null +++ b/internal/registry.go @@ -0,0 +1,30 @@ +package internal + +import ( + "context" + "fmt" + + "github.com/docker/cli/cli/command" + "github.com/sirupsen/logrus" +) + +// InsecureRegistriesFromEngine reads the registry configuration from the daemon and returns +// a list of all insecure ones. +func InsecureRegistriesFromEngine(dockerCli command.Cli) ([]string, error) { + registries := []string{} + + info, err := dockerCli.Client().Info(context.Background()) + if err != nil { + return nil, fmt.Errorf("could not get docker info: %v", err) + } + + for _, reg := range info.RegistryConfig.IndexConfigs { + if !reg.Secure { + registries = append(registries, reg.Name) + } + } + + logrus.Debugf("insecure registries: %v", registries) + + return registries, nil +} diff --git a/internal/store/digest.go b/internal/store/digest.go index dc31f7806..64548f25c 100644 --- a/internal/store/digest.go +++ b/internal/store/digest.go @@ -20,6 +20,13 @@ func ComputeDigest(bundle io.WriterTo) (digest.Digest, error) { return digest.SHA256.FromBytes(b.Bytes()), nil } +func StringToRef(s string) (reference.Reference, error) { + if named, err := reference.ParseNormalizedNamed(s); err == nil { + return reference.TagNameOnly(named), nil + } + return FromString(s) +} + func FromString(s string) (ID, error) { if ok, _ := regexp.MatchString("[a-z0-9]{64}", s); !ok { return ID{}, fmt.Errorf("could not parse '%s' as a valid reference", s)