From 15950a8ee0d49d8ed5f4f9b6cfa31d2145cf74fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Fri, 15 Sep 2023 14:04:56 +0200 Subject: [PATCH 01/10] [WiP] secret creation test --- api/v1/gardenercluster_types.go | 6 + api/v1/zz_generated.deepcopy.go | 16 ++ cmd/main.go | 2 +- ...ager.kyma-project.io_gardenerclusters.yaml | 157 +++++++++++++++++ ...ller.go => gardener_cluster_controller.go} | 12 +- .../gardener_cluster_controller_test.go | 160 ++++++++++++++++++ 6 files changed, 348 insertions(+), 5 deletions(-) create mode 100644 config/crd/bases/infrastructuremanager.kyma-project.io_gardenerclusters.yaml rename internal/controller/{cluster_controller.go => gardener_cluster_controller.go} (84%) create mode 100644 internal/controller/gardener_cluster_controller_test.go diff --git a/api/v1/gardenercluster_types.go b/api/v1/gardenercluster_types.go index 443a99cb..a982c145 100644 --- a/api/v1/gardenercluster_types.go +++ b/api/v1/gardenercluster_types.go @@ -44,6 +44,12 @@ type GardenerClusterList struct { // GardenerClusterSpec defines the desired state of GardenerCluster type GardenerClusterSpec struct { Kubeconfig Kubeconfig `json:"kubeconfig"` + Shoot Shoot `json:"shoot"` +} + +// Shoot defines the name of the Shoot resource +type Shoot struct { + Name string `json:"name"` } // Kubeconfig defines the desired kubeconfig location diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 5ef97f89..97e32c4e 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -89,6 +89,7 @@ func (in *GardenerClusterList) DeepCopyObject() runtime.Object { func (in *GardenerClusterSpec) DeepCopyInto(out *GardenerClusterSpec) { *out = *in out.Kubeconfig = in.Kubeconfig + out.Shoot = in.Shoot } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GardenerClusterSpec. @@ -153,3 +154,18 @@ func (in *Secret) DeepCopy() *Secret { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Shoot) DeepCopyInto(out *Shoot) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Shoot. +func (in *Shoot) DeepCopy() *Shoot { + if in == nil { + return nil + } + out := new(Shoot) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index 4cbaf3f3..68d96390 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -86,7 +86,7 @@ func main() { os.Exit(1) } - if err = (&controller.ClusterReconciler{ + if err = (&controller.GardenerClusterReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { diff --git a/config/crd/bases/infrastructuremanager.kyma-project.io_gardenerclusters.yaml b/config/crd/bases/infrastructuremanager.kyma-project.io_gardenerclusters.yaml new file mode 100644 index 00000000..e571e2e8 --- /dev/null +++ b/config/crd/bases/infrastructuremanager.kyma-project.io_gardenerclusters.yaml @@ -0,0 +1,157 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: gardenerclusters.infrastructuremanager.kyma-project.io +spec: + group: infrastructuremanager.kyma-project.io + names: + kind: GardenerCluster + listKind: GardenerClusterList + plural: gardenerclusters + singular: gardenercluster + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: GardenerCluster is the Schema for the clusters API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GardenerClusterSpec defines the desired state of GardenerCluster + properties: + kubeconfig: + description: Kubeconfig defines the desired kubeconfig location + properties: + secret: + description: SecretKeyRef defines the location, and structure + of the secret containing kubeconfig + properties: + key: + type: string + name: + type: string + namespace: + type: string + required: + - key + - name + - namespace + type: object + required: + - secret + type: object + shoot: + description: Shoot defines the name of the Shoot resource + properties: + name: + type: string + required: + - name + type: object + required: + - kubeconfig + - shoot + type: object + status: + description: GardenerClusterStatus defines the observed state of GardenerCluster + properties: + conditions: + description: List of status conditions to indicate the status of a + ServiceInstance. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + state: + description: State signifies current state of Gardener Cluster. Value + can be one of ("Ready", "Processing", "Error", "Deleting"). + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/internal/controller/cluster_controller.go b/internal/controller/gardener_cluster_controller.go similarity index 84% rename from internal/controller/cluster_controller.go rename to internal/controller/gardener_cluster_controller.go index 97e0f5a9..89c8bfc4 100644 --- a/internal/controller/cluster_controller.go +++ b/internal/controller/gardener_cluster_controller.go @@ -26,8 +26,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -// ClusterReconciler reconciles a GardenerCluster object -type ClusterReconciler struct { +const ( + lastKubeconfigSyncAnnotation = "operator.kyma-project.io/last-sync" +) + +// GardenerClusterReconciler reconciles a GardenerCluster object +type GardenerClusterReconciler struct { client.Client Scheme *runtime.Scheme } @@ -45,7 +49,7 @@ type ClusterReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile -func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive +func (r *GardenerClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive _ = log.FromContext(ctx) // nolint:all TODO(user): your logic here @@ -54,7 +58,7 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // SetupWithManager sets up the controller with the Manager. -func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *GardenerClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&infrastructuremanagerv1.GardenerCluster{}). Complete(r) diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go new file mode 100644 index 00000000..c2819b75 --- /dev/null +++ b/internal/controller/gardener_cluster_controller_test.go @@ -0,0 +1,160 @@ +package controller + +import ( + "context" + imv1 "github.com/kyma-project/infrastructure-manager/api/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "time" +) + +var _ = Describe("Cluster Inventory controller", func() { + Context("Secret with kubeconfig doesn't exist", func() { + kymaName := "kymaname1" + secretName := "secret-name1" + shootName := "shootName1" + namespace := "default" + + It("Create, and remove secret", func() { + By("Create GardenerCluster CR") + // TODO: Cluster Inventory CR should have Cluster scope + + clusterCR := fixGardenerClusterCR(kymaName, namespace, kymaName, shootName, secretName) + Expect(k8sClient.Create(context.Background(), &clusterCR)).To(Succeed()) + + By("Wait for secret creation") + var kubeconfigSecret corev1.Secret + key := types.NamespacedName{Name: secretName, Namespace: namespace} + + Eventually(func() bool { + return k8sClient.Get(context.Background(), key, &kubeconfigSecret) == nil + }, time.Second*30, time.Second*3).Should(BeTrue()) + + err := k8sClient.Get(context.Background(), key, &kubeconfigSecret) + Expect(err).To(BeNil()) + expectedSecret := fixNewSecret(secretName, namespace, kymaName, shootName, "kubeconfig1") + Expect(kubeconfigSecret.Labels).To(Equal(expectedSecret.Labels)) + Expect(kubeconfigSecret.Data).To(Equal(expectedSecret.Data)) + Expect(kubeconfigSecret.Annotations[lastKubeconfigSyncAnnotation]).To(Not(BeEmpty())) + + By("Delete Cluster CR") + Expect(k8sClient.Delete(context.Background(), &clusterCR)).To(Succeed()) + + By("Wait for secret deletion") + Eventually(func() bool { + err := k8sClient.Get(context.Background(), key, &kubeconfigSecret) + + return err != nil && k8serrors.IsNotFound(err) + + }, time.Second*30, time.Second*3).Should(BeTrue()) + }) + }) +}) + +func fixNewSecret(name, namespace, kymaName, shootName, data string) corev1.Secret { + labels := fixSecretLabels(kymaName, shootName) + + builder := newTestSecret(name, namespace) + return builder.WithLabels(labels).WithData(data).ToSecret() +} + +func (sb *TestSecret) WithLabels(labels map[string]string) *TestSecret { + sb.secret.Labels = labels + + return sb +} + +func (sb *TestSecret) WithData(data string) *TestSecret { + sb.secret.Data = (map[string][]byte{"config": []byte(data)}) + + return sb +} + +func (sb *TestSecret) ToSecret() corev1.Secret { + return sb.secret +} + +func newTestSecret(name, namespace string) *TestSecret { + return &TestSecret{ + secret: corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + }, + } +} + +type TestSecret struct { + secret corev1.Secret +} + +func fixSecretLabels(kymaName, shootName string) map[string]string { + labels := fixClusterInventoryLabels(kymaName, shootName) + labels["operator.kyma-project.io/managed-by"] = "lifecycle-manager" + labels["operator.kyma-project.io/cluster-name"] = kymaName + return labels +} + +func fixGardenerClusterCR(name, namespace, kymaName, shootName, secretName string) imv1.GardenerCluster { + return newTestClusterInventoryCR(name, namespace, shootName, secretName). + WithLabels(fixClusterInventoryLabels(kymaName, shootName)).ToCluster() +} + +func (sb *TestGardenerClusterCR) ToCluster() imv1.GardenerCluster { + return sb.gardenerCluster +} + +type TestGardenerClusterCR struct { + gardenerCluster imv1.GardenerCluster +} + +func newTestClusterInventoryCR(name, namespace, shootName, secretName string) *TestGardenerClusterCR { + return &TestGardenerClusterCR{ + gardenerCluster: imv1.GardenerCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: imv1.GardenerClusterSpec{ + Shoot: imv1.Shoot{ + Name: shootName, + }, + Kubeconfig: imv1.Kubeconfig{ + Secret: imv1.Secret{ + Name: name, + Namespace: namespace, + Key: "config", //nolint:all TODO: fill it up with the actual data + }, + }, + }, + }, + } +} + +func (sb *TestGardenerClusterCR) WithLabels(labels map[string]string) *TestGardenerClusterCR { + sb.gardenerCluster.Labels = labels + + return sb +} + +func fixClusterInventoryLabels(kymaName, shootName string) map[string]string { + + labels := map[string]string{} + + labels["kyma-project.io/instance-id"] = "instanceID" + labels["kyma-project.io/runtime-id"] = "runtimeID" + labels["kyma-project.io/broker-plan-id"] = "planID" + labels["kyma-project.io/broker-plan-name"] = "planName" + labels["kyma-project.io/global-account-id"] = "globalAccountID" + labels["kyma-project.io/subaccount-id"] = "subAccountID" + labels["kyma-project.io/shoot-name"] = shootName + labels["kyma-project.io/region"] = "region" + labels["operator.kyma-project.io/kyma-name"] = kymaName + + return labels +} From 16a4223b96eea2c16862d30b0b5062480ab2606d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Fri, 15 Sep 2023 16:48:21 +0200 Subject: [PATCH 02/10] [WiP] secret creation test --- cmd/main.go | 12 +- go.mod | 5 +- go.sum | 1 + .../controller/gardener_cluster_controller.go | 135 ++++++++++++++++-- .../gardener_cluster_controller_test.go | 27 ++-- .../controller/mocks/KubeconfigProvider.go | 48 +++++++ internal/controller/suite_test.go | 29 +++- internal/gardener/KubeConfigProvider.go | 8 ++ 8 files changed, 230 insertions(+), 35 deletions(-) create mode 100644 internal/controller/mocks/KubeconfigProvider.go create mode 100644 internal/gardener/KubeConfigProvider.go diff --git a/cmd/main.go b/cmd/main.go index 68d96390..8bbfd42d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,6 +18,7 @@ package main import ( "flag" + "github.com/kyma-project/infrastructure-manager/internal/gardener" "os" infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" @@ -60,7 +61,9 @@ func main() { opts.BindFlags(flag.CommandLine) flag.Parse() - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + logger := zap.New(zap.UseFlagOptions(&opts)) + + ctrl.SetLogger(logger) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -86,10 +89,9 @@ func main() { os.Exit(1) } - if err = (&controller.GardenerClusterReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { + provider := gardener.KubeconfigProvider{} + + if err = (controller.NewGardenerClusterController(mgr, provider, logger)).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "GardenerCluster") os.Exit(1) } diff --git a/go.mod b/go.mod index c213d1a6..028e751a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.7 github.com/stretchr/testify v1.8.1 + go.uber.org/zap v1.24.0 + k8s.io/api v0.27.2 k8s.io/apimachinery v0.27.2 k8s.io/client-go v0.27.2 sigs.k8s.io/controller-runtime v0.15.0 @@ -47,9 +49,9 @@ require ( github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.24.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.8.0 // indirect @@ -63,7 +65,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.27.2 // indirect k8s.io/apiextensions-apiserver v0.27.2 // indirect k8s.io/component-base v0.27.2 // indirect k8s.io/klog/v2 v2.90.1 // indirect diff --git a/go.sum b/go.sum index 25a88aa4..7291cff2 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= diff --git a/internal/controller/gardener_cluster_controller.go b/internal/controller/gardener_cluster_controller.go index 89c8bfc4..ea2a1727 100644 --- a/internal/controller/gardener_cluster_controller.go +++ b/internal/controller/gardener_cluster_controller.go @@ -18,22 +18,45 @@ package controller import ( "context" - + "errors" + "fmt" + "github.com/go-logr/logr" infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "time" ) const ( lastKubeconfigSyncAnnotation = "operator.kyma-project.io/last-sync" + clusterCRNameLabel = "operator.kyma-project.io/cluster-name" + defaultRequeuInSeconds = 60 * 1000 ) -// GardenerClusterReconciler reconciles a GardenerCluster object -type GardenerClusterReconciler struct { +// GardenerClusterController reconciles a GardenerCluster object +type GardenerClusterController struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + KubeconfigProvider KubeconfigProvider + log logr.Logger +} + +func NewGardenerClusterController(mgr ctrl.Manager, kubeconfigProvider KubeconfigProvider, logger logr.Logger) *GardenerClusterController { + return &GardenerClusterController{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + KubeconfigProvider: kubeconfigProvider, + log: logger, //TODO: + } +} + +//go:generate mockery --name=KubeconfigProvider +type KubeconfigProvider interface { + Fetch(shootName string) (string, error) } //+kubebuilder:rbac:groups=infrastructuremanager.kyma-project.io,resources=clusters,verbs=get;list;watch;create;update;patch;delete @@ -49,16 +72,110 @@ type GardenerClusterReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile -func (r *GardenerClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive - _ = log.FromContext(ctx) +func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive + + r.log.Info("Starting reconciliation loop") + + var cluster infrastructuremanagerv1.GardenerCluster + + err := r.Client.Get(ctx, req.NamespacedName, &cluster) - // nolint:all TODO(user): your logic here + if err != nil { + r.log.Error(err, "could not get the CR for "+req.NamespacedName.Name) + return ctrl.Result{ + Requeue: false, + }, err + } + + secret, err := r.getSecret(cluster.Spec.Shoot.Name) + if err != nil { + r.log.Error(err, "could not get the Secret for "+cluster.Spec.Shoot.Name) + + if !k8serrors.IsNotFound(err) { + return ctrl.Result{ + Requeue: false, + }, err + } + } + + if secret == nil { + r.log.Error(err, "Secret not found, and will be created") + + err = r.createSecret(ctx, cluster) + + if err != nil { + return ctrl.Result{ + Requeue: false, + }, err + } + + //TODO: consider if ctx should not be passed here + } return ctrl.Result{}, nil } +func (r *GardenerClusterController) getSecret(shootName string) (*corev1.Secret, error) { + var secretList corev1.SecretList + + selector := client.MatchingLabels(map[string]string{ + "kyma-project.io/shoot-name": shootName, + }) + + err := r.Client.List(context.Background(), &secretList, selector) //TODO: how this context is used + if err != nil { + return nil, err + } + + size := len(secretList.Items) + + if size == 0 { + return nil, nil + } + + if size > 1 { + return nil, errors.New(fmt.Sprintf("unexpected numer of secrets found for shoot `%s`", shootName)) + } + + return &secretList.Items[0], nil +} + +func (r *GardenerClusterController) createSecret(ctx context.Context, cluster infrastructuremanagerv1.GardenerCluster) error { + secret, err := r.newSecret(cluster) + if err != nil { + return err + } + + return r.Client.Create(ctx, &secret) +} + +func (r *GardenerClusterController) newSecret(cluster infrastructuremanagerv1.GardenerCluster) (corev1.Secret, error) { + labels := map[string]string{} + + for key, val := range cluster.Labels { + labels[key] = val + } + labels["operator.kyma-project.io/managed-by"] = "lifecycle-manager" + labels[clusterCRNameLabel] = cluster.Name + + kubeconfig, err := r.KubeconfigProvider.Fetch(cluster.Spec.Shoot.Name) + if err != nil { + return corev1.Secret{}, err + } + + return corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster.Spec.Kubeconfig.Secret.Name, + Namespace: cluster.Spec.Kubeconfig.Secret.Namespace, + Labels: labels, + Annotations: map[string]string{lastKubeconfigSyncAnnotation: time.Now().UTC().String()}, + }, + StringData: map[string]string{cluster.Spec.Kubeconfig.Secret.Key: kubeconfig}, + }, nil +} + // SetupWithManager sets up the controller with the Manager. -func (r *GardenerClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *GardenerClusterController) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&infrastructuremanagerv1.GardenerCluster{}). Complete(r) diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index c2819b75..14042c54 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -6,7 +6,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "time" @@ -21,7 +20,6 @@ var _ = Describe("Cluster Inventory controller", func() { It("Create, and remove secret", func() { By("Create GardenerCluster CR") - // TODO: Cluster Inventory CR should have Cluster scope clusterCR := fixGardenerClusterCR(kymaName, namespace, kymaName, shootName, secretName) Expect(k8sClient.Create(context.Background(), &clusterCR)).To(Succeed()) @@ -41,16 +39,16 @@ var _ = Describe("Cluster Inventory controller", func() { Expect(kubeconfigSecret.Data).To(Equal(expectedSecret.Data)) Expect(kubeconfigSecret.Annotations[lastKubeconfigSyncAnnotation]).To(Not(BeEmpty())) - By("Delete Cluster CR") - Expect(k8sClient.Delete(context.Background(), &clusterCR)).To(Succeed()) + //By("Delete Cluster CR") + //Expect(k8sClient.Delete(context.Background(), &clusterCR)).To(Succeed()) - By("Wait for secret deletion") - Eventually(func() bool { - err := k8sClient.Get(context.Background(), key, &kubeconfigSecret) - - return err != nil && k8serrors.IsNotFound(err) - - }, time.Second*30, time.Second*3).Should(BeTrue()) + //By("Wait for secret deletion") + //Eventually(func() bool { + // err := k8sClient.Get(context.Background(), key, &kubeconfigSecret) + // + // return err != nil && k8serrors.IsNotFound(err) + // + //}, time.Second*30, time.Second*3).Should(BeTrue()) }) }) }) @@ -101,7 +99,7 @@ func fixSecretLabels(kymaName, shootName string) map[string]string { } func fixGardenerClusterCR(name, namespace, kymaName, shootName, secretName string) imv1.GardenerCluster { - return newTestClusterInventoryCR(name, namespace, shootName, secretName). + return newTestInfrastructureManagerCR(name, namespace, shootName, secretName). WithLabels(fixClusterInventoryLabels(kymaName, shootName)).ToCluster() } @@ -113,7 +111,7 @@ type TestGardenerClusterCR struct { gardenerCluster imv1.GardenerCluster } -func newTestClusterInventoryCR(name, namespace, shootName, secretName string) *TestGardenerClusterCR { +func newTestInfrastructureManagerCR(name, namespace, shootName, secretName string) *TestGardenerClusterCR { return &TestGardenerClusterCR{ gardenerCluster: imv1.GardenerCluster{ ObjectMeta: metav1.ObjectMeta{ @@ -126,7 +124,7 @@ func newTestClusterInventoryCR(name, namespace, shootName, secretName string) *T }, Kubeconfig: imv1.Kubeconfig{ Secret: imv1.Secret{ - Name: name, + Name: secretName, Namespace: namespace, Key: "config", //nolint:all TODO: fill it up with the actual data }, @@ -143,7 +141,6 @@ func (sb *TestGardenerClusterCR) WithLabels(labels map[string]string) *TestGarde } func fixClusterInventoryLabels(kymaName, shootName string) map[string]string { - labels := map[string]string{} labels["kyma-project.io/instance-id"] = "instanceID" diff --git a/internal/controller/mocks/KubeconfigProvider.go b/internal/controller/mocks/KubeconfigProvider.go new file mode 100644 index 00000000..b4efe0ee --- /dev/null +++ b/internal/controller/mocks/KubeconfigProvider.go @@ -0,0 +1,48 @@ +// Code generated by mockery v2.33.2. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// KubeconfigProvider is an autogenerated mock type for the KubeconfigProvider type +type KubeconfigProvider struct { + mock.Mock +} + +// Fetch provides a mock function with given fields: shootName +func (_m *KubeconfigProvider) Fetch(shootName string) (string, error) { + ret := _m.Called(shootName) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(string) (string, error)); ok { + return rf(shootName) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(shootName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(shootName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewKubeconfigProvider creates a new instance of KubeconfigProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewKubeconfigProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *KubeconfigProvider { + mock := &KubeconfigProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 8f0ee1e5..f55ad4b9 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -17,18 +17,19 @@ limitations under the License. package controller import ( - "path/filepath" - "testing" - infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" + "github.com/kyma-project/infrastructure-manager/internal/controller/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "testing" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -45,7 +46,8 @@ func TestControllers(t *testing.T) { } var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + logger := zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)) + logf.SetLogger(logger) By("bootstrapping test environment") testEnv = &envtest.Environment{ @@ -59,7 +61,20 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + kubeconfigProviderMock := &mocks.KubeconfigProvider{} + setupKubeconfigProviderMock(kubeconfigProviderMock) + + mgr, err := ctrl.NewManager(cfg, ctrl.Options{Scheme: scheme.Scheme}) + Expect(err).ToNot(HaveOccurred()) + + controller := NewGardenerClusterController(mgr, kubeconfigProviderMock, logger) + Expect(controller).NotTo(BeNil()) + err = infrastructuremanagerv1.AddToScheme(scheme.Scheme) + + err = controller.SetupWithManager(mgr) + Expect(err).To(BeNil()) + Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme @@ -70,6 +85,12 @@ var _ = BeforeSuite(func() { }) +func setupKubeconfigProviderMock(kpMock *mocks.KubeconfigProvider) { + kpMock.On("Fetch", "shootName1").Return("kubeconfig1", nil) + kpMock.On("Fetch", "shootName2").Return("kubeconfig2", nil) + kpMock.On("Fetch", "shootName3").Return("kubeconfig3", nil) +} + var _ = AfterSuite(func() { By("tearing down the test environment") err := testEnv.Stop() diff --git a/internal/gardener/KubeConfigProvider.go b/internal/gardener/KubeConfigProvider.go new file mode 100644 index 00000000..22f9a6e1 --- /dev/null +++ b/internal/gardener/KubeConfigProvider.go @@ -0,0 +1,8 @@ +package gardener + +type KubeconfigProvider struct { +} + +func (receiver KubeconfigProvider) Fetch(shootName string) (string, error) { + return "", nil +} From 70ba89faaa5ef8f018f737374be8346b04def191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Mon, 18 Sep 2023 14:45:54 +0200 Subject: [PATCH 03/10] Fixes test for secret creation --- .../gardener_cluster_controller_test.go | 35 +++++++------------ internal/controller/suite_test.go | 35 +++++++++++++------ 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index 14042c54..e415f4f2 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -21,8 +21,8 @@ var _ = Describe("Cluster Inventory controller", func() { It("Create, and remove secret", func() { By("Create GardenerCluster CR") - clusterCR := fixGardenerClusterCR(kymaName, namespace, kymaName, shootName, secretName) - Expect(k8sClient.Create(context.Background(), &clusterCR)).To(Succeed()) + gardenerClusterCR := fixGardenerClusterCR(kymaName, namespace, shootName, secretName) + Expect(k8sClient.Create(context.Background(), &gardenerClusterCR)).To(Succeed()) By("Wait for secret creation") var kubeconfigSecret corev1.Secret @@ -38,17 +38,6 @@ var _ = Describe("Cluster Inventory controller", func() { Expect(kubeconfigSecret.Labels).To(Equal(expectedSecret.Labels)) Expect(kubeconfigSecret.Data).To(Equal(expectedSecret.Data)) Expect(kubeconfigSecret.Annotations[lastKubeconfigSyncAnnotation]).To(Not(BeEmpty())) - - //By("Delete Cluster CR") - //Expect(k8sClient.Delete(context.Background(), &clusterCR)).To(Succeed()) - - //By("Wait for secret deletion") - //Eventually(func() bool { - // err := k8sClient.Get(context.Background(), key, &kubeconfigSecret) - // - // return err != nil && k8serrors.IsNotFound(err) - // - //}, time.Second*30, time.Second*3).Should(BeTrue()) }) }) }) @@ -98,19 +87,11 @@ func fixSecretLabels(kymaName, shootName string) map[string]string { return labels } -func fixGardenerClusterCR(name, namespace, kymaName, shootName, secretName string) imv1.GardenerCluster { - return newTestInfrastructureManagerCR(name, namespace, shootName, secretName). +func fixGardenerClusterCR(kymaName, namespace, shootName, secretName string) imv1.GardenerCluster { + return newTestInfrastructureManagerCR(kymaName, namespace, shootName, secretName). WithLabels(fixClusterInventoryLabels(kymaName, shootName)).ToCluster() } -func (sb *TestGardenerClusterCR) ToCluster() imv1.GardenerCluster { - return sb.gardenerCluster -} - -type TestGardenerClusterCR struct { - gardenerCluster imv1.GardenerCluster -} - func newTestInfrastructureManagerCR(name, namespace, shootName, secretName string) *TestGardenerClusterCR { return &TestGardenerClusterCR{ gardenerCluster: imv1.GardenerCluster{ @@ -140,6 +121,14 @@ func (sb *TestGardenerClusterCR) WithLabels(labels map[string]string) *TestGarde return sb } +func (sb *TestGardenerClusterCR) ToCluster() imv1.GardenerCluster { + return sb.gardenerCluster +} + +type TestGardenerClusterCR struct { + gardenerCluster imv1.GardenerCluster +} + func fixClusterInventoryLabels(kymaName, shootName string) map[string]string { labels := map[string]string{} diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index f55ad4b9..12b70f6b 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -17,27 +17,33 @@ limitations under the License. package controller import ( + "context" + "path/filepath" + "testing" + infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" "github.com/kyma-project/infrastructure-manager/internal/controller/mocks" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "path/filepath" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "testing" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var cfg *rest.Config //nolint:gochecknoglobals -var k8sClient client.Client //nolint:gochecknoglobals -var testEnv *envtest.Environment //nolint:gochecknoglobals +var ( + cfg *rest.Config //nolint:gochecknoglobals + k8sClient client.Client //nolint:gochecknoglobals + testEnv *envtest.Environment //nolint:gochecknoglobals + suiteCtx context.Context + cancelSuiteCtx context.CancelFunc +) func TestControllers(t *testing.T) { RegisterFailHandler(Fail) @@ -61,28 +67,34 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - kubeconfigProviderMock := &mocks.KubeconfigProvider{} - setupKubeconfigProviderMock(kubeconfigProviderMock) + err = infrastructuremanagerv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) mgr, err := ctrl.NewManager(cfg, ctrl.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) + kubeconfigProviderMock := &mocks.KubeconfigProvider{} + setupKubeconfigProviderMock(kubeconfigProviderMock) + controller := NewGardenerClusterController(mgr, kubeconfigProviderMock, logger) Expect(controller).NotTo(BeNil()) - err = infrastructuremanagerv1.AddToScheme(scheme.Scheme) - err = controller.SetupWithManager(mgr) Expect(err).To(BeNil()) - Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + go func() { + defer GinkgoRecover() + suiteCtx, cancelSuiteCtx = context.WithCancel(context.Background()) + + err = mgr.Start(suiteCtx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() }) func setupKubeconfigProviderMock(kpMock *mocks.KubeconfigProvider) { @@ -93,6 +105,7 @@ func setupKubeconfigProviderMock(kpMock *mocks.KubeconfigProvider) { var _ = AfterSuite(func() { By("tearing down the test environment") + cancelSuiteCtx() err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) From 44c63cf663261fe8397f8b340abfe873344dd436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Mon, 18 Sep 2023 15:38:52 +0200 Subject: [PATCH 04/10] code polishing --- .../controller/gardener_cluster_controller.go | 26 ++++++++----------- .../gardener_cluster_controller_test.go | 4 +-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/internal/controller/gardener_cluster_controller.go b/internal/controller/gardener_cluster_controller.go index ea2a1727..8410b613 100644 --- a/internal/controller/gardener_cluster_controller.go +++ b/internal/controller/gardener_cluster_controller.go @@ -65,7 +65,6 @@ type KubeconfigProvider interface { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// nolint:all TODO(user): Modify the Reconcile function to compare the state specified by // the GardenerCluster object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. @@ -73,7 +72,6 @@ type KubeconfigProvider interface { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive - r.log.Info("Starting reconciliation loop") var cluster infrastructuremanagerv1.GardenerCluster @@ -82,9 +80,7 @@ func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Requ if err != nil { r.log.Error(err, "could not get the CR for "+req.NamespacedName.Name) - return ctrl.Result{ - Requeue: false, - }, err + return r.ResultWithoutRequeue(), err } secret, err := r.getSecret(cluster.Spec.Shoot.Name) @@ -92,9 +88,7 @@ func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Requ r.log.Error(err, "could not get the Secret for "+cluster.Spec.Shoot.Name) if !k8serrors.IsNotFound(err) { - return ctrl.Result{ - Requeue: false, - }, err + return r.ResultWithoutRequeue(), err } } @@ -104,25 +98,27 @@ func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Requ err = r.createSecret(ctx, cluster) if err != nil { - return ctrl.Result{ - Requeue: false, - }, err + return r.ResultWithoutRequeue(), err } - - //TODO: consider if ctx should not be passed here } return ctrl.Result{}, nil } +func (r *GardenerClusterController) ResultWithoutRequeue() ctrl.Result { + return ctrl.Result{ + Requeue: false, + } +} + func (r *GardenerClusterController) getSecret(shootName string) (*corev1.Secret, error) { var secretList corev1.SecretList - selector := client.MatchingLabels(map[string]string{ + shootNameSelector := client.MatchingLabels(map[string]string{ "kyma-project.io/shoot-name": shootName, }) - err := r.Client.List(context.Background(), &secretList, selector) //TODO: how this context is used + err := r.Client.List(context.Background(), &secretList, shootNameSelector) if err != nil { return nil, err } diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index e415f4f2..2d316f07 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -18,7 +18,7 @@ var _ = Describe("Cluster Inventory controller", func() { shootName := "shootName1" namespace := "default" - It("Create, and remove secret", func() { + It("Create secret", func() { By("Create GardenerCluster CR") gardenerClusterCR := fixGardenerClusterCR(kymaName, namespace, shootName, secretName) @@ -56,7 +56,7 @@ func (sb *TestSecret) WithLabels(labels map[string]string) *TestSecret { } func (sb *TestSecret) WithData(data string) *TestSecret { - sb.secret.Data = (map[string][]byte{"config": []byte(data)}) + sb.secret.Data = map[string][]byte{"config": []byte(data)} return sb } From 14d17ed9a131aa9ada47d701ea6049b5f1b33d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Mon, 18 Sep 2023 16:01:58 +0200 Subject: [PATCH 05/10] fixes linter issues --- cmd/main.go | 2 +- internal/controller/gardener_cluster_controller.go | 11 ++++++----- .../controller/gardener_cluster_controller_test.go | 3 ++- internal/controller/suite_test.go | 4 ++-- internal/gardener/KubeConfigProvider.go | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 8bbfd42d..4b5006c6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,11 +18,11 @@ package main import ( "flag" - "github.com/kyma-project/infrastructure-manager/internal/gardener" "os" infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" "github.com/kyma-project/infrastructure-manager/internal/controller" + "github.com/kyma-project/infrastructure-manager/internal/gardener" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" diff --git a/internal/controller/gardener_cluster_controller.go b/internal/controller/gardener_cluster_controller.go index 8410b613..df4daba5 100644 --- a/internal/controller/gardener_cluster_controller.go +++ b/internal/controller/gardener_cluster_controller.go @@ -18,17 +18,18 @@ package controller import ( "context" - "errors" "fmt" + "time" + "github.com/go-logr/logr" infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "time" ) const ( @@ -50,7 +51,7 @@ func NewGardenerClusterController(mgr ctrl.Manager, kubeconfigProvider Kubeconfi Client: mgr.GetClient(), Scheme: mgr.GetScheme(), KubeconfigProvider: kubeconfigProvider, - log: logger, //TODO: + log: logger, } } @@ -126,11 +127,11 @@ func (r *GardenerClusterController) getSecret(shootName string) (*corev1.Secret, size := len(secretList.Items) if size == 0 { - return nil, nil + return nil, k8serrors.NewNotFound(schema.GroupResource{}, "") //nolint:all TODO: update with valid GroupResource } if size > 1 { - return nil, errors.New(fmt.Sprintf("unexpected numer of secrets found for shoot `%s`", shootName)) + return nil, fmt.Errorf("unexpected numer of secrets found for shoot `%s`", shootName) } return &secretList.Items[0], nil diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index 2d316f07..5df3af21 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -2,13 +2,14 @@ package controller import ( "context" + "time" + imv1 "github.com/kyma-project/infrastructure-manager/api/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "time" ) var _ = Describe("Cluster Inventory controller", func() { diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 12b70f6b..8fd56f39 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -41,8 +41,8 @@ var ( cfg *rest.Config //nolint:gochecknoglobals k8sClient client.Client //nolint:gochecknoglobals testEnv *envtest.Environment //nolint:gochecknoglobals - suiteCtx context.Context - cancelSuiteCtx context.CancelFunc + suiteCtx context.Context //nolint:gochecknoglobals + cancelSuiteCtx context.CancelFunc //nolint:gochecknoglobals ) func TestControllers(t *testing.T) { diff --git a/internal/gardener/KubeConfigProvider.go b/internal/gardener/KubeConfigProvider.go index 22f9a6e1..fefaeec5 100644 --- a/internal/gardener/KubeConfigProvider.go +++ b/internal/gardener/KubeConfigProvider.go @@ -4,5 +4,5 @@ type KubeconfigProvider struct { } func (receiver KubeconfigProvider) Fetch(shootName string) (string, error) { - return "", nil + return "kubeconfig-" + shootName, nil } From fb91a289fbd755136eac5a56f8d7177d3f9fabd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Tue, 19 Sep 2023 08:50:31 +0200 Subject: [PATCH 06/10] changes test description --- internal/controller/gardener_cluster_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index 5df3af21..bba99734 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -12,7 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" ) -var _ = Describe("Cluster Inventory controller", func() { +var _ = Describe("Gardener Cluster controller", func() { Context("Secret with kubeconfig doesn't exist", func() { kymaName := "kymaname1" secretName := "secret-name1" From 8063a19f639a61f67f3966ced368c883df48879d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Tue, 19 Sep 2023 17:12:24 +0200 Subject: [PATCH 07/10] Adjusts Dockerfile to new folder structure --- Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c389c098..d139ea78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,7 @@ COPY go.sum go.sum RUN go mod download # Copy the go source -COPY cmd/main.go cmd/main.go -COPY api/ api/ -COPY internal/controller/ internal/controller/ +COPY . ./ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command From ab86a67e87ab2e23649fd02d438893674d9d2c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Thu, 21 Sep 2023 10:47:29 +0200 Subject: [PATCH 08/10] improves Dockerfile to copy only relevant directories --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d139ea78..cab2a5b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,9 @@ COPY go.sum go.sum RUN go mod download # Copy the go source -COPY . ./ +COPY cmd/main.go cmd/main.go +COPY api/ api/ +COPY internal/ internal/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command From 6a18764b64a9499b867b3ffd3cda8b1c6c1907fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Fri, 22 Sep 2023 08:09:14 +0200 Subject: [PATCH 09/10] fixes reueue when secret is not found and managed-by label --- internal/controller/gardener_cluster_controller.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/controller/gardener_cluster_controller.go b/internal/controller/gardener_cluster_controller.go index df4daba5..eb7bfcd2 100644 --- a/internal/controller/gardener_cluster_controller.go +++ b/internal/controller/gardener_cluster_controller.go @@ -89,7 +89,10 @@ func (r *GardenerClusterController) Reconcile(ctx context.Context, req ctrl.Requ r.log.Error(err, "could not get the Secret for "+cluster.Spec.Shoot.Name) if !k8serrors.IsNotFound(err) { - return r.ResultWithoutRequeue(), err + return ctrl.Result{ + Requeue: true, + RequeueAfter: defaultRequeuInSeconds, + }, err } } @@ -152,7 +155,7 @@ func (r *GardenerClusterController) newSecret(cluster infrastructuremanagerv1.Ga for key, val := range cluster.Labels { labels[key] = val } - labels["operator.kyma-project.io/managed-by"] = "lifecycle-manager" + labels["operator.kyma-project.io/managed-by"] = "infrastructure-manager" labels[clusterCRNameLabel] = cluster.Name kubeconfig, err := r.KubeconfigProvider.Fetch(cluster.Spec.Shoot.Name) From 259850da0219aefd08ed5a5e0d03e967d35f82f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Drzewiecki?= Date: Fri, 22 Sep 2023 08:18:57 +0200 Subject: [PATCH 10/10] fixes unit test --- internal/controller/gardener_cluster_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index bba99734..36d7047f 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -83,7 +83,7 @@ type TestSecret struct { func fixSecretLabels(kymaName, shootName string) map[string]string { labels := fixClusterInventoryLabels(kymaName, shootName) - labels["operator.kyma-project.io/managed-by"] = "lifecycle-manager" + labels["operator.kyma-project.io/managed-by"] = "infrastructure-manager" labels["operator.kyma-project.io/cluster-name"] = kymaName return labels }