github.com/opentofu/opentofu@v1.7.1/internal/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]providers.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) providers.ProviderSchema { 33 return ss.Providers[provider] 34 } 35 36 // ProviderConfig returns the schema for the provider configuration of the 37 // given provider type, or nil if no such schema is available. 38 func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block { 39 return ss.ProviderSchema(provider).Provider.Block 40 } 41 42 // ResourceTypeConfig returns the schema for the configuration of a given 43 // resource type belonging to a given provider type, or nil of no such 44 // schema is available. 45 // 46 // In many cases the provider type is inferrable from the resource type name, 47 // but this is not always true because users can override the provider for 48 // a resource using the "provider" meta-argument. Therefore it's important to 49 // always pass the correct provider name, even though it many cases it feels 50 // redundant. 51 func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { 52 ps := ss.ProviderSchema(provider) 53 if ps.ResourceTypes == nil { 54 return nil, 0 55 } 56 return ps.SchemaForResourceType(resourceMode, resourceType) 57 } 58 59 // ProvisionerConfig returns the schema for the configuration of a given 60 // provisioner, or nil of no such schema is available. 61 func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { 62 return ss.Provisioners[name] 63 } 64 65 // loadSchemas searches the given configuration, state and plan (any of which 66 // may be nil) for constructs that have an associated schema, requests the 67 // necessary schemas from the given component factory (which must _not_ be nil), 68 // and returns a single object representing all of the necessary schemas. 69 // 70 // If an error is returned, it may be a wrapped tfdiags.Diagnostics describing 71 // errors across multiple separate objects. Errors here will usually indicate 72 // either misbehavior on the part of one of the providers or of the provider 73 // protocol itself. When returned with errors, the returned schemas object is 74 // still valid but may be incomplete. 75 func loadSchemas(config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) { 76 schemas := &Schemas{ 77 Providers: map[addrs.Provider]providers.ProviderSchema{}, 78 Provisioners: map[string]*configschema.Block{}, 79 } 80 var diags tfdiags.Diagnostics 81 82 newDiags := loadProviderSchemas(schemas.Providers, config, state, plugins) 83 diags = diags.Append(newDiags) 84 newDiags = loadProvisionerSchemas(schemas.Provisioners, config, plugins) 85 diags = diags.Append(newDiags) 86 87 return schemas, diags.Err() 88 } 89 90 func loadProviderSchemas(schemas map[addrs.Provider]providers.ProviderSchema, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics { 91 var diags tfdiags.Diagnostics 92 93 ensure := func(fqn addrs.Provider) { 94 name := fqn.String() 95 96 if _, exists := schemas[fqn]; exists { 97 return 98 } 99 100 log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name) 101 schema, err := plugins.ProviderSchema(fqn) 102 if err != nil { 103 // We'll put a stub in the map so we won't re-attempt this on 104 // future calls, which would then repeat the same error message 105 // multiple times. 106 schemas[fqn] = providers.ProviderSchema{} 107 diags = diags.Append( 108 tfdiags.Sourceless( 109 tfdiags.Error, 110 "Failed to obtain provider schema", 111 fmt.Sprintf("Could not load the schema for provider %s: %s.", fqn, err), 112 ), 113 ) 114 return 115 } 116 117 schemas[fqn] = schema 118 } 119 120 if config != nil { 121 for _, fqn := range config.ProviderTypes() { 122 ensure(fqn) 123 } 124 } 125 126 if state != nil { 127 needed := providers.AddressedTypesAbs(state.ProviderAddrs()) 128 for _, typeAddr := range needed { 129 ensure(typeAddr) 130 } 131 } 132 133 return diags 134 } 135 136 func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, plugins *contextPlugins) tfdiags.Diagnostics { 137 var diags tfdiags.Diagnostics 138 139 ensure := func(name string) { 140 if _, exists := schemas[name]; exists { 141 return 142 } 143 144 log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name) 145 schema, err := plugins.ProvisionerSchema(name) 146 if err != nil { 147 // We'll put a stub in the map so we won't re-attempt this on 148 // future calls, which would then repeat the same error message 149 // multiple times. 150 schemas[name] = &configschema.Block{} 151 diags = diags.Append( 152 tfdiags.Sourceless( 153 tfdiags.Error, 154 "Failed to obtain provisioner schema", 155 fmt.Sprintf("Could not load the schema for provisioner %q: %s.", name, err), 156 ), 157 ) 158 return 159 } 160 161 schemas[name] = schema 162 } 163 164 if config != nil { 165 for _, rc := range config.Module.ManagedResources { 166 for _, pc := range rc.Managed.Provisioners { 167 ensure(pc.Type) 168 } 169 } 170 171 // Must also visit our child modules, recursively. 172 for _, cc := range config.Children { 173 childDiags := loadProvisionerSchemas(schemas, cc, plugins) 174 diags = diags.Append(childDiags) 175 } 176 } 177 178 return diags 179 }