Merge pull request #1539 from docker/inject

This commit is contained in:
Nicolas De loof 2021-04-23 11:00:02 +02:00 committed by GitHub
commit fa05d4397a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 951 additions and 449 deletions

View File

@ -73,7 +73,7 @@ RUN --mount=target=. \
GOARCH=${TARGETARCH} \
BUILD_TAGS=${BUILD_TAGS} \
GIT_TAG=${GIT_TAG} \
make BINARY=/out/docker -f builder.Makefile cli
make BINARY=/out/docker COMPOSE_BINARY=/out/docker-compose -f builder.Makefile cli
FROM base AS make-cross
ARG BUILD_TAGS
@ -83,7 +83,7 @@ RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
BUILD_TAGS=${BUILD_TAGS} \
GIT_TAG=${GIT_TAG} \
make BINARY=/out/docker -f builder.Makefile cross
make BINARY=/out/docker COMPOSE_BINARY=/out/docker-compose -f builder.Makefile cross
FROM scratch AS protos
COPY --from=make-protos /compose-cli/cli/server/protos .

143
api/compose/delegator.go Normal file
View File

@ -0,0 +1,143 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"github.com/compose-spec/compose-go/types"
)
// ServiceDelegator implements Service by delegating to another implementation. This allows lazy init
type ServiceDelegator struct {
Delegate Service
}
//Build implements Service interface
func (s *ServiceDelegator) Build(ctx context.Context, project *types.Project, options BuildOptions) error {
return s.Delegate.Build(ctx, project, options)
}
//Push implements Service interface
func (s *ServiceDelegator) Push(ctx context.Context, project *types.Project, options PushOptions) error {
return s.Delegate.Push(ctx, project, options)
}
//Pull implements Service interface
func (s *ServiceDelegator) Pull(ctx context.Context, project *types.Project, options PullOptions) error {
return s.Delegate.Pull(ctx, project, options)
}
//Create implements Service interface
func (s *ServiceDelegator) Create(ctx context.Context, project *types.Project, options CreateOptions) error {
return s.Delegate.Create(ctx, project, options)
}
//Start implements Service interface
func (s *ServiceDelegator) Start(ctx context.Context, project *types.Project, options StartOptions) error {
return s.Delegate.Start(ctx, project, options)
}
//Restart implements Service interface
func (s *ServiceDelegator) Restart(ctx context.Context, project *types.Project, options RestartOptions) error {
return s.Delegate.Restart(ctx, project, options)
}
//Stop implements Service interface
func (s *ServiceDelegator) Stop(ctx context.Context, project *types.Project, options StopOptions) error {
return s.Delegate.Stop(ctx, project, options)
}
//Up implements Service interface
func (s *ServiceDelegator) Up(ctx context.Context, project *types.Project, options UpOptions) error {
return s.Delegate.Up(ctx, project, options)
}
//Down implements Service interface
func (s *ServiceDelegator) Down(ctx context.Context, project string, options DownOptions) error {
return s.Delegate.Down(ctx, project, options)
}
//Logs implements Service interface
func (s *ServiceDelegator) Logs(ctx context.Context, project string, consumer LogConsumer, options LogOptions) error {
return s.Delegate.Logs(ctx, project, consumer, options)
}
//Ps implements Service interface
func (s *ServiceDelegator) Ps(ctx context.Context, project string, options PsOptions) ([]ContainerSummary, error) {
return s.Delegate.Ps(ctx, project, options)
}
//List implements Service interface
func (s *ServiceDelegator) List(ctx context.Context, options ListOptions) ([]Stack, error) {
return s.Delegate.List(ctx, options)
}
//Convert implements Service interface
func (s *ServiceDelegator) Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error) {
return s.Delegate.Convert(ctx, project, options)
}
//Kill implements Service interface
func (s *ServiceDelegator) Kill(ctx context.Context, project *types.Project, options KillOptions) error {
return s.Delegate.Kill(ctx, project, options)
}
//RunOneOffContainer implements Service interface
func (s *ServiceDelegator) RunOneOffContainer(ctx context.Context, project *types.Project, options RunOptions) (int, error) {
return s.Delegate.RunOneOffContainer(ctx, project, options)
}
//Remove implements Service interface
func (s *ServiceDelegator) Remove(ctx context.Context, project *types.Project, options RemoveOptions) ([]string, error) {
return s.Delegate.Remove(ctx, project, options)
}
//Exec implements Service interface
func (s *ServiceDelegator) Exec(ctx context.Context, project *types.Project, options RunOptions) error {
return s.Delegate.Exec(ctx, project, options)
}
//Pause implements Service interface
func (s *ServiceDelegator) Pause(ctx context.Context, project string, options PauseOptions) error {
return s.Delegate.Pause(ctx, project, options)
}
//UnPause implements Service interface
func (s *ServiceDelegator) UnPause(ctx context.Context, project string, options PauseOptions) error {
return s.Delegate.UnPause(ctx, project, options)
}
//Top implements Service interface
func (s *ServiceDelegator) Top(ctx context.Context, project string, services []string) ([]ContainerProcSummary, error) {
return s.Delegate.Top(ctx, project, services)
}
//Events implements Service interface
func (s *ServiceDelegator) Events(ctx context.Context, project string, options EventsOptions) error {
return s.Delegate.Events(ctx, project, options)
}
//Port implements Service interface
func (s *ServiceDelegator) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
return s.Delegate.Port(ctx, project, service, port, options)
}
//Images implements Service interface
func (s *ServiceDelegator) Images(ctx context.Context, project string, options ImagesOptions) ([]ImageSummary, error) {
return s.Delegate.Images(ctx, project, options)
}

143
api/compose/noimpl.go Normal file
View File

@ -0,0 +1,143 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose-cli/api/errdefs"
)
// NoImpl implements Service to return ErrNotImplemented
type NoImpl struct{}
//Build implements Service interface
func (s NoImpl) Build(ctx context.Context, project *types.Project, options BuildOptions) error {
return errdefs.ErrNotImplemented
}
//Push implements Service interface
func (s NoImpl) Push(ctx context.Context, project *types.Project, options PushOptions) error {
return errdefs.ErrNotImplemented
}
//Pull implements Service interface
func (s NoImpl) Pull(ctx context.Context, project *types.Project, options PullOptions) error {
return errdefs.ErrNotImplemented
}
//Create implements Service interface
func (s NoImpl) Create(ctx context.Context, project *types.Project, options CreateOptions) error {
return errdefs.ErrNotImplemented
}
//Start implements Service interface
func (s NoImpl) Start(ctx context.Context, project *types.Project, options StartOptions) error {
return errdefs.ErrNotImplemented
}
//Restart implements Service interface
func (s NoImpl) Restart(ctx context.Context, project *types.Project, options RestartOptions) error {
return errdefs.ErrNotImplemented
}
//Stop implements Service interface
func (s NoImpl) Stop(ctx context.Context, project *types.Project, options StopOptions) error {
return errdefs.ErrNotImplemented
}
//Up implements Service interface
func (s NoImpl) Up(ctx context.Context, project *types.Project, options UpOptions) error {
return errdefs.ErrNotImplemented
}
//Down implements Service interface
func (s NoImpl) Down(ctx context.Context, project string, options DownOptions) error {
return errdefs.ErrNotImplemented
}
//Logs implements Service interface
func (s NoImpl) Logs(ctx context.Context, project string, consumer LogConsumer, options LogOptions) error {
return errdefs.ErrNotImplemented
}
//Ps implements Service interface
func (s NoImpl) Ps(ctx context.Context, project string, options PsOptions) ([]ContainerSummary, error) {
return nil, errdefs.ErrNotImplemented
}
//List implements Service interface
func (s NoImpl) List(ctx context.Context, options ListOptions) ([]Stack, error) {
return nil, errdefs.ErrNotImplemented
}
//Convert implements Service interface
func (s NoImpl) Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error) {
return nil, errdefs.ErrNotImplemented
}
//Kill implements Service interface
func (s NoImpl) Kill(ctx context.Context, project *types.Project, options KillOptions) error {
return errdefs.ErrNotImplemented
}
//RunOneOffContainer implements Service interface
func (s NoImpl) RunOneOffContainer(ctx context.Context, project *types.Project, options RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}
//Remove implements Service interface
func (s NoImpl) Remove(ctx context.Context, project *types.Project, options RemoveOptions) ([]string, error) {
return nil, errdefs.ErrNotImplemented
}
//Exec implements Service interface
func (s NoImpl) Exec(ctx context.Context, project *types.Project, options RunOptions) error {
return errdefs.ErrNotImplemented
}
//Pause implements Service interface
func (s NoImpl) Pause(ctx context.Context, project string, options PauseOptions) error {
return errdefs.ErrNotImplemented
}
//UnPause implements Service interface
func (s NoImpl) UnPause(ctx context.Context, project string, options PauseOptions) error {
return errdefs.ErrNotImplemented
}
//Top implements Service interface
func (s NoImpl) Top(ctx context.Context, project string, services []string) ([]ContainerProcSummary, error) {
return nil, errdefs.ErrNotImplemented
}
//Events implements Service interface
func (s NoImpl) Events(ctx context.Context, project string, options EventsOptions) error {
return errdefs.ErrNotImplemented
}
//Port implements Service interface
func (s NoImpl) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
return "", 0, errdefs.ErrNotImplemented
}
//Images implements Service interface
func (s NoImpl) Images(ctx context.Context, project string, options ImagesOptions) ([]ImageSummary, error) {
return nil, errdefs.ErrNotImplemented
}

