github.com/opentofu/opentofu@v1.7.1/internal/legacy/tofu/schemas.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "fmt" 10 "log" 11 12 "github.com/opentofu/opentofu/internal/addrs" 13 "github.com/opentofu/opentofu/internal/configs" 14 "github.com/opentofu/opentofu/internal/configs/configschema" 15 "github.com/opentofu/opentofu/internal/providers" 16 "github.com/opentofu/opentofu/internal/states" 17 "github.com/opentofu/opentofu/internal/tfdiags" 18 ) 19 20 // Schemas is a container for various kinds of schema that OpenTofu needs 21 // during processing. 22 type Schemas struct { 23 Providers map[addrs.Provider]*ProviderSchema 24 Provisioners map[string]*configschema.Block 25 } 26 27 // ProviderSchema returns the entire ProviderSchema object that was produced 28 // by the plugin for the given provider, or nil if no such schema is available. 29 // 30 // It's usually better to go use the more precise methods offered by type 31 // Schemas to handle this detail automatically. 32 func (ss *Schemas) ProviderSchema(provider addrs.Provider) *ProviderSchema { 33 if ss.Providers == nil { 34 return nil 35 } 36 return ss.Providers[provider] 37 } 38 39 // ProviderConfig returns the schema for the provider configuration of the 40 // given provider type, or nil if no such schema is available. 41 func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block { 42 ps := ss.ProviderSchema(provider) 43 if ps == nil { 44 return nil 45 } 46 return ps.Provider 47 } 48 49 // ResourceTypeConfig returns the schema for the configuration of a given 50 // resource type belonging to a given provider type, or nil of no such 51 // schema is available. 52 // 53 // In many cases the provider type is inferrable from the resource type name, 54 // but this is not always true because users can override the provider for 55 // a resource using the "provider" meta-argument. Therefore it's important to 56 // always pass the correct provider name, even though it many cases it feels 57 // redundant. 58 func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { 59 ps := ss.ProviderSchema(provider) 60 if ps == nil || ps.ResourceTypes == nil { 61 return nil, 0 62 } 63 return ps.SchemaForResourceType(resourceMode, resourceType) 64 } 65 66 // ProvisionerConfig returns the schema for the configuration of a given 67 // provisioner, or nil of no such schema is available. 68 func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { 69 return ss.Provisioners[name] 70 } 71 72 // LoadSchemas searches the given configuration, state and plan (any of which 73 // may be nil) for constructs that have an associated schema, requests the 74 // necessary schemas from the given component factory (which must _not_ be nil), 75 // and returns a single object representing all of the necessary schemas. 76 // 77 // If an error is returned, it may be a wrapped tfdiags.Diagnostics describing 78 // errors across multiple separate objects. Errors here will usually indicate 79 // either misbehavior on the part of one of the providers or of the provider 80 // protocol itself. When returned with errors, the returned schemas object is 81 // still valid but may be incomplete. 82 func LoadSchemas(config *configs.Config, state *states.State, components contextComponentFactory) (*Schemas, error) { 83 schemas := &Schemas{ 84 Providers: map[addrs.Provider]*ProviderSchema{}, 85 Provisioners: map[string]*configschema.Block{}, 86 } 87 var diags tfdiags.Diagnostics 88 89 newDiags := loadProviderSchemas(schemas.Providers, config, state, components) 90 diags = diags.Append(newDiags) 91 newDiags = loadProvisionerSchemas(schemas.Provisioners, config, components) 92 diags = diags.Append(newDiags) 93 94 return schemas, diags.Err() 95 } 96 97 func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, components contextComponentFactory) tfdiags.Diagnostics { 98 var diags tfdiags.Diagnostics 99 100 ensure := func(fqn addrs.Provider) { 101 name := fqn.String() 102 103 if _, exists := schemas[fqn]; exists { 104 return 105 } 106 107 log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name) 108 provider, err := components.ResourceProvider(fqn) 109 if err != nil { 110 // We'll put a stub in the map so we won't re-attempt this on 111 // future calls. 112 schemas[fqn] = &ProviderSchema{} 113 diags = diags.Append( 114 fmt.Errorf("Failed to instantiate provider %q to obtain schema: %w", name, err), 115 ) 116 return 117 } 118 defer func() { 119 provider.Close() 120 }() 121 122 resp := provider.GetProviderSchema() 123 if resp.Diagnostics.HasErrors() { 124 // We'll put a stub in the map so we won't re-attempt this on 125 // future calls. 126 schemas[fqn] = &ProviderSchema{} 127 diags = diags.Append( 128 fmt.Errorf("Failed to retrieve schema from provider %q: %w", name, resp.Diagnostics.Err()), 129 ) 130 return 131 } 132 133 s := &ProviderSchema{ 134 Provider: resp.Provider.Block, 135 ResourceTypes: make(map[string]*configschema.Block), 136 DataSources: make(map[string]*configschema.Block), 137 138 ResourceTypeSchemaVersions: make(map[string]uint64), 139 } 140 141 if resp.Provider.Version < 0 { 142 // We're not using the version numbers here yet, but we'll check 143 // for validity anyway in case we start using them in future. 144 diags = diags.Append( 145 fmt.Errorf("invalid negative schema version provider configuration for provider %q", name), 146 ) 147 } 148 149 for t, r := range resp.ResourceTypes { 150 s.ResourceTypes[t] = r.Block 151 s.ResourceTypeSchemaVersions[t] = uint64(r.Version) 152 if r.Version < 0 { 153 diags = diags.Append( 154 fmt.Errorf("invalid negative schema version for resource type %s in provider %q", t, name), 155 ) 156 } 157 } 158 159 for t, d := range resp.DataSources { 160 s.DataSources[t] = d.Block 161 if d.Version < 0 { 162 // We're not using the version numbers here yet, but we'll check 163 // for validity anyway in case we start using them in future. 164 diags = diags.Append( 165 fmt.Errorf("invalid negative schema version for data source %s in provider %q", t, name), 166 ) 167 } 168 } 169 170 schemas[fqn] = s 171 172 if resp.ProviderMeta.Block != nil { 173 s.ProviderMeta = resp.ProviderMeta.Block 174 } 175 } 176 177 if config != nil { 178 for _, fqn := range config.ProviderTypes() { 179 ensure(fqn) 180 } 181 } 182 183 if state != nil { 184 needed := providers.AddressedTypesAbs(state.ProviderAddrs()) 185 for _, typeAddr := range needed { 186 ensure(typeAddr) 187 } 188 } 189 190 return diags 191 } 192 193 func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, components contextComponentFactory) tfdiags.Diagnostics { 194 var diags tfdiags.Diagnostics 195 196 ensure := func(name string) { 197 if _, exists := schemas[name]; exists { 198 return 199 } 200 201 log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name) 202 provisioner, err := components.ResourceProvisioner(name) 203 if err != nil { 204 // We'll put a stub in the map so we won't re-attempt this on 205 // future calls. 206 schemas[name] = &configschema.Block{} 207 diags = diags.Append( 208 fmt.Errorf("Failed to instantiate provisioner %q to obtain schema: %w", name, err), 209 ) 210 return 211 } 212 defer func() { 213 if closer, ok := provisioner.(ResourceProvisionerCloser); ok { 214 closer.Close() 215 } 216 }() 217 218 resp := provisioner.GetSchema() 219 if resp.Diagnostics.HasErrors() { 220 // We'll put a stub in the map so we won't re-attempt this on 221 // future calls. 222 schemas[name] = &configschema.Block{} 223 diags = diags.Append( 224 fmt.Errorf("Failed to retrieve schema from provisioner %q: %w", name, resp.Diagnostics.Err()), 225 ) 226 return 227 } 228 229 schemas[name] = resp.Provisioner 230 } 231 232 if config != nil { 233 for _, rc := range config.Module.ManagedResources { 234 for _, pc := range rc.Managed.Provisioners { 235 ensure(pc.Type) 236 } 237 } 238 239 // Must also visit our child modules, recursively. 240 for _, cc := range config.Children { 241 childDiags := loadProvisionerSchemas(schemas, cc, components) 242 diags = diags.Append(childDiags) 243 } 244 } 245 246 return diags 247 } 248 249 // ProviderSchema represents the schema for a provider's own configuration 250 // and the configuration for some or all of its resources and data sources. 251 // 252 // The completeness of this structure depends on how it was constructed. 253 // When constructed for a configuration, it will generally include only 254 // resource types and data sources used by that configuration. 255 type ProviderSchema struct { 256 Provider *configschema.Block 257 ProviderMeta *configschema.Block 258 ResourceTypes map[string]*configschema.Block 259 DataSources map[string]*configschema.Block 260 261 ResourceTypeSchemaVersions map[string]uint64 262 } 263 264 // SchemaForResourceType attempts to find a schema for the given mode and type. 265 // Returns nil if no such schema is available. 266 func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) { 267 switch mode { 268 case addrs.ManagedResourceMode: 269 return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName] 270 case addrs.DataResourceMode: 271 // Data resources don't have schema versions right now, since state is discarded for each refresh 272 return ps.DataSources[typeName], 0 273 default: 274 // Shouldn't happen, because the above cases are comprehensive. 275 return nil, 0 276 } 277 } 278 279 // SchemaForResourceAddr attempts to find a schema for the mode and type from 280 // the given resource address. Returns nil if no such schema is available. 281 func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) { 282 return ps.SchemaForResourceType(addr.Mode, addr.Type) 283 } 284 285 // ProviderSchemaRequest is used to describe to a ResourceProvider which 286 // aspects of schema are required, when calling the GetSchema method. 287 type ProviderSchemaRequest struct { 288 ResourceTypes []string 289 DataSources []string 290 }