Skip to content

Commit 3a2548b

Browse files
authored
Disconnect cluster in gitops (#3470)
* Add delete service account resources * Test pass for delete service account resources * Add label managed-by to resources when being created during reconciliation * Fix DisconnectCluster function to include retrieving service account name and cluster role binding name and delete resources accordingly * Add gitops disconnect cluster cmd * Add service account name and cluster role binding name flags to gitops disconnect cmd Add check service account name and check cluster role binding name functions to verify their existance with the connect-cluster label * Add existing resources to simpleClientSet when creating it instead of using helper function in service account tests * Make newGitopsClusterScheme unexposed * Move prerun disinheritApiFlags to new function in common app pkg in cmd gitops pkg dir * update go.mod
1 parent 61a0042 commit 3a2548b

File tree

11 files changed

+494
-62
lines changed

11 files changed

+494
-62
lines changed

cmd/gitops/app/connect/clusters/cmd.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package connect
22

33
import (
4-
// "github.com/go-logr/stdr"
5-
64
"github.com/spf13/cobra"
75
"github.com/weaveworks/weave-gitops-enterprise/pkg/cluster/connector"
86
"github.com/weaveworks/weave-gitops/cmd/gitops/config"

cmd/gitops/app/connect/cmd.go

+2-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package connect
33
import (
44
"github.com/spf13/cobra"
55
connect "github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/connect/clusters"
6+
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/pkg/app"
67
"github.com/weaveworks/weave-gitops/cmd/gitops/config"
78
)
89

@@ -13,19 +14,7 @@ func Command(opts *config.Options) *cobra.Command {
1314
Example: `
1415
# Connect a cluster
1516
gitops connect cluster`,
16-
PreRunE: func(cmd *cobra.Command, args []string) error {
17-
names := []string{
18-
"endpoint",
19-
"password",
20-
"username",
21-
}
22-
flags := cmd.InheritedFlags()
23-
for _, name := range names {
24-
err := flags.SetAnnotation(name, cobra.BashCompOneRequiredFlag, []string{"false"})
25-
return err
26-
}
27-
return nil
28-
},
17+
PreRunE: app.DisinheritAPIFlags,
2918
}
3019

3120
cmd.AddCommand(connect.ConnectCommand(opts))
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package disconnect
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/weaveworks/weave-gitops-enterprise/pkg/cluster/connector"
6+
"github.com/weaveworks/weave-gitops/cmd/gitops/config"
7+
"github.com/weaveworks/weave-gitops/core/logger"
8+
"k8s.io/apimachinery/pkg/types"
9+
"sigs.k8s.io/controller-runtime/pkg/log"
10+
)
11+
12+
type disconnectOptionsFlags struct {
13+
RemoteClusterContext string
14+
ServiceAccountName string
15+
ClusterRoleBindingName string
16+
Namespace string
17+
Debug string
18+
}
19+
20+
var disconnectOptionsCmdFlags disconnectOptionsFlags
21+
22+
// DisconnectCommand is the command for disconnect clusters and includes all the required flags
23+
func DisconnectCommand(opts *config.Options) *cobra.Command {
24+
cmd := &cobra.Command{
25+
Use: "cluster",
26+
Aliases: []string{"clusters"},
27+
Short: "Disconnect cluster from remote cluster by deleting resources",
28+
Example: `
29+
# Disconnect cluster
30+
gitops disconnect cluster [PARAMS] <CLUSTER_NAME>
31+
`,
32+
SilenceUsage: true,
33+
SilenceErrors: true,
34+
Args: cobra.MinimumNArgs(1),
35+
RunE: disconnectClusterCmdRunE(opts),
36+
}
37+
38+
cmd.Flags().StringVar(&disconnectOptionsCmdFlags.RemoteClusterContext, "connect-context", "", "Context name of the remote cluster")
39+
cmd.Flags().StringVar(&disconnectOptionsCmdFlags.ServiceAccountName, "service-account", "weave-gitops-enterprise", "Service account name to be created/used")
40+
cmd.Flags().StringVar(&disconnectOptionsCmdFlags.ClusterRoleBindingName, "cluster-role-binding", "weave-gitops-enterprise", "Cluster role binding name to be created/used")
41+
cmd.Flags().StringVarP(&disconnectOptionsCmdFlags.Namespace, "namespace", "n", "default", "Namespace of remote cluster")
42+
cmd.Flags().StringVarP(&disconnectOptionsCmdFlags.Debug, "debug", "d", "INFO", "Verbose level of logs")
43+
44+
return cmd
45+
}
46+
47+
func disconnectClusterCmdRunE(opts *config.Options) func(*cobra.Command, []string) error {
48+
return func(cmd *cobra.Command, args []string) error {
49+
clusterName := args[0]
50+
51+
options := connector.ClusterConnectionOptions{
52+
GitopsClusterName: types.NamespacedName{Name: clusterName, Namespace: disconnectOptionsCmdFlags.Namespace},
53+
ServiceAccountName: disconnectOptionsCmdFlags.ServiceAccountName,
54+
ClusterRoleBindingName: disconnectOptionsCmdFlags.ClusterRoleBindingName,
55+
RemoteClusterContext: disconnectOptionsCmdFlags.RemoteClusterContext,
56+
ConfigPath: opts.Kubeconfig,
57+
}
58+
59+
newLogger, _ := logger.New(disconnectOptionsCmdFlags.Debug, false)
60+
ctx := log.IntoContext(cmd.Context(), newLogger)
61+
62+
return connector.DisconnectCluster(ctx, &options)
63+
64+
}
65+
}

cmd/gitops/app/disconnect/cmd.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package disconnect
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
disconnect "github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/disconnect/clusters"
6+
7+
// "github.com/weaveworks/weave-gitops/cmd/gitops/app"
8+
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/pkg/app"
9+
"github.com/weaveworks/weave-gitops/cmd/gitops/config"
10+
)
11+
12+
// Command returns the command for disconnect
13+
func Command(opts *config.Options) *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "disconnect",
16+
Short: "Disconnect clusters",
17+
Example: `
18+
# Disconnect a cluster
19+
gitops disconnect cluster`,
20+
PreRunE: app.DisinheritAPIFlags,
21+
}
22+
23+
cmd.AddCommand(disconnect.DisconnectCommand(opts))
24+
25+
return cmd
26+
}

