Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cli/command/container/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (
"strings"
"time"

"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/pkg/signal"
"github.com/docker/go-connections/nat"
"github.com/docker/stacks/pkg/compose/loader"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -545,7 +545,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
CPUQuota: copts.cpuQuota,
CPURealtimePeriod: copts.cpuRealtimePeriod,
CPURealtimeRuntime: copts.cpuRealtimeRuntime,
PidsLimit: copts.pidsLimit,
PidsLimit: &copts.pidsLimit,
BlkioWeight: copts.blkioWeight,
BlkioWeightDevice: copts.blkioWeightDevice.GetList(),
BlkioDeviceReadBps: copts.deviceReadBps.GetList(),
Expand Down
42 changes: 42 additions & 0 deletions cli/command/stack/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package stack

import (
"context"
"fmt"
"os"
"strings"

"github.com/docker/cli/cli/command"
"github.com/docker/stacks/pkg/types"
)

// getAuthHeaderForStack looks in the StackCreate for the first available image and uses that
// to get the auth header necessary to talk to that registry
func getAuthHeaderForStack(ctx context.Context, dockerCli command.Cli, stackCreate *types.StackCreate) (string, error) {
// Pick an image from the stack for registry auth selection
var image string
for _, svc := range stackCreate.Spec.Services {
if !strings.Contains(svc.Image, "$") {
image = svc.Image
} else {
image = os.Expand(svc.Image, func(key string) string {
for _, prop := range stackCreate.Spec.PropertyValues {
s := strings.SplitN(prop, "=", 2)
if len(s) != 2 {
continue
}
if s[0] == key {
return s[1]
}
}
return ""
})

}
break
}
if image == "" {
return "", fmt.Errorf("at least one image must be defined to deploy")
}
return command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
}
49 changes: 49 additions & 0 deletions cli/command/stack/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package stack

import (
"context"
"testing"

"github.com/docker/cli/internal/test"
composetypes "github.com/docker/stacks/pkg/compose/types"
stacktypes "github.com/docker/stacks/pkg/types"
"gotest.tools/assert"
)

func TestAuthHeaderForStackNoVars(t *testing.T) {
ctx := context.Background()
cli := test.NewFakeCli(&fakeClient{
version: clientSideStackVersion,
})
stackCreate := &stacktypes.StackCreate{
Spec: stacktypes.StackSpec{
Services: composetypes.Services{
composetypes.ServiceConfig{
Image: "myregistry.com/acme/imagename:latest",
},
},
},
}

_, err := getAuthHeaderForStack(ctx, cli, stackCreate)
assert.NilError(t, err)
}
func TestAuthHeaderForStackWithVars(t *testing.T) {
ctx := context.Background()
cli := test.NewFakeCli(&fakeClient{
version: clientSideStackVersion,
})
stackCreate := &stacktypes.StackCreate{
Spec: stacktypes.StackSpec{
Services: composetypes.Services{
composetypes.ServiceConfig{
Image: "${IMAGE}",
},
},
PropertyValues: []string{"IMAGE=myregistry.com/acme/imagename:latest"},
},
}

_, err := getAuthHeaderForStack(ctx, cli, stackCreate)
assert.NilError(t, err)
}
70 changes: 65 additions & 5 deletions cli/command/stack/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"context"
"strings"

"github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/cli/legacy/compose/convert"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😱

"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
stacktypes "github.com/docker/stacks/pkg/types"
)

type fakeClient struct {
Expand Down Expand Up @@ -37,10 +38,16 @@ type fakeClient struct {

serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)

serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
secretRemoveFunc func(secretID string) error
configRemoveFunc func(configID string) error
serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
secretRemoveFunc func(secretID string) error
configRemoveFunc func(configID string) error
parseComposeInputFunc func(input stacktypes.ComposeInput) (*stacktypes.StackCreate, error)
stackCreateFunc func(stack stacktypes.StackCreate, options stacktypes.StackCreateOptions) (stacktypes.StackCreateResponse, error)
stackListFunc func(options stacktypes.StackListOptions) ([]stacktypes.Stack, error)
stackTaskListFunc func(stackID string) (stacktypes.StackTaskList, error)
stackDeleteFunc func(stackID string) error
stackUpdateFunc func(id string, version stacktypes.Version, spec stacktypes.StackSpec, options stacktypes.StackUpdateOptions) error
}

