Skip to content

Commit 5b5d30a

Browse files
committed
Add the schema traverser package
Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
1 parent c64c45c commit 5b5d30a

File tree

6 files changed

+87
-75
lines changed

6 files changed

+87
-75
lines changed

pkg/config/common.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ package config
77
import (
88
"strings"
99

10-
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
11-
1210
fwresource "github.com/hashicorp/terraform-plugin-framework/resource"
1311
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1412

@@ -29,11 +27,6 @@ const (
2927
PackageNameMonolith = "monolith"
3028
)
3129

32-
const (
33-
// wildcard index for field path expressions
34-
wildcard = "*"
35-
)
36-
3730
// Commonly used resource configurations.
3831
var (
3932
DefaultBasePackages = BasePackages{
@@ -193,12 +186,3 @@ func ManipulateEveryField(r *schema.Resource, op func(sch *schema.Schema)) {
193186
}
194187
}
195188
}
196-
197-
// FieldPathWithWildcard joins the given segment strings into a field path.
198-
func FieldPathWithWildcard(parts []string) string {
199-
seg := make(fieldpath.Segments, len(parts))
200-
for i, p := range parts {
201-
seg[i] = fieldpath.Field(p)
202-
}
203-
return seg.String()
204-
}

pkg/config/provider.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"fmt"
1010
"regexp"
1111

12+
"github.com/crossplane/upjet/pkg/schema/traverser"
13+
1214
tfjson "github.com/hashicorp/terraform-json"
1315
fwprovider "github.com/hashicorp/terraform-plugin-framework/provider"
1416
fwresource "github.com/hashicorp/terraform-plugin-framework/resource"
@@ -355,7 +357,7 @@ func NewProvider(schema []byte, prefix string, modulePath string, metadata []byt
355357
// configuration using:
356358
// - listEmbedder: This traverser marks lists of length at most 1
357359
// (singleton lists) as embedded objects.
358-
if err := Traverse(terraformResource, listEmbedder{r: p.Resources[name]}); err != nil {
360+
if err := traverser.Traverse(terraformResource, listEmbedder{r: p.Resources[name]}); err != nil {
359361
panic(err)
360362
}
361363
}
@@ -439,3 +441,21 @@ func terraformPluginFrameworkResourceFunctionsMap(provider fwprovider.Provider)
439441

440442
return resourceFunctionsMap
441443
}
444+
445+
type listEmbedder struct {
446+
traverser.NoopTraverser
447+
r *Resource
448+
}
449+
450+
func (l listEmbedder) VisitResource(r *traverser.ResourceNode) error {
451+
// this visitor only works on sets and lists with the MaxItems constraint
452+
// of 1.
453+
if r.Schema.Type != schema.TypeList && r.Schema.Type != schema.TypeSet {
454+
return nil
455+
}
456+
if r.Schema.MaxItems != 1 {
457+
return nil
458+
}
459+
l.r.AddSingletonListConversion(traverser.FieldPathWithWildcard(r.TFPath))
460+
return nil
461+
}

pkg/schema/traverser/fieldpath.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package traverser
6+
7+
import "github.com/crossplane/crossplane-runtime/pkg/fieldpath"
8+
9+
const (
10+
// FieldPathWildcard is the wildcard expression in fieldpath indices.
11+
FieldPathWildcard = "*"
12+
)
13+
14+
// FieldPathWithWildcard joins the given segment strings into a field path.
15+
func FieldPathWithWildcard(parts []string) string {
16+
seg := make(fieldpath.Segments, len(parts))
17+
for i, p := range parts {
18+
seg[i] = fieldpath.Field(p)
19+
}
20+
return seg.String()
21+
}
22+
23+
// FieldPath joins the given segment strings into a field path eliminating
24+
// the wildcard index segments.
25+
func FieldPath(parts []string) string {
26+
seg := make(fieldpath.Segments, len(parts))
27+
for i, p := range parts {
28+
if p == FieldPathWildcard {
29+
continue
30+
}
31+
seg[i] = fieldpath.Field(p)
32+
}
33+
return seg.String()
34+
}

pkg/config/traverse.go renamed to pkg/schema/traverser/traverse.go

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
package config
5+
package traverser
66

77
import (
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -11,6 +11,11 @@ import (
1111
"github.com/crossplane/upjet/pkg/types/name"
1212
)
1313

14+
const (
15+
// wildcard index for field path expressions
16+
wildcard = "*"
17+
)
18+
1419
var _ Element = &SchemaNode{}
1520
var _ Element = &ResourceNode{}
1621

@@ -117,30 +122,15 @@ func traverse(tfResource *schema.Resource, pNode Node, visitors ...SchemaTravers
117122
return nil
118123
}
119124

120-
type noopTraverser struct{}
121-
122-
func (n noopTraverser) VisitSchema(*SchemaNode) error {
123-
return nil
124-
}
125+
// NoopTraverser is a traverser that doesn't do anything when visiting a node.
126+
// Meant to make the implementation of visitors easy for the cases they are not
127+
// interested in a specific node type.
128+
type NoopTraverser struct{}
125129

126-
func (n noopTraverser) VisitResource(*ResourceNode) error {
130+
func (n NoopTraverser) VisitSchema(*SchemaNode) error {
127131
return nil
128132
}
129133

130-
type listEmbedder struct {
131-
noopTraverser
132-
r *Resource
133-
}
134-
135-
func (l listEmbedder) VisitResource(r *ResourceNode) error {
136-
// this visitor only works on sets and lists with the MaxItems constraint
137-
// of 1.
138-
if r.Schema.Type != schema.TypeList && r.Schema.Type != schema.TypeSet {
139-
return nil
140-
}
141-
if r.Schema.MaxItems != 1 {
142-
return nil
143-
}
144-
l.r.AddSingletonListConversion(FieldPathWithWildcard(r.TFPath))
134+
func (n NoopTraverser) VisitResource(*ResourceNode) error {
145135
return nil
146136
}

pkg/types/builder.go

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import (
1111
"sort"
1212
"strings"
1313

14-
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
14+
"github.com/crossplane/upjet/pkg/schema/traverser"
15+
1516
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1617
twtypes "github.com/muvaf/typewriter/pkg/types"
1718
"github.com/pkg/errors"
@@ -139,7 +140,7 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa
139140
r := &resource{}
140141
for _, snakeFieldName := range keys {
141142
var reference *config.Reference
142-
cPath := fieldPath(append(tfPath, snakeFieldName))
143+
cPath := traverser.FieldPath(append(tfPath, snakeFieldName))
143144
ref, ok := cfg.References[cPath]
144145
// if a reference is configured and the field does not belong to status
145146
if ok && !IsObservation(res.Schema[snakeFieldName]) {
@@ -239,7 +240,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp
239240
case schema.TypeString:
240241
elemType = types.Universe.Lookup("string").Type()
241242
case schema.TypeMap, schema.TypeList, schema.TypeSet, schema.TypeInvalid:
242-
return nil, nil, errors.Errorf("element type of %s is basic but not one of known basic types", fieldPath(names))
243+
return nil, nil, errors.Errorf("element type of %s is basic but not one of known basic types", traverser.FieldPath(names))
243244
}
244245
initElemType = elemType
245246
case *schema.Schema:
@@ -258,14 +259,14 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp
258259
}
259260
paramType, obsType, initType, err := g.buildResource(et, cfg, f.TerraformPaths, f.CRDPaths, asBlocksMode, names...)
260261
if err != nil {
261-
return nil, nil, errors.Wrapf(err, "cannot infer type from resource schema of element type of %s", fieldPath(names))
262+
return nil, nil, errors.Wrapf(err, "cannot infer type from resource schema of element type of %s", traverser.FieldPath(names))
262263
}
263264
initElemType = initType
264265

265266
switch {
266267
case IsObservation(f.Schema):
267268
if obsType == nil {
268-
return nil, nil, errors.Errorf("element type of %s is computed but the underlying schema does not return observation type", fieldPath(names))
269+
return nil, nil, errors.Errorf("element type of %s is computed but the underlying schema does not return observation type", traverser.FieldPath(names))
269270
}
270271
elemType = obsType
271272
// There are some types that are computed and not optional (observation field) but also has nested fields
@@ -285,7 +286,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp
285286
}
286287
default:
287288
if paramType == nil {
288-
return nil, nil, errors.Errorf("element type of %s is configurable but the underlying schema does not return a parameter type", fieldPath(names))
289+
return nil, nil, errors.Errorf("element type of %s is configurable but the underlying schema does not return a parameter type", traverser.FieldPath(names))
289290
}
290291
elemType = paramType
291292
// There are some types that are parameter field but also has nested fields that can go under status.
@@ -308,7 +309,7 @@ func (g *Builder) buildSchema(f *Field, cfg *config.Resource, names []string, cp
308309
elemType = types.Universe.Lookup("string").Type()
309310
initElemType = elemType
310311
default:
311-
return nil, nil, errors.Errorf("element type of %s should be either schema.Resource or schema.Schema", fieldPath(names))
312+
return nil, nil, errors.Errorf("element type of %s should be either schema.Resource or schema.Schema", traverser.FieldPath(names))
312313
}
313314

314315
// if the singleton list is to be replaced by an embedded object
@@ -339,19 +340,19 @@ type TypeNames struct {
339340
func NewTypeNames(fieldPaths []string, pkg *types.Package, overrideFieldNames map[string]string) (*TypeNames, error) {
340341
paramTypeName, err := generateTypeName("Parameters", pkg, overrideFieldNames, fieldPaths...)
341342
if err != nil {
342-
return nil, errors.Wrapf(err, "cannot generate parameters type name of %s", fieldPath(fieldPaths))
343+
return nil, errors.Wrapf(err, "cannot generate parameters type name of %s", traverser.FieldPath(fieldPaths))
343344
}
344345
paramName := types.NewTypeName(token.NoPos, pkg, paramTypeName, nil)
345346

346347
initTypeName, err := generateTypeName("InitParameters", pkg, overrideFieldNames, fieldPaths...)
347348
if err != nil {
348-
return nil, errors.Wrapf(err, "cannot generate init parameters type name of %s", fieldPath(fieldPaths))
349+
return nil, errors.Wrapf(err, "cannot generate init parameters type name of %s", traverser.FieldPath(fieldPaths))
349350
}
350351
initName := types.NewTypeName(token.NoPos, pkg, initTypeName, nil)
351352

352353
obsTypeName, err := generateTypeName("Observation", pkg, overrideFieldNames, fieldPaths...)
353354
if err != nil {
354-
return nil, errors.Wrapf(err, "cannot generate observation type name of %s", fieldPath(fieldPaths))
355+
return nil, errors.Wrapf(err, "cannot generate observation type name of %s", traverser.FieldPath(fieldPaths))
355356
}
356357
obsName := types.NewTypeName(token.NoPos, pkg, obsTypeName, nil)
357358

@@ -518,25 +519,6 @@ func sortedKeys(m map[string]*schema.Schema) []string {
518519
return keys
519520
}
520521

521-
func fieldPath(parts []string) string {
522-
seg := make(fieldpath.Segments, len(parts))
523-
for i, p := range parts {
524-
if p == wildcard {
525-
continue
526-
}
527-
seg[i] = fieldpath.Field(p)
528-
}
529-
return seg.String()
530-
}
531-
532-
func fieldPathWithWildcard(parts []string) string {
533-
seg := make(fieldpath.Segments, len(parts))
534-
for i, p := range parts {
535-
seg[i] = fieldpath.Field(p)
536-
}
537-
return seg.String()
538-
}
539-
540522
func sanitizePath(p string) string {
541523
for _, reserved := range celReservedKeywords {
542524
if p == reserved {

pkg/types/field.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"sort"
1313
"strings"
1414

15+
"github.com/crossplane/upjet/pkg/schema/traverser"
16+
1517
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1618
"github.com/pkg/errors"
1719
"k8s.io/utils/ptr"
@@ -61,7 +63,7 @@ type Field struct {
6163
func getDocString(cfg *config.Resource, f *Field, tfPath []string) string { //nolint:gocyclo
6264
hName := f.Name.Snake
6365
if len(tfPath) > 0 {
64-
hName = fieldPath(append(tfPath, hName))
66+
hName = traverser.FieldPath(append(tfPath, hName))
6567
}
6668
docString := ""
6769
if cfg.MetaResource != nil {
@@ -156,12 +158,12 @@ func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema,
156158
// like GetIgnoredCanonicalFields where we just make each word
157159
// between points camel case using names.go utilities. If the path
158160
// doesn't match anything, it's no-op in late-init logic anyway.
159-
if ignoreField == fieldPath(f.TerraformPaths) {
160-
cfg.LateInitializer.AddIgnoredCanonicalFields(fieldPath(f.CanonicalPaths))
161+
if ignoreField == traverser.FieldPath(f.TerraformPaths) {
162+
cfg.LateInitializer.AddIgnoredCanonicalFields(traverser.FieldPath(f.CanonicalPaths))
161163
}
162164
}
163165

164-
fieldType, initType, err := g.buildSchema(f, cfg, names, fieldPath(append(tfPath, snakeFieldName)), r)
166+
fieldType, initType, err := g.buildSchema(f, cfg, names, traverser.FieldPath(append(tfPath, snakeFieldName)), r)
165167
if err != nil {
166168
return nil, errors.Wrapf(err, "cannot infer type from schema of field %s", f.Name.Snake)
167169
}
@@ -269,7 +271,7 @@ func NewSensitiveField(g *Builder, cfg *config.Resource, r *resource, sch *schem
269271
}
270272

271273
if IsObservation(f.Schema) {
272-
cfg.Sensitive.AddFieldPath(fieldPathWithWildcard(f.TerraformPaths), "status.atProvider."+fieldPathWithWildcard(f.CRDPaths))
274+
cfg.Sensitive.AddFieldPath(traverser.FieldPathWithWildcard(f.TerraformPaths), "status.atProvider."+traverser.FieldPathWithWildcard(f.CRDPaths))
273275
// Drop an observation field from schema if it is sensitive.
274276
// Data will be stored in connection details secret
275277
return nil, true, nil
@@ -278,9 +280,9 @@ func NewSensitiveField(g *Builder, cfg *config.Resource, r *resource, sch *schem
278280
switch f.FieldType.(type) {
279281
case *types.Slice:
280282
f.CRDPaths[len(f.CRDPaths)-2] = f.CRDPaths[len(f.CRDPaths)-2] + sfx
281-
cfg.Sensitive.AddFieldPath(fieldPathWithWildcard(f.TerraformPaths), "spec.forProvider."+fieldPathWithWildcard(f.CRDPaths))
283+
cfg.Sensitive.AddFieldPath(traverser.FieldPathWithWildcard(f.TerraformPaths), "spec.forProvider."+traverser.FieldPathWithWildcard(f.CRDPaths))
282284
default:
283-
cfg.Sensitive.AddFieldPath(fieldPathWithWildcard(f.TerraformPaths), "spec.forProvider."+fieldPathWithWildcard(f.CRDPaths)+sfx)
285+
cfg.Sensitive.AddFieldPath(traverser.FieldPathWithWildcard(f.TerraformPaths), "spec.forProvider."+traverser.FieldPathWithWildcard(f.CRDPaths)+sfx)
284286
}
285287
// todo(turkenh): do we need to support other field types as sensitive?
286288
if f.FieldType.String() != "string" && f.FieldType.String() != "*string" && f.FieldType.String() != "[]string" &&

0 commit comments

Comments
 (0)