kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/terraform/schemas.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "kubeform.dev/terraform-backend-sdk/addrs" 8 "kubeform.dev/terraform-backend-sdk/configs" 9 "kubeform.dev/terraform-backend-sdk/configs/configschema" 10 "kubeform.dev/terraform-backend-sdk/providers" 11 "kubeform.dev/terraform-backend-sdk/states" 12 "kubeform.dev/terraform-backend-sdk/tfdiags" 13 ) 14 15 // Schemas is a container for various kinds of schema that Terraform needs 16 // during processing. 17 type Schemas struct { 18 Providers map[addrs.Provider]*ProviderSchema 19 Provisioners map[string]*configschema.Block 20 } 21 22 // ProviderSchema returns the entire ProviderSchema object that was produced 23 // by the plugin for the given provider, or nil if no such schema is available. 24 // 25 // It's usually better to go use the more precise methods offered by type 26 // Schemas to handle this detail automatically. 27 func (ss *Schemas) ProviderSchema(provider addrs.Provider) *ProviderSchema { 28 if ss.Providers == nil { 29 return nil 30 } 31 return ss.Providers[provider] 32 } 33 34 // ProviderConfig returns the schema for the provider configuration of the 35 // given provider type, or nil if no such schema is available. 36 func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block { 37 ps := ss.ProviderSchema(provider) 38 if ps == nil { 39 return nil 40 } 41 return ps.Provider 42 } 43 44 // ResourceTypeConfig returns the schema for the configuration of a given 45 // resource type belonging to a given provider type, or nil of no such 46 // schema is available. 47 // 48 // In many cases the provider type is inferrable from the resource type name, 49 // but this is not always true because users can override the provider for 50 // a resource using the "provider" meta-argument. Therefore it's important to 51 // always pass the correct provider name, even though it many cases it feels 52 // redundant. 53 func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { 54 ps := ss.ProviderSchema(provider) 55 if ps == nil || ps.ResourceTypes == nil { 56 return nil, 0 57 } 58 return ps.SchemaForResourceType(resourceMode, resourceType) 59 } 60 61 // ProvisionerConfig returns the schema for the configuration of a given 62 // provisioner, or nil of no such schema is available. 63 func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { 64 return ss.Provisioners[name] 65 } 66 67 // loadSchemas searches the given configuration, state and plan (any of which 68 // may be nil) for constructs that have an associated schema, requests the 69 // necessary schemas from the given component factory (which must _not_ be nil), 70 // and returns a single object representing all of the necessary schemas. 71 // 72 // If an error is returned, it may be a wrapped tfdiags.Diagnostics describing 73 // errors across multiple separate objects. Errors here will usually indicate 74 // either misbehavior on the part of one of the providers or of the provider 75 // protocol itself. When returned with errors, the returned schemas object is 76 // still valid but may be incomplete. 77 func loadSchemas(config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) { 78 schemas := &Schemas{ 79 Providers: map[addrs.Provider]*ProviderSchema{}, 80 Provisioners: map[string]*configschema.Block{}, 81 } 82 var diags tfdiags.Diagnostics 83 84 newDiags := loadProviderSchemas(schemas.Providers, config, state, plugins) 85 diags = diags.Append(newDiags) 86 newDiags = loadProvisionerSchemas(schemas.Provisioners, config, plugins) 87 diags = diags.Append(newDiags) 88 89 return schemas, diags.Err() 90 } 91 92 func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics { 93 var diags tfdiags.Diagnostics 94 95 ensure := func(fqn addrs.Provider) { 96 name := fqn.String() 97 98 if _, exists := schemas[fqn]; exists { 99 return 100 } 101 102 log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name) 103 schema, err := plugins.ProviderSchema(fqn) 104 if err != nil { 105 // We'll put a stub in the map so we won't re-attempt this on 106 // future calls, which would then repeat the same error message 107 // multiple times. 108 schemas[fqn] = &ProviderSchema{} 109 diags = diags.Append( 110 tfdiags.Sourceless( 111 tfdiags.Error, 112 "Failed to obtain provider schema", 113 fmt.Sprintf("Could not load the schema for provider %s: %s.", fqn, err), 114 ), 115 ) 116 return 117 } 118 119 schemas[fqn] = schema 120 } 121 122 if config != nil { 123 for _, fqn := range config.ProviderTypes() { 124 ensure(fqn) 125 } 126 } 127 128 if state != nil { 129 needed := providers.AddressedTypesAbs(state.ProviderAddrs()) 130 for _, typeAddr := range needed { 131 ensure(typeAddr) 132 } 133 } 134 135 return diags 136 } 137 138 func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, plugins *contextPlugins) tfdiags.Diagnostics { 139 var diags tfdiags.Diagnostics 140 141 ensure := func(name string) { 142 if _, exists := schemas[name]; exists { 143 return 144 } 145 146 log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name) 147 schema, err := plugins.ProvisionerSchema(name) 148 if err != nil { 149 // We'll put a stub in the map so we won't re-attempt this on 150 // future calls, which would then repeat the same error message 151 // multiple times. 152 schemas[name] = &configschema.Block{} 153 diags = diags.Append( 154 tfdiags.Sourceless( 155 tfdiags.Error, 156 "Failed to obtain provisioner schema", 157 fmt.Sprintf("Could not load the schema for provisioner %q: %s.", name, err), 158 ), 159 ) 160 return 161 } 162 163 schemas[name] = schema 164 } 165 166 if config != nil { 167 for _, rc := range config.Module.ManagedResources { 168 for _, pc := range rc.Managed.Provisioners { 169 ensure(pc.Type) 170 } 171 } 172 173 // Must also visit our child modules, recursively. 174 for _, cc := range config.Children { 175 childDiags := loadProvisionerSchemas(schemas, cc, plugins) 176 diags = diags.Append(childDiags) 177 } 178 } 179 180 return diags 181 } 182 183 // ProviderSchema represents the schema for a provider's own configuration 184 // and the configuration for some or all of its resources and data sources. 185 // 186 // The completeness of this structure depends on how it was constructed. 187 // When constructed for a configuration, it will generally include only 188 // resource types and data sources used by that configuration. 189 type ProviderSchema struct { 190 Provider *configschema.Block 191 ProviderMeta *configschema.Block 192 ResourceTypes map[string]*configschema.Block 193 DataSources map[string]*configschema.Block 194 195 ResourceTypeSchemaVersions map[string]uint64 196 } 197 198 // SchemaForResourceType attempts to find a schema for the given mode and type. 199 // Returns nil if no such schema is available. 200 func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) { 201 switch mode { 202 case addrs.ManagedResourceMode: 203 return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName] 204 case addrs.DataResourceMode: 205 // Data resources don't have schema versions right now, since state is discarded for each refresh 206 return ps.DataSources[typeName], 0 207 default: 208 // Shouldn't happen, because the above cases are comprehensive. 209 return nil, 0 210 } 211 } 212 213 // SchemaForResourceAddr attempts to find a schema for the mode and type from 214 // the given resource address. Returns nil if no such schema is available. 215 func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) { 216 return ps.SchemaForResourceType(addr.Mode, addr.Type) 217 }