Skip to content

Commit

Permalink
Implement stack ls (docker#4)
Browse files Browse the repository at this point in the history
* Go back to kamoulox master

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>

* Stack LS

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
  • Loading branch information
simonferquel authored and Vincent Demeester committed Oct 11, 2017
1 parent 1b1847d commit 20a80f3
Show file tree
Hide file tree
Showing 671 changed files with 129,862 additions and 28 deletions.
33 changes: 33 additions & 0 deletions cli/command/stack/formater.go
@@ -0,0 +1,33 @@
package stack

import (
"fmt"
"io"
"text/tabwriter"
)

type kformatter struct {
writer *tabwriter.Writer
}

func newKformatter(out io.Writer, headers ...interface{}) kformatter {
kformatter := kformatter{
writer: tabwriter.NewWriter(out, 0, 8, 1, '\t', 0),
}
kformatter.Write(headers...)
return kformatter
}

func (f kformatter) Write(data ...interface{}) {
for i, val := range data {
fmt.Fprint(f.writer, val)
if i != len(data)-1 {
fmt.Fprint(f.writer, "\t")
}
}
fmt.Fprintln(f.writer)
}

func (f kformatter) Flush() {
f.writer.Flush()
}
110 changes: 96 additions & 14 deletions cli/command/stack/list.go
@@ -1,22 +1,34 @@
package stack

import (
"fmt"
"sort"
"time"

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd/api/latest"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
dockerformat "github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/internal/orchestrator"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/kamoulox-compose/pkg/convert/fromCompose"
"github.com/docker/kamoulox-compose/pkg/kube"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/printers"
"vbom.ml/util/sortorder"
)

type listOptions struct {
format string
format string
output string
selector string
}

func newListCommand(dockerCli command.Cli) *cobra.Command {
Expand All @@ -28,16 +40,86 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List stacks",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
if dockerCli.KubernetesEnabled() {
return runListKube(dockerCli, opts, cmd)
}
return runListSwarm(dockerCli, opts)
},
}

