1 package cmd
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "strings"
8
9 pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
10 "github.com/linkerd/linkerd2/pkg/k8s"
11 "github.com/linkerd/linkerd2/pkg/k8s/resource"
12 mc "github.com/linkerd/linkerd2/pkg/multicluster"
13 log "github.com/sirupsen/logrus"
14 "github.com/spf13/cobra"
15 appsv1 "k8s.io/api/apps/v1"
16 coordinationv1 "k8s.io/api/coordination/v1"
17 corev1 "k8s.io/api/core/v1"
18 rbac "k8s.io/api/rbac/v1"
19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20 "k8s.io/client-go/tools/clientcmd"
21 )
22
23 func newUnlinkCommand() *cobra.Command {
24 opts, err := newLinkOptionsWithDefault()
25 if err != nil {
26 fmt.Fprintln(os.Stderr, err)
27 os.Exit(1)
28 }
29
30 cmd := &cobra.Command{
31 Use: "unlink",
32 Short: "Outputs link resources for deletion",
33 Args: cobra.NoArgs,
34 RunE: func(cmd *cobra.Command, args []string) error {
35
36 if opts.clusterName == "" {
37 return errors.New("You need to specify cluster name")
38 }
39
40 rules := clientcmd.NewDefaultClientConfigLoadingRules()
41 rules.ExplicitPath = kubeconfigPath
42 loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})
43 config, err := loader.RawConfig()
44 if err != nil {
45 return err
46 }
47
48 if kubeContext != "" {
49 config.CurrentContext = kubeContext
50 }
51
52 k, err := k8s.NewAPI(kubeconfigPath, config.CurrentContext, impersonate, impersonateGroup, 0)
53 if err != nil {
54 return err
55 }
56
57 l, err := mc.GetLink(cmd.Context(), k.DynamicClient, opts.namespace, opts.clusterName)
58 if err != nil {
59 return err
60 }
61
62 secret := resource.NewNamespaced(corev1.SchemeGroupVersion.String(), "Secret", fmt.Sprintf("cluster-credentials-%s", opts.clusterName), opts.namespace)
63 link := resource.NewNamespaced(k8s.LinkAPIGroupVersion, "Link", opts.clusterName, opts.namespace)
64 clusterRole := resource.New(rbac.SchemeGroupVersion.String(), "ClusterRole", fmt.Sprintf("linkerd-service-mirror-access-local-resources-%s", opts.clusterName))
65 clusterRoleBinding := resource.New(rbac.SchemeGroupVersion.String(), "ClusterRoleBinding", fmt.Sprintf("linkerd-service-mirror-access-local-resources-%s", opts.clusterName))
66 role := resource.NewNamespaced(rbac.SchemeGroupVersion.String(), "Role", fmt.Sprintf("linkerd-service-mirror-read-remote-creds-%s", opts.clusterName), opts.namespace)
67 roleBinding := resource.NewNamespaced(rbac.SchemeGroupVersion.String(), "RoleBinding", fmt.Sprintf("linkerd-service-mirror-read-remote-creds-%s", opts.clusterName), opts.namespace)
68 serviceAccount := resource.NewNamespaced(corev1.SchemeGroupVersion.String(), "ServiceAccount", fmt.Sprintf("linkerd-service-mirror-%s", opts.clusterName), opts.namespace)
69 serviceMirror := resource.NewNamespaced(appsv1.SchemeGroupVersion.String(), "Deployment", fmt.Sprintf("linkerd-service-mirror-%s", opts.clusterName), opts.namespace)
70 lease := resource.NewNamespaced(coordinationv1.SchemeGroupVersion.String(), "Lease", fmt.Sprintf("service-mirror-write-%s", opts.clusterName), opts.namespace)
71
72 resources := []resource.Kubernetes{
73 secret, link, clusterRole, clusterRoleBinding,
74 role, roleBinding, serviceAccount, serviceMirror, lease,
75 }
76
77 if l.ProbeSpec.Path != "" {
78 gatewayMirror := resource.NewNamespaced(corev1.SchemeGroupVersion.String(), "Service", fmt.Sprintf("probe-gateway-%s", opts.clusterName), opts.namespace)
79 resources = append(resources, gatewayMirror)
80 }
81
82 selector := fmt.Sprintf("%s=%s,%s=%s",
83 k8s.MirroredResourceLabel, "true",
84 k8s.RemoteClusterNameLabel, opts.clusterName,
85 )
86 svcList, err := k.CoreV1().Services(metav1.NamespaceAll).List(cmd.Context(), metav1.ListOptions{LabelSelector: selector})
87 if err != nil {
88 return err
89 }
90 for _, svc := range svcList.Items {
91 resources = append(resources,
92 resource.NewNamespaced(corev1.SchemeGroupVersion.String(), "Service", svc.Name, svc.Namespace),
93 )
94 }
95
96 selector = fmt.Sprintf("%s=%s", clusterNameLabel, opts.clusterName)
97 destinationCredentials, err := k.CoreV1().Secrets(controlPlaneNamespace).List(cmd.Context(), metav1.ListOptions{LabelSelector: selector})
98 if err != nil {
99 return err
100 }
101 for _, secret := range destinationCredentials.Items {
102 resources = append(resources,
103 resource.NewNamespaced(corev1.SchemeGroupVersion.String(), "Secret", secret.Name, secret.Namespace),
104 )
105 }
106
107 for _, r := range resources {
108 if err := r.RenderResource(stdout); err != nil {
109 log.Errorf("failed to render resource %s: %s", r.Name, err)
110 }
111 }
112
113 return nil
114 },
115 }
116
117 cmd.Flags().StringVar(&opts.namespace, "namespace", defaultMulticlusterNamespace, "The namespace for the service account")
118 cmd.Flags().StringVar(&opts.clusterName, "cluster-name", "", "Cluster name")
119
120 pkgcmd.ConfigureNamespaceFlagCompletion(
121 cmd, []string{"namespace"},
122 kubeconfigPath, impersonate, impersonateGroup, kubeContext)
123
124 configureClusterNameFlagCompletion(cmd)
125 return cmd
126 }
127
128 func configureClusterNameFlagCompletion(cmd *cobra.Command) {
129 cmd.RegisterFlagCompletionFunc("cluster-name",
130 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
131 k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
132 if err != nil {
133 return nil, cobra.ShellCompDirectiveError
134 }
135
136 cc := k8s.NewCommandCompletion(k8sAPI, corev1.NamespaceAll)
137 results, err := cc.Complete([]string{strings.ToLower(k8s.LinkKind)}, toComplete)
138 if err != nil {
139 return nil, cobra.ShellCompDirectiveError
140 }
141
142 return results, cobra.ShellCompDirectiveDefault
143 })
144 }
145
View as plain text