package kubernetes import ( "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/config/configfile" "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(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { if opts.AllNamespaces || len(opts.Namespaces) == 0 { if isAllNamespacesDisabled(kubeCli.ConfigFile().Kubernetes) { opts.AllNamespaces = true } return getStacksWithAllNamespaces(kubeCli, opts) } return getStacksWithNamespaces(kubeCli, opts, removeDuplicates(opts.Namespaces)) } func isAllNamespacesDisabled(kubeCliConfig *configfile.KubernetesConfig) bool { return kubeCliConfig == nil || kubeCliConfig != nil && kubeCliConfig.AllNamespaces != "disabled" } func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { composeClient, err := kubeCli.composeClient() if err != nil { return nil, err } stackSvc, err := composeClient.Stacks(opts.AllNamespaces) if err != nil { return nil, err } stacks, err := stackSvc.List(metav1.ListOptions{}) if err != nil { return nil, err } var formattedStacks []*formatter.Stack for _, stack := range stacks { formattedStacks = append(formattedStacks, &formatter.Stack{ Name: stack.Name, Services: len(stack.getServices()), Orchestrator: "Kubernetes", Namespace: stack.Namespace, }) } return formattedStacks, nil } 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 namespaces { kubeCli.kubeNamespace = namespace ss, err := getStacks(kubeCli, opts) if err != nil { return nil, err } stacks = append(stacks, ss...) } return stacks, nil } func removeDuplicates(namespaces []string) []string { found := make(map[string]bool) results := namespaces[:0] for _, n := range namespaces { if !found[n] { results = append(results, n) found[n] = true } } return results }