Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move the registration work into k8ssandra-client #39

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0963337
Move the registration work into k8ssandra-client (WIP)
Miles-Garnsey May 2, 2024
3a73b93
De-MC the repo.
Miles-Garnsey May 2, 2024
eea7f97
Fix swapped namespaces.
Miles-Garnsey May 2, 2024
d3204c2
More error handling, more defaults.
Miles-Garnsey May 3, 2024
78eb81a
Make some flags persistent.
Miles-Garnsey May 3, 2024
7ef2598
Error out if you try to register a cluster to itself.
Miles-Garnsey May 3, 2024
82120d9
Remove calls to t.Fatal() as per Micke's request.
Miles-Garnsey May 16, 2024
13ccd30
Micke's feedback, fix up require so it doesn't require *testing.T, en…
Miles-Garnsey May 16, 2024
54bfc21
Resolve Micke's issues with GetClient.
Miles-Garnsey May 16, 2024
4a69e65
Fix unecessary *testing.T.
Miles-Garnsey May 16, 2024
0823adc
Remove unused file.
Miles-Garnsey May 16, 2024
c6f50d9
Rename cmd init func to SetupRegisterClusterCmd and clean up other mi…
Miles-Garnsey May 16, 2024
7795483
Use envtest.RootDir() where possible,
Miles-Garnsey May 16, 2024
f35eda3
File naming.
Miles-Garnsey May 16, 2024
08c6eff
Make client private in Environment.
Miles-Garnsey May 16, 2024
225d5bb
Make sure whole test directory gets cleaned up.
Miles-Garnsey May 17, 2024
96095c7
Ensure the build directory is created if it does not exist in tests.
Miles-Garnsey May 17, 2024
c0197ed
Micke's requested changes.
Miles-Garnsey May 20, 2024
e62fdfb
Remove reconcile.Result
Miles-Garnsey May 20, 2024
12ae412
Clean up handling of error when src and dest context are the same.
Miles-Garnsey May 20, 2024
8e1334a
destination name from source context name as per Alex' request.
Miles-Garnsey May 27, 2024
b191ada
Ensure the default context is set on the registration kubeconfig secret.
Miles-Garnsey May 27, 2024
9f5554d
Use sanitized src context name as default secret and clientConfig names.
Miles-Garnsey May 27, 2024
a358366
Make sure any isnotFound error doesn't stall the process.
Miles-Garnsey May 27, 2024
4994b44
Line up all context names across clientConfig and kubeconfig in secret.
Miles-Garnsey May 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/kubectl-k8ssandra/k8ssandra/k8ssandra.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/config"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/helm"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/operate"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/register"
"github.com/k8ssandra/k8ssandra-client/cmd/kubectl-k8ssandra/users"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -53,6 +54,7 @@ func NewCmd(streams genericclioptions.IOStreams) *cobra.Command {
// cmd.AddCommand(migrate.NewInstallCmd(streams))
cmd.AddCommand(config.NewCmd(streams))
cmd.AddCommand(helm.NewHelmCmd(streams))
register.SetupRegisterClusterCmd(cmd, streams)

// cmd.Flags().BoolVar(&o.listNamespaces, "list", o.listNamespaces, "if true, print the list of all namespaces in the current KUBECONFIG")
o.configFlags.AddFlags(cmd.Flags())
Expand Down
73 changes: 73 additions & 0 deletions cmd/kubectl-k8ssandra/register/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package register
Miles-Garnsey marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"

"github.com/charmbracelet/log"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

var RegisterClusterCmd = &cobra.Command{
Use: "register [flags]",
Short: "register a data plane into the control plane.",
Long: `register creates a ServiceAccount on a source cluster, copies its credentials and then creates a secret containing them on the destination cluster. It then also creates a ClientConfig on the destination cluster to reference the secret.`,
Run: entrypoint,
}

func SetupRegisterClusterCmd(cmd *cobra.Command, streams genericclioptions.IOStreams) {
RegisterClusterCmd.Flags().String("source-kubeconfig",
"",
"path to source cluster's kubeconfig file - defaults to KUBECONFIG then ~/.kube/config")
RegisterClusterCmd.Flags().String("dest-kubeconfig",
"",
"path to destination cluster's kubeconfig file - defaults to KUBECONFIG then ~/.kube/config")
RegisterClusterCmd.Flags().String("source-context", "", "context name for source cluster")
RegisterClusterCmd.Flags().String("dest-context", "", "context name for destination cluster")
Miles-Garnsey marked this conversation as resolved.
Show resolved Hide resolved
RegisterClusterCmd.Flags().String("source-namespace", "k8ssandra-operator", "namespace containing service account for source cluster")
RegisterClusterCmd.Flags().String("dest-namespace", "k8ssandra-operator", "namespace where secret and clientConfig will be created on destination cluster")
RegisterClusterCmd.Flags().String("serviceaccount-name", "k8ssandra-operator", "serviceaccount name for destination cluster")
RegisterClusterCmd.Flags().String("destination-name", "remote-k8ssandra-operator", "name for remote clientConfig and secret on destination cluster")
Miles-Garnsey marked this conversation as resolved.
Show resolved Hide resolved

if err := RegisterClusterCmd.MarkFlagRequired("source-context"); err != nil {
panic(err)
Miles-Garnsey marked this conversation as resolved.
Show resolved Hide resolved

}
if err := RegisterClusterCmd.MarkFlagRequired("dest-context"); err != nil {
panic(err)
}
cmd.AddCommand(RegisterClusterCmd)
}

func entrypoint(cmd *cobra.Command, args []string) {
executor := NewRegistrationExecutorFromRegisterClusterCmd(*cmd)
for i := 0; i < 30; i++ {
res := executor.RegisterCluster()
switch {
case res.IsError():
log.Info("Registration continuing", "msg", res.GetError())
continue
case res.Completed():
log.Info("Registration completed successfully")
return
case res.IsRequeue():
log.Info("Registration continues")
continue
}
}
fmt.Println("Registration failed - retries exceeded")
}

func NewRegistrationExecutorFromRegisterClusterCmd(cmd cobra.Command) *RegistrationExecutor {
return &RegistrationExecutor{
SourceKubeconfig: cmd.Flag("source-kubeconfig").Value.String(),
DestKubeconfig: cmd.Flag("dest-kubeconfig").Value.String(),
SourceContext: cmd.Flag("source-context").Value.String(),
DestContext: cmd.Flag("dest-context").Value.String(),
SourceNamespace: cmd.Flag("source-namespace").Value.String(),
DestNamespace: cmd.Flag("dest-namespace").Value.String(),
ServiceAccount: cmd.Flag("serviceaccount-name").Value.String(),
Context: cmd.Context(),
DestinationName: cmd.Flag("destination-name").Value.String(),
}
}
163 changes: 163 additions & 0 deletions cmd/kubectl-k8ssandra/register/register_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package register

import (
"context"
"os"
"path/filepath"
"testing"
"time"

"github.com/k8ssandra/k8ssandra-client/internal/envtest"
configapi "github.com/k8ssandra/k8ssandra-operator/apis/config/v1beta1"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestRegister(t *testing.T) {
require := require.New(t)
client1 := (*multiEnv)[0].GetClientInNamespace("source-namespace")
client2 := (*multiEnv)[1].GetClientInNamespace("dest-namespace")
require.NoError(client1.Create((*multiEnv)[0].Context, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "source-namespace"}}))
require.NoError(client2.Create((*multiEnv)[1].Context, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "dest-namespace"}}))

