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 }