From 69770e775abd0237a2d5e6ed5616f00f7aea08d7 Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Sat, 5 Mar 2022 23:10:55 +0800 Subject: [PATCH 1/2] Add Unit testcases Added uts Signed-off-by: Zheng Xi Zhou --- controllers/configuration_controller_test.go | 286 ++++++++++++++++++- 1 file changed, 283 insertions(+), 3 deletions(-) diff --git a/controllers/configuration_controller_test.go b/controllers/configuration_controller_test.go index b584dee5..c9580922 100644 --- a/controllers/configuration_controller_test.go +++ b/controllers/configuration_controller_test.go @@ -371,6 +371,7 @@ func TestPreCheck(t *testing.T) { s := runtime.NewScheme() v1beta1.AddToScheme(s) corev1.AddToScheme(s) + corev1.AddToScheme(s) provider := &v1beta1.Provider{ ObjectMeta: v1.ObjectMeta{ Name: "default", @@ -438,6 +439,122 @@ func TestPreCheck(t *testing.T) { }, want: want{}, }, + { + name: "could not find provider", + args: args{ + r: r, + configuration: &v1beta1.Configuration{ + ObjectMeta: v1.ObjectMeta{ + Name: "abc", + }, + Spec: v1beta1.ConfigurationSpec{ + HCL: "bbb", + }, + }, + meta: &TFConfigurationMeta{ + ConfigurationCMName: "abc", + ProviderReference: &crossplane.Reference{ + Namespace: "d", + Name: "default", + }, + }, + }, + want: want{ + errMsg: "provider not found", + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if err := tc.args.r.preCheck(ctx, tc.args.configuration, tc.args.meta); (tc.want.errMsg != "") && + !strings.Contains(err.Error(), tc.want.errMsg) { + t.Errorf("preCheck() error = %v, wantErr %v", err, tc.want.errMsg) + } + }) + } +} + +func TestPreCheckWhenConfigurationIsChanged(t *testing.T) { + r := &ConfigurationReconciler{} + ctx := context.Background() + s := runtime.NewScheme() + v1beta1.AddToScheme(s) + corev1.AddToScheme(s) + corev1.AddToScheme(s) + provider := &v1beta1.Provider{ + ObjectMeta: v1.ObjectMeta{ + Name: "default", + Namespace: "default", + }, + Status: v1beta1.ProviderStatus{ + State: types.ProviderIsNotReady, + }, + } + r.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider).Build() + + provider3 := &v1beta1.Provider{ + ObjectMeta: v1.ObjectMeta{ + Name: "default", + Namespace: "default", + }, + Status: v1beta1.ProviderStatus{ + State: types.ProviderIsNotReady, + }, + } + configurationCM3 := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: "abc", + Namespace: "default", + }, + } + r.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3, configurationCM3).Build() + meta3 := &TFConfigurationMeta{ + ConfigurationCMName: "abc", + ProviderReference: &crossplane.Reference{ + Namespace: "default", + Name: "default", + }, + CompleteConfiguration: "d", + Namespace: "default", + } + + patches := gomonkey.ApplyFunc(reflect.DeepEqual, func(x, y interface{}) bool { + return true + }) + defer patches.Reset() + + type args struct { + r *ConfigurationReconciler + configuration *v1beta1.Configuration + meta *TFConfigurationMeta + } + + type want struct { + errMsg string + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "configuration is changed", + args: args{ + r: r, + configuration: &v1beta1.Configuration{ + ObjectMeta: v1.ObjectMeta{ + Name: "abc", + }, + Spec: v1beta1.ConfigurationSpec{ + HCL: "bbb", + }, + }, + meta: meta3, + }, + want: want{}, + }, } for _, tc := range testcases { @@ -481,11 +598,94 @@ func TestTerraformDestroy(t *testing.T) { k8sClient2 := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, configuration).Build() r2.Client = k8sClient2 + //r3 := &ConfigurationReconciler{} + //provider1.Status.State = types.ProviderIsReady + //job3 := &batchv1.Job{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "a", + // Namespace: "default", + // }, + // Status: batchv1.JobStatus{ + // Succeeded: int32(1), + // }, + //} + //configuration3 := &v1beta1.Configuration{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "default", + // Name: "b", + // }, + //} + //configuration3.Spec.WriteConnectionSecretToReference = &crossplane.SecretReference{ + // Name: "b", + // Namespace: "default", + //} + //k8sClient3 := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, job3, configuration3).Build() + //r3.Client = k8sClient3 + //meta3 := &TFConfigurationMeta{ + // DestroyJobName: "a", + // Namespace: "b", + // DeleteResource: true, + //} + + r4 := &ConfigurationReconciler{} + provider1.Status.State = types.ProviderIsReady + job4 := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "a", + Namespace: "default", + }, + Status: batchv1.JobStatus{ + Succeeded: int32(1), + }, + } + data, _ := json.Marshal(map[string]interface{}{ + "name": "abc", + }) + variables := &runtime.RawExtension{Raw: data} + configuration4 := &v1beta1.Configuration{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "b", + }, + Spec: v1beta1.ConfigurationSpec{ + Variable: variables, + }, + } + configuration4.Spec.WriteConnectionSecretToReference = &crossplane.SecretReference{ + Name: "b", + Namespace: "default", + } + secret4 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "b", + Namespace: "default", + }, + Type: corev1.SecretTypeOpaque, + } + variableSecret4 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "c", + Namespace: "default", + }, + Type: corev1.SecretTypeOpaque, + } + k8sClient4 := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, job4, secret4, variableSecret4, configuration4).Build() + r4.Client = k8sClient4 + meta4 := &TFConfigurationMeta{ + DestroyJobName: "a", + Namespace: "default", + DeleteResource: true, + ProviderReference: &crossplane.Reference{ + Name: "b", + Namespace: "default", + }, + VariableSecretName: "c", + } + type args struct { r *ConfigurationReconciler namespace string configuration *v1beta1.Configuration - k8sClient client.Client meta *TFConfigurationMeta } type want struct { @@ -500,7 +700,6 @@ func TestTerraformDestroy(t *testing.T) { name: "provider is not ready", args: args{ r: r1, - k8sClient: k8sClient1, configuration: &v1beta1.Configuration{}, meta: &TFConfigurationMeta{ ConfigurationCMName: "tf-abc", @@ -515,7 +714,6 @@ func TestTerraformDestroy(t *testing.T) { name: "provider is ready", args: args{ r: r2, - k8sClient: k8sClient2, configuration: configuration, meta: &TFConfigurationMeta{ ConfigurationCMName: "tf-abc", @@ -527,6 +725,15 @@ func TestTerraformDestroy(t *testing.T) { errMsg: "The referenced provider could not be retrieved", }, }, + { + name: "could not directly remove resources, and destroy job completes", + args: args{ + r: r4, + configuration: configuration4, + meta: meta4, + }, + want: want{}, + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -556,3 +763,76 @@ func TestAssembleTerraformJob(t *testing.T) { assert.Equal(t, containers[0].Image, "c") assert.Equal(t, containers[1].Image, "d") } + +func TestGetTFOutputs(t *testing.T) { + type args struct { + ctx context.Context + k8sClient client.Client + configuration v1beta1.Configuration + meta *TFConfigurationMeta + } + type want struct { + property map[string]v1beta1.Property + errMsg string + } + + ctx := context.Background() + k8sClient1 := fake.NewClientBuilder().Build() + meta1 := &TFConfigurationMeta{} + + //scheme := runtime.NewScheme() + //v1beta1.AddToScheme(scheme) + secret2 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "a", + Namespace: "default", + }, + Type: corev1.SecretTypeOpaque, + } + k8sClient2 := fake.NewClientBuilder().WithObjects(secret2).Build() + meta2 := &TFConfigurationMeta{ + BackendSecretName: "a", + TerraformBackendNamespace: "default", + } + + testcases := map[string]struct { + args args + want want + }{ + "could not find backend secret": { + args: args{ + ctx: ctx, + k8sClient: k8sClient1, + meta: meta1, + }, + want: want{ + property: nil, + errMsg: "terraform state file backend secret is not generated", + }, + }, + "no data in a backend secret": { + args: args{ + ctx: ctx, + k8sClient: k8sClient2, + meta: meta2, + }, + want: want{ + property: nil, + errMsg: "failed to get tfstate from Terraform State secret", + }, + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + property, err := tc.args.meta.getTFOutputs(tc.args.ctx, tc.args.k8sClient, tc.args.configuration) + if tc.want.errMsg != "" { + if !strings.Contains(err.Error(), tc.want.errMsg) { + t.Errorf("getTFOutputs() error = %v, wantErr %v", err, tc.want.errMsg) + } + } + assert.Equal(t, tc.want.property, property) + }) + } + +} From 0abf57b90d5cc07bad2dc2a1d3f3e9c6e9591320 Mon Sep 17 00:00:00 2001 From: Zheng Xi Zhou Date: Sun, 6 Mar 2022 00:08:02 +0800 Subject: [PATCH 2/2] add ut for provider controller Signed-off-by: Zheng Xi Zhou --- controllers/provider_controller_test.go | 242 +++++++++++++++++++++++- 1 file changed, 237 insertions(+), 5 deletions(-) diff --git a/controllers/provider_controller_test.go b/controllers/provider_controller_test.go index ea58d88f..e951d7f7 100644 --- a/controllers/provider_controller_test.go +++ b/controllers/provider_controller_test.go @@ -2,10 +2,15 @@ package controllers import ( "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "strings" + "testing" + + . "github.com/agiledragon/gomonkey/v2" "github.com/go-yaml/yaml" - crossplanetypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime" - "github.com/oam-dev/terraform-controller/api/v1beta1" - "github.com/oam-dev/terraform-controller/controllers/provider" + "github.com/pkg/errors" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -13,8 +18,10 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "strings" - "testing" + + crossplanetypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime" + "github.com/oam-dev/terraform-controller/api/v1beta1" + "github.com/oam-dev/terraform-controller/controllers/provider" ) func TestReconcile(t *testing.T) { @@ -143,3 +150,228 @@ func TestReconcile(t *testing.T) { }) } } + +func TestReconcileProviderIsReadyButFailedToUpdateStatus(t *testing.T) { + ctx := context.Background() + s := runtime.NewScheme() + v1beta1.AddToScheme(s) + v1.AddToScheme(s) + + r2 := &ProviderReconciler{} + provider2 := &v1beta1.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "aws", + Namespace: "default", + }, + Spec: v1beta1.ProviderSpec{ + Credentials: v1beta1.ProviderCredentials{ + Source: "Secret", + SecretRef: &crossplanetypes.SecretKeySelector{ + SecretReference: crossplanetypes.SecretReference{ + Name: "abc", + Namespace: "default", + }, + Key: "credentials", + }, + }, + Provider: "aws", + }, + } + + creds, _ := yaml.Marshal(&provider.AWSCredentials{ + AWSAccessKeyID: "a", + AWSSecretAccessKey: "b", + AWSSessionToken: "c", + }) + secret2 := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: "default", + }, + Data: map[string][]byte{ + "credentials": creds, + }, + Type: v1.SecretTypeOpaque, + } + + r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).Build() + + patches := ApplyFunc(apiutil.GVKForObject, func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) { + switch obj.(type) { + case *v1beta1.Provider: + p := obj.(*v1beta1.Provider) + if p.Status.State != "" { + return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx") + } + } + return apiutilGVKForObject(obj, scheme) + }) + defer patches.Reset() + + type args struct { + req reconcile.Request + r *ProviderReconciler + } + + type want struct { + errMsg string + } + + req := ctrl.Request{} + req.NamespacedName = types.NamespacedName{ + Name: "aws", + Namespace: "default", + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "Provider is found", + args: args{ + req: req, + r: r2, + }, + want: want{ + errMsg: "failed to set status", + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if _, err := tc.args.r.Reconcile(ctx, tc.args.req); (tc.want.errMsg != "") && + !strings.Contains(err.Error(), tc.want.errMsg) { + t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg) + } + }) + } +} + +func TestReconcile3(t *testing.T) { + ctx := context.Background() + s := runtime.NewScheme() + v1beta1.AddToScheme(s) + v1.AddToScheme(s) + + r3 := &ProviderReconciler{} + provider3 := &v1beta1.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "aws", + Namespace: "default", + }, + Spec: v1beta1.ProviderSpec{ + Credentials: v1beta1.ProviderCredentials{ + Source: "Secret", + SecretRef: &crossplanetypes.SecretKeySelector{ + SecretReference: crossplanetypes.SecretReference{ + Name: "abc", + Namespace: "default", + }, + Key: "credentials", + }, + }, + Provider: errSettingStatus, + }, + } + + r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build() + + patches := ApplyFunc(apiutil.GVKForObject, func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) { + switch obj.(type) { + case *v1beta1.Provider: + p := obj.(*v1beta1.Provider) + if p.Status.State != "" { + return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx") + } + } + return apiutilGVKForObject(obj, scheme) + }) + defer patches.Reset() + + type args struct { + req reconcile.Request + r *ProviderReconciler + } + + type want struct { + errMsg string + } + + req := ctrl.Request{} + req.NamespacedName = types.NamespacedName{ + Name: "aws", + Namespace: "default", + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "Provider is found, but the secret is not available", + args: args{ + req: req, + r: r3, + }, + want: want{ + errMsg: errSettingStatus, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if _, err := tc.args.r.Reconcile(ctx, tc.args.req); (tc.want.errMsg != "") && + !strings.Contains(err.Error(), tc.want.errMsg) { + t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg) + } + }) + } +} + +func apiutilGVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) { + switch obj.(type) { + case *v1beta1.Provider: + p := obj.(*v1beta1.Provider) + if p.Status.State != "" { + return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx") + } + } + // a copy implementation of `apiutil.GVKForObject` + _, isPartial := obj.(*metav1.PartialObjectMetadata) //nolint:ifshort + _, isPartialList := obj.(*metav1.PartialObjectMetadataList) + if isPartial || isPartialList { + // we require that the GVK be populated in order to recognize the object + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return schema.GroupVersionKind{}, runtime.NewMissingKindErr("unstructured object has no kind") + } + if len(gvk.Version) == 0 { + return schema.GroupVersionKind{}, runtime.NewMissingVersionErr("unstructured object has no version") + } + return gvk, nil + } + + gvks, isUnversioned, err := scheme.ObjectKinds(obj) + if err != nil { + return schema.GroupVersionKind{}, err + } + if isUnversioned { + return schema.GroupVersionKind{}, fmt.Errorf("cannot create group-version-kind for unversioned type %T", obj) + } + + if len(gvks) < 1 { + return schema.GroupVersionKind{}, fmt.Errorf("no group-version-kinds associated with type %T", obj) + } + if len(gvks) > 1 { + // this should only trigger for things like metav1.XYZ -- + // normal versioned types should be fine + return schema.GroupVersionKind{}, fmt.Errorf( + "multiple group-version-kinds associated with type %T, refusing to guess at one", obj) + } + return gvks[0], nil +}