func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
Expand Down Expand Up @@ -190,6 +197,59 @@ func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID stri
}, []byte{}, nil
}

func (cli *fakeClient) ParseComposeInput(ctx context.Context, input stacktypes.ComposeInput) (*stacktypes.StackCreate, error) {
if cli.parseComposeInputFunc != nil {
return cli.parseComposeInputFunc(input)
}

return &stacktypes.StackCreate{}, nil
}

func (cli *fakeClient) StackCreate(ctx context.Context, stack stacktypes.StackCreate, options stacktypes.StackCreateOptions) (stacktypes.StackCreateResponse, error) {

if cli.stackCreateFunc != nil {
return cli.stackCreateFunc(stack, options)
}

return stacktypes.StackCreateResponse{}, nil
}

func (cli *fakeClient) StackList(ctx context.Context, options stacktypes.StackListOptions) ([]stacktypes.Stack, error) {

if cli.stackListFunc != nil {
return cli.stackListFunc(options)
}

return nil, nil
}

func (cli *fakeClient) StackTaskList(ctx context.Context, id string) (stacktypes.StackTaskList, error) {

if cli.stackTaskListFunc != nil {
return cli.stackTaskListFunc(id)
}

return stacktypes.StackTaskList{}, nil
}

func (cli *fakeClient) StackDelete(ctx context.Context, id string) error {

if cli.stackDeleteFunc != nil {
return cli.stackDeleteFunc(id)
}

return nil
}

func (cli *fakeClient) StackUpdate(ctx context.Context, id string, version stacktypes.Version, spec stacktypes.StackSpec, options stacktypes.StackUpdateOptions) error {

if cli.stackUpdateFunc != nil {
return cli.stackUpdateFunc(id, version, spec, options)
}

return nil
}

func serviceFromName(name string) swarm.Service {
return swarm.Service{
ID: "ID-" + name,
Expand Down
5 changes: 3 additions & 2 deletions cli/command/stack/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
newPsCommand(dockerCli, &opts),
newRemoveCommand(dockerCli, &opts),
newServicesCommand(dockerCli, &opts),
newInspectCommand(dockerCli, &opts),
)
flags := cmd.PersistentFlags()
flags.String("kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.String("kubeconfig", "", "Kubernetes config file") // TODO - this should be deprecated
flags.SetAnnotation("kubeconfig", "kubernetes", nil) // TODO - this should be deprecated
flags.String("orchestrator", "", "Orchestrator to use (swarm|kubernetes|all)")
return cmd
}
Expand Down
11 changes: 9 additions & 2 deletions cli/command/stack/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (
"unicode"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/legacy/kubernetes"
"github.com/docker/docker/api/types/versions"
"github.com/spf13/pflag"
)

Expand Down Expand Up @@ -34,7 +35,7 @@ func quotesOrWhitespace(r rune) bool {
return unicode.IsSpace(r) || r == '"' || r == '\''
}

func runOrchestratedCommand(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, swarmCmd func() error, kubernetesCmd func(*kubernetes.KubeCli) error) error {
func runLegacyOrchestratedCommand(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, swarmCmd func() error, kubernetesCmd func(*kubernetes.KubeCli) error) error {
switch {
case commonOrchestrator.HasAll():
return errUnsupportedAllOrchestrator
Expand All @@ -48,3 +49,9 @@ func runOrchestratedCommand(dockerCli command.Cli, flags *pflag.FlagSet, commonO
return swarmCmd()
}
}

const clientSideStackVersion = "1.40"

func hasServerSideStacks(dockerCli command.Cli) bool {
return versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), clientSideStackVersion)
}
76 changes: 63 additions & 13 deletions cli/command/stack/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package stack

import (
"context"
"fmt"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/loader"
"github.com/docker/cli/cli/command/stack/legacy/kubernetes"
legacyloader "github.com/docker/cli/cli/command/stack/legacy/loader"
"github.com/docker/cli/cli/command/stack/legacy/swarm"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/stacks/pkg/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -46,11 +47,7 @@ func newDeployCommand(dockerCli command.Cli, common *commonOptions) *cobra.Comma
return swarm.DeployBundle(context.Background(), dockerCli, opts)
}

config, err := loader.LoadComposefile(dockerCli, opts)
if err != nil {
return err
}
return RunDeploy(dockerCli, cmd.Flags(), config, common.Orchestrator(), opts)
return RunDeploy(dockerCli, cmd.Flags(), common.Orchestrator(), opts)
},
}

