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} \ GOARCH=${TARGETARCH} \
BUILD_TAGS=${BUILD_TAGS} \ BUILD_TAGS=${BUILD_TAGS} \
GIT_TAG=${GIT_TAG} \ 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 FROM base AS make-cross
ARG BUILD_TAGS ARG BUILD_TAGS
@ -83,7 +83,7 @@ RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/root/.cache/go-build \
BUILD_TAGS=${BUILD_TAGS} \ BUILD_TAGS=${BUILD_TAGS} \
GIT_TAG=${GIT_TAG} \ 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 FROM scratch AS protos
COPY --from=make-protos /compose-cli/cli/server/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?=bin/docker
BINARY_WITH_EXTENSION=$(BINARY)$(EXTENSION) BINARY_WITH_EXTENSION=$(BINARY)$(EXTENSION)
COMPOSE_BINARY?=bin/docker-compose
COMPOSE_BINARY_WITH_EXTENSION=$(COMPOSE_BINARY)$(EXTENSION)
WORK_DIR:=$(shell mktemp -d) WORK_DIR:=$(shell mktemp -d)
TAGS:= TAGS:=
@ -42,9 +45,22 @@ ifdef BUILD_TAGS
LINT_TAGS=--build-tags $(BUILD_TAGS) LINT_TAGS=--build-tags $(BUILD_TAGS)
endif 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)),) 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 endif
all: cli all: cli
@ -54,11 +70,15 @@ protos:
protoc -I. --go_out=plugins=grpc,paths=source_relative:. ${PROTOS} protoc -I. --go_out=plugins=grpc,paths=source_relative:. ${PROTOS}
.PHONY: cli .PHONY: cli
cli: cli: compose-plugin
GOOS=${GOOS} GOARCH=${GOARCH} $(GO_BUILD) $(TAGS) -o $(BINARY_WITH_EXTENSION) ./cli 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 .PHONY: cross
cross: cross: cross-compose-plugin
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-amd64 ./cli 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 GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-arm64 ./cli
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(BINARY)-linux-armv6 ./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=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(BINARY)-darwin-arm64 ./cli
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(BINARY)-windows-amd64.exe ./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 .PHONY: test
test: test:
go test $(TAGS) -cover $(shell go list $(TAGS) ./... | grep -vE 'e2e') go test $(TAGS) -cover $(shell go list $(TAGS) ./... | grep -vE 'e2e')
@ -90,14 +120,15 @@ check-go-mod:
.PHONY: package .PHONY: package
package: cross package: cross
mkdir -p dist mkdir -p dist
tar -czf dist/docker-linux-amd64.tar.gz $(TAR_TRANSFORM) packaging/LICENSE $(BINARY)-linux-amd64 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 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 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 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 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 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 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) rm -r $(WORK_DIR)
.PHONY: yamldocs .PHONY: yamldocs

View File

