From 63f42187ca65209aeb5d335051fc8ac95f3d47d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergen=20Yal=C3=A7=C4=B1n?= Date: Thu, 27 Jan 2022 17:27:12 +0300 Subject: [PATCH] Implement a default tag configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergen Yalçın --- pkg/config/resource.go | 63 ++++++++++++- pkg/config/resource_test.go | 110 ++++++++++++++++++++++ pkg/pipeline/controller.go | 2 +- pkg/pipeline/templates/controller.go.tmpl | 17 ++-- 4 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 pkg/config/resource_test.go diff --git a/pkg/config/resource.go b/pkg/config/resource.go index 528d7f78..b64a6d7f 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -18,13 +18,18 @@ package config import ( "context" + "fmt" + "k8s.io/utils/pointer" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/json" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" ) // SetIdentifierArgumentsFn sets the name of the resource in Terraform attributes map, @@ -198,8 +203,60 @@ type OperationTimeouts struct { Delete time.Duration } -// InitializerFn returns the Initializer with a client. -type InitializerFn func(client client.Client) managed.Initializer +// NewInitializerFn returns the Initializer with a client. +type NewInitializerFn func(client client.Client) managed.Initializer + +// TagInitializer returns a tagger to use default tag initializer. +var TagInitializer NewInitializerFn = func(client client.Client) managed.Initializer { + return NewTagger(client, "tags") +} + +// Tagger implements the Initialize function to set external tags +type Tagger struct { + kube client.Client + fieldName string +} + +// NewTagger returns a Tagger object. +func NewTagger(kube client.Client, fieldName string) *Tagger { + return &Tagger{kube: kube, fieldName: fieldName} +} + +// Initialize is a custom initializer for setting external tags +func (t *Tagger) Initialize(ctx context.Context, mg xpresource.Managed) error { + paved, err := fieldpath.PaveObject(mg) + if err != nil { + return err + } + pavedByte, err := setExternalTagsWithPaved(xpresource.GetExternalTags(mg), paved, t.fieldName) + if err != nil { + return err + } + if err := json.Unmarshal(pavedByte, mg); err != nil { + return err + } + if err := t.kube.Update(ctx, mg); err != nil { + return err + } + return nil +} + +func setExternalTagsWithPaved(externalTags map[string]string, paved *fieldpath.Paved, fieldName string) ([]byte, error) { + tags := map[string]*string{ + xpresource.ExternalResourceTagKeyKind: pointer.String(externalTags[xpresource.ExternalResourceTagKeyKind]), + xpresource.ExternalResourceTagKeyName: pointer.String(externalTags[xpresource.ExternalResourceTagKeyName]), + xpresource.ExternalResourceTagKeyProvider: pointer.String(externalTags[xpresource.ExternalResourceTagKeyProvider]), + } + + if err := paved.SetValue(fmt.Sprintf("spec.forProvider.%s", fieldName), tags); err != nil { + return nil, err + } + pavedByte, err := paved.MarshalJSON() + if err != nil { + return nil, err + } + return pavedByte, nil +} // Resource is the set of information that you can override at different steps // of the code generation pipeline. @@ -229,7 +286,7 @@ type Resource struct { // databases. UseAsync bool - Initializers []InitializerFn + InitializerFns []NewInitializerFn // OperationTimeouts allows configuring resource operation timeouts. OperationTimeouts OperationTimeouts diff --git a/pkg/config/resource_test.go b/pkg/config/resource_test.go new file mode 100644 index 00000000..889a03d3 --- /dev/null +++ b/pkg/config/resource_test.go @@ -0,0 +1,110 @@ +package config + +import ( + "context" + "fmt" + "github.com/crossplane/crossplane-runtime/pkg/errors" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + xpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/resource/fake" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/google/go-cmp/cmp" + + "sigs.k8s.io/controller-runtime/pkg/client" + "testing" +) + +const ( + kind = "ACoolService" + name = "example-service" + provider = "ACoolProvider" +) + +func TestTagger_Initialize(t *testing.T) { + errBoom := errors.New("boom") + + type args struct { + mg xpresource.Managed + kube client.Client + } + type want struct { + err error + } + cases := map[string]struct { + args + want + }{ + "Successful": { + args: args{ + mg: &fake.Managed{}, + kube: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil)}, + }, + want: want{ + err: nil, + }, + }, + "Failure": { + args: args{ + mg: &fake.Managed{}, + kube: &test.MockClient{MockUpdate: test.NewMockUpdateFn(errBoom)}, + }, + want: want{ + err: errBoom, + }, + }, + } + for n, tc := range cases { + t.Run(n, func(t *testing.T) { + tagger := NewTagger(tc.kube, "tags") + gotErr := tagger.Initialize(context.TODO(), tc.mg) + if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { + t.Fatalf("generateTypeName(...): -want error, +got error: %s", diff) + } + }) + } +} + +func TestSetExternalTagsWithPaved(t *testing.T) { + type args struct { + externalTags map[string]string + paved *fieldpath.Paved + fieldName string + } + type want struct { + pavedString string + err error + } + cases := map[string]struct { + args + want + }{ + "Successful": { + args: args{ + externalTags: map[string]string{ + xpresource.ExternalResourceTagKeyKind: kind, + xpresource.ExternalResourceTagKeyName: name, + xpresource.ExternalResourceTagKeyProvider: provider, + }, + paved: fieldpath.Pave(map[string]interface{}{}), + fieldName: "tags", + }, + want: want{ + pavedString: fmt.Sprintf(`{"spec":{"forProvider":{"tags":{"%s":"%s","%s":"%s","%s":"%s"}}}}`, + xpresource.ExternalResourceTagKeyKind, kind, + xpresource.ExternalResourceTagKeyName, name, + xpresource.ExternalResourceTagKeyProvider, provider), + }, + }, + } + for n, tc := range cases { + t.Run(n, func(t *testing.T) { + gotByte, gotErr := setExternalTagsWithPaved(tc.externalTags, tc.paved, tc.fieldName) + if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { + t.Fatalf("generateTypeName(...): -want error, +got error: %s", diff) + } + if diff := cmp.Diff(tc.want.pavedString, string(gotByte), test.EquateErrors()); diff != "" { + t.Fatalf("generateTypeName(...): -want gotByte, +got gotByte: %s", diff) + } + }) + } +} diff --git a/pkg/pipeline/controller.go b/pkg/pipeline/controller.go index 26338bb4..0fada37c 100644 --- a/pkg/pipeline/controller.go +++ b/pkg/pipeline/controller.go @@ -63,7 +63,7 @@ func (cg *ControllerGenerator) Generate(cfg *config.Resource, typesPkgPath strin "TypePackageAlias": ctrlFile.Imports.UsePackage(typesPkgPath), "UseAsync": cfg.UseAsync, "ResourceType": cfg.Name, - "Initializers": cfg.Initializers, + "Initializers": cfg.InitializerFns, } filePath := filepath.Join(cg.ControllerGroupDir, strings.ToLower(cfg.Kind), "zz_controller.go") diff --git a/pkg/pipeline/templates/controller.go.tmpl b/pkg/pipeline/templates/controller.go.tmpl index 5ae44ac1..76fec306 100644 --- a/pkg/pipeline/templates/controller.go.tmpl +++ b/pkg/pipeline/templates/controller.go.tmpl @@ -26,12 +26,15 @@ import ( // Setup adds a controller that reconciles {{ .CRD.Kind }} managed resources. func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, s terraform.SetupFn, ws *terraform.WorkspaceStore, cfg *tjconfig.Provider, concurrency int) error { name := managed.ControllerName({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind.String()) - {{- if .Initializers }} var initializers managed.InitializerChain - for _, i := range cfg.Resources["{{ .ResourceType }}"].Initializers { + {{- if .Initializers }} + for _, i := range cfg.Resources["{{ .ResourceType }}"].InitializerFns { initializers = append(initializers,i(mgr.GetClient())) - } - {{- end}} + } + {{- end}} + {{- if not .DisableNameInitializer }} + initializers = append(initializers, managed.NewNameAsExternalName(mgr.GetClient())) + {{- end}} r := managed.NewReconciler(mgr, xpresource.ManagedKind({{ .TypePackageAlias }}{{ .CRD.Kind }}_GroupVersionKind), managed.WithExternalConnecter(tjcontroller.NewConnector(mgr.GetClient(), ws, s, cfg.Resources["{{ .ResourceType }}"], @@ -43,13 +46,7 @@ func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, s terra managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), managed.WithFinalizer(terraform.NewWorkspaceFinalizer(ws, xpresource.NewAPIFinalizer(mgr.GetClient(), managed.FinalizerName))), managed.WithTimeout(3*time.Minute), - {{- if .Initializers }} managed.WithInitializers(initializers), - {{else}} - {{- if .DisableNameInitializer }} - managed.WithInitializers(), - {{- end}} - {{- end}} ) return ctrl.NewControllerManagedBy(mgr).