Expand All @@ -62,20 +59,73 @@ func newDeployCommand(dockerCli command.Cli, common *commonOptions) *cobra.Comma
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
flags.BoolVar(&opts.SendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
flags.SetAnnotation("with-registry-auth", "swarm", nil)
flags.BoolVar(&opts.Prune, "prune", false, "Prune services that are no longer referenced")
flags.BoolVar(&opts.Prune, "prune", false, "Prune services that are no longer referenced") // TODO - deprecate
flags.SetAnnotation("prune", "version", []string{"1.27"})
flags.SetAnnotation("prune", "swarm", nil)
flags.StringVar(&opts.ResolveImage, "resolve-image", swarm.ResolveImageAlways,
`Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`"|"`+swarm.ResolveImageChanged+`"|"`+swarm.ResolveImageNever+`")`)
`Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`"|"`+swarm.ResolveImageChanged+`"|"`+swarm.ResolveImageNever+`")`) // TODO - deprecate
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
flags.SetAnnotation("resolve-image", "swarm", nil)
kubernetes.AddNamespaceFlag(flags)
return cmd
}

// RunDeploy performs a stack deploy against the specified orchestrator
func RunDeploy(dockerCli command.Cli, flags *pflag.FlagSet, config *composetypes.Config, commonOrchestrator command.Orchestrator, opts options.Deploy) error {
return runOrchestratedCommand(dockerCli, flags, commonOrchestrator,
func RunDeploy(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, opts options.Deploy) error {
if hasServerSideStacks(dockerCli) {
ctx := context.Background()
stackCreate, err := LoadComposefile(ctx, dockerCli, opts)
if err != nil {
return err
}
return runServerSideDeploy(ctx, dockerCli, stackCreate, commonOrchestrator, opts)
}
config, err := legacyloader.LoadComposefile(dockerCli, opts)
if err != nil {
return err
}
return runLegacyOrchestratedCommand(dockerCli, flags, commonOrchestrator,
func() error { return swarm.RunDeploy(dockerCli, opts, config) },
func(kli *kubernetes.KubeCli) error { return kubernetes.RunDeploy(kli, opts, config) })
}

func runServerSideDeploy(ctx context.Context, dockerCli command.Cli, stackCreate *types.StackCreate, commonOrchestrator command.Orchestrator, opts options.Deploy) error {
dclient := dockerCli.Client()
name := opts.Namespace

var encodedAuth string
var err error
if opts.SendRegistryAuth {
encodedAuth, err = getAuthHeaderForStack(ctx, dockerCli, stackCreate)
if err != nil {
return err
}
}

// Check for existence first and update if found
stack, err := getStackByName(ctx, dockerCli, string(commonOrchestrator), name)
if err == nil {
fmt.Fprintf(dockerCli.Out(), "Updating stack %s\n", name)
updateOpts := types.StackUpdateOptions{
EncodedRegistryAuth: encodedAuth,
}
return dclient.StackUpdate(ctx, stack.ID, stack.Version, stackCreate.Spec, updateOpts)
}

stackCreate.Orchestrator = types.OrchestratorChoice(commonOrchestrator)
stackCreate.Spec.Metadata.Name = name

createOpts := types.StackCreateOptions{
EncodedRegistryAuth: encodedAuth,
}
resp, err := dclient.StackCreate(ctx, *stackCreate, createOpts)
if err != nil {
return err
}

// TODO Wait for the stack to stabilise - mimic legacy/kubernetes/deploy.go

fmt.Fprintf(dockerCli.Out(), "Deployed Stack %s\n", resp.ID)

return nil
}
Loading