cmd/gitops/app/root/cmd.go

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/connect"
1515
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/create"
1616
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/delete"
17+
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/disconnect"
1718
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/generate"
1819
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/get"
1920
"github.com/weaveworks/weave-gitops-enterprise/cmd/gitops/app/remove"
@@ -142,6 +143,7 @@ func Command(client *adapters.HTTPClient) *cobra.Command {
142143
rootCmd.AddCommand(generate.Command())
143144
rootCmd.AddCommand(bootstrap.Command(options))
144145
rootCmd.AddCommand(connect.Command(options))
146+
rootCmd.AddCommand(disconnect.Command(options))
145147

146148
return rootCmd
147149
}

cmd/gitops/pkg/app/app.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package app
2+
3+
import "github.com/spf13/cobra"
4+
5+
// DisinheritAPIFlags turns off the required flag for CLI options related to accessing the Weave GitOps API.
6+
//
7+
// Currently, our top-level Command defines some top-level flags that are useful for most commands
8+
// including endpoint,username, and passowrd
9+
// but not all commands require access to the API, and so this can be used to disable top-level flags.
10+
func DisinheritAPIFlags(cmd *cobra.Command, args []string) error {
11+
names := []string{
12+
"endpoint",
13+
"password",
14+
"username",
15+
}
16+
flags := cmd.InheritedFlags()
17+
for _, name := range names {
18+
err := flags.SetAnnotation(name, cobra.BashCompOneRequiredFlag, []string{"false"})
19+
return err
20+
}
21+
return nil
22+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ require (
9292
github.com/fluxcd/pkg/tar v0.2.0 // indirect
9393
github.com/gitops-tools/pkg v0.1.0 // indirect
9494
github.com/google/go-containerregistry v0.12.0 // indirect
95+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
9596
)
9697

9798
require (
@@ -120,7 +121,6 @@ require (
120121
github.com/mschoch/smat v0.2.0 // indirect
121122
go.etcd.io/bbolt v1.3.7 // indirect
122123
go.opentelemetry.io/otel/metric v0.37.0 // indirect
123-
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
124124
google.golang.org/api v0.126.0 // indirect
125125
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
126126
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect

pkg/cluster/connector/connector.go

+89-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import (
55

66
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
77
"github.com/weaveworks/weave-gitops/core/logger"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/labels"
810
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/selection"
912
"k8s.io/apimachinery/pkg/types"
1013
"k8s.io/client-go/dynamic"
1114
"k8s.io/client-go/kubernetes"
@@ -42,12 +45,20 @@ type ClusterConnectionOptions struct {
4245
GitopsClusterName types.NamespacedName
4346
}
4447

45-
func getSecretNameForConfig(ctx context.Context, config *rest.Config, options *ClusterConnectionOptions) (string, error) {
48+
func getDynClientAndScheme(config *rest.Config) (dynamic.Interface, *runtime.Scheme, error) {
4649
dynClient, err := dynamic.NewForConfig(config)
4750
if err != nil {
48-
return "", err
51+
return nil, nil, err
4952
}
50-
scheme, err := NewGitopsClusterScheme()
53+
scheme, err := newGitopsClusterScheme()
54+
if err != nil {
55+
return nil, nil, err
56+
}
57+
return dynClient, scheme, nil
58+
}
59+
60+
func getSecretNameForConfig(ctx context.Context, config *rest.Config, options *ClusterConnectionOptions) (string, error) {
61+
dynClient, scheme, err := getDynClientAndScheme(config)
5162
if err != nil {
5263
return "", err
5364
}
@@ -68,7 +79,6 @@ func ConnectCluster(ctx context.Context, options *ClusterConnectionOptions) erro
6879
if err != nil {
6980
return err
7081
}
71-
7282
// Get the context from SpokeClusterContext
7383
spokeClusterConfig, err := configForContext(ctx, pathOpts, options.RemoteClusterContext)
7484
if err != nil {
@@ -109,9 +119,9 @@ func ConnectCluster(ctx context.Context, options *ClusterConnectionOptions) erro
109119
return nil
110120
}
111121

112-
// NewGitopsClusterScheme returns a scheme with the GitopsCluster schema
122+
// newGitopsClusterScheme returns a scheme with the GitopsCluster schema
113123
// information registered.
114-
func NewGitopsClusterScheme() (*runtime.Scheme, error) {
124+
func newGitopsClusterScheme() (*runtime.Scheme, error) {
115125
scheme := runtime.NewScheme()
116126
err := gitopsv1alpha1.AddToScheme(scheme)
117127
if err != nil {
@@ -120,3 +130,76 @@ func NewGitopsClusterScheme() (*runtime.Scheme, error) {
120130

121131
return scheme, nil
122132
}
133+
134+
func deleteGitOpsClusterSecret(ctx context.Context, client kubernetes.Interface, secretName, namespace string) error {
135+
lgr := log.FromContext(ctx)
136+
err := client.CoreV1().Secrets(namespace).Delete(ctx, secretName, metav1.DeleteOptions{})
137+
if err != nil {
138+
return err
139+
}
140+
lgr.V(logger.LogLevelDebug).Info("gitops cluster secret deleted successfully!", "secret", secretName, "namespace", namespace)
141+
return nil
142+
}
143+
144+
// DisconnectCluster disconnects a cluster from a spoke cluster given its name and context
145+
// The Service account, Cluster Role binding and secret are deleted in the remote cluster and secret containing token in hub cluster is deleted
146+
func DisconnectCluster(ctx context.Context, options *ClusterConnectionOptions) error {
147+
lgr := log.FromContext(ctx)
148+
pathOpts := clientcmd.NewDefaultPathOptions()
149+
pathOpts.LoadingRules.ExplicitPath = options.ConfigPath
150+
151+
// load hub kubeconfig
152+
hubClusterConfig, err := configForContext(ctx, pathOpts, "")
153+
if err != nil {
154+
return err
155+
}
156+
157+
// Get the context from SpokeClusterContext
158+
spokeClusterConfig, err := configForContext(ctx, pathOpts, options.RemoteClusterContext)
159+
if err != nil {
160+
return err
161+
}
162+
secretName, err := getSecretNameForConfig(ctx, hubClusterConfig, options)
163+
if err != nil {
164+
return err
165+
}
166+
167+
spokeKubernetesClient, err := kubernetes.NewForConfig(spokeClusterConfig)
168+
if err != nil {
169+
return err
170+
}
171+
172+
managedbyReq, err := labels.NewRequirement("app.kubernetes.io/managed-by", selection.Equals, []string{managedByLabelName})
173+
if err != nil {
174+
return err
175+
}
176+
177+
selector := labels.NewSelector()
178+
selector = selector.Add(*managedbyReq)
179+
err = checkServiceAccountName(ctx, spokeKubernetesClient, options, selector)
180+
if err != nil {
181+
return err
182+
}
183+
err = checkClusterRoleBindingName(ctx, spokeKubernetesClient, options, selector)
184+
if err != nil {
185+
return err
186+
}
187+
188+
err = deleteServiceAccountResources(ctx, spokeKubernetesClient, *options)
189+
if err != nil {
190+
return err
191+
}
192+
193+
hubKubernetesClient, err := kubernetes.NewForConfig(hubClusterConfig)
194+
if err != nil {
195+
return err
196+
}
197+
err = deleteGitOpsClusterSecret(ctx, hubKubernetesClient, secretName, options.GitopsClusterName.Namespace)
198+
if err != nil {
199+
return err
200+
}
201+
202+
lgr.V(logger.LogLevelInfo).Info("Successfully disconnected cluster and deleted resources", "cluster", options.GitopsClusterName)
203+
204+
return nil
205+
}

pkg/cluster/connector/secret_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/stretchr/testify/assert"
99
gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1"
1010
corev1 "k8s.io/api/core/v1"
11-
v1 "k8s.io/api/core/v1"
1211
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1312
"k8s.io/apimachinery/pkg/runtime"
1413
"k8s.io/apimachinery/pkg/types"
@@ -51,7 +50,7 @@ func TestCreateOrUpdateGitOpsClusterSecret(t *testing.T) {
5150
assert.NoError(t, err)
5251

5352
//serialize config
54-
expectedSecret := &v1.Secret{
53+
expectedSecret := &corev1.Secret{
5554
ObjectMeta: metav1.ObjectMeta{
5655
Name: secretName,
5756
Namespace: corev1.NamespaceDefault,
@@ -71,7 +70,7 @@ func TestCreateOrUpdateGitOpsClusterSecret(t *testing.T) {
7170
}
7271

7372
func newTestScheme(t *testing.T) *runtime.Scheme {
74-
scheme, err := NewGitopsClusterScheme()
73+
scheme, err := newGitopsClusterScheme()
7574
assert.NoError(t, err)
7675

7776
return scheme

0 commit comments

Comments
 (0)