2017-11-20 09:30:52 -05:00
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
2018-05-17 12:03:41 -04:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-02-25 08:31:31 -05:00
|
|
|
"io"
|
2018-05-17 12:03:41 -04:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
|
2018-04-26 05:13:14 -04:00
|
|
|
"github.com/docker/cli/cli/command"
|
2018-10-23 11:05:44 -04:00
|
|
|
"github.com/docker/cli/cli/command/stack/formatter"
|
2017-12-04 06:30:39 -05:00
|
|
|
"github.com/docker/cli/cli/command/stack/options"
|
2018-05-15 10:56:45 -04:00
|
|
|
"github.com/docker/cli/cli/config/configfile"
|
2018-05-17 12:03:41 -04:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
core_v1 "k8s.io/api/core/v1"
|
|
|
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
2017-11-20 09:30:52 -05:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
)
|
|
|
|
|
2018-04-26 05:13:14 -04:00
|
|
|
// GetStacks lists the kubernetes stacks
|
2018-05-17 12:03:41 -04:00
|
|
|
func GetStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
|
2018-04-26 05:13:14 -04:00
|
|
|
if opts.AllNamespaces || len(opts.Namespaces) == 0 {
|
2018-05-15 10:56:45 -04:00
|
|
|
if isAllNamespacesDisabled(kubeCli.ConfigFile().Kubernetes) {
|
|
|
|
opts.AllNamespaces = true
|
|
|
|
}
|
2018-05-17 12:03:41 -04:00
|
|
|
return getStacksWithAllNamespaces(kubeCli, opts)
|
2018-04-26 05:13:14 -04:00
|
|
|
}
|
2018-05-17 12:03:41 -04:00
|
|
|
return getStacksWithNamespaces(kubeCli, opts, removeDuplicates(opts.Namespaces))
|
2018-04-26 05:13:14 -04:00
|
|
|
}
|
|
|
|
|
2018-05-15 10:56:45 -04:00
|
|
|
func isAllNamespacesDisabled(kubeCliConfig *configfile.KubernetesConfig) bool {
|
2019-04-02 05:22:06 -04:00
|
|
|
return kubeCliConfig == nil || kubeCliConfig.AllNamespaces != "disabled"
|
2018-05-15 10:56:45 -04:00
|
|
|
}
|
|
|
|
|
2018-04-26 05:13:14 -04:00
|
|
|
func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
|
2018-04-09 09:07:11 -04:00
|
|
|
composeClient, err := kubeCli.composeClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-04-25 08:24:46 -04:00
|
|
|
stackSvc, err := composeClient.Stacks(opts.AllNamespaces)
|
2017-11-20 09:30:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-04-09 09:13:16 -04:00
|
|
|
stacks, err := stackSvc.List(metav1.ListOptions{})
|
2017-11-20 09:30:52 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var formattedStacks []*formatter.Stack
|
2018-01-31 09:37:14 -05:00
|
|
|
for _, stack := range stacks {
|
2017-11-20 09:30:52 -05:00
|
|
|
formattedStacks = append(formattedStacks, &formatter.Stack{
|
2018-06-27 10:41:00 -04:00
|
|
|
Name: stack.Name,
|
2018-03-27 10:38:47 -04:00
|
|
|
Services: len(stack.getServices()),
|
|
|
|
Orchestrator: "Kubernetes",
|
2018-06-27 10:41:00 -04:00
|
|
|
Namespace: stack.Namespace,
|
2017-11-20 09:30:52 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return formattedStacks, nil
|
|
|
|
}
|
2018-04-26 05:13:14 -04:00
|
|
|
|
2018-05-17 12:03:41 -04:00
|
|
|
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()
|
2022-02-25 08:31:31 -05:00
|
|
|
body, err := io.ReadAll(resp.Body)
|
2018-05-17 12:03:41 -04:00
|
|
|
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) {
|
2018-04-26 05:13:14 -04:00
|
|
|
stacks := []*formatter.Stack{}
|
2018-05-17 12:03:41 -04:00
|
|
|
for _, namespace := range namespaces {
|
2018-04-26 05:13:14 -04:00
|
|
|
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
|
|
|
|
}
|