diff --git a/cli/command/stack/kubernetes/list.go b/cli/command/stack/kubernetes/list.go index 27b2a81b7c..4a429731a5 100644 --- a/cli/command/stack/kubernetes/list.go +++ b/cli/command/stack/kubernetes/list.go @@ -1,22 +1,27 @@ package kubernetes import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/stack/options" + "github.com/pkg/errors" + core_v1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // GetStacks lists the kubernetes stacks -func GetStacks(dockerCli command.Cli, opts options.List, kopts Options) ([]*formatter.Stack, error) { - kubeCli, err := WrapCli(dockerCli, kopts) - if err != nil { - return nil, err - } +func GetStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { if opts.AllNamespaces || len(opts.Namespaces) == 0 { - return getStacks(kubeCli, opts) + return getStacksWithAllNamespaces(kubeCli, opts) } - return getStacksWithNamespaces(kubeCli, opts) + return getStacksWithNamespaces(kubeCli, opts, removeDuplicates(opts.Namespaces)) } func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { @@ -44,9 +49,62 @@ func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) return formattedStacks, nil } -func getStacksWithNamespaces(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { +func getStacksWithAllNamespaces(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { + stacks, err := getStacks(kubeCli, opts) + if !apierrs.IsForbidden(err) { + return stacks, err + } + namespaces, err2 := getUserVisibleNamespaces(*kubeCli) + if err2 != nil { + return nil, errors.Wrap(err2, "failed to query user visible namespaces") + } + if namespaces == nil { + // UCP API not present, fall back to Kubernetes error + return nil, err + } + opts.AllNamespaces = false + return getStacksWithNamespaces(kubeCli, opts, namespaces) +} + +func getUserVisibleNamespaces(dockerCli command.Cli) ([]string, error) { + host := dockerCli.Client().DaemonHost() + endpoint, err := url.Parse(host) + if err != nil { + return nil, err + } + endpoint.Scheme = "https" + endpoint.Path = "/kubernetesNamespaces" + resp, err := dockerCli.Client().HTTPClient().Get(endpoint.String()) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(err, "received %d status and unable to read response", resp.StatusCode) + } + switch resp.StatusCode { + case http.StatusOK: + nms := &core_v1.NamespaceList{} + if err := json.Unmarshal(body, nms); err != nil { + return nil, errors.Wrapf(err, "unmarshal failed: %s", string(body)) + } + namespaces := make([]string, len(nms.Items)) + for i, namespace := range nms.Items { + namespaces[i] = namespace.Name + } + return namespaces, nil + case http.StatusNotFound: + // UCP API not present + return nil, nil + default: + return nil, fmt.Errorf("received %d status while retrieving namespaces: %s", resp.StatusCode, string(body)) + } +} + +func getStacksWithNamespaces(kubeCli *KubeCli, opts options.List, namespaces []string) ([]*formatter.Stack, error) { stacks := []*formatter.Stack{} - for _, namespace := range removeDuplicates(opts.Namespaces) { + for _, namespace := range namespaces { kubeCli.kubeNamespace = namespace ss, err := getStacks(kubeCli, opts) if err != nil { diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 26737ec132..92e6a09980 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -47,7 +47,11 @@ func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error stacks = append(stacks, ss...) } if dockerCli.ClientInfo().HasKubernetes() { - ss, err := kubernetes.GetStacks(dockerCli, opts, kubernetes.NewOptions(cmd.Flags())) + kubeCli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + if err != nil { + return err + } + ss, err := kubernetes.GetStacks(kubeCli, opts) if err != nil { return err }