mirror of https://github.com/linkerd/linkerd2.git
233 lines
7.2 KiB
Go
233 lines
7.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/linkerd/linkerd2/pkg/k8s"
|
|
"github.com/linkerd/linkerd2/pkg/k8s/resource"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/selection"
|
|
yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
var (
|
|
// DefaultDockerRegistry specifies the default location for Linkerd's images.
|
|
DefaultDockerRegistry = "cr.l5d.io/linkerd"
|
|
)
|
|
|
|
// GetDefaultNamespace fetches the default namespace
|
|
// used in the current KubeConfig context
|
|
func GetDefaultNamespace(kubeconfigPath, kubeContext string) string {
|
|
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
|
|
|
if kubeconfigPath != "" {
|
|
rules.ExplicitPath = kubeconfigPath
|
|
}
|
|
|
|
overrides := &clientcmd.ConfigOverrides{CurrentContext: kubeContext}
|
|
kubeCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
|
|
ns, _, err := kubeCfg.Namespace()
|
|
|
|
if err != nil {
|
|
log.Warnf(`could not set namespace from kubectl context, using 'default' namespace: %s
|
|
ensure the KUBECONFIG path %s is valid`, err, kubeconfigPath)
|
|
return corev1.NamespaceDefault
|
|
}
|
|
|
|
return ns
|
|
}
|
|
|
|
// Uninstall prints all cluster-scoped resources matching the given selector
|
|
// for the purposes of deleting them.
|
|
func Uninstall(ctx context.Context, k8sAPI *k8s.KubernetesAPI, selector string) error {
|
|
resources, err := resource.FetchKubernetesResources(ctx, k8sAPI,
|
|
metav1.ListOptions{LabelSelector: selector},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(resources) == 0 {
|
|
return errors.New("No resources found to uninstall")
|
|
}
|
|
for _, r := range resources {
|
|
if err := r.RenderResource(os.Stdout); err != nil {
|
|
return fmt.Errorf("error rendering Kubernetes resource: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Prune takes an install manifest and prints all resources on the cluster which
|
|
// match the given label selector but are not in the given manifest. Users are
|
|
// expected to pipe these resources to `kubectl delete` to clean up resources
|
|
// left on the cluster which are no longer part of the install manifest.
|
|
func Prune(ctx context.Context, k8sAPI *k8s.KubernetesAPI, expectedManifests string, selector string) error {
|
|
expectedResources := []resource.Kubernetes{}
|
|
reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(strings.NewReader(expectedManifests), 4096))
|
|
for {
|
|
manifest, err := reader.Read()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
resource := resource.Kubernetes{}
|
|
err = yaml.Unmarshal(manifest, &resource)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error parsing manifest: %s", manifest)
|
|
os.Exit(1)
|
|
}
|
|
expectedResources = append(expectedResources, resource)
|
|
}
|
|
|
|
listOptions := metav1.ListOptions{
|
|
LabelSelector: selector,
|
|
}
|
|
resources, err := resource.FetchPrunableResources(ctx, k8sAPI, metav1.NamespaceAll, listOptions)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error fetching resources: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
for _, resource := range resources {
|
|
// If the resource is not in the expected resource list, render it for
|
|
// pruning.
|
|
if !resourceListContains(expectedResources, resource) {
|
|
if err = resource.RenderResource(os.Stdout); err != nil {
|
|
return fmt.Errorf("error rendering Kubernetes resource: %w\n", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resourceListContains(list []resource.Kubernetes, a resource.Kubernetes) bool {
|
|
for _, r := range list {
|
|
if resourceEquals(a, r) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func resourceEquals(a resource.Kubernetes, b resource.Kubernetes) bool {
|
|
return a.GroupVersionKind().GroupKind() == b.GroupVersionKind().GroupKind() &&
|
|
a.GetName() == b.GetName() &&
|
|
a.GetNamespace() == b.GetNamespace()
|
|
}
|
|
|
|
// ConfigureNamespaceFlagCompletion sets up resource-aware completion for command
|
|
// flags that accept a namespace name
|
|
func ConfigureNamespaceFlagCompletion(
|
|
cmd *cobra.Command,
|
|
flagNames []string,
|
|
kubeconfigPath string,
|
|
impersonate string,
|
|
impersonateGroup []string,
|
|
kubeContext string,
|
|
) {
|
|
for _, flagName := range flagNames {
|
|
cmd.RegisterFlagCompletionFunc(flagName,
|
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
|
|
cc := k8s.NewCommandCompletion(k8sAPI, "")
|
|
results, err := cc.Complete([]string{k8s.Namespace}, toComplete)
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
|
|
return results, cobra.ShellCompDirectiveDefault
|
|
})
|
|
}
|
|
}
|
|
|
|
// ConfigureOutputFlagCompletion sets up resource-aware completion for command
|
|
// flags that accept an output name.
|
|
func ConfigureOutputFlagCompletion(cmd *cobra.Command) {
|
|
cmd.RegisterFlagCompletionFunc("output",
|
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
return []string{"basic", "json", "short", "table"}, cobra.ShellCompDirectiveDefault
|
|
})
|
|
}
|
|
|
|
// ConfigureKubeContextFlagCompletion sets up resource-aware completion for command
|
|
// flags based off of a kubeconfig
|
|
func ConfigureKubeContextFlagCompletion(cmd *cobra.Command, kubeconfigPath string) {
|
|
cmd.RegisterFlagCompletionFunc("context",
|
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
|
rules.ExplicitPath = kubeconfigPath
|
|
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})
|
|
config, err := loader.RawConfig()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
|
|
suggestions := []string{}
|
|
uniqContexts := map[string]struct{}{}
|
|
for ctxName := range config.Contexts {
|
|
if strings.HasPrefix(ctxName, toComplete) {
|
|
if _, ok := uniqContexts[ctxName]; !ok {
|
|
suggestions = append(suggestions, ctxName)
|
|
uniqContexts[ctxName] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return suggestions, cobra.ShellCompDirectiveDefault
|
|
})
|
|
}
|
|
|
|
// GetLabelSelector creates a label selector as a string based on a label key
|
|
// whose value may be in the set provided as an argument to the function. If the
|
|
// value set is empty then the selector will match resources where the label key
|
|
// exists regardless of value.
|
|
func GetLabelSelector(labelKey string, labelValues ...string) (string, error) {
|
|
selectionOp := selection.In
|
|
if len(labelValues) < 1 {
|
|
selectionOp = selection.Exists
|
|
}
|
|
|
|
labelRequirement, err := labels.NewRequirement(labelKey, selectionOp, labelValues)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
selector := labels.NewSelector().Add(*labelRequirement)
|
|
return selector.String(), nil
|
|
}
|
|
|
|
// RegistryOverride replaces the registry-portion of the provided image with the provided registry.
|
|
func RegistryOverride(image, newRegistry string) string {
|
|
if image == "" {
|
|
return image
|
|
}
|
|
registry := newRegistry
|
|
if registry != "" && !strings.HasSuffix(registry, "/") {
|
|
registry += "/"
|
|
}
|
|
imageName := image
|
|
if strings.Contains(image, "/") {
|
|
imageName = image[strings.LastIndex(image, "/")+1:]
|
|
}
|
|
return registry + imageName
|
|
}
|