flags := cmd.Flags()
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
if orchestrator.IsKubernetesEnabled() {
flags.StringVarP(&opts.selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.")
flags.StringVarP(&opts.output, "output", "o", "", "Output format. One of:json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
flags.StringVar(&opts.format, "format", "", "emplate string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. if -o is not defined, assume it is go-template")
} else {
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
}
return cmd
}
func runListKube(dockerCli command.Cli, opts listOptions, cmd *cobra.Command) error {
stackSvc, err := dockerCli.ComposeClient().Stacks()
if err != nil {
return err
}
stacks, err := stackSvc.List(kube.ListOptions{
LabelSelector: opts.selector,
})
if err != nil {
return err
}

if len(stacks.Items) == 0 {
fmt.Fprintln(dockerCli.Out(), "No resources found.")
return nil
}

if opts.format != "" && opts.output == "" {
opts.output = "go-template"
}

if opts.output == "" {
f := newKformatter(dockerCli.Out(), "NAMESPACE", "NAME", "SERVICES", "AGE")
for _, stack := range stacks.Items {
config, _, err := fromCompose.ParseYAMLToStack(stack.Name, stack.Spec)
if err != nil {
return err
}

f.Write(stack.Namespace, stack.Name, len(config.Services), printers.ShortHumanDuration(time.Now().Sub(stack.CreationTimestamp.Time)))
}
f.Flush()

return nil
}

if opts.output == "wide" {
f := newKformatter(dockerCli.Out(), "NAMESPACE", "NAME", "SERVICES", "AGE", "STATUS")
for _, stack := range stacks.Items {
config, _, err := fromCompose.ParseYAMLToStack(stack.Name, stack.Spec)
if err != nil {
return err
}

f.Write(stack.Namespace, stack.Name, len(config.Services), printers.ShortHumanDuration(time.Now().Sub(stack.CreationTimestamp.Time)), stack.Status.Message)
}
f.Flush()

return nil
}
mapper, err := dockerCli.ComposeClient().RESTMapper()
if err != nil {
return err
}
printer, err := cmdutil.PrinterForCommand(cmd, nil, mapper, latest.Scheme, nil, []runtime.Decoder{latest.Codec}, printers.PrintOptions{})
if err != nil {
return err
}

return printer.PrintObj(stacks, dockerCli.Out())
}

func runList(dockerCli command.Cli, opts listOptions) error {
func runListSwarm(dockerCli command.Cli, opts listOptions) error {
client := dockerCli.Client()
ctx := context.Background()

Expand All @@ -47,30 +129,30 @@ func runList(dockerCli command.Cli, opts listOptions) error {
}
format := opts.format
if len(format) == 0 {
format = formatter.TableFormatKey
format = dockerformat.TableFormatKey
}
stackCtx := formatter.Context{
stackCtx := dockerformat.Context{
Output: dockerCli.Out(),
Format: formatter.NewStackFormat(format),
Format: dockerformat.NewStackFormat(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
return dockerformat.StackWrite(stackCtx, stacks)
}

type byName []*formatter.Stack
type byName []*dockerformat.Stack

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) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }

func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
func getStacks(ctx context.Context, apiclient client.APIClient) ([]*dockerformat.Stack, error) {
services, err := apiclient.ServiceList(
ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil {
return nil, err
}
m := make(map[string]*formatter.Stack)
m := make(map[string]*dockerformat.Stack)
for _, service := range services {
labels := service.Spec.Labels
name, ok := labels[convert.LabelNamespace]
Expand All @@ -80,15 +162,15 @@ func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.St
}
ztack, ok := m[name]
if !ok {
m[name] = &formatter.Stack{
m[name] = &dockerformat.Stack{
Name: name,
Services: 1,
}
} else {
ztack.Services++
}
}
var stacks []*formatter.Stack
var stacks []*dockerformat.Stack
for _, stack := range m {
stacks = append(stacks, stack)
}
Expand Down
18 changes: 4 additions & 14 deletions cmd/docker/docker.go
Expand Up @@ -12,6 +12,7 @@ import (
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/debug"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/cli/internal/orchestrator"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/term"
Expand Down Expand Up @@ -45,13 +46,8 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
if cmd.Name() == "daemon" {
return nil
}
orchestrator := getOrchestrator()
kubernetesEnabled := false
switch orchestrator {
case "swarm", "swarmkit", "":
// normal behaviour
case "kubernetes", "kube", "k8s":
kubernetesEnabled = true
kubernetesEnabled := orchestrator.IsKubernetesEnabled()
if kubernetesEnabled {
parent := cmd
// search up the command stack to find the root command
for parent.HasParent() && parent.Parent().HasParent() {
Expand All @@ -62,8 +58,6 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
default:
return errors.New(fmt.Sprintf("unsupport kubernetes command \"%s\"", parent.Name()))
}
default:
return errors.New(fmt.Sprintf("unsupported orchestrator \"%s\", set DOCKER_ORCHESTRATOR to \"swarm\" or \"kubernetes\"", orchestrator))
}
// flags must be the top-level command flags, not cmd.Flags()
opts.Common.SetDefaultOptions(flags)
Expand Down Expand Up @@ -94,10 +88,6 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd
}

func getOrchestrator() string {
return os.Getenv("DOCKER_ORCHESTRATOR")
}

func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
// When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate
// output if the feature is not supported.
Expand Down Expand Up @@ -161,7 +151,7 @@ func initializeDockerCli(dockerCli *command.DockerCli, flags *pflag.FlagSet, opt
// flags must be the top-level command flags, not cmd.Flags()
opts.Common.SetDefaultOptions(flags)
dockerPreRun(opts)
dockerCli.Initialize(opts, false)
dockerCli.Initialize(opts, orchestrator.IsKubernetesEnabled())
}
}

Expand Down
16 changes: 16 additions & 0 deletions internal/orchestrator/kube.go
@@ -0,0 +1,16 @@
package orchestrator

import "os"

func GetOrchestrator() string {
return os.Getenv("DOCKER_ORCHESTRATOR")
}

func IsKubernetesEnabled() bool {
orchestrator := GetOrchestrator()
switch orchestrator {
case "kubernetes", "kube", "k8s":
return true
}
return false
}
7 changes: 7 additions & 0 deletions vendor.conf
@@ -1,5 +1,6 @@
github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
github.com/Azure/go-ansiterm 19f72df4d05d31cbe1c56bfc8045c96babff6c7e
github.com/Azure/go-autorest 5b4b9cc27e8f7150d75a133d74b176a067b7e7b6
github.com/containerd/continuity 22694c680ee48fb8f50015b44618517e2bde77e8
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
Expand All @@ -14,17 +15,21 @@ github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
github.com/coreos/etcd 46ee06a85cdf0c02ea453bbae71c86f27f3f2ee5
github.com/coreos/go-semver 1817cd4bea52af76542157eeabd74b057d1a199e
github.com/coreos/go-systemd 24036eb3df68550d24a2736c5d013f4e83366866
github.com/dgrijalva/jwt-go a539ee1a749a2b895533f979515ac7e6e0f5b650
github.com/elazarl/go-bindata-assetfs 30f82fa23fd844bd5bb1e5f216db87fd77b5eb43
github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46
github.com/emicklei/go-restful-swagger12 dcef7f55730566d41eae5db10e7d6981829720f6
github.com/evanphx/json-patch 944e07253867aacae43c04b2e6a239005443f33a
github.com/exponent-io/jsonpath d6023ce2651d8eafb5c75bb0c7167536102ec9f5
github.com/fatih/camelcase f6a740d52f961c60348ebb109adde9f4635d7540
github.com/fatih/color 5df930a27be2502f99b292b7cc09ebad4d0891f4
github.com/ghodss/yaml 0ca9ea5df5451ffdf184b4428c902747c2c11cd7
github.com/go-openapi/jsonpointer 46af16f9f7b149af66e5d1bd010e3574dc06de98
github.com/go-openapi/jsonreference 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff
github.com/go-openapi/swag 1d0bd113de87027671077d3c71eb3ac5d7dbba72
github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed
github.com/golang/groupcache b710c8433bd175204919eb38776e944233235d03
github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
github.com/googleapis/gnostic e4f56557df6250e1945ee6854f181ce4e1c2c646
github.com/goware/prefixer 395022866408d928fc2439f7eac73dd8d370ec1d
Expand Down Expand Up @@ -57,6 +62,8 @@ k8s.io/apiextensions-apiserver c682349b0d1c12975d8e24a9799b66747255d7a5
k8s.io/apimachinery 7da60ba7ddca684051555f2c558eef2dfebc70d5
k8s.io/apiserver e24df9a2e58151a85874948908a454d511066460
k8s.io/client-go 1be407b92aa39a2f63ddbb3d46104a1fd425fda0
k8s.io/metrics 04fb35bb958e31b9aeb41e99e2bb3d53fd772316
k8s.io/kubernetes effcdda0cee4d4c8d1df832f17bb5fbd281c4bad

# the docker/go package contains a customized version of canonical/json
# and is used by Notary. The package is periodically rebased on current Go versions.
Expand Down

0 comments on commit 20a80f3

Please sign in to comment.