buildDir := filepath.Join(envtest.RootDir(), "build")
testDir := filepath.Join(buildDir, time.Now().String())
Miles-Garnsey marked this conversation as resolved.
Show resolved Hide resolved

if _, err := os.Stat(testDir); os.IsNotExist(err) {
err := os.Mkdir(testDir, os.ModePerm)
require.NoError(err)
} else if err != nil {
require.NoError(err)
}

kc1, err := (*multiEnv)[0].GetKubeconfig()
require.NoError(err)
f1, err := os.Create(testDir + "/kubeconfig1")
require.NoError(err)
t.Cleanup(func() {
require.NoError(f1.Close())
require.NoError(os.RemoveAll(testDir + "/kubeconfig1"))
})
_, err = f1.Write(kc1)
require.NoError(err)

f2, err := os.Create(testDir + "/kubeconfig2")
require.NoError(err)
t.Cleanup(func() {
require.NoError(f2.Close())
require.NoError(os.RemoveAll(testDir + "/kubeconfig2"))
})

kc2, err := (*multiEnv)[1].GetKubeconfig()
require.NoError(err)
_, err = f2.Write(kc2)
require.NoError(err)

ex := RegistrationExecutor{
SourceKubeconfig: testDir + "/kubeconfig1",
DestKubeconfig: testDir + "/kubeconfig2",
SourceContext: "default-context",
DestContext: "default-context",
SourceNamespace: "source-namespace",
DestNamespace: "dest-namespace",
ServiceAccount: "k8ssandra-operator",
Context: context.TODO(),
DestinationName: "test-destination",
}
ctx := context.Background()

