Skip to content

Commit

Permalink
Merge pull request #407 from sergenyalcin/cond-late-init
Browse files Browse the repository at this point in the history
Add a new late-init configuration to skip already filled field in spec.initProvider
  • Loading branch information
sergenyalcin authored May 24, 2024
2 parents 89a7d0a + be0cde1 commit a444cc1
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 5 deletions.
2 changes: 1 addition & 1 deletion pkg/config/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func TestDefaultResource(t *testing.T) {
// TODO(muvaf): Find a way to compare function pointers.
ignoreUnexported := []cmp.Option{
cmpopts.IgnoreFields(Sensitive{}, "fieldPaths", "AdditionalConnectionDetailsFn"),
cmpopts.IgnoreFields(LateInitializer{}, "ignoredCanonicalFieldPaths"),
cmpopts.IgnoreFields(LateInitializer{}, "ignoredCanonicalFieldPaths", "conditionalIgnoredCanonicalFieldPaths"),
cmpopts.IgnoreFields(ExternalName{}, "SetIdentifierArgumentFn", "GetExternalNameFn", "GetIDFn"),
cmpopts.IgnoreUnexported(Resource{}),
cmpopts.IgnoreUnexported(reflect.ValueOf(identityConversion).Elem().Interface()),
Expand Down
23 changes: 23 additions & 0 deletions pkg/config/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,20 @@ type LateInitializer struct {
// "block_device_mappings.ebs".
IgnoredFields []string

// ConditionalIgnoredFields are the field paths to be skipped during
// late-initialization if they are filled in spec.initProvider.
ConditionalIgnoredFields []string

// ignoredCanonicalFieldPaths are the Canonical field paths to be skipped
// during late-initialization. This is filled using the `IgnoredFields`
// field which keeps Terraform paths by converting them to Canonical paths.
ignoredCanonicalFieldPaths []string

// conditionalIgnoredCanonicalFieldPaths are the Canonical field paths to be
// skipped during late-initialization if they are filled in spec.initProvider.
// This is filled using the `ConditionalIgnoredFields` field which keeps
// Terraform paths by converting them to Canonical paths.
conditionalIgnoredCanonicalFieldPaths []string
}

// GetIgnoredCanonicalFields returns the ignoredCanonicalFields
Expand All @@ -239,6 +249,19 @@ func (l *LateInitializer) AddIgnoredCanonicalFields(cf string) {
l.ignoredCanonicalFieldPaths = append(l.ignoredCanonicalFieldPaths, cf)
}

// GetConditionalIgnoredCanonicalFields returns the conditionalIgnoredCanonicalFieldPaths
func (l *LateInitializer) GetConditionalIgnoredCanonicalFields() []string {
return l.conditionalIgnoredCanonicalFieldPaths
}

// AddConditionalIgnoredCanonicalFields sets conditional ignored canonical fields
func (l *LateInitializer) AddConditionalIgnoredCanonicalFields(cf string) {
if l.conditionalIgnoredCanonicalFieldPaths == nil {
l.conditionalIgnoredCanonicalFieldPaths = make([]string, 0)
}
l.conditionalIgnoredCanonicalFieldPaths = append(l.conditionalIgnoredCanonicalFieldPaths, cf)
}

// GetFieldPaths returns the fieldPaths map for Sensitive
func (s *Sensitive) GetFieldPaths() map[string]string {
return s.fieldPaths
Expand Down
9 changes: 9 additions & 0 deletions pkg/pipeline/templates/terraformed.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ func (tr *{{ .CRD.Kind }}) LateInitialize(attrs []byte) (bool, error) {
{{ range .LateInitializer.IgnoredFields -}}
opts = append(opts, resource.WithNameFilter("{{ . }}"))
{{ end }}
{{- if gt (len .LateInitializer.ConditionalIgnoredFields) 0 -}}
initParams, err := tr.GetInitParameters()
if err != nil {
return false, errors.Wrapf(err, "cannot get init parameters for resource '%q'", tr.GetName())
}
{{ range .LateInitializer.ConditionalIgnoredFields -}}
opts = append(opts, resource.WithConditionalFilter("{{ . }}", initParams))
{{ end }}
{{ end }}

li := resource.NewGenericLateInitializer(opts...)
return li.LateInitialize(&tr.Spec.ForProvider, params)
Expand Down
3 changes: 2 additions & 1 deletion pkg/pipeline/terraformed.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func (tg *TerraformedGenerator) Generate(cfgs []*terraformedInput, apiVersion st
"Fields": cfg.Sensitive.GetFieldPaths(),
}
vars["LateInitializer"] = map[string]any{
"IgnoredFields": cfg.LateInitializer.GetIgnoredCanonicalFields(),
"IgnoredFields": cfg.LateInitializer.GetIgnoredCanonicalFields(),
"ConditionalIgnoredFields": cfg.LateInitializer.GetConditionalIgnoredCanonicalFields(),
}

if err := trFile.Write(filePath, vars, os.ModePerm); err != nil {
Expand Down
46 changes: 44 additions & 2 deletions pkg/resource/lateinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import (
"runtime/debug"
"strings"

"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta"
xpresource "github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/crossplane/upjet/pkg/config"
"github.com/crossplane/upjet/pkg/types/name"
)

const (
Expand Down Expand Up @@ -43,8 +45,9 @@ const (

// GenericLateInitializer performs late-initialization of a Terraformed resource.
type GenericLateInitializer struct {
valueFilters []ValueFilter
nameFilters []NameFilter
valueFilters []ValueFilter
nameFilters []NameFilter
conditionalFilters []ConditionalFilter
}

// SetCriticalAnnotations sets the critical annotations of the resource and reports
Expand Down Expand Up @@ -175,6 +178,35 @@ func isZeroValueOmitted(tag string) bool {
return false
}

// ConditionalFilter defines a late-initialization filter on CR field canonical names.
// Fields with matching cnames will not be processed during late-initialization
// if they are filled in spec.initProvider.
type ConditionalFilter func(string) bool

// WithConditionalFilter returns a GenericLateInitializer that causes to
// skip initialization of the field with the specified canonical name
// if the field is filled in spec.initProvider.
func WithConditionalFilter(cName string, initProvider map[string]any) GenericLateInitializerOption {
return func(l *GenericLateInitializer) {
l.conditionalFilters = append(l.conditionalFilters, conditionalFilter(cName, initProvider))
}
}

func conditionalFilter(cName string, initProvider map[string]any) ConditionalFilter {
return func(cn string) bool {
if cName != cn {
return false
}

paved := fieldpath.Pave(initProvider)
value, err := paved.GetValue(name.NewFromCamel(cName).Snake)
if err != nil || value == nil {
return false
}
return true
}
}

// LateInitialize Copy unset (nil) values from responseObject to crObject
// Both crObject and responseObject must be pointers to structs.
// Otherwise, an error will be returned. Returns `true` if at least one field has been stored
Expand Down Expand Up @@ -230,6 +262,16 @@ func (li *GenericLateInitializer) handleStruct(parentName string, desiredObject
continue
}

for _, f := range li.conditionalFilters {
if f(cName) {
filtered = true
break
}
}
if filtered {
continue
}

observedStructField, _ := typeOfObservedObject.FieldByName(desiredStructField.Name)
observedFieldValue := valueOfObservedObject.FieldByName(desiredStructField.Name)
desiredKeepField := false
Expand Down
8 changes: 7 additions & 1 deletion pkg/types/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func getDocString(cfg *config.Resource, f *Field, tfPath []string) string { //no
}

// NewField returns a constructed Field object.
func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool) (*Field, error) {
func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema, snakeFieldName string, tfPath, xpPath, names []string, asBlocksMode bool) (*Field, error) { //nolint:gocyclo // easy to follow
f := &Field{
Schema: sch,
Name: name.NewFromSnake(snakeFieldName),
Expand Down Expand Up @@ -165,6 +165,12 @@ func NewField(g *Builder, cfg *config.Resource, r *resource, sch *schema.Schema,
}
}

for _, ignoreField := range cfg.LateInitializer.ConditionalIgnoredFields {
if ignoreField == traverser.FieldPath(f.TerraformPaths) {
cfg.LateInitializer.AddConditionalIgnoredCanonicalFields(traverser.FieldPath(f.CanonicalPaths))
}
}

fieldType, initType, err := g.buildSchema(f, cfg, names, traverser.FieldPath(append(tfPath, snakeFieldName)), r)
if err != nil {
return nil, errors.Wrapf(err, "cannot infer type from schema of field %s", f.Name.Snake)
Expand Down

0 comments on commit a444cc1

Please sign in to comment.