...

Source file src/github.com/linkerd/linkerd2/pkg/cmd/cmd.go

Documentation: github.com/linkerd/linkerd2/pkg/cmd

     1  package cmd
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/linkerd/linkerd2/pkg/k8s"
    13  	"github.com/linkerd/linkerd2/pkg/k8s/resource"
    14  	log "github.com/sirupsen/logrus"
    15  	"github.com/spf13/cobra"
    16  	corev1 "k8s.io/api/core/v1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/labels"
    19  	"k8s.io/apimachinery/pkg/selection"
    20  	yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
    21  	"k8s.io/client-go/tools/clientcmd"
    22  	"sigs.k8s.io/yaml"
    23  )
    24  
    25  var (
    26  	// DefaultDockerRegistry specifies the default location for Linkerd's images.
    27  	DefaultDockerRegistry = "cr.l5d.io/linkerd"
    28  )
    29  
    30  // GetDefaultNamespace fetches the default namespace
    31  // used in the current KubeConfig context
    32  func GetDefaultNamespace(kubeconfigPath, kubeContext string) string {
    33  	rules := clientcmd.NewDefaultClientConfigLoadingRules()
    34  
    35  	if kubeconfigPath != "" {
    36  		rules.ExplicitPath = kubeconfigPath
    37  	}
    38  
    39  	overrides := &clientcmd.ConfigOverrides{CurrentContext: kubeContext}
    40  	kubeCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
    41  	ns, _, err := kubeCfg.Namespace()
    42  
    43  	if err != nil {
    44  		log.Warnf(`could not set namespace from kubectl context, using 'default' namespace: %s
    45  		 ensure the KUBECONFIG path %s is valid`, err, kubeconfigPath)
    46  		return corev1.NamespaceDefault
    47  	}
    48  
    49  	return ns
    50  }
    51  
    52  // Uninstall prints all cluster-scoped resources matching the given selector
    53  // for the purposes of deleting them.
    54  func Uninstall(ctx context.Context, k8sAPI *k8s.KubernetesAPI, selector string) error {
    55  	resources, err := resource.FetchKubernetesResources(ctx, k8sAPI,
    56  		metav1.ListOptions{LabelSelector: selector},
    57  	)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	if len(resources) == 0 {
    63  		return errors.New("No resources found to uninstall")
    64  	}
    65  	for _, r := range resources {
    66  		if err := r.RenderResource(os.Stdout); err != nil {
    67  			return fmt.Errorf("error rendering Kubernetes resource: %w", err)
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  // Prune takes an install manifest and prints all resources on the cluster which
    74  // match the given label selector but are not in the given manifest. Users are
    75  // expected to pipe these resources to `kubectl delete` to clean up resources
    76  // left on the cluster which are no longer part of the install manifest.
    77  func Prune(ctx context.Context, k8sAPI *k8s.KubernetesAPI, expectedManifests string, selector string) error {
    78  	expectedResources := []resource.Kubernetes{}
    79  	reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(strings.NewReader(expectedManifests), 4096))
    80  	for {
    81  		manifest, err := reader.Read()
    82  		if err != nil {
    83  			if errors.Is(err, io.EOF) {
    84  				break
    85  			}
    86  			return err
    87  		}
    88  		resource := resource.Kubernetes{}
    89  		err = yaml.Unmarshal(manifest, &resource)
    90  		if err != nil {
    91  			fmt.Fprintf(os.Stderr, "error parsing manifest: %s", manifest)
    92  			os.Exit(1)
    93  		}
    94  		expectedResources = append(expectedResources, resource)
    95  	}
    96  
    97  	listOptions := metav1.ListOptions{
    98  		LabelSelector: selector,
    99  	}
   100  	resources, err := resource.FetchPrunableResources(ctx, k8sAPI, metav1.NamespaceAll, listOptions)
   101  	if err != nil {
   102  		fmt.Fprintf(os.Stderr, "error fetching resources: %s\n", err)
   103  		os.Exit(1)
   104  	}
   105  
   106  	for _, resource := range resources {
   107  		// If the resource is not in the expected resource list, render it for
   108  		// pruning.
   109  		if !resourceListContains(expectedResources, resource) {
   110  			if err = resource.RenderResource(os.Stdout); err != nil {
   111  				return fmt.Errorf("error rendering Kubernetes resource: %w\n", err)
   112  			}
   113  		}
   114  	}
   115  	return nil
   116  }
   117  
   118  func resourceListContains(list []resource.Kubernetes, a resource.Kubernetes) bool {
   119  	for _, r := range list {
   120  		if resourceEquals(a, r) {
   121  			return true
   122  		}
   123  	}
   124  	return false
   125  }
   126  
   127  func resourceEquals(a resource.Kubernetes, b resource.Kubernetes) bool {
   128  	return a.GroupVersionKind().GroupKind() == b.GroupVersionKind().GroupKind() &&
   129  		a.GetName() == b.GetName() &&
   130  		a.GetNamespace() == b.GetNamespace()
   131  }
   132  
   133  // ConfigureNamespaceFlagCompletion sets up resource-aware completion for command
   134  // flags that accept a namespace name
   135  func ConfigureNamespaceFlagCompletion(
   136  	cmd *cobra.Command,
   137  	flagNames []string,
   138  	kubeconfigPath string,
   139  	impersonate string,
   140  	impersonateGroup []string,
   141  	kubeContext string,
   142  ) {
   143  	for _, flagName := range flagNames {
   144  		cmd.RegisterFlagCompletionFunc(flagName,
   145  			func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   146  				k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
   147  				if err != nil {
   148  					return nil, cobra.ShellCompDirectiveError
   149  				}
   150  
   151  				cc := k8s.NewCommandCompletion(k8sAPI, "")
   152  				results, err := cc.Complete([]string{k8s.Namespace}, toComplete)
   153  				if err != nil {
   154  					return nil, cobra.ShellCompDirectiveError
   155  				}
   156  
   157  				return results, cobra.ShellCompDirectiveDefault
   158  			})
   159  	}
   160  }
   161  
   162  // ConfigureOutputFlagCompletion sets up resource-aware completion for command
   163  // flags that accept an output name.
   164  func ConfigureOutputFlagCompletion(cmd *cobra.Command) {
   165  	cmd.RegisterFlagCompletionFunc("output",
   166  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   167  			return []string{"basic", "json", "short", "table"}, cobra.ShellCompDirectiveDefault
   168  		})
   169  }
   170  
   171  // ConfigureKubeContextFlagCompletion sets up resource-aware completion for command
   172  // flags based off of a kubeconfig
   173  func ConfigureKubeContextFlagCompletion(cmd *cobra.Command, kubeconfigPath string) {
   174  	cmd.RegisterFlagCompletionFunc("context",
   175  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   176  			rules := clientcmd.NewDefaultClientConfigLoadingRules()
   177  			rules.ExplicitPath = kubeconfigPath
   178  			loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})
   179  			config, err := loader.RawConfig()
   180  			if err != nil {
   181  				return nil, cobra.ShellCompDirectiveError
   182  			}
   183  
   184  			suggestions := []string{}
   185  			uniqContexts := map[string]struct{}{}
   186  			for ctxName := range config.Contexts {
   187  				if strings.HasPrefix(ctxName, toComplete) {
   188  					if _, ok := uniqContexts[ctxName]; !ok {
   189  						suggestions = append(suggestions, ctxName)
   190  						uniqContexts[ctxName] = struct{}{}
   191  					}
   192  				}
   193  			}
   194  
   195  			return suggestions, cobra.ShellCompDirectiveDefault
   196  		})
   197  }
   198  
   199  // GetLabelSelector creates a label selector as a string based on a label key
   200  // whose value may be in the set provided as an argument to the function. If the
   201  // value set is empty then the selector will match resources where the label key
   202  // exists regardless of value.
   203  func GetLabelSelector(labelKey string, labelValues ...string) (string, error) {
   204  	selectionOp := selection.In
   205  	if len(labelValues) < 1 {
   206  		selectionOp = selection.Exists
   207  	}
   208  
   209  	labelRequirement, err := labels.NewRequirement(labelKey, selectionOp, labelValues)
   210  	if err != nil {
   211  		return "", err
   212  	}
   213  
   214  	selector := labels.NewSelector().Add(*labelRequirement)
   215  	return selector.String(), nil
   216  }
   217  
   218  // RegistryOverride replaces the registry-portion of the provided image with the provided registry.
   219  func RegistryOverride(image, newRegistry string) string {
   220  	if image == "" {
   221  		return image
   222  	}
   223  	registry := newRegistry
   224  	if registry != "" && !strings.HasSuffix(registry, "/") {
   225  		registry += "/"
   226  	}
   227  	imageName := image
   228  	if strings.Contains(image, "/") {
   229  		imageName = image[strings.LastIndex(image, "/")+1:]
   230  	}
   231  	return registry + imageName
   232  }
   233  

View as plain text