Skip to content

Commit 648cbd6

Browse files
committed
Initial commit
Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
1 parent 4c67d8e commit 648cbd6

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

pkg/config/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ func DefaultResource(name string, terraformSchema *schema.Resource, terraformPlu
9191
UseAsync: true,
9292
SchemaElementOptions: make(SchemaElementOptions),
9393
ServerSideApplyMergeStrategies: make(ServerSideApplyMergeStrategies),
94+
listConversionPaths: make(map[string]string),
9495
}
9596
for _, f := range opts {
9697
f(r)

pkg/config/resource.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package config
77
import (
88
"context"
99
"fmt"
10+
"strings"
1011
"time"
1112

1213
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -435,6 +436,15 @@ type Resource struct {
435436
// SchemaElementOption for configuring options for schema elements.
436437
SchemaElementOptions SchemaElementOptions
437438

439+
// listConversionPaths is the mapping from the CRD fieldpaths to the
440+
// corresponding Terraform fieldpaths of embedded objects that need to be
441+
// converted into singleton lists (lists of at most one element) at runtime.
442+
// Such fields are lists in the Terraform schema, however upjet generates
443+
// them as nested objects, and we need to convert them back to lists
444+
// at runtime before passing them to the Terraform stack and after reading
445+
// the state from the Terraform stack.
446+
listConversionPaths map[string]string
447+
438448
// TerraformConfigurationInjector allows a managed resource to inject
439449
// configuration values in the Terraform configuration map obtained by
440450
// deserializing its `spec.forProvider` value. Managed resources can
@@ -541,6 +551,42 @@ func (m SchemaElementOptions) AddToObservation(el string) bool {
541551
return m[el] != nil && m[el].AddToObservation
542552
}
543553

554+
// CRDListConversionPaths returns the Resource's runtime CRD list conversion
555+
// paths in fieldpath syntax.
556+
func (r *Resource) CRDListConversionPaths() []string {
557+
l := make([]string, 0, len(r.listConversionPaths))
558+
for k := range r.listConversionPaths {
559+
l = append(l, k)
560+
}
561+
return l
562+
}
563+
564+
// TFListConversionPaths returns the Resource's runtime Terraform list
565+
// conversion paths in fieldpath syntax.
566+
func (r *Resource) TFListConversionPaths() []string {
567+
l := make([]string, 0, len(r.listConversionPaths))
568+
for _, v := range r.listConversionPaths {
569+
l = append(l, v)
570+
}
571+
return l
572+
}
573+
574+
// AddSingletonListConversion configures the list at the specified CRD
575+
// field path and the specified Terraform field path as an embedded object.
576+
// crdPath is the field path expression for the CRD schema and tfPath is
577+
// the field path expression for the Terraform schema corresponding to the
578+
// singleton list to be converted to an embedded object.
579+
// At runtime, upjet will convert such objects back and forth
580+
// from/to singleton lists while communicating with the Terraform stack.
581+
// The specified fieldpath expression must be a wildcard expression such as
582+
// `conditions[*]` or a 0-indexed expression such as `conditions[0]`. Other
583+
// index values are not allowed as this function deals with singleton lists.
584+
func (r *Resource) AddSingletonListConversion(crdPath, tfPath string) {
585+
r.SchemaElementOptions.SetEmbeddedObject(strings.ReplaceAll(
586+
strings.ReplaceAll(crdPath, "[*]", ""), "[0]", ""))
587+
r.listConversionPaths[crdPath] = tfPath
588+
}
589+
544590
// SetEmbeddedObject sets the EmbeddedObject for the specified key.
545591
func (m SchemaElementOptions) SetEmbeddedObject(el string) {
546592
if m[el] == nil {

pkg/controller/external_tfpluginsdk.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package controller
77
import (
88
"context"
99
"fmt"
10+
"slices"
11+
"sort"
1012
"strings"
1113
"time"
1214

@@ -122,6 +124,47 @@ type terraformPluginSDKExternal struct {
122124
opTracker *AsyncTracker
123125
}
124126

127+
func convert(params map[string]any, paths []string, toList bool) (map[string]any, error) {
128+
pv := fieldpath.Pave(params)
129+
130+
if toList {
131+
slices.Sort(paths)
132+
} else {
133+
sort.Slice(paths, func(i, j int) bool {
134+
return paths[i] > paths[j]
135+
})
136+
}
137+
138+
for _, fp := range paths {
139+
exp, err := pv.ExpandWildcards(fp)
140+
if err != nil {
141+
return nil, errors.Wrapf(err, "cannot expand wildcards for the field path expression %s", fp)
142+
}
143+
switch len(exp) {
144+
case 0:
145+
return params, nil
146+
case 1:
147+
v, err := pv.GetValue(exp[0])
148+
if err != nil {
149+
return nil, errors.Wrapf(err, "cannot get the value at the field path %s with the list mode set to %t", exp[0], toList)
150+
}
151+
switch {
152+
case toList:
153+
if err := pv.SetValue(exp[0], []any{v}); err != nil {
154+
return nil, errors.Wrapf(err, "cannot set the singleton list's value at the field path %s", exp[0])
155+
}
156+
default:
157+
if err := pv.SetValue(exp[0], v.([]any)[0]); err != nil {
158+
return nil, errors.Wrapf(err, "cannot set the embedded object's value at the field path %s", exp[0])
159+
}
160+
}
161+
default:
162+
return nil, errors.Errorf("unexpected number of expansions (%d) for the wildcard field path expression %s", len(exp), fp)
163+
}
164+
}
165+
return params, nil
166+
}
167+
125168
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) {
126169
params, err := tr.GetMergedParameters(initParamsMerged)
127170
if err != nil {
@@ -155,7 +198,7 @@ func getExtendedParameters(ctx context.Context, tr resource.Terraformed, externa
155198
params["tags_all"] = params["tags"]
156199
}
157200
}
158-
return params, nil
201+
return convert(params, config.CRDListConversionPaths(), true)
159202
}
160203

161204
func (c *TerraformPluginSDKConnector) processParamsWithHCLParser(schemaMap map[string]*schema.Schema, params map[string]any) map[string]any {
@@ -255,6 +298,10 @@ func (c *TerraformPluginSDKConnector) Connect(ctx context.Context, mg xpresource
255298
if err != nil {
256299
return nil, errors.Wrap(err, "failed to get the observation")
257300
}
301+
tfState, err = convert(tfState, c.config.CRDListConversionPaths(), true)
302+
if err != nil {
303+
return nil, err
304+
}
258305
copyParams := len(tfState) == 0
259306
if err = resource.GetSensitiveParameters(ctx, &APISecretClient{kube: c.kube}, tr, tfState, tr.GetConnectionDetailsMapping()); err != nil {
260307
return nil, errors.Wrap(err, "cannot store sensitive parameters into tfState")
@@ -511,6 +558,10 @@ func (n *terraformPluginSDKExternal) Observe(ctx context.Context, mg xpresource.
511558
}
512559
mg.SetConditions(xpv1.Available())
513560

561+
stateValueMap, err = convert(stateValueMap, n.config.TFListConversionPaths(), false)
562+
if err != nil {
563+
return managed.ExternalObservation{}, err
564+
}
514565
buff, err := json.TFParser.Marshal(stateValueMap)
515566
if err != nil {
516567
return managed.ExternalObservation{}, errors.Wrap(err, "cannot marshal the attributes of the new state for late-initialization")
@@ -623,6 +674,10 @@ func (n *terraformPluginSDKExternal) Create(ctx context.Context, mg xpresource.M
623674
if _, err := n.setExternalName(mg, stateValueMap); err != nil {
624675
return managed.ExternalCreation{}, errors.Wrap(err, "failed to set the external-name of the managed resource during create")
625676
}
677+
stateValueMap, err = convert(stateValueMap, n.config.CRDListConversionPaths(), false)
678+
if err != nil {
679+
return managed.ExternalCreation{}, err
680+
}
626681
err = mg.(resource.Terraformed).SetObservation(stateValueMap)
627682
if err != nil {
628683
return managed.ExternalCreation{}, errors.Errorf("could not set observation: %v", err)

0 commit comments

Comments
 (0)