diff --git a/pkg/config/resource.go b/pkg/config/resource.go index 15df323a..943c54ec 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -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. diff --git a/pkg/config/runtime_conversion.go b/pkg/config/runtime_conversion.go deleted file mode 100644 index a3b0eeae..00000000 --- a/pkg/config/runtime_conversion.go +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2024 The Crossplane Authors -// -// SPDX-License-Identifier: Apache-2.0 - -package config - -// 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 -) - -type TerraformConversion interface { - Convert(params map[string]any, cfg *Resource, mode Mode) (map[string]any, error) -} - -type TerraformConversions []TerraformConversion - -func (tc TerraformConversions) Convert(params map[string]any, cfg *Resource, mode Mode) (map[string]any, error) { - var err error - for _, c := range tc { - params, err = c.Convert(params, cfg, mode) - if err != nil { - return nil, err - } - } - return params, nil -} diff --git a/pkg/config/tf_conversion.go b/pkg/config/tf_conversion.go new file mode 100644 index 00000000..71ff9514 --- /dev/null +++ b/pkg/config/tf_conversion.go @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2024 The Crossplane Authors +// +// 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) +} diff --git a/pkg/controller/external_tfpluginsdk.go b/pkg/controller/external_tfpluginsdk.go index b04ef2e4..8fed8acd 100644 --- a/pkg/controller/external_tfpluginsdk.go +++ b/pkg/controller/external_tfpluginsdk.go @@ -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" @@ -123,7 +122,7 @@ 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") @@ -131,18 +130,18 @@ func getExtendedParameters(ctx context.Context, tr resource.Terraformed, externa 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") } @@ -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 { @@ -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") } @@ -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") } @@ -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") }