@ -24,7 +24,6 @@ import (
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/api/progress"
) )
@ -40,14 +39,14 @@ type buildOptions struct {
memory string memory string
} }
func buildCommand(p *projectOptions) *cobra.Command { func buildCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := buildOptions{ opts := buildOptions{
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "build [SERVICE...]", Use: "build [SERVICE...]",
Short: "Build or rebuild services", 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 != "" { if opts.memory != "" {
fmt.Println("WARNING --memory is ignored as not supported in buildkit.") fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
} }
@ -58,8 +57,8 @@ func buildCommand(p *projectOptions) *cobra.Command {
} }
os.Stdout = devnull 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().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.") 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 return cmd
} }
func runBuild(ctx context.Context, opts buildOptions, services []string) error { func runBuild(ctx context.Context, backend compose.Service, opts buildOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
project, err := opts.toProject(services) project, err := opts.toProject(services)
if err != nil { if err != nil {
return err return err
} }
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) { _, 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, Pull: opts.pull,
Progress: opts.progress, Progress: opts.progress,
Args: types.NewMapping(opts.args), Args: types.NewMapping(opts.args),

View File

@ -17,22 +17,64 @@
package compose package compose
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"os/signal"
"strings" "strings"
"syscall"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
dockercli "github.com/docker/cli/cli"
"github.com/morikuni/aec" "github.com/morikuni/aec"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "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/context/store"
"github.com/docker/compose-cli/api/errdefs"
"github.com/docker/compose-cli/cli/formatter" "github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/cli/metrics" "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 // Warning is a global warning to be displayed to user on command failure
var Warning string var Warning string
@ -104,8 +146,8 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
cli.WithName(o.ProjectName))...) cli.WithName(o.ProjectName))...)
} }
// Command returns the compose command with its child commands // RootCommand returns the compose command with its child commands
func Command(contextType string) *cobra.Command { func RootCommand(contextType string, backend compose.Service) *cobra.Command {
opts := projectOptions{} opts := projectOptions{}
var ansi string var ansi string
var noAnsi bool var noAnsi bool
@ -119,9 +161,20 @@ func Command(contextType string) *cobra.Command {
return cmd.Help() return cmd.Help()
} }
_ = 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 { 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 noAnsi {
if ansi != "auto" { if ansi != "auto" {
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`) 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( command.AddCommand(
upCommand(&opts, contextType), upCommand(&opts, contextType, backend),
downCommand(&opts, contextType), downCommand(&opts, contextType, backend),
startCommand(&opts), startCommand(&opts, backend),
restartCommand(&opts), restartCommand(&opts, backend),
stopCommand(&opts), stopCommand(&opts, backend),
psCommand(&opts), psCommand(&opts, backend),
listCommand(contextType), listCommand(contextType, backend),
logsCommand(&opts, contextType), logsCommand(&opts, contextType, backend),
convertCommand(&opts), convertCommand(&opts, backend),
killCommand(&opts), killCommand(&opts, backend),
runCommand(&opts), runCommand(&opts, backend),
removeCommand(&opts), removeCommand(&opts, backend),
execCommand(&opts), execCommand(&opts, backend),
pauseCommand(&opts), pauseCommand(&opts, backend),
unpauseCommand(&opts), unpauseCommand(&opts, backend),
topCommand(&opts), topCommand(&opts, backend),
eventsCommand(&opts), eventsCommand(&opts, backend),
portCommand(&opts), portCommand(&opts, backend),
imagesCommand(&opts), imagesCommand(&opts, backend),
versionCommand(), versionCommand(),
) )
if contextType == store.LocalContextType || contextType == store.DefaultContextType { if contextType == store.LocalContextType || contextType == store.DefaultContextType {
command.AddCommand( command.AddCommand(
buildCommand(&opts), buildCommand(&opts, backend),
pushCommand(&opts), pushCommand(&opts, backend),
pullCommand(&opts), pullCommand(&opts, backend),
createCommand(&opts), createCommand(&opts, backend),
) )
} }
command.Flags().SetInterspersed(false) command.Flags().SetInterspersed(false)

View File

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

View File

@ -17,9 +17,12 @@
package compose package compose
import ( import (
"context"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/api/compose"
) )
type createOptions struct { type createOptions struct {
@ -28,21 +31,21 @@ type createOptions struct {
noRecreate bool noRecreate bool
} }
func createCommand(p *projectOptions) *cobra.Command { func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := createOptions{ opts := createOptions{
composeOptions: &composeOptions{}, composeOptions: &composeOptions{},
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create [SERVICE...]", Use: "create [SERVICE...]",
Short: "Creates containers for a 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 { if opts.Build && opts.noBuild {
return fmt.Errorf("--build and --no-build are incompatible") return fmt.Errorf("--build and --no-build are incompatible")
} }
if opts.forceRecreate && opts.noRecreate { if opts.forceRecreate && opts.noRecreate {
return fmt.Errorf("--force-recreate and --no-recreate are incompatible") return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
} }
return runCreateStart(cmd.Context(), upOptions{ return runCreateStart(ctx, backend, upOptions{
composeOptions: &composeOptions{ composeOptions: &composeOptions{
projectOptions: p, projectOptions: p,
Build: opts.Build, Build: opts.Build,
@ -52,7 +55,7 @@ func createCommand(p *projectOptions) *cobra.Command {
forceRecreate: opts.forceRecreate, forceRecreate: opts.forceRecreate,
noRecreate: opts.noRecreate, noRecreate: opts.noRecreate,
}, args) }, args)
}, }),
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.") 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/compose-spec/compose-go/types"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store" "github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/api/progress"
@ -39,22 +38,24 @@ type downOptions struct {
images string images string
} }
func downCommand(p *projectOptions, contextType string) *cobra.Command { func downCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command {
opts := downOptions{ opts := downOptions{
projectOptions: p, projectOptions: p,
} }
downCmd := &cobra.Command{ downCmd := &cobra.Command{
Use: "down", Use: "down",
Short: "Stop and remove containers, networks", 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") opts.timeChanged = cmd.Flags().Changed("timeout")
},
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.images != "" { if opts.images != "" {
if opts.images != "all" && opts.images != "local" { if opts.images != "all" && opts.images != "local" {
return fmt.Errorf("invalid value for --rmi: %q", opts.images) return fmt.Errorf("invalid value for --rmi: %q", opts.images)
} }
} }
return runDown(cmd.Context(), opts) return runDown(ctx, backend, opts)
}, }),
} }
flags := downCmd.Flags() flags := downCmd.Flags()
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") 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 return downCmd
} }
func runDown(ctx context.Context, opts downOptions) error { func runDown(ctx context.Context, backend compose.Service, opts downOptions) error {
c, err := client.New(ctx) _, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
if err != nil {
return err
}
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
name := opts.ProjectName name := opts.ProjectName
var project *types.Project var project *types.Project
if opts.ProjectName == "" { if opts.ProjectName == "" {
@ -91,7 +87,7 @@ func runDown(ctx context.Context, opts downOptions) error {
timeoutValue := time.Duration(opts.timeout) * time.Second timeoutValue := time.Duration(opts.timeout) * time.Second
timeout = &timeoutValue timeout = &timeoutValue
} }
return name, c.ComposeService().Down(ctx, name, compose.DownOptions{ return name, backend.Down(ctx, name, compose.DownOptions{
RemoveOrphans: opts.removeOrphans, RemoveOrphans: opts.removeOrphans,
Project: project, Project: project,
Timeout: timeout, Timeout: timeout,

View File

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

View File

@ -24,7 +24,6 @@ import (
"github.com/containerd/console" "github.com/containerd/console"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
) )
@ -43,7 +42,7 @@ type execOpts struct {
privileged bool privileged bool
} }
func execCommand(p *projectOptions) *cobra.Command { func execCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := execOpts{ opts := execOpts{
composeOptions: &composeOptions{ composeOptions: &composeOptions{
projectOptions: p, projectOptions: p,
@ -53,13 +52,13 @@ func execCommand(p *projectOptions) *cobra.Command {
Use: "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]", Use: "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]",
Short: "Execute a command in a running container.", Short: "Execute a command in a running container.",
Args: cobra.MinimumNArgs(2), 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 { if len(args) > 1 {
opts.command = args[1:] opts.command = args[1:]
} }
opts.service = args[0] 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.") 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 return runCmd
} }
func runExec(ctx context.Context, opts execOpts) error { func runExec(ctx context.Context, backend compose.Service, opts execOpts) error {
c, err := client.New(ctx)
if err != nil {
return err
}
project, err := opts.toProject(nil) project, err := opts.toProject(nil)
if err != nil { if err != nil {
return err return err
@ -114,5 +108,5 @@ func runExec(ctx context.Context, opts execOpts) error {
execOpts.Writer = con execOpts.Writer = con
execOpts.Reader = 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/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/cli/formatter" "github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/utils" "github.com/docker/compose-cli/utils"
@ -40,33 +39,28 @@ type imageOptions struct {
Quiet bool Quiet bool
} }
func imagesCommand(p *projectOptions) *cobra.Command { func imagesCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := imageOptions{ opts := imageOptions{
projectOptions: p, projectOptions: p,
} }
imgCmd := &cobra.Command{ imgCmd := &cobra.Command{
Use: "images [SERVICE...]", Use: "images [SERVICE...]",
Short: "List images used by the created containers", Short: "List images used by the created containers",
RunE: func(cmd *cobra.Command, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(cmd.Context(), opts, args) return runImages(ctx, backend, opts, args)
}, }),
} }
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs") imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
return imgCmd return imgCmd
} }
func runImages(ctx context.Context, opts imageOptions, services []string) error { func runImages(ctx context.Context, backend compose.Service, opts imageOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
projectName, err := opts.toProjectName() projectName, err := opts.toProjectName()
if err != nil { if err != nil {
return err return err
} }
images, err := c.ComposeService().Images(ctx, projectName, compose.ImagesOptions{ images, err := backend.Images(ctx, projectName, compose.ImagesOptions{
Services: services, Services: services,
}) })
if err != nil { if err != nil {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/utils/prompt" "github.com/docker/compose-cli/utils/prompt"
@ -36,7 +35,7 @@ type removeOptions struct {
volumes bool volumes bool
} }
func removeCommand(p *projectOptions) *cobra.Command { func removeCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := removeOptions{ opts := removeOptions{
projectOptions: p, 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". can override this with -v. To list all volumes, use "docker volume ls".
Any data which is not in a volume will be lost.`, Any data which is not in a volume will be lost.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runRemove(cmd.Context(), opts, args) return runRemove(ctx, backend, opts, args)
}, }),
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal") 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 return cmd
} }
func runRemove(ctx context.Context, opts removeOptions, services []string) error { func runRemove(ctx context.Context, backend compose.Service, opts removeOptions, services []string) error {
c, err := client.New(ctx)
if err != nil {
return err
}
project, err := opts.toProject(services) project, err := opts.toProject(services)
if err != nil { if err != nil {
return err return err
@ -73,7 +67,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
if opts.stop { if opts.stop {
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) { _, 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, Services: services,
}) })
return "", err 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, DryRun: true,
Services: services, 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 = 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, Volumes: opts.volumes,
Force: opts.force, Force: opts.force,
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,9 +17,11 @@
package config package config
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/docker/compose-cli/api/config" "github.com/docker/compose-cli/api/config"
@ -44,3 +46,36 @@ func confDir() string {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
return filepath.Join(home, config.ConfigFileDir) 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" "time"
"github.com/docker/cli/cli" "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/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -48,6 +45,7 @@ import (
"github.com/docker/compose-cli/cli/cmd/logout" "github.com/docker/compose-cli/cli/cmd/logout"
"github.com/docker/compose-cli/cli/cmd/run" "github.com/docker/compose-cli/cli/cmd/run"
"github.com/docker/compose-cli/cli/cmd/volume" "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/metrics"
"github.com/docker/compose-cli/cli/mobycli" "github.com/docker/compose-cli/cli/mobycli"
cliopts "github.com/docker/compose-cli/cli/options" cliopts "github.com/docker/compose-cli/cli/options"
@ -62,7 +60,6 @@ import (
var ( var (
contextAgnosticCommands = map[string]struct{}{ contextAgnosticCommands = map[string]struct{}{
"compose": {},
"context": {}, "context": {},
"login": {}, "login": {},
"logout": {}, "logout": {},
@ -198,7 +195,7 @@ func main() {
configDir := opts.Config configDir := opts.Config
config.WithDir(configDir) config.WithDir(configDir)
currentContext := determineCurrentContext(opts.Context, configDir, opts.Hosts) currentContext := cliconfig.GetCurrentContext(opts.Context, configDir, opts.Hosts)
apicontext.WithCurrentContext(currentContext) apicontext.WithCurrentContext(currentContext)
s, err := store.New(configDir) s, err := store.New(configDir)
@ -221,7 +218,7 @@ func main() {
root.AddCommand( root.AddCommand(
run.Command(ctype), run.Command(ctype),
compose.Command(ctype), compose.RootCommand(ctype, service.ComposeService()),
volume.Command(ctype), volume.Command(ctype),
) )
@ -234,27 +231,7 @@ func main() {
func getBackend(ctype string, configDir string, opts cliopts.GlobalOpts) (backend.Service, error) { func getBackend(ctype string, configDir string, opts cliopts.GlobalOpts) (backend.Service, error) {
switch ctype { switch ctype {
case store.DefaultContextType, store.LocalContextType: case store.DefaultContextType, store.LocalContextType:
configFile, err := cliconfig.Load(configDir) return local.GetLocalBackend(configDir, opts)
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
} }
service, err := backend.Get(ctype) service, err := backend.Get(ctype)
if errdefs.IsNotFoundError(err) { if errdefs.IsNotFoundError(err) {
@ -311,6 +288,7 @@ func exit(ctx string, err error, ctype string) {
} }
if compose.Warning != "" { if compose.Warning != "" {
logrus.Warn(err)
fmt.Fprintln(os.Stderr, compose.Warning) fmt.Fprintln(os.Stderr, compose.Warning)
} }
@ -354,38 +332,6 @@ func newSigContext() (context.Context, func()) {
return ctx, cancel 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)) { func walk(c *cobra.Command, f func(*cobra.Command)) {
f(c) f(c)
for _, c := range c.Commands() { for _, c := range c.Commands() {

View File

@ -17,53 +17,17 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"gotest.tools/v3/assert" "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"
"github.com/docker/compose-cli/cli/cmd/context" "github.com/docker/compose-cli/cli/cmd/context"
"github.com/docker/compose-cli/cli/cmd/login" "github.com/docker/compose-cli/cli/cmd/login"
"github.com/docker/compose-cli/cli/cmd/run" "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) { func TestCheckOwnCommand(t *testing.T) {
assert.Assert(t, isContextAgnosticCommand(login.Command())) assert.Assert(t, isContextAgnosticCommand(login.Command()))
assert.Assert(t, isContextAgnosticCommand(context.Command())) assert.Assert(t, isContextAgnosticCommand(context.Command()))

View File

@ -55,3 +55,26 @@ var (
// PullFailure failure while pulling image // PullFailure failure while pulling image
PullFailure = FailureCategory{MetricsStatus: PullFailureStatus, ExitCode: 18} 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 err != nil {
if exiterr, ok := err.(*exec.ExitError); ok { if exiterr, ok := err.(*exec.ExitError); ok {
exitCode := exiterr.ExitCode() exitCode := exiterr.ExitCode()
if exitCode == 130 { metrics.Track(store.DefaultContextType, os.Args[1:], metrics.ByExitCode(exitCode).MetricsStatus)
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.CanceledStatus) os.Exit(exitCode)
} else {
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
}
os.Exit(exiterr.ExitCode())
} }
metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus) metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)

View File

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

View File

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

View File

@ -17,6 +17,11 @@
package local package local
import ( 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/docker/client"
"github.com/docker/compose-cli/api/backend" "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/resources"
"github.com/docker/compose-cli/api/secrets" "github.com/docker/compose-cli/api/secrets"
"github.com/docker/compose-cli/api/volumes" "github.com/docker/compose-cli/api/volumes"
cliopts "github.com/docker/compose-cli/cli/options"
local_compose "github.com/docker/compose-cli/local/compose" 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 // NewService build a backend for "local" context, using Docker API client
func NewService(apiClient client.APIClient) backend.Service { func NewService(apiClient client.APIClient) backend.Service {
file := cliconfig.LoadDefaultConfigFile(os.Stderr)
return &local{ return &local{
containerService: &containerService{apiClient}, containerService: &containerService{apiClient},
volumeService: &volumeService{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 { func (s *local) ContainerService() containers.Service {
return s.containerService return s.containerService
} }

View File

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

View File

@ -26,20 +26,23 @@ import (
"github.com/docker/compose-cli/api/errdefs" "github.com/docker/compose-cli/api/errdefs"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/config/configfile"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/sanathkr/go-yaml" "github.com/sanathkr/go-yaml"
) )
// NewComposeService create a local implementation of the compose.Service API // 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{ return &composeService{
apiClient: apiClient, apiClient: apiClient,
configFile: configFile,
} }
} }
type composeService struct { 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 { 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/compose-spec/compose-go/types"
"github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/reference"
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
cliconfig "github.com/docker/cli/cli/config"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/compose" "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/api/progress"
"github.com/docker/compose-cli/cli/metrics" "github.com/docker/compose-cli/cli/metrics"
) )
func (s *composeService) Pull(ctx context.Context, project *types.Project, opts compose.PullOptions) error { 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) info, err := s.apiClient.Info(ctx)
if err != nil { if err != nil {
return err return err
@ -67,7 +61,7 @@ func (s *composeService) Pull(ctx context.Context, project *types.Project, opts
continue continue
} }
eg.Go(func() error { 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 err != nil {
if !opts.IgnoreFailures { if !opts.IgnoreFailures {
return err return err

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"testing" "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) { func TestComposePull(t *testing.T) {
c := NewParallelE2eCLI(t, binDir) 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 package e2e
import ( import (
"bytes"
"fmt" "fmt"
"os/exec"
"strings"
"syscall"
"testing" "testing"
"time" "time"
@ -84,3 +88,69 @@ func TestComposeMetrics(t *testing.T) {
}, usage) }, 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.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} return &E2eCLI{binDir, d, t}
} }
@ -117,7 +128,7 @@ func SetupExistingCLI() (string, func(), error) {
return "", nil, err return "", nil, err
} }
bin, err := findExecutable([]string{"../../bin", "../../../bin"}) bin, err := findExecutable(DockerExecutableName, []string{"../../bin", "../../../bin"})
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -133,9 +144,9 @@ func SetupExistingCLI() (string, func(), error) {
return d, cleanup, nil return d, cleanup, nil
} }
func findExecutable(paths []string) (string, error) { func findExecutable(executableName string, paths []string) (string, error) {
for _, p := range paths { for _, p := range paths {
bin, err := filepath.Abs(path.Join(p, DockerExecutableName)) bin, err := filepath.Abs(path.Join(p, executableName))
if err != nil { if err != nil {
return "", err 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)) 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 // PathEnvVar returns path (os sensitive) for running test
func (c *E2eCLI) PathEnvVar() string { func (c *E2eCLI) PathEnvVar() string {
path := c.BinDir + ":" + os.Getenv("PATH") path := c.BinDir + ":" + os.Getenv("PATH")