Move api/client -> cli/command
Using
gomvpkg
-from github.com/docker/docker/api/client
-to github.com/docker/docker/cli/command
-vcs_mv_cmd 'git mv {{.Src}} {{.Dst}}'
Signed-off-by: Daniel Nephin <dnephin@docker.com>
Upstream-commit: 0640a14b4fcba3715f7cc3bc9444f3c7f4827edd
Component: engine
This commit is contained in:
39
components/engine/cli/command/stack/cmd.go
Normal file
39
components/engine/cli/command/stack/cmd.go
Normal file
@ -0,0 +1,39 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStackCommand returns a cobra command for `stack` subcommands
|
||||
func NewStackCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "stack",
|
||||
Short: "Manage Docker stacks",
|
||||
Args: cli.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newConfigCommand(dockerCli),
|
||||
newDeployCommand(dockerCli),
|
||||
newRemoveCommand(dockerCli),
|
||||
newServicesCommand(dockerCli),
|
||||
newPsCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
||||
func NewTopLevelDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := newDeployCommand(dockerCli)
|
||||
// Remove the aliases at the top level
|
||||
cmd.Aliases = []string{}
|
||||
return cmd
|
||||
}
|
||||
18
components/engine/cli/command/stack/cmd_stub.go
Normal file
18
components/engine/cli/command/stack/cmd_stub.go
Normal file
@ -0,0 +1,18 @@
|
||||
// +build !experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStackCommand returns no command
|
||||
func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{}
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand returns no command
|
||||
func NewTopLevelDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{}
|
||||
}
|
||||
50
components/engine/cli/command/stack/common.go
Normal file
50
components/engine/cli/command/stack/common.go
Normal file
@ -0,0 +1,50 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
labelNamespace = "com.docker.stack.namespace"
|
||||
)
|
||||
|
||||
func getStackLabels(namespace string, labels map[string]string) map[string]string {
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
labels[labelNamespace] = namespace
|
||||
return labels
|
||||
}
|
||||
|
||||
func getStackFilter(namespace string) filters.Args {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", labelNamespace+"="+namespace)
|
||||
return filter
|
||||
}
|
||||
|
||||
func getServices(
|
||||
ctx context.Context,
|
||||
apiclient client.APIClient,
|
||||
namespace string,
|
||||
) ([]swarm.Service, error) {
|
||||
return apiclient.ServiceList(
|
||||
ctx,
|
||||
types.ServiceListOptions{Filter: getStackFilter(namespace)})
|
||||
}
|
||||
|
||||
func getNetworks(
|
||||
ctx context.Context,
|
||||
apiclient client.APIClient,
|
||||
namespace string,
|
||||
) ([]types.NetworkResource, error) {
|
||||
return apiclient.NetworkList(
|
||||
ctx,
|
||||
types.NetworkListOptions{Filters: getStackFilter(namespace)})
|
||||
}
|
||||
41
components/engine/cli/command/stack/config.go
Normal file
41
components/engine/cli/command/stack/config.go
Normal file
@ -0,0 +1,41 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/api/client/bundlefile"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type configOptions struct {
|
||||
bundlefile string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func newConfigCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts configOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "config [OPTIONS] STACK",
|
||||
Short: "Print the stack configuration",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
return runConfig(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
addBundlefileFlag(&opts.bundlefile, flags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConfig(dockerCli *client.DockerCli, opts configOptions) error {
|
||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bundlefile.Print(dockerCli.Out(), bundle)
|
||||
}
|
||||
236
components/engine/cli/command/stack/deploy.go
Normal file
236
components/engine/cli/command/stack/deploy.go
Normal file
@ -0,0 +1,236 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/api/client/bundlefile"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNetworkDriver = "overlay"
|
||||
)
|
||||
|
||||
type deployOptions struct {
|
||||
bundlefile string
|
||||
namespace string
|
||||
sendRegistryAuth bool
|
||||
}
|
||||
|
||||
func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts deployOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy [OPTIONS] STACK",
|
||||
Aliases: []string{"up"},
|
||||
Short: "Create and update a stack from a Distributed Application Bundle (DAB)",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
return runDeploy(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
addBundlefileFlag(&opts.bundlefile, flags)
|
||||
addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
|
||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := dockerCli.Client().Info(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Swarm.ControlAvailable {
|
||||
return fmt.Errorf("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
|
||||
}
|
||||
|
||||
networks := getUniqueNetworkNames(bundle.Services)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
return deployServices(ctx, dockerCli, bundle.Services, opts.namespace, opts.sendRegistryAuth)
|
||||
}
|
||||
|
||||
func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
|
||||
networkSet := make(map[string]bool)
|
||||
for _, service := range services {
|
||||
for _, network := range service.Networks {
|
||||
networkSet[network] = true
|
||||
}
|
||||
}
|
||||
|
||||
networks := []string{}
|
||||
for network := range networkSet {
|
||||
networks = append(networks, network)
|
||||
}
|
||||
return networks
|
||||
}
|
||||
|
||||
func updateNetworks(
|
||||
ctx context.Context,
|
||||
dockerCli *client.DockerCli,
|
||||
networks []string,
|
||||
namespace string,
|
||||
) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
existingNetworks, err := getNetworks(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingNetworkMap := make(map[string]types.NetworkResource)
|
||||
for _, network := range existingNetworks {
|
||||
existingNetworkMap[network.Name] = network
|
||||
}
|
||||
|
||||
createOpts := types.NetworkCreate{
|
||||
Labels: getStackLabels(namespace, nil),
|
||||
Driver: defaultNetworkDriver,
|
||||
}
|
||||
|
||||
for _, internalName := range networks {
|
||||
name := fmt.Sprintf("%s_%s", namespace, internalName)
|
||||
|
||||
if _, exists := existingNetworkMap[name]; exists {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
|
||||
if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
|
||||
nets := []swarm.NetworkAttachmentConfig{}
|
||||
for _, network := range networks {
|
||||
nets = append(nets, swarm.NetworkAttachmentConfig{
|
||||
Target: namespace + "_" + network,
|
||||
Aliases: []string{name},
|
||||
})
|
||||
}
|
||||
return nets
|
||||
}
|
||||
|
||||
func deployServices(
|
||||
ctx context.Context,
|
||||
dockerCli *client.DockerCli,
|
||||
services map[string]bundlefile.Service,
|
||||
namespace string,
|
||||
sendAuth bool,
|
||||
) error {
|
||||
apiClient := dockerCli.Client()
|
||||
out := dockerCli.Out()
|
||||
|
||||
existingServices, err := getServices(ctx, apiClient, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingServiceMap := make(map[string]swarm.Service)
|
||||
for _, service := range existingServices {
|
||||
existingServiceMap[service.Spec.Name] = service
|
||||
}
|
||||
|
||||
for internalName, service := range services {
|
||||
name := fmt.Sprintf("%s_%s", namespace, internalName)
|
||||
|
||||
var ports []swarm.PortConfig
|
||||
for _, portSpec := range service.Ports {
|
||||
ports = append(ports, swarm.PortConfig{
|
||||
Protocol: swarm.PortConfigProtocol(portSpec.Protocol),
|
||||
TargetPort: portSpec.Port,
|
||||
})
|
||||
}
|
||||
|
||||
serviceSpec := swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: name,
|
||||
Labels: getStackLabels(namespace, service.Labels),
|
||||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: swarm.ContainerSpec{
|
||||
Image: service.Image,
|
||||
Command: service.Command,
|
||||
Args: service.Args,
|
||||
Env: service.Env,
|
||||
// Service Labels will not be copied to Containers
|
||||
// automatically during the deployment so we apply
|
||||
// it here.
|
||||
Labels: getStackLabels(namespace, nil),
|
||||
},
|
||||
},
|
||||
EndpointSpec: &swarm.EndpointSpec{
|
||||
Ports: ports,
|
||||
},
|
||||
Networks: convertNetworks(service.Networks, namespace, internalName),
|
||||
}
|
||||
|
||||
cspec := &serviceSpec.TaskTemplate.ContainerSpec
|
||||
if service.WorkingDir != nil {
|
||||
cspec.Dir = *service.WorkingDir
|
||||
}
|
||||
if service.User != nil {
|
||||
cspec.User = *service.User
|
||||
}
|
||||
|
||||
encodedAuth := ""
|
||||
if sendAuth {
|
||||
// Retrieve encoded auth token from the image reference
|
||||
image := serviceSpec.TaskTemplate.ContainerSpec.Image
|
||||
encodedAuth, err = dockerCli.RetrieveAuthTokenFromImage(ctx, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if service, exists := existingServiceMap[name]; exists {
|
||||
fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
|
||||
|
||||
updateOpts := types.ServiceUpdateOptions{}
|
||||
if sendAuth {
|
||||
updateOpts.EncodedRegistryAuth = encodedAuth
|
||||
}
|
||||
if err := apiClient.ServiceUpdate(
|
||||
ctx,
|
||||
service.ID,
|
||||
service.Version,
|
||||
serviceSpec,
|
||||
updateOpts,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(out, "Creating service %s\n", name)
|
||||
|
||||
createOpts := types.ServiceCreateOptions{}
|
||||
if sendAuth {
|
||||
createOpts.EncodedRegistryAuth = encodedAuth
|
||||
}
|
||||
if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
49
components/engine/cli/command/stack/opts.go
Normal file
49
components/engine/cli/command/stack/opts.go
Normal file
@ -0,0 +1,49 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/api/client/bundlefile"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
|
||||
flags.StringVar(
|
||||
opt,
|
||||
"file", "",
|
||||
"Path to a Distributed Application Bundle file (Default: STACK.dab)")
|
||||
}
|
||||
|
||||
func addRegistryAuthFlag(opt *bool, flags *pflag.FlagSet) {
|
||||
flags.BoolVar(opt, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
|
||||
}
|
||||
|
||||
func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
|
||||
defaultPath := fmt.Sprintf("%s.dab", namespace)
|
||||
|
||||
if path == "" {
|
||||
path = defaultPath
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Bundle %s not found. Specify the path with --file",
|
||||
path)
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
|
||||
reader, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
bundle, err := bundlefile.LoadFile(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading %s: %v\n", path, err)
|
||||
}
|
||||
return bundle, err
|
||||
}
|
||||
72
components/engine/cli/command/stack/ps.go
Normal file
72
components/engine/cli/command/stack/ps.go
Normal file
@ -0,0 +1,72 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/api/client/idresolver"
|
||||
"github.com/docker/docker/api/client/task"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type psOptions struct {
|
||||
all bool
|
||||
filter opts.FilterOpt
|
||||
noTrunc bool
|
||||
namespace string
|
||||
noResolve bool
|
||||
}
|
||||
|
||||
func newPsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
opts := psOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ps [OPTIONS] STACK",
|
||||
Short: "List the tasks in the stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
return runPS(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPS(dockerCli *client.DockerCli, opts psOptions) error {
|
||||
namespace := opts.namespace
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
filter := opts.filter.Value()
|
||||
filter.Add("label", labelNamespace+"="+opts.namespace)
|
||||
if !opts.all && !filter.Include("desired-state") {
|
||||
filter.Add("desired-state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired-state", string(swarm.TaskStateAccepted))
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: filter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve), opts.noTrunc)
|
||||
}
|
||||
75
components/engine/cli/command/stack/remove.go
Normal file
75
components/engine/cli/command/stack/remove.go
Normal file
@ -0,0 +1,75 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
namespace string
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts removeOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm STACK",
|
||||
Aliases: []string{"remove", "down"},
|
||||
Short: "Remove the stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
return runRemove(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli *client.DockerCli, opts removeOptions) error {
|
||||
namespace := opts.namespace
|
||||
client := dockerCli.Client()
|
||||
stderr := dockerCli.Err()
|
||||
ctx := context.Background()
|
||||
hasError := false
|
||||
|
||||
services, err := getServices(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, service := range services {
|
||||
fmt.Fprintf(stderr, "Removing service %s\n", service.Spec.Name)
|
||||
if err := client.ServiceRemove(ctx, service.ID); err != nil {
|
||||
hasError = true
|
||||
fmt.Fprintf(stderr, "Failed to remove service %s: %s", service.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
networks, err := getNetworks(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, network := range networks {
|
||||
fmt.Fprintf(stderr, "Removing network %s\n", network.Name)
|
||||
if err := client.NetworkRemove(ctx, network.ID); err != nil {
|
||||
hasError = true
|
||||
fmt.Fprintf(stderr, "Failed to remove network %s: %s", network.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(services) == 0 && len(networks) == 0 {
|
||||
fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasError {
|
||||
return fmt.Errorf("Failed to remove some resources")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
87
components/engine/cli/command/stack/services.go
Normal file
87
components/engine/cli/command/stack/services.go
Normal file
@ -0,0 +1,87 @@
|
||||
// +build experimental
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/api/client/service"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
|
||||
)
|
||||
|
||||
type servicesOptions struct {
|
||||
quiet bool
|
||||
filter opts.FilterOpt
|
||||
namespace string
|
||||
}
|
||||
|
||||
func newServicesCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
opts := servicesOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "services [OPTIONS] STACK",
|
||||
Short: "List the services in the stack",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
return runServices(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runServices(dockerCli *client.DockerCli, opts servicesOptions) error {
|
||||
ctx := context.Background()
|
||||
client := dockerCli.Client()
|
||||
|
||||
filter := opts.filter.Value()
|
||||
filter.Add("label", labelNamespace+"="+opts.namespace)
|
||||
|
||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filter: filter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := dockerCli.Out()
|
||||
|
||||
// if no services in this stack, print message and exit 0
|
||||
if len(services) == 0 {
|
||||
fmt.Fprintf(out, "Nothing found in stack: %s\n", opts.namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
service.PrintQuiet(out, services)
|
||||
} else {
|
||||
taskFilter := filters.NewArgs()
|
||||
for _, service := range services {
|
||||
taskFilter.Add("service", service.ID)
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: taskFilter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.PrintNotQuiet(out, services, nodes, tasks)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user