Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Configure custom keys with function instead of string
Browse files Browse the repository at this point in the history
Signed-off-by: Hasan Turken <turkenh@gmail.com>
  • Loading branch information
turkenh committed Oct 20, 2021
1 parent 67e1ebb commit 8bd021f
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 90 deletions.
2 changes: 1 addition & 1 deletion pkg/config/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type Sensitive struct {

// CustomKeysFunctionPath is the path for function adding custom connection
// details keys
CustomKeysFunctionPath string
CustomKeysFn CustomConnectionKeysFn

fieldPaths map[string]string
}
Expand Down
44 changes: 20 additions & 24 deletions pkg/controller/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/crossplane-contrib/terrajet/pkg/config"
"github.com/crossplane-contrib/terrajet/pkg/resource"
"github.com/crossplane-contrib/terrajet/pkg/resource/json"
"github.com/crossplane-contrib/terrajet/pkg/terraform"
Expand All @@ -47,20 +48,13 @@ const (
// Option allows you to configure Connector.
type Option func(*Connector)

// UseAsync configures the controller to use async variant of the functions
// of the Terraform client.
func UseAsync() Option {
return func(c *Connector) {
c.async = true
}
}

// NewConnector returns a new Connector object.
func NewConnector(kube client.Client, ws Store, sf terraform.SetupFn, opts ...Option) *Connector {
func NewConnector(kube client.Client, ws Store, sf terraform.SetupFn, cfg config.Resource, opts ...Option) *Connector {
c := &Connector{
kube: kube,
getTerraformSetup: sf,
store: ws,
config: cfg,
}
for _, f := range opts {
f(c)
Expand All @@ -74,7 +68,7 @@ type Connector struct {
kube client.Client
store Store
getTerraformSetup terraform.SetupFn
async bool
config config.Resource
}

// Connect makes sure the underlying client is ready to issue requests to the
Expand All @@ -90,23 +84,22 @@ func (c *Connector) Connect(ctx context.Context, mg xpresource.Managed) (managed
return nil, errors.Wrap(err, errGetTerraformSetup)
}

tf, err := c.store.Workspace(ctx, &APISecretClient{kube: c.kube}, tr, ts)
tf, err := c.store.Workspace(ctx, &APISecretClient{kube: c.kube}, tr, ts, c.config)
if err != nil {
return nil, errors.Wrap(err, errGetWorkspace)
}

return &external{
kube: c.kube,
workspace: tf,
async: c.async,
config: c.config,
}, nil
}

type external struct {
kube client.Client
workspace Workspace

async bool
config config.Resource
}

func (e *external) Observe(ctx context.Context, mg xpresource.Managed) (managed.ExternalObservation, error) { // nolint:gocyclo
Expand All @@ -123,7 +116,7 @@ func (e *external) Observe(ctx context.Context, mg xpresource.Managed) (managed.
if err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, errRefresh)
}
if e.async {
if e.config.UseAsync {
tr.SetConditions(resource.LastOperationCondition(res.LastOperationError))
if err := e.kube.Status().Update(ctx, tr); err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, errStatusUpdate)
Expand Down Expand Up @@ -160,7 +153,7 @@ func (e *external) Observe(ctx context.Context, mg xpresource.Managed) (managed.
return managed.ExternalObservation{}, errors.Wrap(err, "cannot late initialize parameters")
}

conn, err := getConnectionDetails(attr, tr)
conn, err := getConnectionDetails(attr, tr, e.config)
if err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, "cannot get connection details")
}
Expand Down Expand Up @@ -188,7 +181,7 @@ func (e *external) Create(ctx context.Context, mg xpresource.Managed) (managed.E
if !ok {
return managed.ExternalCreation{}, errors.New(errUnexpectedObject)
}
if e.async {
if e.config.UseAsync {
return managed.ExternalCreation{}, errors.Wrap(e.workspace.ApplyAsync(CriticalAnnotationsCallback(e.kube, tr)), errStartAsyncApply)
}
res, err := e.workspace.Apply(ctx)
Expand All @@ -200,7 +193,7 @@ func (e *external) Create(ctx context.Context, mg xpresource.Managed) (managed.E
return managed.ExternalCreation{}, errors.Wrap(err, "cannot unmarshal state attributes")
}

conn, err := getConnectionDetails(attr, tr)
conn, err := getConnectionDetails(attr, tr, e.config)
if err != nil {
return managed.ExternalCreation{}, errors.Wrap(err, "cannot get connection details")
}
Expand All @@ -211,7 +204,7 @@ func (e *external) Create(ctx context.Context, mg xpresource.Managed) (managed.E
}

func (e *external) Update(ctx context.Context, mg xpresource.Managed) (managed.ExternalUpdate, error) {
if e.async {
if e.config.UseAsync {
return managed.ExternalUpdate{}, errors.Wrap(e.workspace.ApplyAsync(terraform.NopCallbackFn), errStartAsyncApply)
}
tr, ok := mg.(resource.Terraformed)
Expand All @@ -230,7 +223,7 @@ func (e *external) Update(ctx context.Context, mg xpresource.Managed) (managed.E
}

func (e *external) Delete(ctx context.Context, _ xpresource.Managed) error {
if e.async {
if e.config.UseAsync {
return errors.Wrap(e.workspace.DestroyAsync(), errStartAsyncDestroy)
}
return errors.Wrap(e.workspace.Destroy(ctx), errDestroy)
Expand Down Expand Up @@ -283,15 +276,18 @@ func CriticalAnnotationsCallback(kube client.Client, tr resource.Terraformed) te
}
}

func getConnectionDetails(attr map[string]interface{}, tr resource.Terraformed) (managed.ConnectionDetails, error) {
func getConnectionDetails(attr map[string]interface{}, tr resource.Terraformed, cfg config.Resource) (managed.ConnectionDetails, error) {
conn, err := resource.GetSensitiveAttributes(attr, tr.GetConnectionDetailsMapping(), tr.GetTerraformResourceIDField())
if err != nil {
return nil, errors.Wrap(err, "cannot get connection details")
}

custom, err := tr.GetAdditionalConnectionDetails(attr)
if err != nil {
return nil, errors.Wrap(err, "cannot get custom connection keys")
custom := map[string][]byte{}
// TODO(turkenh): Once we have automatic defaulting, remove this if check.
if cfg.Sensitive.CustomKeysFn != nil {
if custom, err = cfg.Sensitive.CustomKeysFn(attr); err != nil {
return nil, errors.Wrap(err, "cannot get custom connection keys")
}
}

if conn == nil {
Expand Down
66 changes: 34 additions & 32 deletions pkg/controller/external_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/crossplane-contrib/terrajet/pkg/config"
"github.com/crossplane-contrib/terrajet/pkg/resource"
"github.com/crossplane-contrib/terrajet/pkg/resource/fake"
"github.com/crossplane-contrib/terrajet/pkg/resource/json"
Expand Down Expand Up @@ -83,18 +84,19 @@ func (c WorkspaceFns) Plan(ctx context.Context) (terraform.PlanResult, error) {
}

type StoreFns struct {
WorkspaceFn func(ctx context.Context, c resource.SecretClient, tr resource.Terraformed, ts terraform.Setup) (*terraform.Workspace, error)
WorkspaceFn func(ctx context.Context, c resource.SecretClient, tr resource.Terraformed, ts terraform.Setup, cfg config.Resource) (*terraform.Workspace, error)
}

func (s StoreFns) Workspace(ctx context.Context, c resource.SecretClient, tr resource.Terraformed, ts terraform.Setup) (*terraform.Workspace, error) {
return s.WorkspaceFn(ctx, c, tr, ts)
func (s StoreFns) Workspace(ctx context.Context, c resource.SecretClient, tr resource.Terraformed, ts terraform.Setup, cfg config.Resource) (*terraform.Workspace, error) {
return s.WorkspaceFn(ctx, c, tr, ts, cfg)
}

func TestConnect(t *testing.T) {
type args struct {
setupFn terraform.SetupFn
store Store
obj xpresource.Managed
cfg config.Resource
}
type want struct {
err error
Expand Down Expand Up @@ -132,7 +134,7 @@ func TestConnect(t *testing.T) {
return terraform.Setup{}, nil
},
store: StoreFns{
WorkspaceFn: func(_ context.Context, _ resource.SecretClient, _ resource.Terraformed, _ terraform.Setup) (*terraform.Workspace, error) {
WorkspaceFn: func(_ context.Context, _ resource.SecretClient, _ resource.Terraformed, _ terraform.Setup, _ config.Resource) (*terraform.Workspace, error) {
return nil, errBoom
},
},
Expand All @@ -148,7 +150,7 @@ func TestConnect(t *testing.T) {
return terraform.Setup{}, nil
},
store: StoreFns{
WorkspaceFn: func(_ context.Context, _ resource.SecretClient, _ resource.Terraformed, _ terraform.Setup) (*terraform.Workspace, error) {
WorkspaceFn: func(_ context.Context, _ resource.SecretClient, _ resource.Terraformed, _ terraform.Setup, _ config.Resource) (*terraform.Workspace, error) {
return nil, nil
},
},
Expand All @@ -157,7 +159,7 @@ func TestConnect(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
c := NewConnector(nil, tc.args.store, tc.args.setupFn)
c := NewConnector(nil, tc.args.store, tc.args.setupFn, tc.args.cfg)
_, err := c.Connect(context.TODO(), tc.args.obj)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nConnect(...): -want error, +got error:\n%s", tc.reason, diff)
Expand All @@ -168,10 +170,10 @@ func TestConnect(t *testing.T) {

func TestObserve(t *testing.T) {
type args struct {
w Workspace
kube client.Client
async bool
obj xpresource.Managed
w Workspace
kube client.Client
cfg config.Resource
obj xpresource.Managed
}
type want struct {
obs managed.ExternalObservation
Expand Down Expand Up @@ -237,7 +239,7 @@ func TestObserve(t *testing.T) {
"LastOperationFailed": {
reason: "It should report the last operation error without failing",
args: args{
async: true,
cfg: config.Resource{UseAsync: true},
obj: &fake.Terraformed{
MetadataProvider: fake.MetadataProvider{
IDField: "id",
Expand Down Expand Up @@ -269,8 +271,8 @@ func TestObserve(t *testing.T) {
"StatusUpdateFailed": {
reason: "It should fail if status cannot be updated",
args: args{
async: true,
obj: &fake.Terraformed{},
cfg: config.Resource{UseAsync: true},
obj: &fake.Terraformed{},
kube: &test.MockClient{
MockStatusUpdate: test.NewMockStatusUpdateFn(errBoom),
},
Expand Down Expand Up @@ -340,7 +342,7 @@ func TestObserve(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
e := &external{workspace: tc.w, kube: tc.kube, async: tc.async}
e := &external{workspace: tc.w, kube: tc.kube, config: tc.cfg}
_, err := e.Observe(context.TODO(), tc.args.obj)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nObserve(...): -want error, +got error:\n%s", tc.reason, diff)
Expand All @@ -351,9 +353,9 @@ func TestObserve(t *testing.T) {

func TestCreate(t *testing.T) {
type args struct {
w Workspace
async bool
obj xpresource.Managed
w Workspace
cfg config.Resource
obj xpresource.Managed
}
type want struct {
err error
Expand All @@ -374,8 +376,8 @@ func TestCreate(t *testing.T) {
"AsyncFailed": {
reason: "It should return error if it cannot trigger the async apply",
args: args{
async: true,
obj: &fake.Terraformed{},
cfg: config.Resource{UseAsync: true},
obj: &fake.Terraformed{},
w: WorkspaceFns{
ApplyAsyncFn: func(_ terraform.CallbackFn) error {
return errBoom
Expand Down Expand Up @@ -403,7 +405,7 @@ func TestCreate(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
e := &external{workspace: tc.w, async: tc.async}
e := &external{workspace: tc.w, config: tc.cfg}
_, err := e.Create(context.TODO(), tc.args.obj)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nCreate(...): -want error, +got error:\n%s", tc.reason, diff)
Expand All @@ -414,9 +416,9 @@ func TestCreate(t *testing.T) {

func TestUpdate(t *testing.T) {
type args struct {
w Workspace
async bool
obj xpresource.Managed
w Workspace
cfg config.Resource
obj xpresource.Managed
}
type want struct {
err error
Expand All @@ -437,8 +439,8 @@ func TestUpdate(t *testing.T) {
"AsyncFailed": {
reason: "It should return error if it cannot trigger the async apply",
args: args{
async: true,
obj: &fake.Terraformed{},
cfg: config.Resource{UseAsync: true},
obj: &fake.Terraformed{},
w: WorkspaceFns{
ApplyAsyncFn: func(_ terraform.CallbackFn) error {
return errBoom
Expand Down Expand Up @@ -466,7 +468,7 @@ func TestUpdate(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
e := &external{workspace: tc.w, async: tc.async}
e := &external{workspace: tc.w, config: tc.cfg}
_, err := e.Update(context.TODO(), tc.args.obj)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nCreate(...): -want error, +got error:\n%s", tc.reason, diff)
Expand All @@ -477,9 +479,9 @@ func TestUpdate(t *testing.T) {

func TestDelete(t *testing.T) {
type args struct {
w Workspace
async bool
obj xpresource.Managed
w Workspace
cfg config.Resource
obj xpresource.Managed
}
type want struct {
err error
Expand All @@ -492,8 +494,8 @@ func TestDelete(t *testing.T) {
"AsyncFailed": {
reason: "It should return error if it cannot trigger the async destroy",
args: args{
async: true,
obj: &fake.Terraformed{},
cfg: config.Resource{UseAsync: true},
obj: &fake.Terraformed{},
w: WorkspaceFns{
DestroyAsyncFn: func() error {
return errBoom
Expand Down Expand Up @@ -521,7 +523,7 @@ func TestDelete(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
e := &external{workspace: tc.w, async: tc.async}
e := &external{workspace: tc.w, config: tc.cfg}
err := e.Delete(context.TODO(), tc.args.obj)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nCreate(...): -want error, +got error:\n%s", tc.reason, diff)
Expand Down
3 changes: 2 additions & 1 deletion pkg/controller/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controller
import (
"context"

"github.com/crossplane-contrib/terrajet/pkg/config"
"github.com/crossplane-contrib/terrajet/pkg/resource"
"github.com/crossplane-contrib/terrajet/pkg/terraform"
)
Expand All @@ -39,5 +40,5 @@ type Workspace interface {

// Store is where we can get access to the Terraform workspace of given resource.
type Store interface {
Workspace(ctx context.Context, c resource.SecretClient, tr resource.Terraformed, ts terraform.Setup) (*terraform.Workspace, error)
Workspace(ctx context.Context, c resource.SecretClient, tr resource.Terraformed, ts terraform.Setup, cfg config.Resource) (*terraform.Workspace, error)
}
2 changes: 1 addition & 1 deletion pkg/pipeline/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (cg *ControllerGenerator) Generate(c *config.Resource, typesPkgPath string)
},
"DisableNameInitializer": c.ExternalName.DisableNameInitializer,
"TypePackageAlias": ctrlFile.Imports.UsePackage(typesPkgPath),
"UseAsync": c.UseAsync,
"ResourceType": c.TerraformResourceType,
}

filePath := filepath.Join(cg.ControllerGroupDir, strings.ToLower(c.Kind), "zz_controller.go")
Expand Down
6 changes: 1 addition & 5 deletions pkg/pipeline/templates/controller.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, s terra
name := managed.ControllerName({{ .TypePackageAlias }}{{ .CRD.Kind }}GroupVersionKind.String())
r := managed.NewReconciler(mgr,
xpresource.ManagedKind({{ .TypePackageAlias }}{{ .CRD.Kind }}GroupVersionKind),
managed.WithExternalConnecter(tjcontroller.NewConnector(mgr.GetClient(), ws, s,
{{- if .UseAsync }}
tjcontroller.UseAsync(),
{{- end}}
)),
managed.WithExternalConnecter(tjcontroller.NewConnector(mgr.GetClient(), ws, s, config.Store.GetForResource("{{ .ResourceType }}"))),
managed.WithLogger(l.WithValues("controller", name)),
managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))),
managed.WithFinalizer(terraform.NewWorkspaceFinalizer(ws, xpresource.NewAPIFinalizer(mgr.GetClient(), managed.FinalizerName))),
Expand Down
Loading

0 comments on commit 8bd021f

Please sign in to comment.