require.Eventually(func() bool {
res := ex.RegisterCluster()
switch {
case res.IsDone():
return true
case res.IsError():
t.Log(res.GetError())
if res.GetError().Error() == "no secret found for service account k8ssandra-operator" {
return true
}
}
return false
}, time.Second*30, time.Second*5)

// This relies on a controller that is not running in the envtest.

desiredSaSecret := &corev1.Secret{}
require.NoError(client1.Get(context.Background(), client.ObjectKey{Name: "k8ssandra-operator-secret", Namespace: "source-namespace"}, desiredSaSecret))
patch := client.MergeFrom(desiredSaSecret.DeepCopy())
desiredSaSecret.Data = map[string][]byte{
"token": []byte("test-token"),
"ca.crt": []byte("test-ca"),
}
require.NoError(client1.Patch(ctx, desiredSaSecret, patch))

desiredSa := &corev1.ServiceAccount{}
require.NoError(client1.Get(
context.Background(),
client.ObjectKey{Name: "k8ssandra-operator", Namespace: "source-namespace"},
desiredSa))

patch = client.MergeFrom(desiredSa.DeepCopy())
desiredSa.Secrets = []corev1.ObjectReference{
{
Name: "k8ssandra-operator-secret",
},
}
require.NoError(client1.Patch(ctx, desiredSa, patch))

// Continue reconciliation

require.Eventually(func() bool {
res := ex.RegisterCluster()
switch {
case res.IsDone():
return true
case res.IsError():
t.Log(res.GetError())
return false
}
return false
}, time.Second*300, time.Second*1)

if err := configapi.AddToScheme(client2.Scheme()); err != nil {
require.NoError(err)
}
destSecret := &corev1.Secret{}
require.Eventually(func() bool {
err = client2.Get(ctx,
client.ObjectKey{Name: "test-destination", Namespace: "dest-namespace"}, destSecret)
if err != nil {
t.Log("didn't find dest secret")
return false
}
clientConfig := &configapi.ClientConfig{}
err = client2.Get(ctx,
client.ObjectKey{Name: "test-destination", Namespace: "dest-namespace"}, clientConfig)
if err != nil {
t.Log("didn't find dest client config")
return false
}
return err == nil
}, time.Second*60, time.Second*5)

destKubeconfig := ClientConfigFromSecret(destSecret)
require.Equal(
desiredSaSecret.Data["ca.crt"],
destKubeconfig.Clusters["cluster"].CertificateAuthorityData)

require.Equal(
string(desiredSaSecret.Data["token"]),
destKubeconfig.AuthInfos["cluster"].Token)
}

func ClientConfigFromSecret(s *corev1.Secret) clientcmdapi.Config {
out, err := clientcmd.Load(s.Data["kubeconfig"])
if err != nil {
panic(err)
}
return *out
}
Loading
Loading