mirror of https://github.com/docker/cli.git
Refactor stack command
- Define command and subcommands only once - Use annotations for k8s or swarm specific flags or subcommands Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
0508c09494
commit
dedd0db51a
|
@ -135,9 +135,11 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Experimental field")
|
return errors.Wrap(err, "Experimental field")
|
||||||
}
|
}
|
||||||
|
orchestrator := GetOrchestrator(cli.configFile.Orchestrator)
|
||||||
cli.clientInfo = ClientInfo{
|
cli.clientInfo = ClientInfo{
|
||||||
DefaultVersion: cli.client.ClientVersion(),
|
DefaultVersion: cli.client.ClientVersion(),
|
||||||
HasExperimental: hasExperimental,
|
HasExperimental: hasExperimental,
|
||||||
|
HasKubernetes: orchestrator == OrchestratorKubernetes,
|
||||||
}
|
}
|
||||||
cli.initializeFromClient()
|
cli.initializeFromClient()
|
||||||
return nil
|
return nil
|
||||||
|
@ -202,6 +204,7 @@ type ServerInfo struct {
|
||||||
// ClientInfo stores details about the supported features of the client
|
// ClientInfo stores details about the supported features of the client
|
||||||
type ClientInfo struct {
|
type ClientInfo struct {
|
||||||
HasExperimental bool
|
HasExperimental bool
|
||||||
|
HasKubernetes bool
|
||||||
DefaultVersion string
|
DefaultVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,15 @@ package command
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cliconfig "github.com/docker/cli/cli/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Orchestrator type acts as an enum describing supported orchestrators.
|
// Orchestrator type acts as an enum describing supported orchestrators.
|
||||||
type Orchestrator string
|
type Orchestrator string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Kubernetes orchestrator
|
// OrchestratorKubernetes orchestrator
|
||||||
OrchestratorKubernetes = Orchestrator("kubernetes")
|
OrchestratorKubernetes = Orchestrator("kubernetes")
|
||||||
// Swarm orchestrator
|
// OrchestratorSwarm orchestrator
|
||||||
OrchestratorSwarm = Orchestrator("swarm")
|
OrchestratorSwarm = Orchestrator("swarm")
|
||||||
orchestratorUnset = Orchestrator("unset")
|
orchestratorUnset = Orchestrator("unset")
|
||||||
|
|
||||||
|
@ -34,17 +32,15 @@ func normalize(flag string) Orchestrator {
|
||||||
|
|
||||||
// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file
|
// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file
|
||||||
// orchestrator value and returns user defined Orchestrator.
|
// orchestrator value and returns user defined Orchestrator.
|
||||||
func GetOrchestrator(dockerCli Cli) Orchestrator {
|
func GetOrchestrator(orchestrator string) Orchestrator {
|
||||||
// Check environment variable
|
// Check environment variable
|
||||||
env := os.Getenv(dockerOrchestrator)
|
env := os.Getenv(dockerOrchestrator)
|
||||||
if o := normalize(env); o != orchestratorUnset {
|
if o := normalize(env); o != orchestratorUnset {
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
// Check config file
|
// Check specified orchestrator
|
||||||
if configFile := cliconfig.LoadDefaultConfigFile(dockerCli.Err()); configFile != nil {
|
if o := normalize(orchestrator); o != orchestratorUnset {
|
||||||
if o := normalize(configFile.Orchestrator); o != orchestratorUnset {
|
return o
|
||||||
return o
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing set, use default orchestrator
|
// Nothing set, use default orchestrator
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/compose/convert"
|
||||||
|
"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"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeClient struct {
|
||||||
|
client.Client
|
||||||
|
|
||||||
|
version string
|
||||||
|
|
||||||
|
services []string
|
||||||
|
networks []string
|
||||||
|
secrets []string
|
||||||
|
configs []string
|
||||||
|
|
||||||
|
removedServices []string
|
||||||
|
removedNetworks []string
|
||||||
|
removedSecrets []string
|
||||||
|
removedConfigs []string
|
||||||
|
|
||||||
|
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
|
||||||
|
networkListFunc func(options types.NetworkListOptions) ([]types.NetworkResource, error)
|
||||||
|
secretListFunc func(options types.SecretListOptions) ([]swarm.Secret, error)
|
||||||
|
configListFunc func(options types.ConfigListOptions) ([]swarm.Config, error)
|
||||||
|
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
|
||||||
|
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||||
|
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
||||||
|
return types.Version{
|
||||||
|
Version: "docker-dev",
|
||||||
|
APIVersion: api.DefaultVersion,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ClientVersion() string {
|
||||||
|
return cli.version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||||
|
if cli.serviceListFunc != nil {
|
||||||
|
return cli.serviceListFunc(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := namespaceFromFilters(options.Filters)
|
||||||
|
servicesList := []swarm.Service{}
|
||||||
|
for _, name := range cli.services {
|
||||||
|
if belongToNamespace(name, namespace) {
|
||||||
|
servicesList = append(servicesList, serviceFromName(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return servicesList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||||
|
if cli.networkListFunc != nil {
|
||||||
|
return cli.networkListFunc(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := namespaceFromFilters(options.Filters)
|
||||||
|
networksList := []types.NetworkResource{}
|
||||||
|
for _, name := range cli.networks {
|
||||||
|
if belongToNamespace(name, namespace) {
|
||||||
|
networksList = append(networksList, networkFromName(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return networksList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
|
||||||
|
if cli.secretListFunc != nil {
|
||||||
|
return cli.secretListFunc(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := namespaceFromFilters(options.Filters)
|
||||||
|
secretsList := []swarm.Secret{}
|
||||||
|
for _, name := range cli.secrets {
|
||||||
|
if belongToNamespace(name, namespace) {
|
||||||
|
secretsList = append(secretsList, secretFromName(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return secretsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
|
if cli.configListFunc != nil {
|
||||||
|
return cli.configListFunc(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := namespaceFromFilters(options.Filters)
|
||||||
|
configsList := []swarm.Config{}
|
||||||
|
for _, name := range cli.configs {
|
||||||
|
if belongToNamespace(name, namespace) {
|
||||||
|
configsList = append(configsList, configFromName(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||||
|
if cli.taskListFunc != nil {
|
||||||
|
return cli.taskListFunc(options)
|
||||||
|
}
|
||||||
|
return []swarm.Task{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||||
|
if cli.nodeListFunc != nil {
|
||||||
|
return cli.nodeListFunc(options)
|
||||||
|
}
|
||||||
|
return []swarm.Node{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) {
|
||||||
|
if cli.nodeInspectWithRaw != nil {
|
||||||
|
return cli.nodeInspectWithRaw(ref)
|
||||||
|
}
|
||||||
|
return swarm.Node{}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
|
||||||
|
if cli.serviceUpdateFunc != nil {
|
||||||
|
return cli.serviceUpdateFunc(serviceID, version, service, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ServiceUpdateResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
|
||||||
|
if cli.serviceRemoveFunc != nil {
|
||||||
|
return cli.serviceRemoveFunc(serviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.removedServices = append(cli.removedServices, serviceID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
|
||||||
|
if cli.networkRemoveFunc != nil {
|
||||||
|
return cli.networkRemoveFunc(networkID)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.removedNetworks = append(cli.removedNetworks, networkID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error {
|
||||||
|
if cli.secretRemoveFunc != nil {
|
||||||
|
return cli.secretRemoveFunc(secretID)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.removedSecrets = append(cli.removedSecrets, secretID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error {
|
||||||
|
if cli.configRemoveFunc != nil {
|
||||||
|
return cli.configRemoveFunc(configID)
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.removedConfigs = append(cli.removedConfigs, configID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceFromName(name string) swarm.Service {
|
||||||
|
return swarm.Service{
|
||||||
|
ID: "ID-" + name,
|
||||||
|
Spec: swarm.ServiceSpec{
|
||||||
|
Annotations: swarm.Annotations{Name: name},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func networkFromName(name string) types.NetworkResource {
|
||||||
|
return types.NetworkResource{
|
||||||
|
ID: "ID-" + name,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func secretFromName(name string) swarm.Secret {
|
||||||
|
return swarm.Secret{
|
||||||
|
ID: "ID-" + name,
|
||||||
|
Spec: swarm.SecretSpec{
|
||||||
|
Annotations: swarm.Annotations{Name: name},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configFromName(name string) swarm.Config {
|
||||||
|
return swarm.Config{
|
||||||
|
ID: "ID-" + name,
|
||||||
|
Spec: swarm.ConfigSpec{
|
||||||
|
Annotations: swarm.Annotations{Name: name},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func namespaceFromFilters(filters filters.Args) string {
|
||||||
|
label := filters.Get("label")[0]
|
||||||
|
return strings.TrimPrefix(label, convert.LabelNamespace+"=")
|
||||||
|
}
|
||||||
|
|
||||||
|
func belongToNamespace(id, namespace string) bool {
|
||||||
|
return strings.HasPrefix(id, namespace+"_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectName(namespace, name string) string {
|
||||||
|
return namespace + "_" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectID(name string) string {
|
||||||
|
return "ID-" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildObjectIDs(objectNames []string) []string {
|
||||||
|
IDs := make([]string, len(objectNames))
|
||||||
|
for i, name := range objectNames {
|
||||||
|
IDs[i] = objectID(name)
|
||||||
|
}
|
||||||
|
return IDs
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ package stack
|
||||||
import (
|
import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/stack/kubernetes"
|
|
||||||
"github.com/docker/cli/cli/command/stack/swarm"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,26 +15,24 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCli.Err()),
|
||||||
Annotations: map[string]string{"version": "1.25"},
|
Annotations: map[string]string{"version": "1.25"},
|
||||||
}
|
}
|
||||||
switch command.GetOrchestrator(dockerCli) {
|
cmd.AddCommand(
|
||||||
case command.OrchestratorKubernetes:
|
newDeployCommand(dockerCli),
|
||||||
kubernetes.AddStackCommands(cmd, dockerCli)
|
newListCommand(dockerCli),
|
||||||
case command.OrchestratorSwarm:
|
newPsCommand(dockerCli),
|
||||||
swarm.AddStackCommands(cmd, dockerCli)
|
newRemoveCommand(dockerCli),
|
||||||
}
|
newServicesCommand(dockerCli),
|
||||||
|
)
|
||||||
|
flags := cmd.PersistentFlags()
|
||||||
|
flags.String("namespace", "default", "Kubernetes namespace to use")
|
||||||
|
flags.SetAnnotation("namespace", "kubernetes", nil)
|
||||||
|
flags.String("kubeconfig", "", "Kubernetes config file")
|
||||||
|
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
||||||
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var cmd *cobra.Command
|
cmd := newDeployCommand(dockerCli)
|
||||||
switch command.GetOrchestrator(dockerCli) {
|
|
||||||
case command.OrchestratorKubernetes:
|
|
||||||
cmd = kubernetes.NewTopLevelDeployCommand(dockerCli)
|
|
||||||
case command.OrchestratorSwarm:
|
|
||||||
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
|
|
||||||
default:
|
|
||||||
cmd = swarm.NewTopLevelDeployCommand(dockerCli)
|
|
||||||
}
|
|
||||||
// Remove the aliases at the top level
|
// Remove the aliases at the top level
|
||||||
cmd.Aliases = []string{}
|
cmd.Aliases = []string{}
|
||||||
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}
|
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/bundlefile"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddComposefileFlag adds compose-file file to the specified flagset
|
|
||||||
func AddComposefileFlag(opt *string, flags *pflag.FlagSet) {
|
|
||||||
flags.StringVarP(opt, "compose-file", "c", "", "Path to a Compose file")
|
|
||||||
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBundlefileFlag adds bundle-file file to the specified flagset
|
|
||||||
func AddBundlefileFlag(opt *string, flags *pflag.FlagSet) {
|
|
||||||
flags.StringVar(opt, "bundle-file", "", "Path to a Distributed Application Bundle file")
|
|
||||||
flags.SetAnnotation("bundle-file", "experimental", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRegistryAuthFlag adds with-registry-auth file to the specified flagset
|
|
||||||
func AddRegistryAuthFlag(opt *bool, flags *pflag.FlagSet) {
|
|
||||||
flags.BoolVar(opt, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadBundlefile loads a bundle-file from the specified path
|
|
||||||
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, errors.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, errors.Errorf("Error reading %s: %v\n", path, err)
|
|
||||||
}
|
|
||||||
return bundle, err
|
|
||||||
}
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/options"
|
||||||
|
"github.com/docker/cli/cli/command/stack/swarm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
var opts options.Deploy
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "deploy [OPTIONS] STACK",
|
||||||
|
Aliases: []string{"up"},
|
||||||
|
Short: "Deploy a new stack or update an existing stack",
|
||||||
|
Args: cli.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
opts.Namespace = args[0]
|
||||||
|
if dockerCli.ClientInfo().HasKubernetes {
|
||||||
|
kli, err := kubernetes.WrapCli(dockerCli, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kubernetes.RunDeploy(kli, opts)
|
||||||
|
}
|
||||||
|
return swarm.RunDeploy(dockerCli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.Bundlefile, "bundle-file", "", "Path to a Distributed Application Bundle file")
|
||||||
|
flags.SetAnnotation("bundle-file", "experimental", nil)
|
||||||
|
flags.SetAnnotation("bundle-file", "swarm", nil)
|
||||||
|
flags.StringVarP(&opts.Composefile, "compose-file", "c", "", "Path to a Compose file")
|
||||||
|
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.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+`")`)
|
||||||
|
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
|
||||||
|
flags.SetAnnotation("resolve-image", "swarm", nil)
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset_generated/clientset/typed/compose/v1beta1"
|
||||||
|
"github.com/docker/docker/pkg/homedir"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KubeCli holds kubernetes specifics (client, namespace) with the command.Cli
|
||||||
|
type KubeCli struct {
|
||||||
|
command.Cli
|
||||||
|
kubeConfig *restclient.Config
|
||||||
|
kubeNamespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapCli wraps command.Cli with kubernetes specifiecs
|
||||||
|
func WrapCli(dockerCli command.Cli, cmd *cobra.Command) (*KubeCli, error) {
|
||||||
|
var err error
|
||||||
|
cli := &KubeCli{
|
||||||
|
Cli: dockerCli,
|
||||||
|
kubeNamespace: "default",
|
||||||
|
}
|
||||||
|
if cmd.PersistentFlags().Changed("namespace") {
|
||||||
|
cli.kubeNamespace, err = cmd.PersistentFlags().GetString("namespace")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kubeConfig := ""
|
||||||
|
if cmd.PersistentFlags().Changed("kubeconfig") {
|
||||||
|
kubeConfig, err = cmd.PersistentFlags().GetString("namespace")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kubeConfig == "" {
|
||||||
|
if config := os.Getenv("KUBECONFIG"); config != "" {
|
||||||
|
kubeConfig = config
|
||||||
|
} else {
|
||||||
|
kubeConfig = filepath.Join(homedir.Get(), ".kube/config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cli.kubeConfig = config
|
||||||
|
|
||||||
|
return cli, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KubeCli) composeClient() (*Factory, error) {
|
||||||
|
return NewFactory(c.kubeNamespace, c.kubeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KubeCli) stacks() (composev1beta1.StackInterface, error) {
|
||||||
|
err := APIPresent(c.kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSet, err := composev1beta1.NewForConfig(c.kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSet.Stacks(c.kubeNamespace), nil
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
package kubernetes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset_generated/clientset/typed/compose/v1beta1"
|
|
||||||
"github.com/docker/docker/pkg/homedir"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddStackCommands adds `stack` subcommands
|
|
||||||
func AddStackCommands(root *cobra.Command, dockerCli command.Cli) {
|
|
||||||
var kubeCli kubeCli
|
|
||||||
configureCommand(root, &kubeCli)
|
|
||||||
root.AddCommand(
|
|
||||||
newDeployCommand(dockerCli, &kubeCli),
|
|
||||||
newListCommand(dockerCli, &kubeCli),
|
|
||||||
newRemoveCommand(dockerCli, &kubeCli),
|
|
||||||
newServicesCommand(dockerCli, &kubeCli),
|
|
||||||
newPsCommand(dockerCli, &kubeCli),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
|
||||||
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
var kubeCli kubeCli
|
|
||||||
cmd := newDeployCommand(dockerCli, &kubeCli)
|
|
||||||
configureCommand(cmd, &kubeCli)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureCommand(root *cobra.Command, kubeCli *kubeCli) {
|
|
||||||
var (
|
|
||||||
kubeOpts kubeOptions
|
|
||||||
)
|
|
||||||
kubeOpts.installFlags(root.PersistentFlags())
|
|
||||||
preRunE := root.PersistentPreRunE
|
|
||||||
root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
|
||||||
if preRunE != nil {
|
|
||||||
if err := preRunE(cmd, args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kubeCli.kubeNamespace = kubeOpts.namespace
|
|
||||||
if kubeCli.kubeNamespace == "" {
|
|
||||||
kubeCli.kubeNamespace = "default"
|
|
||||||
}
|
|
||||||
// Read kube config flag and environment variable
|
|
||||||
if kubeOpts.kubeconfig == "" {
|
|
||||||
if config := os.Getenv("KUBECONFIG"); config != "" {
|
|
||||||
kubeOpts.kubeconfig = config
|
|
||||||
} else {
|
|
||||||
kubeOpts.kubeconfig = filepath.Join(homedir.Get(), ".kube/config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", kubeOpts.kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
kubeCli.kubeConfig = config
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KubeOptions are options specific to kubernetes
|
|
||||||
type kubeOptions struct {
|
|
||||||
namespace string
|
|
||||||
kubeconfig string
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallFlags adds flags for the common options on the FlagSet
|
|
||||||
func (opts *kubeOptions) installFlags(flags *pflag.FlagSet) {
|
|
||||||
flags.StringVar(&opts.namespace, "namespace", "default", "Kubernetes namespace to use")
|
|
||||||
flags.StringVar(&opts.kubeconfig, "kubeconfig", "", "Kubernetes config file")
|
|
||||||
}
|
|
||||||
|
|
||||||
type kubeCli struct {
|
|
||||||
kubeConfig *restclient.Config
|
|
||||||
kubeNamespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *kubeCli) ComposeClient() (*Factory, error) {
|
|
||||||
return NewFactory(c.kubeNamespace, c.kubeConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *kubeCli) KubeConfig() *restclient.Config {
|
|
||||||
return c.kubeConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *kubeCli) Stacks() (composev1beta1.StackInterface, error) {
|
|
||||||
err := APIPresent(c.kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clientSet, err := composev1beta1.NewForConfig(c.kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientSet.Stacks(c.kubeNamespace), nil
|
|
||||||
}
|
|
|
@ -5,51 +5,26 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/cli/cli/command/stack/common"
|
|
||||||
composeTypes "github.com/docker/cli/cli/compose/types"
|
composeTypes "github.com/docker/cli/cli/compose/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type deployOptions struct {
|
// RunDeploy is the kubernetes implementation of docker stack deploy
|
||||||
composefile string
|
func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
|
||||||
stack string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDeployCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
|
||||||
var opts deployOptions
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "deploy [OPTIONS] STACK",
|
|
||||||
Aliases: []string{"up"},
|
|
||||||
Short: "Deploy a new stack or update an existing stack",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.stack = args[0]
|
|
||||||
return runDeploy(dockerCli, kubeCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
common.AddComposefileFlag(&opts.composefile, flags)
|
|
||||||
// FIXME(vdemeester) other flags ? (bundlefile, registry-auth, prune, resolve-image) ?
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDeploy(dockerCli command.Cli, kubeCli *kubeCli, opts deployOptions) error {
|
|
||||||
cmdOut := dockerCli.Out()
|
cmdOut := dockerCli.Out()
|
||||||
// Check arguments
|
// Check arguments
|
||||||
if opts.composefile == "" {
|
if opts.Composefile == "" {
|
||||||
return errors.Errorf("Please specify a Compose file (with --compose-file).")
|
return errors.Errorf("Please specify a Compose file (with --compose-file).")
|
||||||
}
|
}
|
||||||
// Initialize clients
|
// Initialize clients
|
||||||
stacks, err := kubeCli.Stacks()
|
stacks, err := dockerCli.stacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
composeClient, err := kubeCli.ComposeClient()
|
composeClient, err := dockerCli.composeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -62,7 +37,7 @@ func runDeploy(dockerCli command.Cli, kubeCli *kubeCli, opts deployOptions) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the compose file
|
// Parse the compose file
|
||||||
stack, cfg, err := LoadStack(opts.stack, opts.composefile)
|
stack, cfg, err := LoadStack(opts.Namespace, opts.Composefile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,42 +3,19 @@ package kubernetes
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/spf13/cobra"
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"vbom.ml/util/sortorder"
|
"vbom.ml/util/sortorder"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listOptions struct {
|
// RunList is the kubernetes implementation of docker stack ls
|
||||||
format string
|
func RunList(dockerCli *KubeCli, opts options.List) error {
|
||||||
}
|
stacks, err := getStacks(dockerCli)
|
||||||
|
|
||||||
func newListCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
|
||||||
opts := listOptions{}
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Short: "List stacks",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runList(dockerCli, kubeCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runList(dockerCli command.Cli, kubeCli *kubeCli, opts listOptions) error {
|
|
||||||
stacks, err := getStacks(kubeCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
format := opts.format
|
format := opts.Format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
}
|
}
|
||||||
|
@ -56,8 +33,8 @@ func (n byName) Len() int { return len(n) }
|
||||||
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
||||||
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
|
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
|
||||||
|
|
||||||
func getStacks(kubeCli *kubeCli) ([]*formatter.Stack, error) {
|
func getStacks(kubeCli *KubeCli) ([]*formatter.Stack, error) {
|
||||||
stackSvc, err := kubeCli.Stacks()
|
stackSvc, err := kubeCli.stacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/command/task"
|
"github.com/docker/cli/cli/command/task"
|
||||||
"github.com/docker/cli/opts"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
|
@ -18,46 +16,16 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type psOptions struct {
|
// RunPS is the kubernetes implementation of docker stack ps
|
||||||
filter opts.FilterOpt
|
func RunPS(dockerCli *KubeCli, options options.PS) error {
|
||||||
noTrunc bool
|
namespace := options.Namespace
|
||||||
namespace string
|
|
||||||
noResolve bool
|
|
||||||
quiet bool
|
|
||||||
format string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPsCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
|
||||||
options := 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 {
|
|
||||||
options.namespace = args[0]
|
|
||||||
return runPS(dockerCli, kubeCli, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
|
||||||
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
|
||||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPS(dockerCli command.Cli, kubeCli *kubeCli, options psOptions) error {
|
|
||||||
namespace := options.namespace
|
|
||||||
|
|
||||||
// Initialize clients
|
// Initialize clients
|
||||||
client, err := kubeCli.ComposeClient()
|
client, err := dockerCli.composeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
stacks, err := kubeCli.Stacks()
|
stacks, err := dockerCli.stacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -77,17 +45,17 @@ func runPS(dockerCli command.Cli, kubeCli *kubeCli, options psOptions) error {
|
||||||
return fmt.Errorf("nothing found in stack: %s", namespace)
|
return fmt.Errorf("nothing found in stack: %s", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.format
|
format := options.Format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
|
format = task.DefaultFormat(dockerCli.ConfigFile(), options.Quiet)
|
||||||
}
|
}
|
||||||
nodeResolver := makeNodeResolver(options.noResolve, client.Nodes())
|
nodeResolver := makeNodeResolver(options.NoResolve, client.Nodes())
|
||||||
|
|
||||||
tasks := make([]swarm.Task, len(pods))
|
tasks := make([]swarm.Task, len(pods))
|
||||||
for i, pod := range pods {
|
for i, pod := range pods {
|
||||||
tasks[i] = podToTask(pod)
|
tasks[i] = podToTask(pod)
|
||||||
}
|
}
|
||||||
return print(dockerCli, tasks, pods, nodeResolver, !options.noTrunc, options.quiet, format)
|
return print(dockerCli, tasks, pods, nodeResolver, !options.NoTrunc, options.Quiet, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
type idResolver func(name string) (string, error)
|
type idResolver func(name string) (string, error)
|
||||||
|
|
|
@ -3,38 +3,17 @@ package kubernetes
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type removeOptions struct {
|
// RunRemove is the kubernetes implementation of docker stack remove
|
||||||
stacks []string
|
func RunRemove(dockerCli *KubeCli, opts options.Remove) error {
|
||||||
}
|
stacks, err := dockerCli.stacks()
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
|
||||||
var opts removeOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rm STACK [STACK...]",
|
|
||||||
Aliases: []string{"remove", "down"},
|
|
||||||
Short: "Remove one or more stacks",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.stacks = args
|
|
||||||
return runRemove(dockerCli, kubeCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli command.Cli, kubeCli *kubeCli, opts removeOptions) error {
|
|
||||||
stacks, err := kubeCli.Stacks()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, stack := range opts.stacks {
|
for _, stack := range opts.Namespaces {
|
||||||
fmt.Fprintf(dockerCli.Out(), "Removing stack: %s\n", stack)
|
fmt.Fprintf(dockerCli.Out(), "Removing stack: %s\n", stack)
|
||||||
err := stacks.Delete(stack, &metav1.DeleteOptions{})
|
err := stacks.Delete(stack, &metav1.DeleteOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3,62 +3,36 @@ package kubernetes
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/kubernetes/labels"
|
"github.com/docker/cli/kubernetes/labels"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type servicesOptions struct {
|
// RunServices is the kubernetes implementation of docker stack services
|
||||||
quiet bool
|
func RunServices(dockerCli *KubeCli, opts options.Services) error {
|
||||||
format string
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServicesCommand(dockerCli command.Cli, kubeCli *kubeCli) *cobra.Command {
|
|
||||||
var options servicesOptions
|
|
||||||
|
|
||||||
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 {
|
|
||||||
options.namespace = args[0]
|
|
||||||
return runServices(dockerCli, kubeCli, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServices(dockerCli command.Cli, kubeCli *kubeCli, options servicesOptions) error {
|
|
||||||
// Initialize clients
|
// Initialize clients
|
||||||
client, err := kubeCli.ComposeClient()
|
client, err := dockerCli.composeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
stacks, err := kubeCli.Stacks()
|
stacks, err := dockerCli.stacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
replicas := client.ReplicaSets()
|
replicas := client.ReplicaSets()
|
||||||
|
|
||||||
if _, err := stacks.Get(options.namespace, metav1.GetOptions{}); err != nil {
|
if _, err := stacks.Get(opts.Namespace, metav1.GetOptions{}); err != nil {
|
||||||
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", options.namespace)
|
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", opts.Namespace)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
replicasList, err := replicas.List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(options.namespace)})
|
replicasList, err := replicas.List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(opts.Namespace)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesList, err := client.Services().List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(options.namespace)})
|
servicesList, err := client.Services().List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(opts.Namespace)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -69,13 +43,13 @@ func runServices(dockerCli command.Cli, kubeCli *kubeCli, options servicesOption
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.quiet {
|
if opts.Quiet {
|
||||||
info = map[string]formatter.ServiceListInfo{}
|
info = map[string]formatter.ServiceListInfo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.format
|
format := opts.Format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
|
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet {
|
||||||
format = dockerCli.ConfigFile().ServicesFormat
|
format = dockerCli.ConfigFile().ServicesFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -84,7 +58,7 @@ func runServices(dockerCli command.Cli, kubeCli *kubeCli, options servicesOption
|
||||||
|
|
||||||
servicesCtx := formatter.Context{
|
servicesCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewServiceListFormat(format, options.quiet),
|
Format: formatter.NewServiceListFormat(format, opts.Quiet),
|
||||||
}
|
}
|
||||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/options"
|
||||||
|
"github.com/docker/cli/cli/command/stack/swarm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
opts := options.List{}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Aliases: []string{"list"},
|
||||||
|
Short: "List stacks",
|
||||||
|
Args: cli.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if dockerCli.ClientInfo().HasKubernetes {
|
||||||
|
kli, err := kubernetes.WrapCli(dockerCli, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kubernetes.RunList(kli, opts)
|
||||||
|
}
|
||||||
|
return swarm.RunList(dockerCli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.Format, "format", "", "Pretty-print stacks using a Go template")
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package swarm
|
package stack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
|
@ -0,0 +1,41 @@
|
||||||
|
package options
|
||||||
|
|
||||||
|
import "github.com/docker/cli/opts"
|
||||||
|
|
||||||
|
// Deploy holds docker stack deploy options
|
||||||
|
type Deploy struct {
|
||||||
|
Bundlefile string
|
||||||
|
Composefile string
|
||||||
|
Namespace string
|
||||||
|
ResolveImage string
|
||||||
|
SendRegistryAuth bool
|
||||||
|
Prune bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// List holds docker stack ls options
|
||||||
|
type List struct {
|
||||||
|
Format string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PS holds docker stack ps options
|
||||||
|
type PS struct {
|
||||||
|
Filter opts.FilterOpt
|
||||||
|
NoTrunc bool
|
||||||
|
Namespace string
|
||||||
|
NoResolve bool
|
||||||
|
Quiet bool
|
||||||
|
Format string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove holds docker stack remove options
|
||||||
|
type Remove struct {
|
||||||
|
Namespaces []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Services holds docker stack services options
|
||||||
|
type Services struct {
|
||||||
|
Quiet bool
|
||||||
|
Format string
|
||||||
|
Filter opts.FilterOpt
|
||||||
|
Namespace string
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/options"
|
||||||
|
"github.com/docker/cli/cli/command/stack/swarm"
|
||||||
|
cliopts "github.com/docker/cli/opts"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
opts := options.PS{Filter: cliopts.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]
|
||||||
|
if dockerCli.ClientInfo().HasKubernetes {
|
||||||
|
kli, err := kubernetes.WrapCli(dockerCli, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kubernetes.RunPS(kli, opts)
|
||||||
|
}
|
||||||
|
return swarm.RunPS(dockerCli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
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")
|
||||||
|
flags.SetAnnotation("filter", "swarm", nil)
|
||||||
|
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display task IDs")
|
||||||
|
flags.StringVar(&opts.Format, "format", "", "Pretty-print tasks using a Go template")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package swarm
|
package stack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
|
@ -0,0 +1,33 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/options"
|
||||||
|
"github.com/docker/cli/cli/command/stack/swarm"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
var opts options.Remove
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "rm STACK [STACK...]",
|
||||||
|
Aliases: []string{"remove", "down"},
|
||||||
|
Short: "Remove one or more stacks",
|
||||||
|
Args: cli.RequiresMinArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
opts.Namespaces = args
|
||||||
|
if dockerCli.ClientInfo().HasKubernetes {
|
||||||
|
kli, err := kubernetes.WrapCli(dockerCli, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kubernetes.RunRemove(kli, opts)
|
||||||
|
}
|
||||||
|
return swarm.RunRemove(dockerCli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package swarm
|
package stack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -0,0 +1,39 @@
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/options"
|
||||||
|
"github.com/docker/cli/cli/command/stack/swarm"
|
||||||
|
cliopts "github.com/docker/cli/opts"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newServicesCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
opts := options.Services{Filter: cliopts.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]
|
||||||
|
if dockerCli.ClientInfo().HasKubernetes {
|
||||||
|
kli, err := kubernetes.WrapCli(dockerCli, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kubernetes.RunServices(kli, opts)
|
||||||
|
}
|
||||||
|
return swarm.RunServices(dockerCli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||||
|
flags.StringVar(&opts.Format, "format", "", "Pretty-print services using a Go template")
|
||||||
|
flags.VarP(&opts.Filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
flags.SetAnnotation("filter", "swarm", nil)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package swarm
|
package stack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
|
@ -1,22 +0,0 @@
|
||||||
package swarm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddStackCommands adds `stack` subcommands
|
|
||||||
func AddStackCommands(root *cobra.Command, dockerCli command.Cli) {
|
|
||||||
root.AddCommand(
|
|
||||||
newDeployCommand(dockerCli),
|
|
||||||
newListCommand(dockerCli),
|
|
||||||
newRemoveCommand(dockerCli),
|
|
||||||
newServicesCommand(dockerCli),
|
|
||||||
newPsCommand(dockerCli),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
|
||||||
func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
return newDeployCommand(dockerCli)
|
|
||||||
}
|
|
|
@ -3,60 +3,25 @@ package swarm
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/stack/common"
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/compose/convert"
|
"github.com/docker/cli/cli/compose/convert"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Resolve image constants
|
||||||
const (
|
const (
|
||||||
defaultNetworkDriver = "overlay"
|
defaultNetworkDriver = "overlay"
|
||||||
resolveImageAlways = "always"
|
ResolveImageAlways = "always"
|
||||||
resolveImageChanged = "changed"
|
ResolveImageChanged = "changed"
|
||||||
resolveImageNever = "never"
|
ResolveImageNever = "never"
|
||||||
)
|
)
|
||||||
|
|
||||||
type deployOptions struct {
|
// RunDeploy is the swarm implementation of docker stack deploy
|
||||||
bundlefile string
|
func RunDeploy(dockerCli command.Cli, opts options.Deploy) error {
|
||||||
composefile string
|
|
||||||
namespace string
|
|
||||||
resolveImage string
|
|
||||||
sendRegistryAuth bool
|
|
||||||
prune bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDeployCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
var opts deployOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "deploy [OPTIONS] STACK",
|
|
||||||
Aliases: []string{"up"},
|
|
||||||
Short: "Deploy a new stack or update an existing stack",
|
|
||||||
Args: cli.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.namespace = args[0]
|
|
||||||
return runDeploy(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
common.AddBundlefileFlag(&opts.bundlefile, flags)
|
|
||||||
common.AddComposefileFlag(&opts.composefile, flags)
|
|
||||||
common.AddRegistryAuthFlag(&opts.sendRegistryAuth, flags)
|
|
||||||
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
|
|
||||||
flags.SetAnnotation("prune", "version", []string{"1.27"})
|
|
||||||
flags.StringVar(&opts.resolveImage, "resolve-image", resolveImageAlways,
|
|
||||||
`Query the registry to resolve image digest and supported platforms ("`+resolveImageAlways+`"|"`+resolveImageChanged+`"|"`+resolveImageNever+`")`)
|
|
||||||
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDeploy(dockerCli command.Cli, opts deployOptions) error {
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if err := validateResolveImageFlag(dockerCli, &opts); err != nil {
|
if err := validateResolveImageFlag(dockerCli, &opts); err != nil {
|
||||||
|
@ -64,11 +29,11 @@ func runDeploy(dockerCli command.Cli, opts deployOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case opts.bundlefile == "" && opts.composefile == "":
|
case opts.Bundlefile == "" && opts.Composefile == "":
|
||||||
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
|
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
|
||||||
case opts.bundlefile != "" && opts.composefile != "":
|
case opts.Bundlefile != "" && opts.Composefile != "":
|
||||||
return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
|
return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
|
||||||
case opts.bundlefile != "":
|
case opts.Bundlefile != "":
|
||||||
return deployBundle(ctx, dockerCli, opts)
|
return deployBundle(ctx, dockerCli, opts)
|
||||||
default:
|
default:
|
||||||
return deployCompose(ctx, dockerCli, opts)
|
return deployCompose(ctx, dockerCli, opts)
|
||||||
|
@ -77,14 +42,14 @@ func runDeploy(dockerCli command.Cli, opts deployOptions) error {
|
||||||
|
|
||||||
// validateResolveImageFlag validates the opts.resolveImage command line option
|
// validateResolveImageFlag validates the opts.resolveImage command line option
|
||||||
// and also turns image resolution off if the version is older than 1.30
|
// and also turns image resolution off if the version is older than 1.30
|
||||||
func validateResolveImageFlag(dockerCli command.Cli, opts *deployOptions) error {
|
func validateResolveImageFlag(dockerCli command.Cli, opts *options.Deploy) error {
|
||||||
if opts.resolveImage != resolveImageAlways && opts.resolveImage != resolveImageChanged && opts.resolveImage != resolveImageNever {
|
if opts.ResolveImage != ResolveImageAlways && opts.ResolveImage != ResolveImageChanged && opts.ResolveImage != ResolveImageNever {
|
||||||
return errors.Errorf("Invalid option %s for flag --resolve-image", opts.resolveImage)
|
return errors.Errorf("Invalid option %s for flag --resolve-image", opts.ResolveImage)
|
||||||
}
|
}
|
||||||
// client side image resolution should not be done when the supported
|
// client side image resolution should not be done when the supported
|
||||||
// server version is older than 1.30
|
// server version is older than 1.30
|
||||||
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
|
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
|
||||||
opts.resolveImage = resolveImageNever
|
opts.ResolveImage = ResolveImageNever
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
package swarm
|
package swarm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/stack/common"
|
"github.com/docker/cli/cli/command/bundlefile"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/compose/convert"
|
"github.com/docker/cli/cli/compose/convert"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions) error {
|
func deployBundle(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
|
||||||
bundle, err := common.LoadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
bundle, err := loadBundlefile(dockerCli.Err(), opts.Namespace, opts.Bundlefile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -20,9 +26,9 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace := convert.NewNamespace(opts.namespace)
|
namespace := convert.NewNamespace(opts.Namespace)
|
||||||
|
|
||||||
if opts.prune {
|
if opts.Prune {
|
||||||
services := map[string]struct{}{}
|
services := map[string]struct{}{}
|
||||||
for service := range bundle.Services {
|
for service := range bundle.Services {
|
||||||
services[service] = struct{}{}
|
services[service] = struct{}{}
|
||||||
|
@ -88,5 +94,31 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions
|
||||||
if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
|
if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.resolveImage)
|
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, errors.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, errors.Errorf("Error reading %s: %v\n", path, err)
|
||||||
|
}
|
||||||
|
return bundle, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package common
|
package swarm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -32,7 +32,7 @@ func TestLoadBundlefileErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
_, err := LoadBundlefile(&bytes.Buffer{}, tc.namespace, tc.path)
|
_, err := loadBundlefile(&bytes.Buffer{}, tc.namespace, tc.path)
|
||||||
assert.Error(t, err, tc.expectedError)
|
assert.Error(t, err, tc.expectedError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func TestLoadBundlefile(t *testing.T) {
|
||||||
|
|
||||||
namespace := ""
|
namespace := ""
|
||||||
path := filepath.Join("testdata", "bundlefile_with_two_services.dab")
|
path := filepath.Join("testdata", "bundlefile_with_two_services.dab")
|
||||||
bundleFile, err := LoadBundlefile(buf, namespace, path)
|
bundleFile, err := loadBundlefile(buf, namespace, path)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, len(bundleFile.Services), 2)
|
assert.Equal(t, len(bundleFile.Services), 2)
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/compose/convert"
|
"github.com/docker/cli/cli/compose/convert"
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
"github.com/docker/cli/cli/compose/loader"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
|
@ -22,8 +23,8 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOptions) error {
|
func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
|
||||||
configDetails, err := getConfigDetails(opts.composefile, dockerCli.In())
|
configDetails, err := getConfigDetails(opts.Composefile, dockerCli.In())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -54,9 +55,9 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace := convert.NewNamespace(opts.namespace)
|
namespace := convert.NewNamespace(opts.Namespace)
|
||||||
|
|
||||||
if opts.prune {
|
if opts.Prune {
|
||||||
services := map[string]struct{}{}
|
services := map[string]struct{}{}
|
||||||
for _, service := range config.Services {
|
for _, service := range config.Services {
|
||||||
services[service.Name] = struct{}{}
|
services[service.Name] = struct{}{}
|
||||||
|
@ -93,7 +94,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.resolveImage)
|
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
|
func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
|
||||||
|
@ -339,7 +340,7 @@ func deployServices(
|
||||||
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]):
|
case resolveImage == ResolveImageAlways || (resolveImage == ResolveImageChanged && image != service.Spec.Labels[convert.LabelImage]):
|
||||||
// image should be updated by the server using QueryRegistry
|
// image should be updated by the server using QueryRegistry
|
||||||
updateOpts.QueryRegistry = true
|
updateOpts.QueryRegistry = true
|
||||||
case image == service.Spec.Labels[convert.LabelImage]:
|
case image == service.Spec.Labels[convert.LabelImage]:
|
||||||
|
@ -369,7 +370,7 @@ func deployServices(
|
||||||
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
||||||
|
|
||||||
// query registry if flag disabling it was not set
|
// query registry if flag disabling it was not set
|
||||||
if resolveImage == resolveImageAlways || resolveImage == resolveImageChanged {
|
if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged {
|
||||||
createOpts.QueryRegistry = true
|
createOpts.QueryRegistry = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := deployServices(ctx, client, spec, namespace, false, resolveImageChanged)
|
err := deployServices(ctx, client, spec, namespace, false, ResolveImageChanged)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testcase.expectedQueryRegistry, receivedOptions.QueryRegistry)
|
assert.Equal(t, testcase.expectedQueryRegistry, receivedOptions.QueryRegistry)
|
||||||
assert.Equal(t, testcase.expectedImage, receivedService.TaskTemplate.ContainerSpec.Image)
|
assert.Equal(t, testcase.expectedImage, receivedService.TaskTemplate.ContainerSpec.Image)
|
||||||
|
|
|
@ -3,41 +3,19 @@ package swarm
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/compose/convert"
|
"github.com/docker/cli/cli/compose/convert"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"vbom.ml/util/sortorder"
|
"vbom.ml/util/sortorder"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listOptions struct {
|
// RunList is the swarm implementation of docker stack ls
|
||||||
format string
|
func RunList(dockerCli command.Cli, opts options.List) error {
|
||||||
}
|
|
||||||
|
|
||||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
opts := listOptions{}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ls",
|
|
||||||
Aliases: []string{"list"},
|
|
||||||
Short: "List stacks",
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runList(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runList(dockerCli command.Cli, opts listOptions) error {
|
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -45,7 +23,7 @@ func runList(dockerCli command.Cli, opts listOptions) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
format := opts.format
|
format := opts.Format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,53 +3,21 @@ package swarm
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/idresolver"
|
"github.com/docker/cli/cli/command/idresolver"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/cli/cli/command/task"
|
"github.com/docker/cli/cli/command/task"
|
||||||
"github.com/docker/cli/opts"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type psOptions struct {
|
// RunPS is the swarm implementation of docker stack ps
|
||||||
filter opts.FilterOpt
|
func RunPS(dockerCli command.Cli, opts options.PS) error {
|
||||||
noTrunc bool
|
namespace := opts.Namespace
|
||||||
namespace string
|
|
||||||
noResolve bool
|
|
||||||
quiet bool
|
|
||||||
format string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
options := 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 {
|
|
||||||
options.namespace = args[0]
|
|
||||||
return runPS(dockerCli, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
|
||||||
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
|
||||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPS(dockerCli command.Cli, options psOptions) error {
|
|
||||||
namespace := options.namespace
|
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
filter := getStackFilterFromOpt(options.namespace, options.filter)
|
filter := getStackFilterFromOpt(opts.Namespace, opts.Filter)
|
||||||
|
|
||||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -60,10 +28,10 @@ func runPS(dockerCli command.Cli, options psOptions) error {
|
||||||
return fmt.Errorf("nothing found in stack: %s", namespace)
|
return fmt.Errorf("nothing found in stack: %s", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.format
|
format := opts.Format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
|
format = task.DefaultFormat(dockerCli.ConfigFile(), opts.Quiet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format)
|
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.NoResolve), !opts.NoTrunc, opts.Quiet, format)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,38 +5,18 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type removeOptions struct {
|
// RunRemove is the swarm implementation of docker stack remove
|
||||||
namespaces []string
|
func RunRemove(dockerCli command.Cli, opts options.Remove) error {
|
||||||
}
|
namespaces := opts.Namespaces
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
var opts removeOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rm STACK [STACK...]",
|
|
||||||
Aliases: []string{"remove", "down"},
|
|
||||||
Short: "Remove one or more stacks",
|
|
||||||
Args: cli.RequiresMinArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
opts.namespaces = args
|
|
||||||
return runRemove(dockerCli, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRemove(dockerCli command.Cli, opts removeOptions) error {
|
|
||||||
namespaces := opts.namespaces
|
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
@ -3,49 +3,21 @@ package swarm
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/service"
|
"github.com/docker/cli/cli/command/service"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/cli/command/stack/options"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type servicesOptions struct {
|
// RunServices is the swarm implementation of docker stack services
|
||||||
quiet bool
|
func RunServices(dockerCli command.Cli, opts options.Services) error {
|
||||||
format string
|
|
||||||
filter opts.FilterOpt
|
|
||||||
namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServicesCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
options := 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 {
|
|
||||||
options.namespace = args[0]
|
|
||||||
return runServices(dockerCli, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
|
|
||||||
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServices(dockerCli command.Cli, options servicesOptions) error {
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
|
|
||||||
filter := getStackFilterFromOpt(options.namespace, options.filter)
|
filter := getStackFilterFromOpt(opts.Namespace, opts.Filter)
|
||||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
|
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -53,12 +25,12 @@ func runServices(dockerCli command.Cli, options servicesOptions) error {
|
||||||
|
|
||||||
// if no services in this stack, print message and exit 0
|
// if no services in this stack, print message and exit 0
|
||||||
if len(services) == 0 {
|
if len(services) == 0 {
|
||||||
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", options.namespace)
|
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", opts.Namespace)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info := map[string]formatter.ServiceListInfo{}
|
info := map[string]formatter.ServiceListInfo{}
|
||||||
if !options.quiet {
|
if !opts.Quiet {
|
||||||
taskFilter := filters.NewArgs()
|
taskFilter := filters.NewArgs()
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
taskFilter.Add("service", service.ID)
|
taskFilter.Add("service", service.ID)
|
||||||
|
@ -77,9 +49,9 @@ func runServices(dockerCli command.Cli, options servicesOptions) error {
|
||||||
info = service.GetServicesStatus(services, nodes, tasks)
|
info = service.GetServicesStatus(services, nodes, tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.format
|
format := opts.Format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
|
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet {
|
||||||
format = dockerCli.ConfigFile().ServicesFormat
|
format = dockerCli.ConfigFile().ServicesFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -88,7 +60,7 @@ func runServices(dockerCli command.Cli, options servicesOptions) error {
|
||||||
|
|
||||||
servicesCtx := formatter.Context{
|
servicesCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewServiceListFormat(format, options.quiet),
|
Format: formatter.NewServiceListFormat(format, opts.Quiet),
|
||||||
}
|
}
|
||||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,8 +137,12 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
||||||
BuildTime: cli.BuildTime,
|
BuildTime: cli.BuildTime,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
|
<<<<<<< HEAD
|
||||||
Experimental: dockerCli.ClientInfo().HasExperimental,
|
Experimental: dockerCli.ClientInfo().HasExperimental,
|
||||||
Orchestrator: string(command.GetOrchestrator(dockerCli)),
|
Orchestrator: string(command.GetOrchestrator(dockerCli)),
|
||||||
|
=======
|
||||||
|
Orchestrator: string(command.GetOrchestrator(dockerCli.ConfigFile().Orchestrator)),
|
||||||
|
>>>>>>> Refactor stack command
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
vd.Client.Platform.Name = cli.PlatformName
|
vd.Client.Platform.Name = cli.PlatformName
|
||||||
|
|
|
@ -195,6 +195,7 @@ type versionDetails interface {
|
||||||
Client() client.APIClient
|
Client() client.APIClient
|
||||||
ClientInfo() command.ClientInfo
|
ClientInfo() command.ClientInfo
|
||||||
ServerInfo() command.ServerInfo
|
ServerInfo() command.ServerInfo
|
||||||
|
ClientInfo() command.ClientInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
|
@ -202,6 +203,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
osType := details.ServerInfo().OSType
|
osType := details.ServerInfo().OSType
|
||||||
hasExperimental := details.ServerInfo().HasExperimental
|
hasExperimental := details.ServerInfo().HasExperimental
|
||||||
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||||
|
hasKubernetes := details.ClientInfo().HasKubernetes
|
||||||
|
|
||||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||||
// hide experimental flags
|
// hide experimental flags
|
||||||
|
@ -215,6 +217,15 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
f.Hidden = true
|
f.Hidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !hasKubernetes {
|
||||||
|
if _, ok := f.Annotations["kubernetes"]; ok {
|
||||||
|
f.Hidden = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := f.Annotations["swarm"]; ok {
|
||||||
|
f.Hidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hide flags not supported by the server
|
// hide flags not supported by the server
|
||||||
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
||||||
|
@ -235,6 +246,16 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !hasKubernetes {
|
||||||
|
if _, ok := subcmd.Annotations["kubernetes"]; ok {
|
||||||
|
subcmd.Hidden = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := subcmd.Annotations["swarm"]; ok {
|
||||||
|
subcmd.Hidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hide subcommands not supported by the server
|
// hide subcommands not supported by the server
|
||||||
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
||||||
subcmd.Hidden = true
|
subcmd.Hidden = true
|
||||||
|
@ -243,23 +264,22 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSupported(cmd *cobra.Command, details versionDetails) error {
|
func isSupported(cmd *cobra.Command, details versionDetails) error {
|
||||||
|
if err := areSubcommandsSupported(cmd, details); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := areFlagsSupported(cmd, details); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
|
||||||
clientVersion := details.Client().ClientVersion()
|
clientVersion := details.Client().ClientVersion()
|
||||||
osType := details.ServerInfo().OSType
|
osType := details.ServerInfo().OSType
|
||||||
hasExperimental := details.ServerInfo().HasExperimental
|
hasExperimental := details.ServerInfo().HasExperimental
|
||||||
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
hasKubernetes := details.ClientInfo().HasKubernetes
|
||||||
|
|
||||||
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
|
||||||
for curr := cmd; curr != nil; curr = curr.Parent() {
|
|
||||||
if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
|
|
||||||
return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
|
|
||||||
}
|
|
||||||
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
|
|
||||||
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
|
|
||||||
}
|
|
||||||
if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
|
||||||
return fmt.Errorf("%s is only supported when experimental cli features are enabled", cmd.CommandPath())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := []string{}
|
errs := []string{}
|
||||||
|
|
||||||
|
@ -279,12 +299,45 @@ func isSupported(cmd *cobra.Command, details versionDetails) error {
|
||||||
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
||||||
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported when experimental cli features are enabled", f.Name))
|
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported when experimental cli features are enabled", f.Name))
|
||||||
}
|
}
|
||||||
|
if _, ok := f.Annotations["kubernetes"]; ok && !hasKubernetes {
|
||||||
|
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker cli with kubernetes features enabled", f.Name))
|
||||||
|
}
|
||||||
|
if _, ok := f.Annotations["swarm"]; ok && hasKubernetes {
|
||||||
|
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker cli with swarm features enabled", f.Name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return errors.New(strings.Join(errs, "\n"))
|
return errors.New(strings.Join(errs, "\n"))
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
||||||
|
func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
|
||||||
|
clientVersion := details.Client().ClientVersion()
|
||||||
|
hasExperimental := details.ServerInfo().HasExperimental
|
||||||
|
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||||
|
hasKubernetes := details.ClientInfo().HasKubernetes
|
||||||
|
|
||||||
|
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
||||||
|
for curr := cmd; curr != nil; curr = curr.Parent() {
|
||||||
|
if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
|
||||||
|
return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
|
||||||
|
}
|
||||||
|
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
|
||||||
|
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
|
||||||
|
}
|
||||||
|
if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
||||||
|
return fmt.Errorf("%s is only supported when experimental cli features are enabled", cmd.CommandPath())
|
||||||
|
}
|
||||||
|
if _, ok := curr.Annotations["kubernetes"]; ok && !hasKubernetes {
|
||||||
|
return fmt.Errorf("%s is only supported on a Docker cli with kubernetes features enabled", cmd.CommandPath())
|
||||||
|
}
|
||||||
|
if _, ok := curr.Annotations["swarm"]; ok && hasKubernetes {
|
||||||
|
return fmt.Errorf("%s is only supported on a Docker cli with swarm features enabled", cmd.CommandPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue