From 739e064ca0e9a7ce908f998cf2e737f37bace38c Mon Sep 17 00:00:00 2001 From: Furkat Gofurov Date: Fri, 17 May 2024 17:50:43 +0500 Subject: [PATCH 1/6] Add RKE2Config webhook implementation Signed-off-by: Furkat Gofurov --- exp/etcdrestore/webhooks/installsh.go | 6 + exp/etcdrestore/webhooks/rke2config.go | 448 ++++++++++++++++++++++++- 2 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 exp/etcdrestore/webhooks/installsh.go diff --git a/exp/etcdrestore/webhooks/installsh.go b/exp/etcdrestore/webhooks/installsh.go new file mode 100644 index 00000000..30f9a035 --- /dev/null +++ b/exp/etcdrestore/webhooks/installsh.go @@ -0,0 +1,6 @@ +package webhooks + +var installsh = ` +#!/bin/sh +echo "Starting the install script" +` diff --git a/exp/etcdrestore/webhooks/rke2config.go b/exp/etcdrestore/webhooks/rke2config.go index 5f2ff08d..9815ec95 100644 --- a/exp/etcdrestore/webhooks/rke2config.go +++ b/exp/etcdrestore/webhooks/rke2config.go @@ -18,16 +18,37 @@ package webhooks import ( "context" + "encoding/json" + "fmt" + "strings" bootstrapv1 "github.com/rancher-sandbox/cluster-api-provider-rke2/bootstrap/api/v1beta1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/cluster-api/controllers/remote" ) +const ( + rke2ConfigNameLabel = "cluster-api.cattle.io/rke2config-name" + planSecretNameLabel = "cluster-api.cattle.io/plan-secret-name" + serviceAccountSecretLabel = "cluster-api.cattle.io/service-account.name" + secretTypeMachinePlan = "cluster-api.cattle.io/machine-plan" + defaultFileOwner = "root:root" +) + // RKE2ConfigWebhook defines a webhook for RKE2Config. type RKE2ConfigWebhook struct { client.Client @@ -45,6 +66,431 @@ func (r *RKE2ConfigWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error { } // Default implements webhook.Defaulter so a webhook will be registered for the type. -func (r *RKE2ConfigWebhook) Default(_ context.Context, _ runtime.Object) error { +func (r *RKE2ConfigWebhook) Default(ctx context.Context, obj runtime.Object) error { + logger := log.FromContext(ctx) + + logger.Info("Configuring system agent on for RKE2Config") + + rke2Config, ok := obj.(*bootstrapv1.RKE2Config) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("expected a RKE2Config but got a %T", obj)) + } + + planSecretName := strings.Join([]string{rke2Config.Name, "rke2config", "plan"}, "-") + + if err := r.createSecretPlanResources(ctx, planSecretName, rke2Config); err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to create secret plan resources: %s", err)) + } + + serviceAccountToken, err := r.EnsureServiceAccountSecretPopulated(ctx, planSecretName) + if err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to ensure service account secret is populated: %s", err)) + } + + logger.Info("Service account secret is populated") + + serverUrlSetting := &unstructured.Unstructured{} + serverUrlSetting.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "management.cattle.io", + Kind: "Setting", + Version: "v3", + }) + + if err := r.Get(context.Background(), client.ObjectKey{ + Name: "server-url", + }, serverUrlSetting); err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to get server url setting: %s", err)) + } + serverUrl, ok := serverUrlSetting.Object["value"].(string) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("failed to get server url setting: %s", err)) + } + + if serverUrl == "" { + return apierrors.NewBadRequest("server url setting is empty") + } + + caSetting := &unstructured.Unstructured{} + caSetting.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "management.cattle.io", + Kind: "Setting", + Version: "v3", + }) + if err := r.Get(context.Background(), client.ObjectKey{ + Name: "cacerts", + }, caSetting); err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to get ca setting: %s", err)) + } + + pem, ok := caSetting.Object["value"].(string) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("failed to get ca setting: %s", err)) + } + + if err := r.CreateConnectInfoJson(ctx, rke2Config, planSecretName, serverUrl, pem, serviceAccountToken); err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to create connect info json: %s", err)) + } + + if err := r.CreateSystemAgentInstallScript(ctx, serverUrl, rke2Config); err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to create system agent install script: %s", err)) + } + + if err := r.CreateConfigYAML(rke2Config); err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to create config.yaml: %s", err)) + } + + r.AddPostInstallCommands(rke2Config) + + return nil +} + +// createSecretPlanResources creates the secret, role, rolebinding, and service account for the plan. +func (r *RKE2ConfigWebhook) createSecretPlanResources(ctx context.Context, planSecretName string, rke2Config *bootstrapv1.RKE2Config) error { + logger := log.FromContext(ctx) + + logger.Info("Creating secret plan resources") + + var errs []error + + // Create the ServiceAccount first to later pass to the RoleBinding creation + sa := r.createServiceAccount(planSecretName, rke2Config) + + resources := []client.Object{ + sa, + r.createSecret(planSecretName, rke2Config), + r.createRole(planSecretName, rke2Config), + r.createRoleBinding(sa.Name, sa.Namespace, planSecretName, rke2Config), + r.createServiceAccountSecret(planSecretName, rke2Config), + } + + for _, resource := range resources { + if err := r.Create(ctx, resource); err != nil { + if !apierrors.IsAlreadyExists(err) { + errs = append(errs, fmt.Errorf("failed to create %s %s: %w", resource.GetObjectKind().GroupVersionKind().String(), resource.GetName(), err)) + } + } + } + + if len(errs) > 0 { + return fmt.Errorf("errors occurred during resource creation: %v", errs) + } + + return nil +} + +// createServiceAccount creates a ServiceAccount for the plan. +func (r *RKE2ConfigWebhook) createServiceAccount(planSecretName string, rke2Config *bootstrapv1.RKE2Config) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: planSecretName, + Namespace: rke2Config.Namespace, + Labels: map[string]string{ + rke2ConfigNameLabel: rke2Config.Name, + planSecretNameLabel: planSecretName, + }, + }, + } +} + +// createSecret creates a Secret for the plan. +func (r *RKE2ConfigWebhook) createSecret(planSecretName string, rke2Config *bootstrapv1.RKE2Config) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: planSecretName, + Namespace: rke2Config.Namespace, + Labels: map[string]string{ + rke2ConfigNameLabel: rke2Config.Name, + }, + }, + Type: secretTypeMachinePlan, + } +} + +// createRole creates a Role for the plan. +func (r *RKE2ConfigWebhook) createRole(planSecretName string, rke2Config *bootstrapv1.RKE2Config) *rbacv1.Role { + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: planSecretName, + Namespace: rke2Config.Namespace, + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"watch", "get", "update", "list"}, + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{planSecretName}, + }, + }, + } +} + +// createRoleBinding creates a RoleBinding for the plan. +func (r *RKE2ConfigWebhook) createRoleBinding(serviceAccountName, serviceAccountNamespace, planSecretName string, rke2Config *bootstrapv1.RKE2Config) *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: planSecretName, + Namespace: rke2Config.Namespace, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: serviceAccountName, + Namespace: serviceAccountNamespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: planSecretName, + }, + } +} + +// createServiceAccountSecret creates a Secret for the ServiceAccount token. +func (r *RKE2ConfigWebhook) createServiceAccountSecret(planSecretName string, rke2Config *bootstrapv1.RKE2Config) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-token", planSecretName), + Namespace: rke2Config.Namespace, + Annotations: map[string]string{ + "kubernetes.io/service-account.name": planSecretName, + }, + Labels: map[string]string{ + serviceAccountSecretLabel: planSecretName, + }, + }, + Type: corev1.SecretTypeServiceAccountToken, + } +} + +// ensureServiceAccountSecretPopulated ensures the ServiceAccount secret is populated. +func (r *RKE2ConfigWebhook) EnsureServiceAccountSecretPopulated(ctx context.Context, planSecretName string) ([]byte, error) { + logger := log.FromContext(ctx) + + logger.Info("Ensuring service account secret is populated") + + serviceAccountToken := []byte{} + + if err := retry.OnError(retry.DefaultRetry, func(err error) bool { + return true + }, func() error { + secretList := &corev1.SecretList{} + + if err := r.List(ctx, secretList, client.MatchingLabels{serviceAccountSecretLabel: planSecretName}); err != nil { + err = fmt.Errorf("failed to list secrets: %w", err) + logger.Error(err, "failed to list secrets") + return err + } + + if len(secretList.Items) == 0 || len(secretList.Items) > 1 { + err := fmt.Errorf("secret for %s doesn't exist, or more than one secret exists", planSecretName) + logger.Error(err, "secret for %s doesn't exist, or more than one secret exists", "secret", planSecretName) + return err + } + + saSecret := secretList.Items[0] + + if len(saSecret.Data[corev1.ServiceAccountTokenKey]) == 0 { + err := fmt.Errorf("secret %s not yet populated", planSecretName) + logger.Error(err, "Secret %s not yet populated", "secret", planSecretName) + return err + } + + serviceAccountToken = saSecret.Data[corev1.ServiceAccountTokenKey] + + return nil + }); err != nil { + return nil, err + } + return serviceAccountToken, nil +} + +// createConnectInfoJson creates the connect-info-config.json file. +func (r *RKE2ConfigWebhook) CreateConnectInfoJson(ctx context.Context, rke2Config *bootstrapv1.RKE2Config, planSecretName, serverUrl, pem string, serviceAccountToken []byte) error { + connectInfoJsonPath := "/etc/rancher/agent/connect-info-config.json" + + filePaths := make(map[string]struct{}) + for _, file := range rke2Config.Spec.Files { + filePaths[file.Path] = struct{}{} + } + + if _, exists := filePaths[connectInfoJsonPath]; exists { + return nil + } + + kubeConfig, err := clientcmd.Write(clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{ + "agent": { + Server: serverUrl, + CertificateAuthorityData: []byte(pem), + }, + }, + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "agent": { + Token: string(serviceAccountToken), + }, + }, + Contexts: map[string]*clientcmdapi.Context{ + "agent": { + Cluster: "agent", + AuthInfo: "agent", + }, + }, + CurrentContext: "agent", + }) + + if err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("failed to write kubeconfig: %s", err)) + } + + connectInfoConfig := struct { + Namespace string `json:"namespace"` + SecretName string `json:"secretName"` + KubeConfig string `json:"kubeConfig"` + }{ + Namespace: rke2Config.Namespace, + SecretName: planSecretName, + KubeConfig: string(kubeConfig), + } + + connectInfoConfigJson, err := json.MarshalIndent(connectInfoConfig, "", " ") + if err != nil { + return err + } + + connectInfoConfigSecretName := fmt.Sprintf("%s-system-agent-connect-info-config", rke2Config.Name) + connectInfoConfigKey := "connect-info-config.json" + + if err := r.Create(ctx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: connectInfoConfigSecretName, + Namespace: rke2Config.Namespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: rke2Config.APIVersion, + Kind: rke2Config.Kind, + Name: rke2Config.Name, + UID: rke2Config.UID, + }, + }, + }, + Data: map[string][]byte{ + connectInfoConfigKey: connectInfoConfigJson, + }, + }, + ); err != nil { + if !apierrors.IsAlreadyExists(err) { + return err + } + } + + rke2Config.Spec.Files = append(rke2Config.Spec.Files, bootstrapv1.File{ + Path: connectInfoJsonPath, + Owner: defaultFileOwner, + Permissions: "0600", + ContentFrom: &bootstrapv1.FileSource{ + Secret: bootstrapv1.SecretFileSource{ + Name: connectInfoConfigSecretName, + Key: connectInfoConfigKey, + }, + }, + }) + return nil } + +// createSystemAgentInstallScript creates the system-agent-install.sh script. +func (r *RKE2ConfigWebhook) CreateSystemAgentInstallScript(ctx context.Context, serverUrl string, rke2Config *bootstrapv1.RKE2Config) error { + systemAgentInstallScriptPath := "/opt/system-agent-install.sh" + + filePaths := make(map[string]struct{}) + for _, file := range rke2Config.Spec.Files { + filePaths[file.Path] = struct{}{} + } + + if _, exists := filePaths[systemAgentInstallScriptPath]; exists { + return nil + } + + installScriptSecretName := fmt.Sprintf("%s-system-agent-install-script", rke2Config.Name) + installScriptKey := "install.sh" + + serverUrlBash := fmt.Sprintf("CATTLE_SERVER=%s\n", serverUrl) + + if err := r.Create(ctx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: installScriptSecretName, + Namespace: rke2Config.Namespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: rke2Config.APIVersion, + Kind: rke2Config.Kind, + Name: rke2Config.Name, + UID: rke2Config.UID, + }, + }, + }, + Data: map[string][]byte{ + installScriptKey: []byte(fmt.Sprintf("%s%s", serverUrlBash, installsh)), + }, + }, + ); err != nil { + if !apierrors.IsAlreadyExists(err) { + return err + } + } + + rke2Config.Spec.Files = append(rke2Config.Spec.Files, bootstrapv1.File{ + Path: systemAgentInstallScriptPath, + Owner: defaultFileOwner, + Permissions: "0600", + ContentFrom: &bootstrapv1.FileSource{ + Secret: bootstrapv1.SecretFileSource{ + Name: installScriptSecretName, + Key: installScriptKey, + }, + }, + }) + + return nil +} + +// createConfigYAML creates the config.yaml file. +func (r *RKE2ConfigWebhook) CreateConfigYAML(rke2Config *bootstrapv1.RKE2Config) error { + configYAMLPath := "/etc/rancher/agent/config.yaml" + + filePaths := make(map[string]struct{}) + for _, file := range rke2Config.Spec.Files { + filePaths[file.Path] = struct{}{} + } + + if _, exists := filePaths[configYAMLPath]; exists { + return nil + } + + rke2Config.Spec.Files = append(rke2Config.Spec.Files, bootstrapv1.File{ + Path: configYAMLPath, + Owner: defaultFileOwner, + Permissions: "0600", + Content: `workDirectory: /var/lib/rancher/agent/work +localPlanDirectory: /var/lib/rancher/agent/plans +interlockDirectory: /var/lib/rancher/agent/interlock +remoteEnabled: true +connectionInfoFile: /etc/rancher/agent/connect-info-config.json +preserveWorkDirectory: true`, + }) + + return nil +} + +// addPostInstallCommands adds the post install command to the RKE2Config. +func (r *RKE2ConfigWebhook) AddPostInstallCommands(rke2Config *bootstrapv1.RKE2Config) { + postInstallCommand := "sudo sh /opt/system-agent-install.sh" + + for _, cmd := range rke2Config.Spec.PostRKE2Commands { + if cmd == postInstallCommand { + return + } + } + rke2Config.Spec.PostRKE2Commands = append(rke2Config.Spec.PostRKE2Commands, postInstallCommand) +} From b10e7aa4f1611dafffff79087c6d7570112daf01 Mon Sep 17 00:00:00 2001 From: Furkat Gofurov Date: Tue, 21 May 2024 02:55:07 +0500 Subject: [PATCH 2/6] Add etcdrestore webhook tests Signed-off-by: Furkat Gofurov --- Makefile | 5 + exp/etcdrestore/go.mod | 1 + exp/etcdrestore/webhooks/rke2config_test.go | 275 ++++++++++++++++++++ exp/etcdrestore/webhooks/suite_test.go | 121 +++++++++ 4 files changed, 402 insertions(+) create mode 100644 exp/etcdrestore/webhooks/rke2config_test.go create mode 100644 exp/etcdrestore/webhooks/suite_test.go diff --git a/Makefile b/Makefile index 4af946f6..247d04e5 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ BIN_DIR := bin TEST_DIR := test TOOLS_DIR := hack/tools TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/$(BIN_DIR)) +EXP_ETCDRESTORE_DIR := exp/etcdrestore $(TOOLS_BIN_DIR): mkdir -p $@ @@ -300,6 +301,10 @@ test: $(SETUP_ENVTEST) manifests ## Run tests. go clean -testcache KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./... $(TEST_ARGS) +.PHONY: test-exp-etcdrestore +test-exp-etcdrestore: $(SETUP_ENVTEST) ## Run tests for experimental etcdrestore API. + cd $(EXP_ETCDRESTORE_DIR); KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./... $(TEST_ARGS) + ##@ Build .PHONY: build diff --git a/exp/etcdrestore/go.mod b/exp/etcdrestore/go.mod index c1211454..7c671ee9 100644 --- a/exp/etcdrestore/go.mod +++ b/exp/etcdrestore/go.mod @@ -33,6 +33,7 @@ require ( github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect diff --git a/exp/etcdrestore/webhooks/rke2config_test.go b/exp/etcdrestore/webhooks/rke2config_test.go new file mode 100644 index 00000000..3ebcb3aa --- /dev/null +++ b/exp/etcdrestore/webhooks/rke2config_test.go @@ -0,0 +1,275 @@ +/* +Copyright © 2023 - 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhooks + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + bootstrapv1 "github.com/rancher-sandbox/cluster-api-provider-rke2/bootstrap/api/v1beta1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/cluster-api/controllers/remote" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + rke2Config *bootstrapv1.RKE2Config + r *RKE2ConfigWebhook + fakeClient client.Client + fakeTracker *remote.ClusterCacheTracker + serviceAccountName string + serviceAccountNamespace string + planSecretName string + serverUrl string + pem string + token []byte +) + +type mockClient struct { + client.Client + listFunc func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error +} + +var _ = Describe("RKE2ConfigWebhook tests", func() { + BeforeEach(func() { + ctx = context.Background() + serviceAccountName = "service-account" + serviceAccountNamespace = "service-account-namespace" + planSecretName = "plan-secret" + serverUrl = "https://example.com" + pem = "test-pem" + token = []byte("test-token") + + rke2Config = &bootstrapv1.RKE2Config{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + UID: "test-uid", + Namespace: "test-namespace", + }, + Spec: bootstrapv1.RKE2ConfigSpec{ + Files: []bootstrapv1.File{}, + }, + } + fakeClient = fake.NewClientBuilder().WithObjects(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + serviceAccountSecretLabel: planSecretName, + }, + }, + Data: map[string][]byte{ + corev1.ServiceAccountTokenKey: []byte("test-token"), + }, + }).Build() + fakeTracker = new(remote.ClusterCacheTracker) + + r = &RKE2ConfigWebhook{ + Client: fakeClient, + Tracker: fakeTracker, + } + }) + + It("Should return error when non-RKE2Config object is passed", func() { + err := r.Default(ctx, &corev1.Pod{}) + Expect(err).To(HaveOccurred()) + Expect(apierrors.IsBadRequest(err)).To(BeTrue()) + }) + + It("Should create secret plan resources without error", func() { + err := r.createSecretPlanResources(ctx, planSecretName, rke2Config) + Expect(err).NotTo(HaveOccurred()) + + // Check if the resources are created + serviceAccount := &corev1.ServiceAccount{} + err = fakeClient.Get(ctx, types.NamespacedName{Name: planSecretName, Namespace: rke2Config.Namespace}, serviceAccount) + Expect(err).NotTo(HaveOccurred()) + + secret := &corev1.Secret{} + err = fakeClient.Get(ctx, types.NamespacedName{Name: planSecretName, Namespace: rke2Config.Namespace}, secret) + Expect(err).NotTo(HaveOccurred()) + + role := &rbacv1.Role{} + err = fakeClient.Get(ctx, types.NamespacedName{Name: planSecretName, Namespace: rke2Config.Namespace}, role) + Expect(err).NotTo(HaveOccurred()) + + roleBinding := &rbacv1.RoleBinding{} + err = fakeClient.Get(ctx, types.NamespacedName{Name: planSecretName, Namespace: rke2Config.Namespace}, roleBinding) + Expect(err).NotTo(HaveOccurred()) + }) + + It("Should create a service account with the correct properties", func() { + serviceAccount := r.createServiceAccount(planSecretName, rke2Config) + + Expect(serviceAccount.ObjectMeta.Name).To(Equal(planSecretName)) + Expect(serviceAccount.ObjectMeta.Namespace).To(Equal(rke2Config.Namespace)) + Expect(serviceAccount.ObjectMeta.Labels[rke2ConfigNameLabel]).To(Equal(rke2Config.Name)) + Expect(serviceAccount.ObjectMeta.Labels[planSecretNameLabel]).To(Equal(planSecretName)) + }) + + It("Should create a secret with the correct properties", func() { + secret := r.createSecret(planSecretName, rke2Config) + + Expect(secret.ObjectMeta.Name).To(Equal(planSecretName)) + Expect(secret.ObjectMeta.Namespace).To(Equal(rke2Config.Namespace)) + Expect(secret.ObjectMeta.Labels[rke2ConfigNameLabel]).To(Equal(rke2Config.Name)) + Expect(string(secret.Type)).To(Equal(secretTypeMachinePlan)) + }) + + It("Should create a role with the correct properties", func() { + role := r.createRole(planSecretName, rke2Config) + + Expect(role.ObjectMeta.Name).To(Equal(planSecretName)) + Expect(role.ObjectMeta.Namespace).To(Equal(rke2Config.Namespace)) + Expect(role.Rules[0].Verbs).To(Equal([]string{"watch", "get", "update", "list"})) + Expect(role.Rules[0].APIGroups).To(Equal([]string{""})) + Expect(role.Rules[0].Resources).To(Equal([]string{"secrets"})) + Expect(role.Rules[0].ResourceNames).To(Equal([]string{planSecretName})) + }) + + It("Should create a role binding with the correct properties", func() { + roleBinding := r.createRoleBinding(serviceAccountName, serviceAccountNamespace, planSecretName, rke2Config) + + Expect(roleBinding.ObjectMeta.Name).To(Equal(planSecretName)) + Expect(roleBinding.ObjectMeta.Namespace).To(Equal(rke2Config.Namespace)) + Expect(roleBinding.Subjects[0].Kind).To(Equal("ServiceAccount")) + Expect(roleBinding.Subjects[0].Name).To(Equal(serviceAccountName)) + Expect(roleBinding.Subjects[0].Namespace).To(Equal(serviceAccountNamespace)) + Expect(roleBinding.RoleRef.APIGroup).To(Equal(rbacv1.GroupName)) + Expect(roleBinding.RoleRef.Kind).To(Equal("Role")) + Expect(roleBinding.RoleRef.Name).To(Equal(planSecretName)) + }) + + It("Should create a service account secret with the correct properties", func() { + secret := r.createServiceAccountSecret(planSecretName, rke2Config) + + Expect(secret.ObjectMeta.Name).To(Equal(fmt.Sprintf("%s-token", planSecretName))) + Expect(secret.ObjectMeta.Namespace).To(Equal(rke2Config.Namespace)) + Expect(secret.ObjectMeta.Annotations["kubernetes.io/service-account.name"]).To(Equal(planSecretName)) + Expect(secret.ObjectMeta.Labels[serviceAccountSecretLabel]).To(Equal(planSecretName)) + Expect(secret.Type).To(Equal(corev1.SecretTypeServiceAccountToken)) + }) + + It("Should return service account token when secret is present and populated", func() { + token, err := r.EnsureServiceAccountSecretPopulated(ctx, planSecretName) + Expect(err).ToNot(HaveOccurred()) + Expect(token).To(Equal([]byte("test-token"))) + }) + + It("Should add connect-info-config.json when it's not present", func() { + err := r.CreateConnectInfoJson(ctx, rke2Config, "plan-secret", serverUrl, pem, token) + Expect(err).ToNot(HaveOccurred()) + + Expect(rke2Config.Spec.Files).To(ContainElement(bootstrapv1.File{ + Path: "/etc/rancher/agent/connect-info-config.json", + Owner: defaultFileOwner, + Permissions: "0600", + ContentFrom: &bootstrapv1.FileSource{ + Secret: bootstrapv1.SecretFileSource{ + Name: "test-system-agent-connect-info-config", + Key: "connect-info-config.json", + }, + }, + })) + }) + + It("Should not add connect-info-config.json when it's already present", func() { + rke2Config.Spec.Files = append(rke2Config.Spec.Files, bootstrapv1.File{ + Path: "/etc/rancher/agent/connect-info-config.json", + }) + + err := r.CreateConnectInfoJson(ctx, rke2Config, "plan-secret", serverUrl, pem, token) + Expect(err).ToNot(HaveOccurred()) + + Expect(rke2Config.Spec.Files).To(HaveLen(1)) + }) + + It("Should add system-agent-install.sh when it's not present", func() { + err := r.CreateSystemAgentInstallScript(ctx, serverUrl, rke2Config) + Expect(err).ToNot(HaveOccurred()) + + Expect(rke2Config.Spec.Files).To(ContainElement(bootstrapv1.File{ + Path: "/opt/system-agent-install.sh", + Owner: defaultFileOwner, + Permissions: "0600", + ContentFrom: &bootstrapv1.FileSource{ + Secret: bootstrapv1.SecretFileSource{ + Name: "test-system-agent-install-script", + Key: "install.sh", + }, + }, + })) + }) + + It("Should not add system-agent-install.sh when it's already present", func() { + rke2Config.Spec.Files = append(rke2Config.Spec.Files, bootstrapv1.File{ + Path: "/opt/system-agent-install.sh", + }) + + err := r.CreateSystemAgentInstallScript(ctx, serverUrl, rke2Config) + Expect(err).ToNot(HaveOccurred()) + + Expect(rke2Config.Spec.Files).To(HaveLen(1)) + }) + + It("Should add config.yaml when it's not present", func() { + err := r.CreateConfigYAML(rke2Config) + Expect(err).ToNot(HaveOccurred()) + + Expect(rke2Config.Spec.Files).To(ContainElement(bootstrapv1.File{ + Path: "/etc/rancher/agent/config.yaml", + Owner: defaultFileOwner, + Permissions: "0600", + Content: `workDirectory: /var/lib/rancher/agent/work +localPlanDirectory: /var/lib/rancher/agent/plans +interlockDirectory: /var/lib/rancher/agent/interlock +remoteEnabled: true +connectionInfoFile: /etc/rancher/agent/connect-info-config.json +preserveWorkDirectory: true`, + })) + }) + + It("Should not add config.yaml when it's already present", func() { + rke2Config.Spec.Files = append(rke2Config.Spec.Files, bootstrapv1.File{ + Path: "/etc/rancher/agent/config.yaml", + }) + + err := r.CreateConfigYAML(rke2Config) + Expect(err).ToNot(HaveOccurred()) + + Expect(rke2Config.Spec.Files).To(HaveLen(1)) + }) + + It("Should add post-install command when it's not present", func() { + r.AddPostInstallCommands(rke2Config) + + Expect(rke2Config.Spec.PostRKE2Commands).To(ContainElement("sudo sh /opt/system-agent-install.sh")) + }) + + It("Should not add post-install command when it's already present", func() { + rke2Config.Spec.PostRKE2Commands = append(rke2Config.Spec.PostRKE2Commands, "sudo sh /opt/system-agent-install.sh") + + r.AddPostInstallCommands(rke2Config) + + Expect(rke2Config.Spec.PostRKE2Commands).To(HaveLen(1)) + }) +}) diff --git a/exp/etcdrestore/webhooks/suite_test.go b/exp/etcdrestore/webhooks/suite_test.go new file mode 100644 index 00000000..2c00c608 --- /dev/null +++ b/exp/etcdrestore/webhooks/suite_test.go @@ -0,0 +1,121 @@ +/* +Copyright © 2023 - 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhooks + +import ( + "fmt" + "path" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/rancher/turtles/internal/test/helpers" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + + snapshotrestorev1 "github.com/rancher/turtles/exp/etcdrestore/api/v1alpha1" + operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + cfg *rest.Config + cl client.Client + testEnv *helpers.TestEnvironment + kubeConfigBytes []byte + ctx = ctrl.SetupSignalHandler() +) + +func setup() { + utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme)) + utilruntime.Must(operatorv1.AddToScheme(scheme.Scheme)) + utilruntime.Must(snapshotrestorev1.AddToScheme(scheme.Scheme)) + + testEnvConfig := helpers.NewTestEnvironmentConfiguration( + path.Join("config", "exp", "etcdrestore", "crd", "bases"), + ) + + var err error + + testEnv, err = testEnvConfig.Build() + if err != nil { + panic(err) + } + + cfg = testEnv.Config + cl = testEnv.Client + + go func() { + fmt.Println("Starting the manager") + if err := testEnv.StartManager(ctx); err != nil { + panic(fmt.Sprintf("Failed to start the envtest manager: %v", err)) + } + }() +} + +func teardown() { + if err := testEnv.Stop(); err != nil { + panic(fmt.Sprintf("Failed to stop envtest: %v", err)) + } +} + +func TestWebhooks(t *testing.T) { + RegisterFailHandler(Fail) + setup() + defer teardown() + RunSpecs(t, "Rancher Turtles Experimental Webhooks Suite") +} + +var _ = BeforeSuite(func() { + var err error + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + kubeConfig := &api.Config{ + Kind: "Config", + APIVersion: "v1", + Clusters: map[string]*api.Cluster{ + "envtest": { + Server: cfg.Host, + CertificateAuthorityData: cfg.CAData, + }, + }, + Contexts: map[string]*api.Context{ + "envtest": { + Cluster: "envtest", + AuthInfo: "envtest", + }, + }, + AuthInfos: map[string]*api.AuthInfo{ + "envtest": { + ClientKeyData: cfg.KeyData, + ClientCertificateData: cfg.CertData, + }, + }, + CurrentContext: "envtest", + } + + kubeConfigBytes, err = clientcmd.Write(*kubeConfig) + Expect(err).NotTo(HaveOccurred()) +}) From c7c310d2e301ee53a407675df3b70a8ca4989d05 Mon Sep 17 00:00:00 2001 From: Furkat Gofurov Date: Mon, 3 Jun 2024 15:18:04 +0300 Subject: [PATCH 3/6] Always run 'make test-exp-etcdrestore' as part of the 'make test' Makefile target Signed-off-by: Furkat Gofurov --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 247d04e5..816091dd 100644 --- a/Makefile +++ b/Makefile @@ -297,7 +297,7 @@ ARTIFACTS ?= ${ROOT_DIR}/_artifacts KUBEBUILDER_ASSETS ?= $(shell $(SETUP_ENVTEST) use --use-env -p path $(KUBEBUILDER_ENVTEST_KUBERNETES_VERSION)) .PHONY: test -test: $(SETUP_ENVTEST) manifests ## Run tests. +test: $(SETUP_ENVTEST) manifests test-exp-etcdrestore ## Run all generators and exp tests. go clean -testcache KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./... $(TEST_ARGS) From fdfffaf5fcd29242bbe4dbcf1951fd8874f8a965 Mon Sep 17 00:00:00 2001 From: Furkat Gofurov Date: Wed, 17 Jul 2024 11:39:33 +0300 Subject: [PATCH 4/6] Rename CAPRKE2 imports after org rename Signed-off-by: Furkat Gofurov --- exp/etcdrestore/go.mod | 4 ++-- exp/etcdrestore/go.sum | 8 ++++---- exp/etcdrestore/main.go | 2 +- exp/etcdrestore/webhooks/rke2config.go | 2 +- exp/etcdrestore/webhooks/rke2config_test.go | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/exp/etcdrestore/go.mod b/exp/etcdrestore/go.mod index 7c671ee9..4e220d72 100644 --- a/exp/etcdrestore/go.mod +++ b/exp/etcdrestore/go.mod @@ -7,7 +7,7 @@ replace github.com/rancher/turtles => ../.. require ( github.com/onsi/ginkgo/v2 v2.17.2 github.com/onsi/gomega v1.33.1 - github.com/rancher-sandbox/cluster-api-provider-rke2 v0.3.0 + github.com/rancher/cluster-api-provider-rke2 v0.5.0 github.com/rancher/turtles v0.0.0-00010101000000-000000000000 github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace k8s.io/api v0.29.4 @@ -15,7 +15,7 @@ require ( k8s.io/client-go v0.29.4 k8s.io/component-base v0.29.4 k8s.io/klog/v2 v2.110.1 - sigs.k8s.io/cluster-api v1.7.1 + sigs.k8s.io/cluster-api v1.7.2 sigs.k8s.io/cluster-api-operator v0.12.0 sigs.k8s.io/controller-runtime v0.17.3 ) diff --git a/exp/etcdrestore/go.sum b/exp/etcdrestore/go.sum index 5286ec13..863f32b0 100644 --- a/exp/etcdrestore/go.sum +++ b/exp/etcdrestore/go.sum @@ -141,8 +141,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rancher-sandbox/cluster-api-provider-rke2 v0.3.0 h1:HhdHPhleqypIjib7oDCOFO69QrzufhrulBGupvLg2qA= -github.com/rancher-sandbox/cluster-api-provider-rke2 v0.3.0/go.mod h1:UAGaFOkIoCyZBhDkdeEMPjLxuLJRDefKnAdsgQoOmOU= +github.com/rancher/cluster-api-provider-rke2 v0.5.0 h1:l+E3GqgLPlo9WIE6yamBiYGG3aPk4qbx6XhfqJ8WQKs= +github.com/rancher/cluster-api-provider-rke2 v0.5.0/go.mod h1:V4jxqD4v1xQFSbgk57IECmXS4Y2DPyhrUDAsg/p75+k= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= @@ -277,8 +277,8 @@ k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/A k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/cluster-api v1.7.1 h1:JkMAbAMzBM+WBHxXLTJXTiCisv1PAaHRzld/3qrmLYY= -sigs.k8s.io/cluster-api v1.7.1/go.mod h1:V9ZhKLvQtsDODwjXOKgbitjyCmC71yMBwDcMyNNIov0= +sigs.k8s.io/cluster-api v1.7.2 h1:bRE8zoao7ajuLC0HijqfZVcubKQCPlZ04HMgcA53FGE= +sigs.k8s.io/cluster-api v1.7.2/go.mod h1:V9ZhKLvQtsDODwjXOKgbitjyCmC71yMBwDcMyNNIov0= sigs.k8s.io/cluster-api-operator v0.12.0 h1:7EBStWwkvWg+yZW8Uq8XihmQdFXcEXZOEYfjqdJEN18= sigs.k8s.io/cluster-api-operator v0.12.0/go.mod h1:GcWnGrKz74LS/E0IuFcT6YHBtL7zR9DNqjyVezf72mk= sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= diff --git a/exp/etcdrestore/main.go b/exp/etcdrestore/main.go index 833e305b..7390e89f 100644 --- a/exp/etcdrestore/main.go +++ b/exp/etcdrestore/main.go @@ -23,7 +23,7 @@ import ( "os" "time" - bootstrapv1 "github.com/rancher-sandbox/cluster-api-provider-rke2/bootstrap/api/v1beta1" + bootstrapv1 "github.com/rancher/cluster-api-provider-rke2/bootstrap/api/v1beta1" snapshotrestorev1 "github.com/rancher/turtles/exp/etcdrestore/api/v1alpha1" expcontrollers "github.com/rancher/turtles/exp/etcdrestore/controllers" expwebhooks "github.com/rancher/turtles/exp/etcdrestore/webhooks" diff --git a/exp/etcdrestore/webhooks/rke2config.go b/exp/etcdrestore/webhooks/rke2config.go index 9815ec95..e753d38a 100644 --- a/exp/etcdrestore/webhooks/rke2config.go +++ b/exp/etcdrestore/webhooks/rke2config.go @@ -22,7 +22,7 @@ import ( "fmt" "strings" - bootstrapv1 "github.com/rancher-sandbox/cluster-api-provider-rke2/bootstrap/api/v1beta1" + bootstrapv1 "github.com/rancher/cluster-api-provider-rke2/bootstrap/api/v1beta1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/exp/etcdrestore/webhooks/rke2config_test.go b/exp/etcdrestore/webhooks/rke2config_test.go index 3ebcb3aa..5d8faff7 100644 --- a/exp/etcdrestore/webhooks/rke2config_test.go +++ b/exp/etcdrestore/webhooks/rke2config_test.go @@ -22,7 +22,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - bootstrapv1 "github.com/rancher-sandbox/cluster-api-provider-rke2/bootstrap/api/v1beta1" + bootstrapv1 "github.com/rancher/cluster-api-provider-rke2/bootstrap/api/v1beta1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" From e2c87703250c8f658c4fb374be561a623fbb90d2 Mon Sep 17 00:00:00 2001 From: Furkat Gofurov Date: Wed, 17 Jul 2024 11:42:15 +0300 Subject: [PATCH 5/6] Bump CAPRKE2 to latest v0.5.0 release Signed-off-by: Furkat Gofurov --- charts/rancher-turtles/templates/clusterctl-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/rancher-turtles/templates/clusterctl-config.yaml b/charts/rancher-turtles/templates/clusterctl-config.yaml index 7e55a31a..7424ae99 100644 --- a/charts/rancher-turtles/templates/clusterctl-config.yaml +++ b/charts/rancher-turtles/templates/clusterctl-config.yaml @@ -37,7 +37,7 @@ data: url: "https://github.com/kubernetes-sigs/cluster-api/releases/v1.4.6/bootstrap-components.yaml" type: "BootstrapProvider" - name: "rke2" - url: "https://github.com/rancher/cluster-api-provider-rke2/releases/v0.4.1/bootstrap-components.yaml" + url: "https://github.com/rancher/cluster-api-provider-rke2/releases/v0.5.0/bootstrap-components.yaml" type: "BootstrapProvider" # ControlPlane providers @@ -45,7 +45,7 @@ data: url: "https://github.com/kubernetes-sigs/cluster-api/releases/v1.4.6/control-plane-components.yaml" type: "ControlPlaneProvider" - name: "rke2" - url: "https://github.com/rancher/cluster-api-provider-rke2/releases/v0.4.1/control-plane-components.yaml" + url: "https://github.com/rancher/cluster-api-provider-rke2/releases/v0.5.0/control-plane-components.yaml" type: "ControlPlaneProvider" # Addon providers From 85faf791bc9fbea1ef8fd786f7f17b4466c0f588 Mon Sep 17 00:00:00 2001 From: Furkat Gofurov Date: Tue, 30 Jul 2024 11:21:57 +0300 Subject: [PATCH 6/6] Address review comments Signed-off-by: Furkat Gofurov --- exp/etcdrestore/webhooks/rke2config.go | 29 +++++++++------------ exp/etcdrestore/webhooks/rke2config_test.go | 20 +++++++------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/exp/etcdrestore/webhooks/rke2config.go b/exp/etcdrestore/webhooks/rke2config.go index e753d38a..8d5b1860 100644 --- a/exp/etcdrestore/webhooks/rke2config.go +++ b/exp/etcdrestore/webhooks/rke2config.go @@ -82,7 +82,7 @@ func (r *RKE2ConfigWebhook) Default(ctx context.Context, obj runtime.Object) err return apierrors.NewBadRequest(fmt.Sprintf("failed to create secret plan resources: %s", err)) } - serviceAccountToken, err := r.EnsureServiceAccountSecretPopulated(ctx, planSecretName) + serviceAccountToken, err := r.ensureServiceAccountSecretPopulated(ctx, planSecretName) if err != nil { return apierrors.NewBadRequest(fmt.Sprintf("failed to ensure service account secret is populated: %s", err)) } @@ -127,15 +127,15 @@ func (r *RKE2ConfigWebhook) Default(ctx context.Context, obj runtime.Object) err return apierrors.NewBadRequest(fmt.Sprintf("failed to get ca setting: %s", err)) } - if err := r.CreateConnectInfoJson(ctx, rke2Config, planSecretName, serverUrl, pem, serviceAccountToken); err != nil { + if err := r.createConnectInfoJson(ctx, rke2Config, planSecretName, serverUrl, pem, serviceAccountToken); err != nil { return apierrors.NewBadRequest(fmt.Sprintf("failed to create connect info json: %s", err)) } - if err := r.CreateSystemAgentInstallScript(ctx, serverUrl, rke2Config); err != nil { + if err := r.createSystemAgentInstallScript(ctx, serverUrl, rke2Config); err != nil { return apierrors.NewBadRequest(fmt.Sprintf("failed to create system agent install script: %s", err)) } - if err := r.CreateConfigYAML(rke2Config); err != nil { + if err := r.createConfigYAML(rke2Config); err != nil { return apierrors.NewBadRequest(fmt.Sprintf("failed to create config.yaml: %s", err)) } @@ -152,14 +152,11 @@ func (r *RKE2ConfigWebhook) createSecretPlanResources(ctx context.Context, planS var errs []error - // Create the ServiceAccount first to later pass to the RoleBinding creation - sa := r.createServiceAccount(planSecretName, rke2Config) - resources := []client.Object{ - sa, + r.createServiceAccount(planSecretName, rke2Config), r.createSecret(planSecretName, rke2Config), r.createRole(planSecretName, rke2Config), - r.createRoleBinding(sa.Name, sa.Namespace, planSecretName, rke2Config), + r.createRoleBinding(planSecretName, rke2Config), r.createServiceAccountSecret(planSecretName, rke2Config), } @@ -225,7 +222,7 @@ func (r *RKE2ConfigWebhook) createRole(planSecretName string, rke2Config *bootst } // createRoleBinding creates a RoleBinding for the plan. -func (r *RKE2ConfigWebhook) createRoleBinding(serviceAccountName, serviceAccountNamespace, planSecretName string, rke2Config *bootstrapv1.RKE2Config) *rbacv1.RoleBinding { +func (r *RKE2ConfigWebhook) createRoleBinding(planSecretName string, rke2Config *bootstrapv1.RKE2Config) *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: planSecretName, @@ -234,8 +231,8 @@ func (r *RKE2ConfigWebhook) createRoleBinding(serviceAccountName, serviceAccount Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", - Name: serviceAccountName, - Namespace: serviceAccountNamespace, + Name: planSecretName, + Namespace: rke2Config.Namespace, }, }, RoleRef: rbacv1.RoleRef{ @@ -264,7 +261,7 @@ func (r *RKE2ConfigWebhook) createServiceAccountSecret(planSecretName string, rk } // ensureServiceAccountSecretPopulated ensures the ServiceAccount secret is populated. -func (r *RKE2ConfigWebhook) EnsureServiceAccountSecretPopulated(ctx context.Context, planSecretName string) ([]byte, error) { +func (r *RKE2ConfigWebhook) ensureServiceAccountSecretPopulated(ctx context.Context, planSecretName string) ([]byte, error) { logger := log.FromContext(ctx) logger.Info("Ensuring service account secret is populated") @@ -306,7 +303,7 @@ func (r *RKE2ConfigWebhook) EnsureServiceAccountSecretPopulated(ctx context.Cont } // createConnectInfoJson creates the connect-info-config.json file. -func (r *RKE2ConfigWebhook) CreateConnectInfoJson(ctx context.Context, rke2Config *bootstrapv1.RKE2Config, planSecretName, serverUrl, pem string, serviceAccountToken []byte) error { +func (r *RKE2ConfigWebhook) createConnectInfoJson(ctx context.Context, rke2Config *bootstrapv1.RKE2Config, planSecretName, serverUrl, pem string, serviceAccountToken []byte) error { connectInfoJsonPath := "/etc/rancher/agent/connect-info-config.json" filePaths := make(map[string]struct{}) @@ -400,7 +397,7 @@ func (r *RKE2ConfigWebhook) CreateConnectInfoJson(ctx context.Context, rke2Confi } // createSystemAgentInstallScript creates the system-agent-install.sh script. -func (r *RKE2ConfigWebhook) CreateSystemAgentInstallScript(ctx context.Context, serverUrl string, rke2Config *bootstrapv1.RKE2Config) error { +func (r *RKE2ConfigWebhook) createSystemAgentInstallScript(ctx context.Context, serverUrl string, rke2Config *bootstrapv1.RKE2Config) error { systemAgentInstallScriptPath := "/opt/system-agent-install.sh" filePaths := make(map[string]struct{}) @@ -456,7 +453,7 @@ func (r *RKE2ConfigWebhook) CreateSystemAgentInstallScript(ctx context.Context, } // createConfigYAML creates the config.yaml file. -func (r *RKE2ConfigWebhook) CreateConfigYAML(rke2Config *bootstrapv1.RKE2Config) error { +func (r *RKE2ConfigWebhook) createConfigYAML(rke2Config *bootstrapv1.RKE2Config) error { configYAMLPath := "/etc/rancher/agent/config.yaml" filePaths := make(map[string]struct{}) diff --git a/exp/etcdrestore/webhooks/rke2config_test.go b/exp/etcdrestore/webhooks/rke2config_test.go index 5d8faff7..cf3a459e 100644 --- a/exp/etcdrestore/webhooks/rke2config_test.go +++ b/exp/etcdrestore/webhooks/rke2config_test.go @@ -147,13 +147,13 @@ var _ = Describe("RKE2ConfigWebhook tests", func() { }) It("Should create a role binding with the correct properties", func() { - roleBinding := r.createRoleBinding(serviceAccountName, serviceAccountNamespace, planSecretName, rke2Config) + roleBinding := r.createRoleBinding(planSecretName, rke2Config) Expect(roleBinding.ObjectMeta.Name).To(Equal(planSecretName)) Expect(roleBinding.ObjectMeta.Namespace).To(Equal(rke2Config.Namespace)) Expect(roleBinding.Subjects[0].Kind).To(Equal("ServiceAccount")) - Expect(roleBinding.Subjects[0].Name).To(Equal(serviceAccountName)) - Expect(roleBinding.Subjects[0].Namespace).To(Equal(serviceAccountNamespace)) + Expect(roleBinding.Subjects[0].Name).To(Equal(planSecretName)) + Expect(roleBinding.Subjects[0].Namespace).To(Equal(rke2Config.Namespace)) Expect(roleBinding.RoleRef.APIGroup).To(Equal(rbacv1.GroupName)) Expect(roleBinding.RoleRef.Kind).To(Equal("Role")) Expect(roleBinding.RoleRef.Name).To(Equal(planSecretName)) @@ -170,13 +170,13 @@ var _ = Describe("RKE2ConfigWebhook tests", func() { }) It("Should return service account token when secret is present and populated", func() { - token, err := r.EnsureServiceAccountSecretPopulated(ctx, planSecretName) + token, err := r.ensureServiceAccountSecretPopulated(ctx, planSecretName) Expect(err).ToNot(HaveOccurred()) Expect(token).To(Equal([]byte("test-token"))) }) It("Should add connect-info-config.json when it's not present", func() { - err := r.CreateConnectInfoJson(ctx, rke2Config, "plan-secret", serverUrl, pem, token) + err := r.createConnectInfoJson(ctx, rke2Config, "plan-secret", serverUrl, pem, token) Expect(err).ToNot(HaveOccurred()) Expect(rke2Config.Spec.Files).To(ContainElement(bootstrapv1.File{ @@ -197,14 +197,14 @@ var _ = Describe("RKE2ConfigWebhook tests", func() { Path: "/etc/rancher/agent/connect-info-config.json", }) - err := r.CreateConnectInfoJson(ctx, rke2Config, "plan-secret", serverUrl, pem, token) + err := r.createConnectInfoJson(ctx, rke2Config, "plan-secret", serverUrl, pem, token) Expect(err).ToNot(HaveOccurred()) Expect(rke2Config.Spec.Files).To(HaveLen(1)) }) It("Should add system-agent-install.sh when it's not present", func() { - err := r.CreateSystemAgentInstallScript(ctx, serverUrl, rke2Config) + err := r.createSystemAgentInstallScript(ctx, serverUrl, rke2Config) Expect(err).ToNot(HaveOccurred()) Expect(rke2Config.Spec.Files).To(ContainElement(bootstrapv1.File{ @@ -225,14 +225,14 @@ var _ = Describe("RKE2ConfigWebhook tests", func() { Path: "/opt/system-agent-install.sh", }) - err := r.CreateSystemAgentInstallScript(ctx, serverUrl, rke2Config) + err := r.createSystemAgentInstallScript(ctx, serverUrl, rke2Config) Expect(err).ToNot(HaveOccurred()) Expect(rke2Config.Spec.Files).To(HaveLen(1)) }) It("Should add config.yaml when it's not present", func() { - err := r.CreateConfigYAML(rke2Config) + err := r.createConfigYAML(rke2Config) Expect(err).ToNot(HaveOccurred()) Expect(rke2Config.Spec.Files).To(ContainElement(bootstrapv1.File{ @@ -253,7 +253,7 @@ preserveWorkDirectory: true`, Path: "/etc/rancher/agent/config.yaml", }) - err := r.CreateConfigYAML(rke2Config) + err := r.createConfigYAML(rke2Config) Expect(err).ToNot(HaveOccurred()) Expect(rke2Config.Spec.Files).To(HaveLen(1))