github.com/crossplane/upjet@v1.3.0/pkg/config/resource.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package config
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"time"
    11  
    12  	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
    13  	"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
    14  	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
    15  	xpresource "github.com/crossplane/crossplane-runtime/pkg/resource"
    16  	fwresource "github.com/hashicorp/terraform-plugin-framework/resource"
    17  	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    18  	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
    19  	"github.com/pkg/errors"
    20  	"k8s.io/apimachinery/pkg/util/json"
    21  	"k8s.io/apimachinery/pkg/util/sets"
    22  	"k8s.io/utils/ptr"
    23  	"sigs.k8s.io/controller-runtime/pkg/client"
    24  
    25  	"github.com/crossplane/upjet/pkg/config/conversion"
    26  	"github.com/crossplane/upjet/pkg/registry"
    27  )
    28  
    29  // A ListType is a type of list.
    30  type ListType string
    31  
    32  // Types of lists.
    33  const (
    34  	// ListTypeAtomic means the entire list is replaced during merge. At any
    35  	// point in time, a single manager owns the list.
    36  	ListTypeAtomic ListType = "atomic"
    37  
    38  	// ListTypeSet can be granularly merged, and different managers can own
    39  	// different elements in the list. The list can include only scalar
    40  	// elements.
    41  	ListTypeSet ListType = "set"
    42  
    43  	// ListTypeMap can be granularly merged, and different managers can own
    44  	// different elements in the list. The list can include only nested types
    45  	// (i.e. objects).
    46  	ListTypeMap ListType = "map"
    47  )
    48  
    49  // A MapType is a type of map.
    50  type MapType string
    51  
    52  // Types of maps.
    53  const (
    54  	// MapTypeAtomic means that the map can only be entirely replaced by a
    55  	// single manager.
    56  	MapTypeAtomic MapType = "atomic"
    57  
    58  	// MapTypeGranular means that the map supports separate managers updating
    59  	// individual fields.
    60  	MapTypeGranular MapType = "granular"
    61  )
    62  
    63  // A StructType is a type of struct.
    64  type StructType string
    65  
    66  // Struct types.
    67  const (
    68  	// StructTypeAtomic means that the struct can only be entirely replaced by a
    69  	// single manager.
    70  	StructTypeAtomic StructType = "atomic"
    71  
    72  	// StructTypeGranular means that the struct supports separate managers
    73  	// updating individual fields.
    74  	StructTypeGranular StructType = "granular"
    75  )
    76  
    77  // SetIdentifierArgumentsFn sets the name of the resource in Terraform attributes map,
    78  // i.e. Main HCL file.
    79  type SetIdentifierArgumentsFn func(base map[string]any, externalName string)
    80  
    81  // NopSetIdentifierArgument does nothing. It's useful for cases where the external
    82  // name is calculated by provider and doesn't have any effect on spec fields.
    83  var NopSetIdentifierArgument SetIdentifierArgumentsFn = func(_ map[string]any, _ string) {}
    84  
    85  // GetIDFn returns the ID to be used in TF State file, i.e. "id" field in
    86  // terraform.tfstate.
    87  type GetIDFn func(ctx context.Context, externalName string, parameters map[string]any, terraformProviderConfig map[string]any) (string, error)
    88  
    89  // ExternalNameAsID returns the name to be used as ID in TF State file.
    90  var ExternalNameAsID GetIDFn = func(_ context.Context, externalName string, _ map[string]any, _ map[string]any) (string, error) {
    91  	return externalName, nil
    92  }
    93  
    94  // GetExternalNameFn returns the external name extracted from the TF State.
    95  type GetExternalNameFn func(tfstate map[string]any) (string, error)
    96  
    97  // IDAsExternalName returns the TF State ID as external name.
    98  var IDAsExternalName GetExternalNameFn = func(tfstate map[string]any) (string, error) {
    99  	if id, ok := tfstate["id"].(string); ok && id != "" {
   100  		return id, nil
   101  	}
   102  	return "", errors.New("cannot find id in tfstate")
   103  }
   104  
   105  // AdditionalConnectionDetailsFn functions adds custom keys to connection details
   106  // secret using input terraform attributes
   107  type AdditionalConnectionDetailsFn func(attr map[string]any) (map[string][]byte, error)
   108  
   109  // NopAdditionalConnectionDetails does nothing, when no additional connection
   110  // details configuration function provided.
   111  var NopAdditionalConnectionDetails AdditionalConnectionDetailsFn = func(_ map[string]any) (map[string][]byte, error) {
   112  	return nil, nil
   113  }
   114  
   115  // ExternalName contains all information that is necessary for naming operations,
   116  // such as removal of those fields from spec schema and calling Configure function
   117  // to fill attributes with information given in external name.
   118  type ExternalName struct {
   119  	// SetIdentifierArgumentFn sets the name of the resource in Terraform argument
   120  	// map. In many cases, there is a field called "name" in the HCL schema, however,
   121  	// there are cases like RDS DB Cluster where the name field in HCL is called
   122  	// "cluster_identifier". This function is the place that you can take external
   123  	// name and assign it to that specific key for that resource type.
   124  	SetIdentifierArgumentFn SetIdentifierArgumentsFn
   125  
   126  	// GetExternalNameFn returns the external name extracted from TF State. In most cases,
   127  	// "id" field contains all the information you need. You'll need to extract
   128  	// the format that is decided for external name annotation to use.
   129  	// For example the following is an Azure resource ID:
   130  	// /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1
   131  	// The function should return "mygroup1" so that it can be used to set external
   132  	// name if it was not set already.
   133  	GetExternalNameFn GetExternalNameFn
   134  
   135  	// GetIDFn returns the string that will be used as "id" key in TF state. In
   136  	// many cases, external name format is the same as "id" but when it is not
   137  	// we may need information from other places to construct it. For example,
   138  	// the following is an Azure resource ID:
   139  	// /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1
   140  	// The function here should use information from supplied arguments to
   141  	// construct this ID, i.e. "mygroup1" from external name, subscription ID
   142  	// from terraformProviderConfig, and others from parameters map if needed.
   143  	GetIDFn GetIDFn
   144  
   145  	// OmittedFields are the ones you'd like to be removed from the schema since
   146  	// they are specified via external name. For example, if you set
   147  	// "cluster_identifier" in SetIdentifierArgumentFn, then you need to omit
   148  	// that field.
   149  	// You can omit only the top level fields.
   150  	// No field is omitted by default.
   151  	OmittedFields []string
   152  
   153  	// DisableNameInitializer allows you to specify whether the name initializer
   154  	// that sets external name to metadata.name if none specified should be disabled.
   155  	// It needs to be disabled for resources whose external identifier is randomly
   156  	// assigned by the provider, like AWS VPC where it gets vpc-21kn123 identifier
   157  	// and not let you name it.
   158  	DisableNameInitializer bool
   159  
   160  	// IdentifierFields are the fields that are used to construct external
   161  	// resource identifier. We need to know these fields no matter what the
   162  	// management policy is including the Observe Only, different from other
   163  	// (required) fields.
   164  	IdentifierFields []string
   165  }
   166  
   167  // References represents reference resolver configurations for the fields of a
   168  // given resource. Key should be the field path of the field to be referenced.
   169  type References map[string]Reference
   170  
   171  // Reference represents the Crossplane options used to generate
   172  // reference resolvers for fields
   173  type Reference struct {
   174  	// Type is the type name of the CRD if it is in the same package or
   175  	// <package-path>.<type-name> if it is in a different package.
   176  	Type string
   177  	// TerraformName is the name of the Terraform resource
   178  	// which will be referenced. The supplied resource name is
   179  	// converted to a type name of the corresponding CRD using
   180  	// the configured TerraformTypeMapper.
   181  	TerraformName string
   182  	// Extractor is the function to be used to extract value from the
   183  	// referenced type. Defaults to getting external name.
   184  	// Optional
   185  	Extractor string
   186  	// RefFieldName is the field name for the Reference field. Defaults to
   187  	// <field-name>Ref or <field-name>Refs.
   188  	// Optional
   189  	RefFieldName string
   190  	// SelectorFieldName is the field name for the Selector field. Defaults to
   191  	// <field-name>Selector.
   192  	// Optional
   193  	SelectorFieldName string
   194  }
   195  
   196  // Sensitive represents configurations to handle sensitive information
   197  type Sensitive struct {
   198  	// AdditionalConnectionDetailsFn is the path for function adding additional
   199  	// connection details keys
   200  	AdditionalConnectionDetailsFn AdditionalConnectionDetailsFn
   201  
   202  	// fieldPaths keeps the mapping of sensitive fields in Terraform schema with
   203  	// terraform field path as key and xp field path as value.
   204  	fieldPaths map[string]string
   205  }
   206  
   207  // LateInitializer represents configurations that control
   208  // late-initialization behaviour
   209  type LateInitializer struct {
   210  	// IgnoredFields are the field paths to be skipped during
   211  	// late-initialization. Similar to other configurations, these paths are
   212  	// Terraform field paths concatenated with dots. For example, if we want to
   213  	// ignore "ebs" block in "aws_launch_template", we should add
   214  	// "block_device_mappings.ebs".
   215  	IgnoredFields []string
   216  
   217  	// ignoredCanonicalFieldPaths are the Canonical field paths to be skipped
   218  	// during late-initialization. This is filled using the `IgnoredFields`
   219  	// field which keeps Terraform paths by converting them to Canonical paths.
   220  	ignoredCanonicalFieldPaths []string
   221  }
   222  
   223  // GetIgnoredCanonicalFields returns the ignoredCanonicalFields
   224  func (l *LateInitializer) GetIgnoredCanonicalFields() []string {
   225  	return l.ignoredCanonicalFieldPaths
   226  }
   227  
   228  // AddIgnoredCanonicalFields sets ignored canonical fields
   229  func (l *LateInitializer) AddIgnoredCanonicalFields(cf string) {
   230  	if l.ignoredCanonicalFieldPaths == nil {
   231  		l.ignoredCanonicalFieldPaths = make([]string, 0)
   232  	}
   233  	l.ignoredCanonicalFieldPaths = append(l.ignoredCanonicalFieldPaths, cf)
   234  }
   235  
   236  // GetFieldPaths returns the fieldPaths map for Sensitive
   237  func (s *Sensitive) GetFieldPaths() map[string]string {
   238  	return s.fieldPaths
   239  }
   240  
   241  // AddFieldPath adds the given tf path and xp path to the fieldPaths map.
   242  func (s *Sensitive) AddFieldPath(tf, xp string) {
   243  	if s.fieldPaths == nil {
   244  		s.fieldPaths = make(map[string]string)
   245  	}
   246  	s.fieldPaths[tf] = xp
   247  }
   248  
   249  // OperationTimeouts allows configuring resource operation timeouts:
   250  // https://www.terraform.io/language/resources/syntax#operation-timeouts
   251  // Please note that, not all resources support configuring timeouts.
   252  type OperationTimeouts struct {
   253  	Read   time.Duration
   254  	Create time.Duration
   255  	Update time.Duration
   256  	Delete time.Duration
   257  }
   258  
   259  // NewInitializerFn returns the Initializer with a client.
   260  type NewInitializerFn func(client client.Client) managed.Initializer
   261  
   262  // TagInitializer returns a tagger to use default tag initializer.
   263  var TagInitializer NewInitializerFn = func(client client.Client) managed.Initializer {
   264  	return NewTagger(client, "tags")
   265  }
   266  
   267  // Tagger implements the Initialize function to set external tags
   268  type Tagger struct {
   269  	kube      client.Client
   270  	fieldName string
   271  }
   272  
   273  // NewTagger returns a Tagger object.
   274  func NewTagger(kube client.Client, fieldName string) *Tagger {
   275  	return &Tagger{kube: kube, fieldName: fieldName}
   276  }
   277  
   278  // Initialize is a custom initializer for setting external tags
   279  func (t *Tagger) Initialize(ctx context.Context, mg xpresource.Managed) error {
   280  	if sets.New[xpv1.ManagementAction](mg.GetManagementPolicies()...).Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve)) {
   281  		// We don't want to add tags to the spec.forProvider if the resource is
   282  		// only being Observed.
   283  		return nil
   284  	}
   285  	paved, err := fieldpath.PaveObject(mg)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	pavedByte, err := setExternalTagsWithPaved(xpresource.GetExternalTags(mg), paved, t.fieldName)
   290  	if err != nil {
   291  		return err
   292  	}
   293  	if err := json.Unmarshal(pavedByte, mg); err != nil {
   294  		return err
   295  	}
   296  	if err := t.kube.Update(ctx, mg); err != nil {
   297  		return err
   298  	}
   299  	return nil
   300  }
   301  
   302  func setExternalTagsWithPaved(externalTags map[string]string, paved *fieldpath.Paved, fieldName string) ([]byte, error) {
   303  	tags := map[string]*string{
   304  		xpresource.ExternalResourceTagKeyKind:     ptr.To(externalTags[xpresource.ExternalResourceTagKeyKind]),
   305  		xpresource.ExternalResourceTagKeyName:     ptr.To(externalTags[xpresource.ExternalResourceTagKeyName]),
   306  		xpresource.ExternalResourceTagKeyProvider: ptr.To(externalTags[xpresource.ExternalResourceTagKeyProvider]),
   307  	}
   308  
   309  	if err := paved.SetValue(fmt.Sprintf("spec.forProvider.%s", fieldName), tags); err != nil {
   310  		return nil, err
   311  	}
   312  	pavedByte, err := paved.MarshalJSON()
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  	return pavedByte, nil
   317  }
   318  
   319  type InjectedKey struct {
   320  	Key          string
   321  	DefaultValue string
   322  }
   323  
   324  // ListMapKeys is the list map keys when the server-side apply merge strategy
   325  // islistType=map.
   326  type ListMapKeys struct {
   327  	// InjectedKey can be used to inject the specified index key
   328  	// into the generated CRD schema for the list object when
   329  	// the SSA merge strategy for the parent list is `map`.
   330  	// If a non-zero `InjectedKey` is specified, then a field of type string with
   331  	// the specified name is injected into the Terraform schema and used as
   332  	// a list map key together with any other existing keys specified in `Keys`.
   333  	InjectedKey InjectedKey
   334  	// Keys is the set of list map keys to be used while SSA merges list items.
   335  	// If InjectedKey is non-zero, then it's automatically put into Keys and
   336  	// you must not specify the InjectedKey in Keys explicitly.
   337  	Keys []string
   338  }
   339  
   340  // ListMergeStrategy configures the corresponding field as list
   341  // and configures its server-side apply merge strategy.
   342  type ListMergeStrategy struct {
   343  	// ListMapKeys is the list map keys when the SSA merge strategy is
   344  	// `listType=map`.  The keys specified here must be a set of scalar Terraform
   345  	// argument names to be used as the list map keys for the object list.
   346  	ListMapKeys ListMapKeys
   347  	// MergeStrategy is the SSA merge strategy for an object list. Valid values
   348  	// are: `atomic`, `set` and `map`
   349  	MergeStrategy ListType
   350  }
   351  
   352  // MergeStrategy configures the server-side apply merge strategy for the
   353  // corresponding field. One and only one of the pointer members can be set
   354  // and the specified merge strategy configuration must match the field's
   355  // type, e.g., you cannot set MapMergeStrategy for a field of type list.
   356  type MergeStrategy struct {
   357  	ListMergeStrategy   ListMergeStrategy
   358  	MapMergeStrategy    MapType
   359  	StructMergeStrategy StructType
   360  }
   361  
   362  // ServerSideApplyMergeStrategies configures the server-side apply merge strategy
   363  // for the field at the specified path as the map key. The key is
   364  // a Terraform configuration argument path such as a.b.c, without any
   365  // index notation (i.e., array/map components do not need indices).
   366  // It's an error to set a configuration option which does not match
   367  // the object type at the specified path or to leave the corresponding
   368  // configuration entry empty. For example, if the field at path a.b.c is
   369  // a list, then ListMergeStrategy must be set and it should be the only
   370  // configuration entry set.
   371  type ServerSideApplyMergeStrategies map[string]MergeStrategy
   372  
   373  // Resource is the set of information that you can override at different steps
   374  // of the code generation pipeline.
   375  type Resource struct {
   376  	// Name is the name of the resource type in Terraform,
   377  	// e.g. aws_rds_cluster.
   378  	Name string
   379  
   380  	// TerraformResource is the Terraform representation of the
   381  	// Terraform Plugin SDKv2 based resource.
   382  	TerraformResource *schema.Resource
   383  
   384  	// TerraformPluginFrameworkResource is the Terraform representation
   385  	// of the TF Plugin Framework based resource
   386  	TerraformPluginFrameworkResource fwresource.Resource
   387  
   388  	// ShortGroup is the short name of the API group of this CRD. The full
   389  	// CRD API group is calculated by adding the group suffix of the provider.
   390  	// For example, ShortGroup could be `ec2` where group suffix of the
   391  	// provider is `aws.crossplane.io` and in that case, the full group would
   392  	// be `ec2.aws.crossplane.io`
   393  	ShortGroup string
   394  
   395  	// Version is the version CRD will have.
   396  	Version string
   397  
   398  	// Kind is the kind of the CRD.
   399  	Kind string
   400  
   401  	// UseAsync should be enabled for resource whose creation and/or deletion
   402  	// takes more than 1 minute to complete such as Kubernetes clusters or
   403  	// databases.
   404  	UseAsync bool
   405  
   406  	// InitializerFns specifies the initializer functions to be used
   407  	// for this Resource.
   408  	InitializerFns []NewInitializerFn
   409  
   410  	// OperationTimeouts allows configuring resource operation timeouts.
   411  	OperationTimeouts OperationTimeouts
   412  
   413  	// ExternalName allows you to specify a custom ExternalName.
   414  	ExternalName ExternalName
   415  
   416  	// References keeps the configuration to build cross resource references
   417  	References References
   418  
   419  	// Sensitive keeps the configuration to handle sensitive information
   420  	Sensitive Sensitive
   421  
   422  	// LateInitializer configuration to control late-initialization behaviour
   423  	LateInitializer LateInitializer
   424  
   425  	// MetaResource is the metadata associated with the resource scraped from
   426  	// the Terraform registry.
   427  	MetaResource *registry.Resource
   428  
   429  	// Path is the resource path for the API server endpoint. It defaults to
   430  	// the plural name of the generated CRD. Overriding this sets both the
   431  	// path and the plural name for the generated CRD.
   432  	Path string
   433  
   434  	// SchemaElementOptions is a map from the schema element paths to
   435  	// SchemaElementOption for configuring options for schema elements.
   436  	SchemaElementOptions SchemaElementOptions
   437  
   438  	// TerraformConfigurationInjector allows a managed resource to inject
   439  	// configuration values in the Terraform configuration map obtained by
   440  	// deserializing its `spec.forProvider` value. Managed resources can
   441  	// use this resource configuration option to inject Terraform
   442  	// configuration parameters into their deserialized configuration maps,
   443  	// if the deserialization skips certain fields.
   444  	TerraformConfigurationInjector ConfigurationInjector
   445  
   446  	// TerraformCustomDiff allows a resource.Terraformed to customize how its
   447  	// Terraform InstanceDiff is computed during reconciliation.
   448  	TerraformCustomDiff CustomDiff
   449  
   450  	// ServerSideApplyMergeStrategies configures the server-side apply merge
   451  	// strategy for the fields at the given map keys. The map key is
   452  	// a Terraform configuration argument path such as a.b.c, without any
   453  	// index notation (i.e., array/map components do not need indices).
   454  	ServerSideApplyMergeStrategies ServerSideApplyMergeStrategies
   455  
   456  	Conversions []conversion.Conversion
   457  
   458  	// useTerraformPluginSDKClient indicates that a plugin SDK external client should
   459  	// be generated instead of the Terraform CLI-forking client.
   460  	useTerraformPluginSDKClient bool
   461  
   462  	// useTerraformPluginFrameworkClient indicates that a Terraform
   463  	// Plugin Framework external client should be generated instead of
   464  	// the Terraform Plugin SDKv2 client.
   465  	useTerraformPluginFrameworkClient bool
   466  
   467  	// OverrideFieldNames allows to manually override the relevant field name to
   468  	// avoid possible Go struct name conflicts that may occur after Multiversion
   469  	// CRDs support. During field generation, there may be fields with the same
   470  	// struct name calculated in the same group. For example, let X and Y
   471  	// resources in the same API group have a field named Tag. This field is an
   472  	// object type and the name calculated for the struct to be generated is
   473  	// TagParameters (for spec) for both resources. To avoid this conflict, upjet
   474  	// looks at all previously created structs in the package during generation
   475  	// and if there is a conflict, it puts the Kind name of the related resource
   476  	// in front of the next one: YTagParameters.
   477  	// With Multiversion CRDs support, the above conflict scenario cannot be
   478  	// solved in the generator when the old API group is preserved and not
   479  	// regenerated, because the generator does not know the object names in the
   480  	// old version. For example, a new API version is generated for resource X. In
   481  	// this case, no generation is done for the old version of X and when Y is
   482  	// generated, the generator is not aware of the TagParameters in X and
   483  	// generates TagParameters instead of YTagParameters. Thus, two object types
   484  	// with the same name are generated in the same package. This can be overcome
   485  	// by using this configuration API.
   486  	// The key of the map indicates the name of the field that is generated and
   487  	// causes the conflict, while the value indicates the name used to avoid the
   488  	// conflict. By convention, also used in upjet, the field name is preceded by
   489  	// the value of the generated Kind, for example:
   490  	// "TagParameters": "ClusterTagParameters"
   491  	OverrideFieldNames map[string]string
   492  
   493  	// requiredFields are the fields that will be marked as required in the
   494  	// generated CRD schema, although they are not required in the TF schema.
   495  	requiredFields []string
   496  }
   497  
   498  // RequiredFields returns slice of the marked as required fieldpaths.
   499  func (r *Resource) RequiredFields() []string {
   500  	return r.requiredFields
   501  }
   502  
   503  // ShouldUseTerraformPluginSDKClient returns whether to generate an SDKv2-based
   504  // external client for this Resource.
   505  func (r *Resource) ShouldUseTerraformPluginSDKClient() bool {
   506  	return r.useTerraformPluginSDKClient
   507  }
   508  
   509  // ShouldUseTerraformPluginFrameworkClient returns whether to generate a
   510  // Terraform Plugin Framework-based external client for this Resource.
   511  func (r *Resource) ShouldUseTerraformPluginFrameworkClient() bool {
   512  	return r.useTerraformPluginFrameworkClient
   513  }
   514  
   515  // CustomDiff customizes the computed Terraform InstanceDiff. This can be used
   516  // in cases where, for example, changes in a certain argument should just be
   517  // dismissed. The new InstanceDiff is returned along with any errors.
   518  type CustomDiff func(diff *terraform.InstanceDiff, state *terraform.InstanceState, config *terraform.ResourceConfig) (*terraform.InstanceDiff, error)
   519  
   520  // ConfigurationInjector is a function that injects Terraform configuration
   521  // values from the specified managed resource into the specified configuration
   522  // map. jsonMap is the map obtained by converting the `spec.forProvider` using
   523  // the JSON tags and tfMap is obtained by using the TF tags.
   524  type ConfigurationInjector func(jsonMap map[string]any, tfMap map[string]any) error
   525  
   526  // SchemaElementOptions represents schema element options for the
   527  // schema elements of a Resource.
   528  type SchemaElementOptions map[string]*SchemaElementOption
   529  
   530  // SetAddToObservation sets the AddToObservation for the specified key.
   531  func (m SchemaElementOptions) SetAddToObservation(el string) {
   532  	if m[el] == nil {
   533  		m[el] = &SchemaElementOption{}
   534  	}
   535  	m[el].AddToObservation = true
   536  }
   537  
   538  // AddToObservation returns true if the schema element at the specified path
   539  // should be added to the CRD type's Observation type.
   540  func (m SchemaElementOptions) AddToObservation(el string) bool {
   541  	return m[el] != nil && m[el].AddToObservation
   542  }
   543  
   544  // SetEmbeddedObject sets the EmbeddedObject for the specified key.
   545  func (m SchemaElementOptions) SetEmbeddedObject(el string) {
   546  	if m[el] == nil {
   547  		m[el] = &SchemaElementOption{}
   548  	}
   549  	m[el].EmbeddedObject = true
   550  }
   551  
   552  // EmbeddedObject returns true if the schema element at the specified path
   553  // should be generated as an embedded object.
   554  func (m SchemaElementOptions) EmbeddedObject(el string) bool {
   555  	return m[el] != nil && m[el].EmbeddedObject
   556  }
   557  
   558  // SchemaElementOption represents configuration options on a schema element.
   559  type SchemaElementOption struct {
   560  	// AddToObservation is set to true if the field represented by
   561  	// a schema element is to be added to the generated CRD type's
   562  	// Observation type.
   563  	AddToObservation bool
   564  	// EmbeddedObject  is set to true if the field represented by
   565  	// a schema element is to be embedded into its parent instead of being
   566  	// generated as a single element list.
   567  	EmbeddedObject bool
   568  }