Skip to content

Commit

Permalink
Add config.NewTFSingletonConversion that returns a new TerraformConve…
Browse files Browse the repository at this point in the history
…rsion to convert between

singleton lists and embedded objects when parameters pass Crossplane and Terraform boundaries.

Signed-off-by: Alper Rifat Ulucinar <[email protected]>
  • Loading branch information
ulucinar committed May 9, 2024
1 parent b5dbcc5 commit c606b4c
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 45 deletions.
2 changes: 1 addition & 1 deletion pkg/config/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ type Resource struct {
// TerraformConversions is the list of conversions to be invoked when passing
// data from the Crossplane layer to the Terraform layer and when reading
// data (state) from the Terraform layer to be used in the Crossplane layer.
TerraformConversions []conversion.TerraformConversion
TerraformConversions []TerraformConversion

// useTerraformPluginSDKClient indicates that a plugin SDK external client should
// be generated instead of the Terraform CLI-forking client.
Expand Down
32 changes: 0 additions & 32 deletions pkg/config/runtime_conversion.go

This file was deleted.

72 changes: 72 additions & 0 deletions pkg/config/tf_conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2024 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

package config

import (
"github.com/pkg/errors"

"github.com/crossplane/upjet/pkg/config/conversion"
)

// Mode denotes the mode of the runtime Terraform conversion, e.g.,
// conversion from Crossplane parameters to Terraform arguments, or
// conversion from Terraform state to Crossplane state.
type Mode int

const (
ToTerraform Mode = iota
FromTerraform
)

// String returns a string representation of the conversion mode.
func (m Mode) String() string {
switch m {
case ToTerraform:
return "toTerraform"
case FromTerraform:
return "fromTerraform"
default:
return "unknown"
}
}

type TerraformConversion interface {
Convert(params map[string]any, r *Resource, mode Mode) (map[string]any, error)
}

// ApplyTFConversions applies the configured Terraform conversions on the
// specified params map in the given mode, i.e., from Crossplane layer to the
// Terraform layer or vice versa.
func (r *Resource) ApplyTFConversions(params map[string]any, mode Mode) (map[string]any, error) {
var err error
for _, c := range r.TerraformConversions {
params, err = c.Convert(params, r, mode)
if err != nil {
return nil, err
}
}
return params, nil
}

type singletonListConversion struct{}

// NewTFSingletonConversion initializes a new TerraformConversion to convert
// between singleton lists and embedded objects in the exchanged data
// at runtime between the Crossplane & Terraform layers.
func NewTFSingletonConversion() TerraformConversion {
return singletonListConversion{}
}

func (s singletonListConversion) Convert(params map[string]any, r *Resource, mode Mode) (map[string]any, error) {
var err error
var m map[string]any
switch mode {
case FromTerraform:
m, err = conversion.Convert(params, r.TFListConversionPaths(), conversion.ToEmbeddedObject)
case ToTerraform:
m, err = conversion.Convert(params, r.TFListConversionPaths(), conversion.ToSingletonList)
}
return m, errors.Wrapf(err, "failed to convert between Crossplane and Terraform layers in mode %q", mode)
}
23 changes: 11 additions & 12 deletions pkg/controller/external_tfpluginsdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/crossplane/upjet/pkg/config"
"github.com/crossplane/upjet/pkg/config/conversion"
"github.com/crossplane/upjet/pkg/metrics"
"github.com/crossplane/upjet/pkg/resource"
"github.com/crossplane/upjet/pkg/resource/json"
Expand Down Expand Up @@ -123,26 +122,26 @@ type terraformPluginSDKExternal struct {
opTracker *AsyncTracker
}

func getExtendedParameters(ctx context.Context, tr resource.Terraformed, externalName string, config *config.Resource, ts terraform.Setup, initParamsMerged bool, kube client.Client) (map[string]any, error) {
func getExtendedParameters(ctx context.Context, tr resource.Terraformed, externalName string, cfg *config.Resource, ts terraform.Setup, initParamsMerged bool, kube client.Client) (map[string]any, error) {
params, err := tr.GetMergedParameters(initParamsMerged)
if err != nil {
return nil, errors.Wrap(err, "cannot get merged parameters")
}
if err = resource.GetSensitiveParameters(ctx, &APISecretClient{kube: kube}, tr, params, tr.GetConnectionDetailsMapping()); err != nil {
return nil, errors.Wrap(err, "cannot store sensitive parameters into params")
}
config.ExternalName.SetIdentifierArgumentFn(params, externalName)
if config.TerraformConfigurationInjector != nil {
cfg.ExternalName.SetIdentifierArgumentFn(params, externalName)
if cfg.TerraformConfigurationInjector != nil {
m, err := getJSONMap(tr)
if err != nil {
return nil, errors.Wrap(err, "cannot get JSON map for the managed resource's spec.forProvider value")
}
if err := config.TerraformConfigurationInjector(m, params); err != nil {
if err := cfg.TerraformConfigurationInjector(m, params); err != nil {
return nil, errors.Wrap(err, "cannot invoke the configured TerraformConfigurationInjector")
}
}

tfID, err := config.ExternalName.GetIDFn(ctx, externalName, params, ts.Map())
tfID, err := cfg.ExternalName.GetIDFn(ctx, externalName, params, ts.Map())
if err != nil {
return nil, errors.Wrap(err, "cannot get ID")
}
Expand All @@ -151,12 +150,12 @@ func getExtendedParameters(ctx context.Context, tr resource.Terraformed, externa
// not all providers may have this attribute
// TODO: tags-tags_all implementation is AWS specific.
// Consider making this logic independent of provider.
if config.TerraformResource != nil {
if _, ok := config.TerraformResource.CoreConfigSchema().Attributes["tags_all"]; ok {
if cfg.TerraformResource != nil {
if _, ok := cfg.TerraformResource.CoreConfigSchema().Attributes["tags_all"]; ok {
params["tags_all"] = params["tags"]
}
}
return conversion.Convert(params, config.TFListConversionPaths(), conversion.ToSingletonList)
return cfg.ApplyTFConversions(params, config.ToTerraform)
}

func (c *TerraformPluginSDKConnector) processParamsWithHCLParser(schemaMap map[string]*schema.Schema, params map[string]any) map[string]any {
Expand Down Expand Up @@ -256,7 +255,7 @@ func (c *TerraformPluginSDKConnector) Connect(ctx context.Context, mg xpresource
if err != nil {
return nil, errors.Wrap(err, "failed to get the observation")
}
tfState, err = conversion.Convert(tfState, c.config.TFListConversionPaths(), conversion.ToSingletonList)
tfState, err = c.config.ApplyTFConversions(tfState, config.ToTerraform)
if err != nil {
return nil, errors.Wrap(err, "failed to run the API converters on the Terraform state")
}
Expand Down Expand Up @@ -525,7 +524,7 @@ func (n *terraformPluginSDKExternal) Observe(ctx context.Context, mg xpresource.
return managed.ExternalObservation{}, errors.Wrap(err, "cannot get connection details")
}

stateValueMap, err = conversion.Convert(stateValueMap, n.config.TFListConversionPaths(), conversion.ToEmbeddedObject)
stateValueMap, err = n.config.ApplyTFConversions(stateValueMap, config.FromTerraform)
if err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, "cannot convert the singleton lists in the observed state value map into embedded objects")
}
Expand Down Expand Up @@ -645,7 +644,7 @@ func (n *terraformPluginSDKExternal) Create(ctx context.Context, mg xpresource.M
return managed.ExternalCreation{}, errors.Wrap(err, "cannot get connection details")
}

stateValueMap, err = conversion.Convert(stateValueMap, n.config.TFListConversionPaths(), conversion.ToEmbeddedObject)
stateValueMap, err = n.config.ApplyTFConversions(stateValueMap, config.FromTerraform)
if err != nil {
return managed.ExternalCreation{}, errors.Wrap(err, "cannot convert the singleton lists in the state value map of the newly created resource into embedded objects")
}
Expand Down

0 comments on commit c606b4c

Please sign in to comment.