github.com/crossplane/upjet@v1.3.0/pkg/config/common.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  	"strings"
     9  
    10  	fwresource "github.com/hashicorp/terraform-plugin-framework/resource"
    11  	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    12  
    13  	"github.com/crossplane/upjet/pkg/registry"
    14  	tjname "github.com/crossplane/upjet/pkg/types/name"
    15  )
    16  
    17  const (
    18  	// PackageNameConfig is the name of the provider subpackage that contains
    19  	// the base resources (e.g., ProviderConfig, ProviderConfigUsage,
    20  	// StoreConfig. etc.).
    21  	// TODO: we should be careful that there may also exist short groups with
    22  	// these names. We can consider making these configurable by the provider
    23  	// maintainer.
    24  	PackageNameConfig = "config"
    25  	// PackageNameMonolith is the name of the backwards-compatible
    26  	// provider subpackage that contains the all the resources.
    27  	PackageNameMonolith = "monolith"
    28  )
    29  
    30  // Commonly used resource configurations.
    31  var (
    32  	DefaultBasePackages = BasePackages{
    33  		APIVersion: []string{
    34  			// Default package for ProviderConfig APIs
    35  			"apis/v1alpha1",
    36  			"apis/v1beta1",
    37  		},
    38  
    39  		Controller: []string{
    40  			// Default package for ProviderConfig controllers
    41  			"internal/controller/providerconfig",
    42  		},
    43  		ControllerMap: map[string]string{
    44  			// Default package for ProviderConfig controllers
    45  			"internal/controller/providerconfig": PackageNameConfig,
    46  		},
    47  	}
    48  
    49  	// NopSensitive does nothing.
    50  	NopSensitive = Sensitive{
    51  		AdditionalConnectionDetailsFn: NopAdditionalConnectionDetails,
    52  	}
    53  )
    54  
    55  // ResourceOption allows setting optional fields of a Resource object.
    56  type ResourceOption func(*Resource)
    57  
    58  // DefaultResource keeps an initial default configuration for all resources of a
    59  // provider.
    60  func DefaultResource(name string, terraformSchema *schema.Resource, terraformPluginFrameworkResource fwresource.Resource, terraformRegistry *registry.Resource, opts ...ResourceOption) *Resource {
    61  	words := strings.Split(name, "_")
    62  	// As group name we default to the second element if resource name
    63  	// has at least 3 elements, otherwise, we took the first element as
    64  	// default group name, examples:
    65  	// - aws_rds_cluster => rds
    66  	// - aws_rds_cluster_parameter_group => rds
    67  	// - kafka_topic => kafka
    68  	group := words[1]
    69  	// As kind, we default to camel case version of what is left after dropping
    70  	// elements before what is selected as group:
    71  	// - aws_rds_cluster => Cluster
    72  	// - aws_rds_cluster_parameter_group => ClusterParameterGroup
    73  	// - kafka_topic => Topic
    74  	kind := tjname.NewFromSnake(strings.Join(words[2:], "_")).Camel
    75  	if len(words) < 3 {
    76  		group = words[0]
    77  		kind = tjname.NewFromSnake(words[1]).Camel
    78  	}
    79  
    80  	r := &Resource{
    81  		Name:                             name,
    82  		TerraformResource:                terraformSchema,
    83  		TerraformPluginFrameworkResource: terraformPluginFrameworkResource,
    84  		MetaResource:                     terraformRegistry,
    85  		ShortGroup:                       group,
    86  		Kind:                             kind,
    87  		Version:                          "v1alpha1",
    88  		ExternalName:                     NameAsIdentifier,
    89  		References:                       make(References),
    90  		Sensitive:                        NopSensitive,
    91  		UseAsync:                         true,
    92  		SchemaElementOptions:             make(SchemaElementOptions),
    93  		ServerSideApplyMergeStrategies:   make(ServerSideApplyMergeStrategies),
    94  	}
    95  	for _, f := range opts {
    96  		f(r)
    97  	}
    98  	return r
    99  }
   100  
   101  // MoveToStatus moves given fields and their leaf fields to the status as
   102  // a whole. It's used mostly in cases where there is a field that is
   103  // represented as a separate CRD, hence you'd like to remove that field from
   104  // spec.
   105  func MoveToStatus(sch *schema.Resource, fieldpaths ...string) {
   106  	for _, f := range fieldpaths {
   107  		s := GetSchema(sch, f)
   108  		if s == nil {
   109  			return
   110  		}
   111  		s.Optional = false
   112  		s.Computed = true
   113  
   114  		// We need to move all nodes of that field to status.
   115  		if el, ok := s.Elem.(*schema.Resource); ok {
   116  			l := make([]string, len(el.Schema))
   117  			i := 0
   118  			for fi := range el.Schema {
   119  				l[i] = fi
   120  				i++
   121  			}
   122  			MoveToStatus(el, l...)
   123  		}
   124  	}
   125  }
   126  
   127  // MarkAsRequired marks the given fieldpaths as required without manipulating
   128  // the native field schema.
   129  func (r *Resource) MarkAsRequired(fieldpaths ...string) {
   130  	r.requiredFields = append(r.requiredFields, fieldpaths...)
   131  }
   132  
   133  // MarkAsRequired marks the schema of the given fieldpath as required. It's most
   134  // useful in cases where external name contains an optional parameter that is
   135  // defaulted by the provider but we need it to exist or to fix plain buggy
   136  // schemas.
   137  // Deprecated: Use Resource.MarkAsRequired instead.
   138  // This function will be removed in future versions.
   139  func MarkAsRequired(sch *schema.Resource, fieldpaths ...string) {
   140  	for _, fieldpath := range fieldpaths {
   141  		if s := GetSchema(sch, fieldpath); s != nil {
   142  			s.Computed = false
   143  			s.Optional = false
   144  		}
   145  	}
   146  }
   147  
   148  // GetSchema returns the schema of the field whose fieldpath is given.
   149  // Returns nil if Schema is not found at the specified path.
   150  func GetSchema(sch *schema.Resource, fieldpath string) *schema.Schema {
   151  	current := sch
   152  	fields := strings.Split(fieldpath, ".")
   153  	final := fields[len(fields)-1]
   154  	formers := fields[:len(fields)-1]
   155  	for _, field := range formers {
   156  		s, ok := current.Schema[field]
   157  		if !ok {
   158  			return nil
   159  		}
   160  		if s.Elem == nil {
   161  			return nil
   162  		}
   163  		res, rok := s.Elem.(*schema.Resource)
   164  		if !rok {
   165  			return nil
   166  		}
   167  		current = res
   168  	}
   169  	s, ok := current.Schema[final]
   170  	if !ok {
   171  		return nil
   172  	}
   173  	return s
   174  }
   175  
   176  // ManipulateEveryField manipulates all fields in the schema by
   177  // input function.
   178  func ManipulateEveryField(r *schema.Resource, op func(sch *schema.Schema)) {
   179  	for _, s := range r.Schema {
   180  		if s == nil {
   181  			return
   182  		}
   183  		op(s)
   184  		if el, ok := s.Elem.(*schema.Resource); ok {
   185  			ManipulateEveryField(el, op)
   186  		}
   187  	}
   188  }