View File

@ -34,6 +34,9 @@ GO_BUILD=$(STATIC_FLAGS) go build -trimpath -ldflags=$(LDFLAGS)
BINARY?=bin/docker
BINARY_WITH_EXTENSION=$(BINARY)$(EXTENSION)
COMPOSE_BINARY?=bin/docker-compose
COMPOSE_BINARY_WITH_EXTENSION=$(COMPOSE_BINARY)$(EXTENSION)
WORK_DIR:=$(shell mktemp -d)
TAGS:=
@ -42,9 +45,22 @@ ifdef BUILD_TAGS
LINT_TAGS=--build-tags $(BUILD_TAGS)
endif
TAR_TRANSFORM:=--transform s/packaging/docker/ --transform s/bin/docker/ --transform s/docker-linux-amd64/docker/ --transform s/docker-darwin-amd64/docker/ --transform s/docker-linux-arm64/docker/ --transform s/docker-linux-armv6/docker/ --transform s/docker-linux-armv7/docker/ --transform s/docker-darwin-arm64/docker/
TAR_TRANSFORM:=--transform s/packaging/docker/ --transform s/bin/docker/ \
--transform s/docker-linux-amd64/docker/ --transform s/docker-linux-arm64/docker/ \
--transform s/docker-linux-armv6/docker/ --transform s/docker-linux-armv7/docker/ \
--transform s/docker-darwin-amd64/docker/ --transform s/docker-darwin-arm64/docker/ \
--transform s/docker-compose-linux-amd64/docker-compose/ --transform s/docker-compose-linux-arm64/docker-compose/ \
--transform s/docker-compose-linux-armv6/docker-compose/ --transform s/docker-compose-linux-armv7/docker-compose/ \
--transform s/docker-compose-darwin-amd64/docker-compose/ --transform s/docker-compose-darwin-arm64/docker-compose/
ifneq ($(findstring bsd,$(shell tar --version)),)
TAR_TRANSFORM=-s /packaging/docker/ -s /bin/docker/ -s /docker-linux-amd64/docker/ -s /docker-darwin-amd64/docker/ -s /docker-linux-arm64/docker/ -s /docker-linux-armv6/docker/ -s /docker-linux-armv7/docker/ -s /docker-darwin-arm64/docker/
TAR_TRANSFORM=-s /packaging/docker/ -s /bin/docker/ \
-s /docker-linux-amd64/docker/ -s /docker-linux-arm64/docker/ \
-s /docker-linux-armv6/docker/ -s /docker-linux-armv7/docker/ \
-s /docker-darwin-amd64/docker/ -s /docker-darwin-arm64/docker/ \
-s /docker-compose-linux-amd64/docker-compose/ -s /docker-compose-linux-arm64/docker-compose/ \
-s /docker-compose-linux-armv6/docker-compose/ -s /docker-compose-linux-armv7/docker-compose/ \
-s /docker-compose-darwin-amd64/docker-compose/ -s /docker-compose-darwin-arm64/docker-compose/
endif
all: cli
@ -54,11 +70,15 @@ protos:
protoc -I. --go_out=plugins=grpc,paths=source_relative:. ${PROTOS}
.PHONY: cli
cli:
cli: compose-plugin
GOOS=${GOOS} GOARCH=${GOARCH} $(GO_BUILD) $(TAGS) -o $(BINARY_WITH_EXTENSION) ./cli
.PHONY: compose-plugin
compose-plugin:
GOOS=${GOOS} GOARCH=${GOARCH} $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY_WITH_EXTENSION) .
.PHONY: cross
cross:
cross: cross-compose-plugin
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-amd64 ./cli
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-arm64 ./cli
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-armv6 ./cli
@ -67,6 +87,16 @@ cross:
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(BINARY)-darwin-arm64 ./cli
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-windows-amd64.exe ./cli
.PHONY: cross-compose-plugin
cross-compose-plugin:
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-amd64 .
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-arm64 .
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 .
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 .
GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-amd64 .
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-arm64 .
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-windows-amd64.exe .
.PHONY: test
test:
go test $(TAGS) -cover $(shell go list $(TAGS) ./... | grep -vE 'e2e')
@ -90,14 +120,15 @@ check-go-mod:
.PHONY: package
package: cross
mkdir -p dist
tar -czf dist/docker-linux-amd64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-amd64
tar -czf dist/docker-linux-arm64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-arm64
tar -czf dist/docker-linux-armv6.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-armv6
tar -czf dist/docker-linux-armv7.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-armv7
tar -czf dist/docker-darwin-amd64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-darwin-amd64
tar -czf dist/docker-darwin-arm64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-darwin-arm64
tar -czf dist/docker-linux-amd64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-amd64 $(COMPOSE_BINARY)-linux-amd64
tar -czf dist/docker-linux-arm64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-arm64 $(COMPOSE_BINARY)-linux-arm64
tar -czf dist/docker-linux-armv6.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-armv6 $(COMPOSE_BINARY)-linux-armv6
tar -czf dist/docker-linux-armv7.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-armv7 $(COMPOSE_BINARY)-linux-armv7
tar -czf dist/docker-darwin-amd64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-darwin-amd64 $(COMPOSE_BINARY)-darwin-amd64
tar -czf dist/docker-darwin-arm64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-darwin-arm64 $(COMPOSE_BINARY)-darwin-arm64
cp $(BINARY)-windows-amd64.exe $(WORK_DIR)/docker.exe
rm -f dist/docker-windows-amd64.zip && zip dist/docker-windows-amd64.zip -j packaging/LICENSE $(WORK_DIR)/docker.exe
cp $(COMPOSE_BINARY)-windows-amd64.exe $(WORK_DIR)/docker-compose.exe
rm -f dist/docker-windows-amd64.zip && zip dist/docker-windows-amd64.zip -j packaging/LICENSE $(WORK_DIR)/docker.exe $(WORK_DIR)/docker-compose.exe
rm -r $(WORK_DIR)
.PHONY: yamldocs

View File

@ -24,7 +24,6 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
)
@ -40,14 +39,14 @@ type buildOptions struct {
memory string
}
func buildCommand(p *projectOptions) *cobra.Command {
func buildCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := buildOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "build [SERVICE...]",
Short: "Build or rebuild services",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.memory != "" {
fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
}
@ -58,8 +57,8 @@ func buildCommand(p *projectOptions) *cobra.Command {
}
os.Stdout = devnull
}
return runBuild(cmd.Context(), opts, args)
},
return runBuild(ctx, backend, opts, args)
}),
}
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
@ -80,19 +79,14 @@ func buildCommand(p *projectOptions) *cobra.Command {
return cmd
}
func runBuild(ctx context.Context, opts buildOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runBuild(ctx context.Context, backend compose.Service, opts buildOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Build(ctx, project, compose.BuildOptions{
return "", backend.Build(ctx, project, compose.BuildOptions{
Pull: opts.pull,
Progress: opts.progress,
Args: types.NewMapping(opts.args),

View File

@ -17,22 +17,64 @@
package compose
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
dockercli "github.com/docker/cli/cli"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/api/errdefs"
"github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/cli/metrics"
)
//Command defines a compose CLI command as a func with args
type Command func(context.Context, []string) error
//Adapt a Command func to cobra library
func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
contextString := fmt.Sprintf("%s", ctx)
if !strings.HasSuffix(contextString, ".WithCancel") { // need to handle cancel
cancellableCtx, cancel := context.WithCancel(cmd.Context())
ctx = cancellableCtx
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-s
cancel()
}()
}
err := fn(ctx, args)
var composeErr metrics.ComposeError
if errdefs.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
err = dockercli.StatusError{
StatusCode: 130,
Status: metrics.CanceledStatus,
}
}
if errors.As(err, &composeErr) {
err = dockercli.StatusError{
StatusCode: composeErr.GetMetricsFailureCategory().ExitCode,
Status: err.Error(),
}
}
return err
}
}
// Warning is a global warning to be displayed to user on command failure
var Warning string
@ -104,8 +146,8 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
cli.WithName(o.ProjectName))...)
}
// Command returns the compose command with its child commands
func Command(contextType string) *cobra.Command {
// RootCommand returns the compose command with its child commands
func RootCommand(contextType string, backend compose.Service) *cobra.Command {
opts := projectOptions{}
var ansi string
var noAnsi bool
@ -119,9 +161,20 @@ func Command(contextType string) *cobra.Command {
return cmd.Help()
}
_ = cmd.Help()
return fmt.Errorf("unknown docker command: %q", "compose "+args[0])
return dockercli.StatusError{
StatusCode: metrics.CommandSyntaxFailure.ExitCode,
Status: fmt.Sprintf("unknown docker command: %q", "compose "+args[0]),
}
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
parent := cmd.Root()
parentPrerun := parent.PersistentPreRunE
if parentPrerun != nil {
err := parentPrerun(cmd, args)
if err != nil {
return err
}
}
if noAnsi {
if ansi != "auto" {
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
@ -146,34 +199,34 @@ func Command(contextType string) *cobra.Command {
}
command.AddCommand(
upCommand(&opts, contextType),
downCommand(&opts, contextType),
startCommand(&opts),
restartCommand(&opts),
stopCommand(&opts),
psCommand(&opts),
listCommand(contextType),
logsCommand(&opts, contextType),
convertCommand(&opts),
killCommand(&opts),
runCommand(&opts),
removeCommand(&opts),
execCommand(&opts),
pauseCommand(&opts),
unpauseCommand(&opts),
topCommand(&opts),
eventsCommand(&opts),
portCommand(&opts),
imagesCommand(&opts),
upCommand(&opts, contextType, backend),
downCommand(&opts, contextType, backend),
startCommand(&opts, backend),
restartCommand(&opts, backend),
stopCommand(&opts, backend),
psCommand(&opts, backend),
listCommand(contextType, backend),
logsCommand(&opts, contextType, backend),
convertCommand(&opts, backend),
killCommand(&opts, backend),
runCommand(&opts, backend),
removeCommand(&opts, backend),
execCommand(&opts, backend),
pauseCommand(&opts, backend),
unpauseCommand(&opts, backend),
topCommand(&opts, backend),
eventsCommand(&opts, backend),
portCommand(&opts, backend),
imagesCommand(&opts, backend),
versionCommand(),
)
if contextType == store.LocalContextType || contextType == store.DefaultContextType {
command.AddCommand(
buildCommand(&opts),
pushCommand(&opts),
pullCommand(&opts),
createCommand(&opts),
buildCommand(&opts, backend),
pushCommand(&opts, backend),
pullCommand(&opts, backend),
createCommand(&opts, backend),
)
}
command.Flags().SetInterspersed(false)

View File

@ -31,9 +31,7 @@ import (
"github.com/opencontainers/go-digest"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/config"
"github.com/docker/compose-cli/utils"
)
@ -52,7 +50,7 @@ type convertOptions struct {
var addFlagsFuncs []func(cmd *cobra.Command, opts *convertOptions)
func convertCommand(p *projectOptions) *cobra.Command {
func convertCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := convertOptions{
projectOptions: p,
}
@ -60,7 +58,7 @@ func convertCommand(p *projectOptions) *cobra.Command {
Aliases: []string{"config"},
Use: "convert SERVICES",
Short: "Converts the compose file to platform's canonical format",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.quiet {
devnull, err := os.Open(os.DevNull)
if err != nil {
@ -81,8 +79,8 @@ func convertCommand(p *projectOptions) *cobra.Command {
return runProfiles(opts, args)
}
return runConvert(cmd.Context(), opts, args)
},
return runConvert(ctx, backend, opts, args)
}),
}
flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
@ -102,23 +100,15 @@ func convertCommand(p *projectOptions) *cobra.Command {
return cmd
}
func runConvert(ctx context.Context, opts convertOptions, services []string) error {
func runConvert(ctx context.Context, backend compose.Service, opts convertOptions, services []string) error {
var json []byte
c, err := client.New(ctx)
if err != nil {
return err
}
project, err := opts.toProject(services, cli.WithInterpolation(!opts.noInterpolate))
if err != nil {
return err
}
if opts.resolve {
configFile, err := cliconfig.Load(config.Dir())
if err != nil {
return err
}
configFile := cliconfig.LoadDefaultConfigFile(os.Stderr)
resolver := remotes.CreateResolver(configFile)
err = project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
@ -130,7 +120,7 @@ func runConvert(ctx context.Context, opts convertOptions, services []string) err
}
}
json, err = c.ComposeService().Convert(ctx, project, compose.ConvertOptions{
json, err = backend.Convert(ctx, project, compose.ConvertOptions{
Format: opts.Format,
Output: opts.Output,
})

View File

@ -17,9 +17,12 @@
package compose
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/compose"
)
type createOptions struct {
@ -28,21 +31,21 @@ type createOptions struct {
noRecreate bool
}
func createCommand(p *projectOptions) *cobra.Command {
func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := createOptions{
composeOptions: &composeOptions{},
}
cmd := &cobra.Command{
Use: "create [SERVICE...]",
Short: "Creates containers for a service.",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.Build && opts.noBuild {
return fmt.Errorf("--build and --no-build are incompatible")
}
if opts.forceRecreate && opts.noRecreate {
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
}
return runCreateStart(cmd.Context(), upOptions{
return runCreateStart(ctx, backend, upOptions{
composeOptions: &composeOptions{
projectOptions: p,
Build: opts.Build,
@ -52,7 +55,7 @@ func createCommand(p *projectOptions) *cobra.Command {
forceRecreate: opts.forceRecreate,
noRecreate: opts.noRecreate,
}, args)
},
}),
}
flags := cmd.Flags()
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")

View File

@ -24,7 +24,6 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/api/progress"
@ -39,22 +38,24 @@ type downOptions struct {
images string
}
func downCommand(p *projectOptions, contextType string) *cobra.Command {
func downCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command {
opts := downOptions{
projectOptions: p,
}
downCmd := &cobra.Command{
Use: "down",
Short: "Stop and remove containers, networks",
RunE: func(cmd *cobra.Command, args []string) error {
PreRun: func(cmd *cobra.Command, args []string) {
opts.timeChanged = cmd.Flags().Changed("timeout")
},
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.images != "" {
if opts.images != "all" && opts.images != "local" {
return fmt.Errorf("invalid value for --rmi: %q", opts.images)
}
}
return runDown(cmd.Context(), opts)
},
return runDown(ctx, backend, opts)
}),
}
flags := downCmd.Flags()
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
@ -68,13 +69,8 @@ func downCommand(p *projectOptions, contextType string) *cobra.Command {
return downCmd
}
func runDown(ctx context.Context, opts downOptions) error {
c, err := client.New(ctx)
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
func runDown(ctx context.Context, backend compose.Service, opts downOptions) error {
_, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
name := opts.ProjectName
var project *types.Project
if opts.ProjectName == "" {
@ -91,7 +87,7 @@ func runDown(ctx context.Context, opts downOptions) error {
timeoutValue := time.Duration(opts.timeout) * time.Second
timeout = &timeoutValue
}
return name, c.ComposeService().Down(ctx, name, compose.DownOptions{
return name, backend.Down(ctx, name, compose.DownOptions{
RemoveOrphans: opts.removeOrphans,
Project: project,
Timeout: timeout,

View File

@ -21,7 +21,6 @@ import (
"encoding/json"
"fmt"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/spf13/cobra"
@ -32,7 +31,7 @@ type eventsOpts struct {
json bool
}
func eventsCommand(p *projectOptions) *cobra.Command {
func eventsCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := eventsOpts{
composeOptions: &composeOptions{
projectOptions: p,
@ -41,27 +40,22 @@ func eventsCommand(p *projectOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "events [options] [--] [SERVICE...]",
Short: "Receive real time events from containers.",
RunE: func(cmd *cobra.Command, args []string) error {
return runEvents(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runEvents(ctx, backend, opts, args)
}),
}
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
return cmd
}
func runEvents(ctx context.Context, opts eventsOpts, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runEvents(ctx context.Context, backend compose.Service, opts eventsOpts, services []string) error {
project, err := opts.toProjectName()
if err != nil {
return err
}
return c.ComposeService().Events(ctx, project, compose.EventsOptions{
return backend.Events(ctx, project, compose.EventsOptions{
Services: services,
Consumer: func(event compose.Event) error {
if opts.json {

View File

@ -24,7 +24,6 @@ import (
"github.com/containerd/console"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
)
@ -43,7 +42,7 @@ type execOpts struct {
privileged bool
}
func execCommand(p *projectOptions) *cobra.Command {
func execCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := execOpts{
composeOptions: &composeOptions{
projectOptions: p,
@ -53,13 +52,13 @@ func execCommand(p *projectOptions) *cobra.Command {
Use: "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]",
Short: "Execute a command in a running container.",
Args: cobra.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
if len(args) > 1 {
opts.command = args[1:]
}
opts.service = args[0]
return runExec(cmd.Context(), opts)
},
return runExec(ctx, backend, opts)
}),
}
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
@ -74,12 +73,7 @@ func execCommand(p *projectOptions) *cobra.Command {
return runCmd
}
func runExec(ctx context.Context, opts execOpts) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runExec(ctx context.Context, backend compose.Service, opts execOpts) error {
project, err := opts.toProject(nil)
if err != nil {
return err
@ -114,5 +108,5 @@ func runExec(ctx context.Context, opts execOpts) error {
execOpts.Writer = con
execOpts.Reader = con
}
return c.ComposeService().Exec(ctx, project, execOpts)
return backend.Exec(ctx, project, execOpts)
}

View File

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/utils"
@ -40,33 +39,28 @@ type imageOptions struct {
Quiet bool
}
func imagesCommand(p *projectOptions) *cobra.Command {
func imagesCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := imageOptions{
projectOptions: p,
}
imgCmd := &cobra.Command{
Use: "images [SERVICE...]",
Short: "List images used by the created containers",
RunE: func(cmd *cobra.Command, args []string) error {
return runImages(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(ctx, backend, opts, args)
}),
}
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
return imgCmd
}
func runImages(ctx context.Context, opts imageOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runImages(ctx context.Context, backend compose.Service, opts imageOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
images, err := c.ComposeService().Images(ctx, projectName, compose.ImagesOptions{
images, err := backend.Images(ctx, projectName, compose.ImagesOptions{
Services: services,
})
if err != nil {

View File

@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
)
@ -30,16 +29,16 @@ type killOptions struct {
Signal string
}
func killCommand(p *projectOptions) *cobra.Command {
func killCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := killOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "kill [options] [SERVICE...]",
Short: "Force stop service containers.",
RunE: func(cmd *cobra.Command, args []string) error {
return runKill(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runKill(ctx, backend, opts, args)
}),
}
flags := cmd.Flags()
@ -48,16 +47,12 @@ func killCommand(p *projectOptions) *cobra.Command {
return cmd
}
func runKill(ctx context.Context, opts killOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runKill(ctx context.Context, backend compose.Service, opts killOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
}
return c.ComposeService().Kill(ctx, project, compose.KillOptions{
return backend.Kill(ctx, project, compose.KillOptions{
Signal: opts.Signal,
})
}

View File

@ -26,7 +26,6 @@ import (
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/cli/formatter"
@ -39,14 +38,14 @@ type lsOptions struct {
Filter opts.FilterOpt
}
func listCommand(contextType string) *cobra.Command {
func listCommand(contextType string, backend compose.Service) *cobra.Command {
opts := lsOptions{Filter: opts.NewFilterOpt()}
lsCmd := &cobra.Command{
Use: "ls",
Short: "List running compose projects",
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), opts)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runList(ctx, backend, opts)
}),
}
lsCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
lsCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs.")
@ -62,18 +61,14 @@ var acceptedListFilters = map[string]bool{
"name": true,
}
func runList(ctx context.Context, opts lsOptions) error {
func runList(ctx context.Context, backend compose.Service, opts lsOptions) error {
filters := opts.Filter.Value()
err := filters.Validate(acceptedListFilters)
if err != nil {
return err
}
c, err := client.New(ctx)
if err != nil {
return err
}
stackList, err := c.ComposeService().List(ctx, compose.ListOptions{All: opts.All})
stackList, err := backend.List(ctx, compose.ListOptions{All: opts.All})
if err != nil {
return err
}

View File

@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/cli/formatter"
@ -38,16 +37,16 @@ type logsOptions struct {
timestamps bool
}
func logsCommand(p *projectOptions, contextType string) *cobra.Command {
func logsCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command {
opts := logsOptions{
projectOptions: p,
}
logsCmd := &cobra.Command{
Use: "logs [service...]",
Short: "View output from containers",
RunE: func(cmd *cobra.Command, args []string) error {
return runLogs(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runLogs(ctx, backend, opts, args)
}),
}
flags := logsCmd.Flags()
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
@ -61,18 +60,13 @@ func logsCommand(p *projectOptions, contextType string) *cobra.Command {
return logsCmd
}
func runLogs(ctx context.Context, opts logsOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runLogs(ctx context.Context, backend compose.Service, opts logsOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
consumer := formatter.NewLogConsumer(ctx, os.Stdout, !opts.noColor, !opts.noPrefix)
return c.ComposeService().Logs(ctx, projectName, consumer, compose.LogOptions{
return backend.Logs(ctx, projectName, consumer, compose.LogOptions{
Services: services,
Follow: opts.follow,
Tail: opts.tail,

View File

@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
)
@ -30,33 +29,28 @@ type pauseOptions struct {
*projectOptions
}
func pauseCommand(p *projectOptions) *cobra.Command {
func pauseCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := pauseOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "pause [SERVICE...]",
Short: "pause services",
RunE: func(cmd *cobra.Command, args []string) error {
return runPause(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPause(ctx, backend, opts, args)
}),
}
return cmd
}
func runPause(ctx context.Context, opts pauseOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runPause(ctx context.Context, backend compose.Service, opts pauseOptions, services []string) error {
project, err := opts.toProjectName()
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Pause(ctx, project, compose.PauseOptions{
return "", backend.Pause(ctx, project, compose.PauseOptions{
Services: services,
})
})
@ -67,33 +61,28 @@ type unpauseOptions struct {
*projectOptions
}
func unpauseCommand(p *projectOptions) *cobra.Command {
func unpauseCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := unpauseOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "unpause [SERVICE...]",
Short: "unpause services",
RunE: func(cmd *cobra.Command, args []string) error {
return runUnPause(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runUnPause(ctx, backend, opts, args)
}),
}
return cmd
}
func runUnPause(ctx context.Context, opts unpauseOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runUnPause(ctx context.Context, backend compose.Service, opts unpauseOptions, services []string) error {
project, err := opts.toProjectName()
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().UnPause(ctx, project, compose.PauseOptions{
return "", backend.UnPause(ctx, project, compose.PauseOptions{
Services: services,
})
})

View File

@ -23,7 +23,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
)
@ -33,7 +32,7 @@ type portOptions struct {
index int
}
func portCommand(p *projectOptions) *cobra.Command {
func portCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := portOptions{
projectOptions: p,
}
@ -41,30 +40,25 @@ func portCommand(p *projectOptions) *cobra.Command {
Use: "port [options] [--] SERVICE PRIVATE_PORT",
Short: "Print the public port for a port binding.",
Args: cobra.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
port, err := strconv.Atoi(args[1])
if err != nil {
return err
}
return runPort(cmd.Context(), opts, args[0], port)
},
return runPort(ctx, backend, opts, args[0], port)
}),
}
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
return cmd
}
func runPort(ctx context.Context, opts portOptions, service string, port int) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runPort(ctx context.Context, backend compose.Service, opts portOptions, service string, port int) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
ip, port, err := c.ComposeService().Port(ctx, projectName, service, port, compose.PortOptions{
ip, port, err := backend.Port(ctx, projectName, service, port, compose.PortOptions{
Protocol: opts.protocol,
Index: opts.index,
})

View File

@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/utils"
@ -40,16 +39,16 @@ type psOptions struct {
Services bool
}
func psCommand(p *projectOptions) *cobra.Command {
func psCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := psOptions{
projectOptions: p,
}
psCmd := &cobra.Command{
Use: "ps",
Short: "List containers",
RunE: func(cmd *cobra.Command, args []string) error {
return runPs(cmd.Context(), opts)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPs(ctx, backend, opts)
}),
}
psCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
psCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
@ -58,17 +57,12 @@ func psCommand(p *projectOptions) *cobra.Command {
return psCmd
}
func runPs(ctx context.Context, opts psOptions) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runPs(ctx context.Context, backend compose.Service, opts psOptions) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
containers, err := c.ComposeService().Ps(ctx, projectName, compose.PsOptions{
containers, err := backend.Ps(ctx, projectName, compose.PsOptions{
All: opts.All,
})
if err != nil {

View File

@ -24,7 +24,6 @@ import (
"github.com/morikuni/aec"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/utils"
@ -40,19 +39,19 @@ type pullOptions struct {
ignorePullFailures bool
}
func pullCommand(p *projectOptions) *cobra.Command {
func pullCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := pullOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "pull [SERVICE...]",
Short: "Pull service images",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.noParallel {
fmt.Fprint(os.Stderr, aec.Apply("option '--no-parallel' is DEPRECATED and will be ignored.\n", aec.RedF))
}
return runPull(cmd.Context(), opts, args)
},
return runPull(ctx, backend, opts, args)
}),
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")
@ -65,12 +64,7 @@ func pullCommand(p *projectOptions) *cobra.Command {
return cmd
}
func runPull(ctx context.Context, opts pullOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runPull(ctx context.Context, backend compose.Service, opts pullOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
@ -94,11 +88,11 @@ func runPull(ctx context.Context, opts pullOptions, services []string) error {
}
if opts.quiet {
return c.ComposeService().Pull(ctx, project, apiOpts)
return backend.Pull(ctx, project, apiOpts)
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Pull(ctx, project, apiOpts)
return "", backend.Pull(ctx, project, apiOpts)
})
return err
}

View File

@ -21,7 +21,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
)
@ -33,35 +32,30 @@ type pushOptions struct {
Ignorefailures bool
}
func pushCommand(p *projectOptions) *cobra.Command {
func pushCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := pushOptions{
projectOptions: p,
}
pushCmd := &cobra.Command{
Use: "push [SERVICE...]",
Short: "Push service images",
RunE: func(cmd *cobra.Command, args []string) error {
return runPush(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPush(ctx, backend, opts, args)
}),
}
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
return pushCmd
}
func runPush(ctx context.Context, opts pushOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runPush(ctx context.Context, backend compose.Service, opts pushOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Push(ctx, project, compose.PushOptions{
return "", backend.Push(ctx, project, compose.PushOptions{
IgnoreFailures: opts.Ignorefailures,
})
})

View File

@ -21,7 +21,6 @@ import (
"fmt"
"strings"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/utils/prompt"
@ -36,7 +35,7 @@ type removeOptions struct {
volumes bool
}
func removeCommand(p *projectOptions) *cobra.Command {
func removeCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := removeOptions{
projectOptions: p,
}
@ -49,9 +48,9 @@ By default, anonymous volumes attached to containers will not be removed. You
can override this with -v. To list all volumes, use "docker volume ls".
Any data which is not in a volume will be lost.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runRemove(ctx, backend, opts, args)
}),
}
f := cmd.Flags()
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
@ -60,12 +59,7 @@ Any data which is not in a volume will be lost.`,
return cmd
}
func runRemove(ctx context.Context, opts removeOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runRemove(ctx context.Context, backend compose.Service, opts removeOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
@ -73,7 +67,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
if opts.stop {
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
err := c.ComposeService().Stop(ctx, project, compose.StopOptions{
err := backend.Stop(ctx, project, compose.StopOptions{
Services: services,
})
return "", err
@ -83,7 +77,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
}
}
reosurces, err := c.ComposeService().Remove(ctx, project, compose.RemoveOptions{
reosurces, err := backend.Remove(ctx, project, compose.RemoveOptions{
DryRun: true,
Services: services,
})
@ -109,7 +103,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
_, err = c.ComposeService().Remove(ctx, project, compose.RemoveOptions{
_, err = backend.Remove(ctx, project, compose.RemoveOptions{
Volumes: opts.volumes,
Force: opts.force,
})

View File

@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
)
@ -32,16 +31,16 @@ type restartOptions struct {
timeout int
}
func restartCommand(p *projectOptions) *cobra.Command {
func restartCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := restartOptions{
projectOptions: p,
}
restartCmd := &cobra.Command{
Use: "restart",
Short: "Restart containers",
RunE: func(cmd *cobra.Command, args []string) error {
return runRestart(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runRestart(ctx, backend, opts, args)
}),
}
flags := restartCmd.Flags()
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
@ -49,12 +48,7 @@ func restartCommand(p *projectOptions) *cobra.Command {
return restartCmd
}
func runRestart(ctx context.Context, opts restartOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runRestart(ctx context.Context, backend compose.Service, opts restartOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
@ -62,7 +56,7 @@ func runRestart(ctx context.Context, opts restartOptions, services []string) err
timeout := time.Duration(opts.timeout) * time.Second
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Restart(ctx, project, compose.RestartOptions{
return "", backend.Restart(ctx, project, compose.RestartOptions{
Timeout: &timeout,
})
})

View File

@ -28,7 +28,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/cli/cli"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
)
@ -100,7 +99,7 @@ func (opts runOptions) apply(project *types.Project) error {
return nil
}
func runCommand(p *projectOptions) *cobra.Command {
func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := runOptions{
composeOptions: &composeOptions{
projectOptions: p,
@ -110,7 +109,7 @@ func runCommand(p *projectOptions) *cobra.Command {
Use: "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]",
Short: "Run a one-off command on a service.",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: Adapt(func(ctx context.Context, args []string) error {
if len(args) > 1 {
opts.Command = args[1:]
}
@ -118,8 +117,8 @@ func runCommand(p *projectOptions) *cobra.Command {
if len(opts.publish) > 0 && opts.servicePorts {
return fmt.Errorf("--service-ports and --publish are incompatible")
}
return runRun(cmd.Context(), opts)
},
return runRun(ctx, backend, opts)
}),
}
flags := cmd.Flags()
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
@ -141,8 +140,8 @@ func runCommand(p *projectOptions) *cobra.Command {
return cmd
}
func runRun(ctx context.Context, opts runOptions) error {
c, project, err := setup(ctx, *opts.composeOptions, []string{opts.Service})
func runRun(ctx context.Context, backend compose.Service, opts runOptions) error {
project, err := setup(*opts.composeOptions, []string{opts.Service})
if err != nil {
return err
}
@ -153,7 +152,7 @@ func runRun(ctx context.Context, opts runOptions) error {
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", startDependencies(ctx, c, *project, opts.Service)
return "", startDependencies(ctx, backend, *project, opts.Service)
})
if err != nil {
return err
@ -194,7 +193,7 @@ func runRun(ctx context.Context, opts runOptions) error {
UseNetworkAliases: opts.useAliases,
Index: 0,
}
exitCode, err := c.ComposeService().RunOneOffContainer(ctx, project, runOpts)
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
if exitCode != 0 {
errMsg := ""
if err != nil {
@ -205,7 +204,7 @@ func runRun(ctx context.Context, opts runOptions) error {
return err
}
func startDependencies(ctx context.Context, c *client.Client, project types.Project, requestedServiceName string) error {
func startDependencies(ctx context.Context, backend compose.Service, project types.Project, requestedServiceName string) error {
dependencies := types.Services{}
var requestedService types.ServiceConfig
for _, service := range project.Services {
@ -218,10 +217,10 @@ func startDependencies(ctx context.Context, c *client.Client, project types.Proj
project.Services = dependencies
project.DisabledServices = append(project.DisabledServices, requestedService)
if err := c.ComposeService().Create(ctx, &project, compose.CreateOptions{}); err != nil {
if err := backend.Create(ctx, &project, compose.CreateOptions{}); err != nil {
return err
}
if err := c.ComposeService().Start(ctx, &project, compose.StartOptions{}); err != nil {
if err := backend.Start(ctx, &project, compose.StartOptions{}); err != nil {
return err
}
return nil

View File

@ -19,7 +19,6 @@ package compose
import (
"context"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
@ -30,33 +29,28 @@ type startOptions struct {
*projectOptions
}
func startCommand(p *projectOptions) *cobra.Command {
func startCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := startOptions{
projectOptions: p,
}
startCmd := &cobra.Command{
Use: "start [SERVICE...]",
Short: "Start services",
RunE: func(cmd *cobra.Command, args []string) error {
return runStart(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runStart(ctx, backend, opts, args)
}),
}
return startCmd
}
func runStart(ctx context.Context, opts startOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runStart(ctx context.Context, backend compose.Service, opts startOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Start(ctx, project, compose.StartOptions{})
return "", backend.Start(ctx, project, compose.StartOptions{})
})
return err
}

View File

@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
)
@ -33,17 +32,19 @@ type stopOptions struct {
timeout int
}
func stopCommand(p *projectOptions) *cobra.Command {
func stopCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := stopOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "stop [SERVICE...]",
Short: "Stop services",
RunE: func(cmd *cobra.Command, args []string) error {
PreRun: func(cmd *cobra.Command, args []string) {
opts.timeChanged = cmd.Flags().Changed("timeout")
return runStop(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runStop(ctx, backend, opts, args)
}),
}
flags := cmd.Flags()
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
@ -51,12 +52,7 @@ func stopCommand(p *projectOptions) *cobra.Command {
return cmd
}
func runStop(ctx context.Context, opts stopOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runStop(ctx context.Context, backend compose.Service, opts stopOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
@ -68,7 +64,7 @@ func runStop(ctx context.Context, opts stopOptions, services []string) error {
timeout = &timeoutValue
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Stop(ctx, project, compose.StopOptions{
return "", backend.Stop(ctx, project, compose.StopOptions{
Timeout: timeout,
Services: services,
})

View File

@ -27,37 +27,33 @@ import (
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
)
type topOptions struct {
*projectOptions
}
func topCommand(p *projectOptions) *cobra.Command {
func topCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := topOptions{
projectOptions: p,
}
topCmd := &cobra.Command{
Use: "top",
Short: "Display the running processes",
RunE: func(cmd *cobra.Command, args []string) error {
return runTop(cmd.Context(), opts, args)
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runTop(ctx, backend, opts, args)
}),
}
return topCmd
}
func runTop(ctx context.Context, opts topOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
func runTop(ctx context.Context, backend compose.Service, opts topOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
containers, err := c.ComposeService().Top(ctx, projectName, services)
containers, err := backend.Top(ctx, projectName, services)
if err != nil {
return err
}

View File

@ -33,7 +33,6 @@ import (
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/api/progress"
@ -140,7 +139,7 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
return nil
}
func upCommand(p *projectOptions, contextType string) *cobra.Command {
func upCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command {
opts := upOptions{
composeOptions: &composeOptions{
projectOptions: p,
@ -149,8 +148,10 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
upCmd := &cobra.Command{
Use: "up [SERVICE...]",
Short: "Create and start containers",
RunE: func(cmd *cobra.Command, args []string) error {
PreRun: func(cmd *cobra.Command, args []string) {
opts.timeChanged = cmd.Flags().Changed("timeout")
},
RunE: Adapt(func(ctx context.Context, args []string) error {
switch contextType {
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
if opts.exitCodeFrom != "" {
@ -168,11 +169,11 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
if opts.recreateDeps && opts.noRecreate {
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
}
return runCreateStart(cmd.Context(), opts, args)
return runCreateStart(ctx, backend, opts, args)
default:
return runUp(cmd.Context(), opts, args)
return runUp(ctx, backend, opts, args)
}
},
}),
}
flags := upCmd.Flags()
flags.StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
@ -204,8 +205,8 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
return upCmd
}
func runUp(ctx context.Context, opts upOptions, services []string) error {
c, project, err := setup(ctx, *opts.composeOptions, services)
func runUp(ctx context.Context, backend compose.Service, opts upOptions, services []string) error {
project, err := setup(*opts.composeOptions, services)
if err != nil {
return err
}
@ -216,7 +217,7 @@ func runUp(ctx context.Context, opts upOptions, services []string) error {
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
return "", c.ComposeService().Up(ctx, project, compose.UpOptions{
return "", backend.Up(ctx, project, compose.UpOptions{
Detach: opts.Detach,
QuietPull: opts.quietPull,
})
@ -224,8 +225,8 @@ func runUp(ctx context.Context, opts upOptions, services []string) error {
return err
}
func runCreateStart(ctx context.Context, opts upOptions, services []string) error {
c, project, err := setup(ctx, *opts.composeOptions, services)
func runCreateStart(ctx context.Context, backend compose.Service, opts upOptions, services []string) error {
project, err := setup(*opts.composeOptions, services)
if err != nil {
return err
}
@ -240,7 +241,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
err := c.ComposeService().Create(ctx, project, compose.CreateOptions{
err := backend.Create(ctx, project, compose.CreateOptions{
Services: services,
RemoveOrphans: opts.removeOrphans,
Recreate: opts.recreateStrategy(),
@ -253,7 +254,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
return "", err
}
if opts.Detach {
err = c.ComposeService().Start(ctx, project, compose.StartOptions{})
err = backend.Start(ctx, project, compose.StartOptions{})
}
return "", err
})
@ -285,10 +286,10 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
_, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
go func() {
<-signalChan
c.ComposeService().Kill(ctx, project, compose.KillOptions{}) // nolint:errcheck
backend.Kill(ctx, project, compose.KillOptions{}) // nolint:errcheck
}()
return "", c.ComposeService().Stop(ctx, project, compose.StopOptions{})
return "", backend.Stop(ctx, project, compose.StopOptions{})
})
return err
}
@ -311,7 +312,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
return err
})
err = c.ComposeService().Start(ctx, project, compose.StartOptions{
err = backend.Start(ctx, project, compose.StartOptions{
Attach: func(event compose.ContainerEvent) {
queue <- event
},
@ -351,15 +352,10 @@ func setServiceScale(project *types.Project, name string, replicas int) error {
return fmt.Errorf("unknown service %q", name)
}
func setup(ctx context.Context, opts composeOptions, services []string) (*client.Client, *types.Project, error) {
c, err := client.New(ctx)
if err != nil {
return nil, nil, err
}
func setup(opts composeOptions, services []string) (*types.Project, error) {
project, err := opts.toProject(services)
if err != nil {
return nil, nil, err
return nil, err
}
if opts.DomainName != "" {
@ -397,7 +393,7 @@ func setup(ctx context.Context, opts composeOptions, services []string) (*client
project.Services = services
}
return c, project, nil
return project, nil
}
type printer struct {

View File

@ -17,9 +17,11 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/docker/compose-cli/api/config"
@ -44,3 +46,36 @@ func confDir() string {
home, _ := os.UserHomeDir()
return filepath.Join(home, config.ConfigFileDir)
}
// GetCurrentContext get current context based on opts, env vars
func GetCurrentContext(contextOpt string, configDir string, hosts []string) string {
// host and context flags cannot be both set at the same time -- the local backend enforces this when resolving hostname
// -H flag disables context --> set default as current
if len(hosts) > 0 {
return "default"
}
// DOCKER_HOST disables context --> set default as current
if _, present := os.LookupEnv("DOCKER_HOST"); present {
return "default"
}
res := contextOpt
if res == "" {
// check if DOCKER_CONTEXT env variable was set
if _, present := os.LookupEnv("DOCKER_CONTEXT"); present {
res = os.Getenv("DOCKER_CONTEXT")
}
if res == "" {
config, err := config.LoadFile(configDir)
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING"))
return "default"
}
res = config.CurrentContext
}
}
if res == "" {
res = "default"
}
return res
}

61
cli/config/flags_test.go Normal file
View File

@ -0,0 +1,61 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"gotest.tools/v3/assert"
"github.com/docker/compose-cli/api/config"
)
var contextSetConfig = []byte(`{
"currentContext": "some-context"
}`)
func TestDetermineCurrentContext(t *testing.T) {
d, err := ioutil.TempDir("", "")
// nolint errcheck
defer os.RemoveAll(d)
assert.NilError(t, err)
err = ioutil.WriteFile(filepath.Join(d, config.ConfigFileName), contextSetConfig, 0644)
assert.NilError(t, err)
// If nothing set, fallback to default
c := GetCurrentContext("", "", []string{})
assert.Equal(t, c, "default")
// If context flag set, use that
c = GetCurrentContext("other-context", "", []string{})
assert.Equal(t, c, "other-context")
// If no context flag, use config
c = GetCurrentContext("", d, []string{})
assert.Equal(t, c, "some-context")
// Ensure context flag overrides config
c = GetCurrentContext("other-context", d, []string{})
assert.Equal(t, "other-context", c)
// Ensure host flag overrides context
c = GetCurrentContext("other-context", d, []string{"hostname"})
assert.Equal(t, "default", c)
}

View File

@ -29,9 +29,6 @@ import (
"time"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config"
cliflags "github.com/docker/cli/cli/flags"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -48,6 +45,7 @@ import (
"github.com/docker/compose-cli/cli/cmd/logout"
"github.com/docker/compose-cli/cli/cmd/run"
"github.com/docker/compose-cli/cli/cmd/volume"
cliconfig "github.com/docker/compose-cli/cli/config"
"github.com/docker/compose-cli/cli/metrics"
"github.com/docker/compose-cli/cli/mobycli"
cliopts "github.com/docker/compose-cli/cli/options"
@ -62,7 +60,6 @@ import (
var (
contextAgnosticCommands = map[string]struct{}{
"compose": {},
"context": {},
"login": {},
"logout": {},
@ -198,7 +195,7 @@ func main() {
configDir := opts.Config
config.WithDir(configDir)
currentContext := determineCurrentContext(opts.Context, configDir, opts.Hosts)
currentContext := cliconfig.GetCurrentContext(opts.Context, configDir, opts.Hosts)
apicontext.WithCurrentContext(currentContext)
s, err := store.New(configDir)
@ -221,7 +218,7 @@ func main() {
root.AddCommand(
run.Command(ctype),
compose.Command(ctype),
compose.RootCommand(ctype, service.ComposeService()),
volume.Command(ctype),
)
@ -234,27 +231,7 @@ func main() {
func getBackend(ctype string, configDir string, opts cliopts.GlobalOpts) (backend.Service, error) {
switch ctype {
case store.DefaultContextType, store.LocalContextType:
configFile, err := cliconfig.Load(configDir)
if err != nil {
return nil, err
}
options := cliflags.CommonOptions{
Context: opts.Context,
Debug: opts.Debug,
Hosts: opts.Hosts,
LogLevel: opts.LogLevel,
}
if opts.TLSVerify {
options.TLS = opts.TLS
options.TLSVerify = opts.TLSVerify
options.TLSOptions = opts.TLSOptions
}
apiClient, err := command.NewAPIClientFromFlags(&options, configFile)
if err != nil {
return nil, err
}
return local.NewService(apiClient), nil
return local.GetLocalBackend(configDir, opts)
}
service, err := backend.Get(ctype)
if errdefs.IsNotFoundError(err) {
@ -311,6 +288,7 @@ func exit(ctx string, err error, ctype string) {
}
if compose.Warning != "" {
logrus.Warn(err)
fmt.Fprintln(os.Stderr, compose.Warning)
}
@ -354,38 +332,6 @@ func newSigContext() (context.Context, func()) {
return ctx, cancel
}
func determineCurrentContext(flag string, configDir string, hosts []string) string {
// host and context flags cannot be both set at the same time -- the local backend enforces this when resolving hostname
// -H flag disables context --> set default as current
if len(hosts) > 0 {
return "default"
}
// DOCKER_HOST disables context --> set default as current
if _, present := os.LookupEnv("DOCKER_HOST"); present {
return "default"
}
res := flag
if res == "" {
// check if DOCKER_CONTEXT env variable was set
if _, present := os.LookupEnv("DOCKER_CONTEXT"); present {
res = os.Getenv("DOCKER_CONTEXT")
}
if res == "" {
config, err := config.LoadFile(configDir)
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING"))
return "default"
}
res = config.CurrentContext
}
}
if res == "" {
res = "default"
}
return res
}
func walk(c *cobra.Command, f func(*cobra.Command)) {
f(c)
for _, c := range c.Commands() {

View File

@ -17,53 +17,17 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"gotest.tools/v3/assert"
"github.com/docker/compose-cli/api/config"
"github.com/docker/compose-cli/cli/cmd"
"github.com/docker/compose-cli/cli/cmd/context"
"github.com/docker/compose-cli/cli/cmd/login"
"github.com/docker/compose-cli/cli/cmd/run"
)
var contextSetConfig = []byte(`{
"currentContext": "some-context"
}`)
func TestDetermineCurrentContext(t *testing.T) {
d, err := ioutil.TempDir("", "")
// nolint errcheck
defer os.RemoveAll(d)
assert.NilError(t, err)
err = ioutil.WriteFile(filepath.Join(d, config.ConfigFileName), contextSetConfig, 0644)
assert.NilError(t, err)
// If nothing set, fallback to default
c := determineCurrentContext("", "", []string{})
assert.Equal(t, c, "default")
// If context flag set, use that
c = determineCurrentContext("other-context", "", []string{})
assert.Equal(t, c, "other-context")
// If no context flag, use config
c = determineCurrentContext("", d, []string{})
assert.Equal(t, c, "some-context")
// Ensure context flag overrides config
c = determineCurrentContext("other-context", d, []string{})
assert.Equal(t, "other-context", c)
// Ensure host flag overrides context
c = determineCurrentContext("other-context", d, []string{"hostname"})
assert.Equal(t, "default", c)
}
func TestCheckOwnCommand(t *testing.T) {
assert.Assert(t, isContextAgnosticCommand(login.Command()))
assert.Assert(t, isContextAgnosticCommand(context.Command()))

View File

@ -55,3 +55,26 @@ var (
// PullFailure failure while pulling image
PullFailure = FailureCategory{MetricsStatus: PullFailureStatus, ExitCode: 18}
)
//ByExitCode retrieve FailureCategory based on command exit code
func ByExitCode(exitCode int) FailureCategory {
switch exitCode {
case 0:
return FailureCategory{MetricsStatus: SuccessStatus, ExitCode: 0}
case 14:
return FileNotFoundFailure
case 15:
return ComposeParseFailure
case 16:
return CommandSyntaxFailure
case 17:
return BuildFailure
case 18:
return PullFailure
case 130:
return FailureCategory{MetricsStatus: CanceledStatus, ExitCode: exitCode}
default:
return FailureCategory{MetricsStatus: FailureStatus, ExitCode: exitCode}
}
}

View File

@ -68,12 +68,8 @@ func Exec(root *cobra.Command) {
if err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
exitCode := exiterr.ExitCode()
if exitCode == 130 {
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.CanceledStatus)
} else {
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
}
os.Exit(exiterr.ExitCode())
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.ByExitCode(exitCode).MetricsStatus)
os.Exit(exitCode)
}
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
fmt.Fprintln(os.Stderr, err)

View File

@ -35,7 +35,7 @@ const descriptionSourcePath = "docs/reference/"
func generateCliYaml(opts *options) error {
cmd := &cobra.Command{Use: "docker"}
cmd.AddCommand(compose.Command("local"))
cmd.AddCommand(compose.RootCommand("local", nil))
disableFlagsInUseLine(cmd)
source := filepath.Join(opts.source, descriptionSourcePath)
if err := loadLongDescription(cmd, source); err != nil {

View File

@ -17,8 +17,9 @@
package local
import (
local_compose "github.com/docker/compose-cli/local/compose"
"os"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/docker/client"
"github.com/docker/compose-cli/api/backend"
@ -29,6 +30,7 @@ import (
"github.com/docker/compose-cli/api/resources"
"github.com/docker/compose-cli/api/secrets"
"github.com/docker/compose-cli/api/volumes"
local_compose "github.com/docker/compose-cli/local/compose"
)
const backendType = store.EcsLocalSimulationContextType
@ -50,7 +52,7 @@ func service() (backend.Service, error) {
return &ecsLocalSimulation{
moby: apiClient,
compose: local_compose.NewComposeService(apiClient),
compose: local_compose.NewComposeService(apiClient, cliconfig.LoadDefaultConfigFile(os.Stderr)),
}, nil
}

View File

@ -17,6 +17,11 @@
package local
import (
"os"
"github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/docker/client"
"github.com/docker/compose-cli/api/backend"
@ -25,6 +30,7 @@ import (
"github.com/docker/compose-cli/api/resources"
"github.com/docker/compose-cli/api/secrets"
"github.com/docker/compose-cli/api/volumes"
cliopts "github.com/docker/compose-cli/cli/options"
local_compose "github.com/docker/compose-cli/local/compose"
)
@ -36,13 +42,39 @@ type local struct {
// NewService build a backend for "local" context, using Docker API client
func NewService(apiClient client.APIClient) backend.Service {
file := cliconfig.LoadDefaultConfigFile(os.Stderr)
return &local{
containerService: &containerService{apiClient},
volumeService: &volumeService{apiClient},
composeService: local_compose.NewComposeService(apiClient),
composeService: local_compose.NewComposeService(apiClient, file),
}
}
// GetLocalBackend initialize local backend
func GetLocalBackend(configDir string, opts cliopts.GlobalOpts) (backend.Service, error) {
configFile, err := cliconfig.Load(configDir)
if err != nil {
return nil, err
}
options := cliflags.CommonOptions{
Context: opts.Context,
Debug: opts.Debug,
Hosts: opts.Hosts,
LogLevel: opts.LogLevel,
}
if opts.TLSVerify {
options.TLS = opts.TLS
options.TLSVerify = opts.TLSVerify
options.TLSOptions = opts.TLSOptions
}
apiClient, err := command.NewAPIClientFromFlags(&options, configFile)
if err != nil {
return nil, err
}
return NewService(apiClient), nil
}
func (s *local) ContainerService() containers.Service {
return s.containerService
}

View File

@ -28,13 +28,11 @@ import (
"github.com/docker/buildx/driver"
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
"github.com/docker/buildx/util/progress"
cliconfig "github.com/docker/cli/cli/config"
moby "github.com/docker/docker/api/types"
bclient "github.com/moby/buildkit/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/config"
composeprogress "github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/cli/metrics"
"github.com/docker/compose-cli/utils"
@ -195,12 +193,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opts
}
const drivername = "default"
configFile, err := cliconfig.Load(config.Dir())
if err != nil {
return nil, err
}
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, configFile, nil, nil, "", nil, nil, project.WorkingDir)
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
if err != nil {
return nil, err
}

View File

@ -26,20 +26,23 @@ import (
"github.com/docker/compose-cli/api/errdefs"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/config/configfile"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/sanathkr/go-yaml"
)
// NewComposeService create a local implementation of the compose.Service API
func NewComposeService(apiClient client.APIClient) compose.Service {
func NewComposeService(apiClient client.APIClient, configFile *configfile.ConfigFile) compose.Service {
return &composeService{
apiClient: apiClient,
apiClient: apiClient,
configFile: configFile,
}
}
type composeService struct {
apiClient client.APIClient
apiClient client.APIClient
configFile *configfile.ConfigFile
}
func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {

View File

@ -27,23 +27,17 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/distribution/distribution/v3/reference"
"github.com/docker/buildx/driver"
cliconfig "github.com/docker/cli/cli/config"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/config"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/cli/metrics"
)
func (s *composeService) Pull(ctx context.Context, project *types.Project, opts compose.PullOptions) error {
configFile, err := cliconfig.Load(config.Dir())
if err != nil {
return err
}
info, err := s.apiClient.Info(ctx)
if err != nil {
return err
@ -67,7 +61,7 @@ func (s *composeService) Pull(ctx context.Context, project *types.Project, opts
continue
}
eg.Go(func() error {
err := s.pullServiceImage(ctx, service, info, configFile, w)
err := s.pullServiceImage(ctx, service, info, s.configFile, w)
if err != nil {
if !opts.IgnoreFailures {
return err

View File

@ -20,6 +20,7 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
@ -131,6 +132,26 @@ func TestLocalComposeUp(t *testing.T) {
})
}
func TestComposeUsingCliPlugin(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
err := os.Remove(filepath.Join(c.ConfigDir, "cli-plugins", "docker-compose"))
assert.NilError(t, err)
res := c.RunDockerOrExitError("compose", "ls")
res.Assert(t, icmd.Expected{Err: "'compose' is not a docker command", ExitCode: 1})
}
func TestComposeCliPluginWithoutCloudIntegration(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
err := os.Remove(filepath.Join(binDir, "docker"))
assert.NilError(t, err)
err = os.Rename(filepath.Join(binDir, "com.docker.cli"), filepath.Join(binDir, "docker"))
assert.NilError(t, err)
res := c.RunDockerOrExitError("compose", "ls")
res.Assert(t, icmd.Expected{Out: "NAME STATUS", ExitCode: 0})
}
func TestComposePull(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)

View File

@ -0,0 +1,3 @@
services:
service1:
build: service1

View File

@ -0,0 +1,17 @@
# Copyright 2020 Docker Compose CLI authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM busybox
RUN sleep infinity

View File

@ -17,7 +17,11 @@
package e2e
import (
"bytes"
"fmt"
"os/exec"
"strings"
"syscall"
"testing"
"time"
@ -84,3 +88,69 @@ func TestComposeMetrics(t *testing.T) {
}, usage)
})
}
func TestComposeCancel(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
s := NewMetricsServer(c.MetricsSocket())
s.Start()
defer s.Stop()
started := false
for i := 0; i < 30; i++ {
c.RunDockerCmd("help", "ps")
if len(s.GetUsage()) > 0 {
started = true
fmt.Printf(" [%s] Server up in %d ms\n", t.Name(), i*100)
break
}
time.Sleep(100 * time.Millisecond)
}
assert.Assert(t, started, "Metrics mock server not available after 3 secs")
t.Run("metrics on cancel Compose build", func(t *testing.T) {
s.ResetUsage()
c.RunDockerCmd("compose", "ls")
buildProjectPath := "../compose/fixtures/build-infinite/docker-compose.yml"
// require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal.
// sending kill signal
cmd, stdout, stderr, err := StartWithNewGroupID(c.NewDockerCmd("compose", "-f", buildProjectPath, "build", "--progress", "plain"))
assert.NilError(t, err)
c.WaitForCondition(func() (bool, string) {
out := stdout.String()
errors := stderr.String()
return strings.Contains(out, "RUN sleep infinity"), fmt.Sprintf("'RUN sleep infinity' not found in : \n%s\nStderr: \n%s\n", out, errors)
}, 30*time.Second, 1*time.Second)
err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default
assert.NilError(t, err)
c.WaitForCondition(func() (bool, string) {
out := stdout.String()
errors := stderr.String()
return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out, errors)
}, 10*time.Second, 1*time.Second)
usage := s.GetUsage()
assert.DeepEqual(t, []string{
`{"command":"compose ls","context":"moby","source":"cli","status":"success"}`,
`{"command":"compose build","context":"moby","source":"cli","status":"canceled"}`,
}, usage)
})
}
func StartWithNewGroupID(command icmd.Cmd) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) {
cmd := exec.Command(command.Command[0], command.Command[1:]...)
cmd.Env = command.Env
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Start()
return cmd, &stdout, &stderr, err
}

66
main.go Normal file
View File

@ -0,0 +1,66 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"strings"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
api "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/cli/cmd/compose"
"github.com/docker/compose-cli/cli/metrics"
"github.com/docker/compose-cli/internal"
impl "github.com/docker/compose-cli/local/compose"
)
func main() {
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
lazyInit := api.ServiceDelegator{
Delegate: api.NoImpl{},
}
cmd := compose.RootCommand(store.DefaultContextType, &lazyInit)
originalPreRun := cmd.PersistentPreRunE
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
return err
}
lazyInit.Delegate = impl.NewComposeService(dockerCli.Client(), dockerCli.ConfigFile())
if originalPreRun != nil {
return originalPreRun(cmd, args)
}
return nil
}
cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
return dockercli.StatusError{
StatusCode: metrics.CommandSyntaxFailure.ExitCode,
Status: err.Error(),
}
})
return cmd
},
manager.Metadata{
SchemaVersion: "0.1.0",
Vendor: "Docker Inc.",
Version: strings.TrimPrefix(internal.Version, "v"),
})
}

View File

@ -85,6 +85,17 @@ func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
_ = os.RemoveAll(d)
})
_ = os.MkdirAll(filepath.Join(d, "cli-plugins"), 0755)
composePluginFile := "docker-compose"
if runtime.GOOS == "windows" {
composePluginFile += ".exe"
}
composePlugin, _ := findExecutable(composePluginFile, []string{"../../bin", "../../../bin"})
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", composePluginFile))
if err != nil {
panic(err)
}
return &E2eCLI{binDir, d, t}
}
@ -117,7 +128,7 @@ func SetupExistingCLI() (string, func(), error) {
return "", nil, err
}
bin, err := findExecutable([]string{"../../bin", "../../../bin"})
bin, err := findExecutable(DockerExecutableName, []string{"../../bin", "../../../bin"})
if err != nil {
return "", nil, err
}
@ -133,9 +144,9 @@ func SetupExistingCLI() (string, func(), error) {
return d, cleanup, nil
}
func findExecutable(paths []string) (string, error) {
func findExecutable(executableName string, paths []string) (string, error) {
for _, p := range paths {
bin, err := filepath.Abs(path.Join(p, DockerExecutableName))
bin, err := filepath.Abs(path.Join(p, executableName))
if err != nil {
return "", err
}
@ -241,6 +252,18 @@ func (c *E2eCLI) WaitForCmdResult(command icmd.Cmd, predicate func(*icmd.Result)
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
}
// WaitForCondition wait for predicate to execute to true
func (c *E2eCLI) WaitForCondition(predicate func() (bool, string), timeout time.Duration, delay time.Duration) {
checkStopped := func(logt poll.LogT) poll.Result {
pass, description := predicate()
if !pass {
return poll.Continue("Condition not met: %q", description)
}
return poll.Success()
}
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
}
// PathEnvVar returns path (os sensitive) for running test
func (c *E2eCLI) PathEnvVar() string {
path := c.BinDir + ":" + os.Getenv("PATH")