github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/schemas.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 9 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 10 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/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[string]*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(typeName string) *ProviderSchema { 28 if ss.Providers == nil { 29 return nil 30 } 31 return ss.Providers[typeName] 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(typeName string) *configschema.Block { 37 ps := ss.ProviderSchema(typeName) 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(providerType string, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { 54 ps := ss.ProviderSchema(providerType) 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, components contextComponentFactory) (*Schemas, error) { 78 schemas := &Schemas{ 79 Providers: map[string]*ProviderSchema{}, 80 Provisioners: map[string]*configschema.Block{}, 81 } 82 var diags tfdiags.Diagnostics 83 84 newDiags := loadProviderSchemas(schemas.Providers, config, state, components) 85 diags = diags.Append(newDiags) 86 newDiags = loadProvisionerSchemas(schemas.Provisioners, config, components) 87 diags = diags.Append(newDiags) 88 89 return schemas, diags.Err() 90 } 91 92 func loadProviderSchemas(schemas map[string]*ProviderSchema, config *configs.Config, state *states.State, components contextComponentFactory) tfdiags.Diagnostics { 93 var diags tfdiags.Diagnostics 94 95 ensure := func(typeName string) { 96 if _, exists := schemas[typeName]; exists { 97 return 98 } 99 100 log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", typeName) 101 provider, err := components.ResourceProvider(typeName, "early/"+typeName) 102 if err != nil { 103 // We'll put a stub in the map so we won't re-attempt this on 104 // future calls. 105 schemas[typeName] = &ProviderSchema{} 106 diags = diags.Append( 107 fmt.Errorf("Failed to instantiate provider %q to obtain schema: %s", typeName, err), 108 ) 109 return 110 } 111 defer func() { 112 provider.Close() 113 }() 114 115 resp := provider.GetSchema() 116 if resp.Diagnostics.HasErrors() { 117 // We'll put a stub in the map so we won't re-attempt this on 118 // future calls. 119 schemas[typeName] = &ProviderSchema{} 120 diags = diags.Append( 121 fmt.Errorf("Failed to retrieve schema from provider %q: %s", typeName, resp.Diagnostics.Err()), 122 ) 123 return 124 } 125 126 s := &ProviderSchema{ 127 Provider: resp.Provider.Block, 128 ResourceTypes: make(map[string]*configschema.Block), 129 DataSources: make(map[string]*configschema.Block), 130 131 ResourceTypeSchemaVersions: make(map[string]uint64), 132 } 133 134 if resp.Provider.Version < 0 { 135 // We're not using the version numbers here yet, but we'll check 136 // for validity anyway in case we start using them in future. 137 diags = diags.Append( 138 fmt.Errorf("invalid negative schema version provider configuration for provider %q", typeName), 139 ) 140 } 141 142 for t, r := range resp.ResourceTypes { 143 s.ResourceTypes[t] = r.Block 144 s.ResourceTypeSchemaVersions[t] = uint64(r.Version) 145 if r.Version < 0 { 146 diags = diags.Append( 147 fmt.Errorf("invalid negative schema version for resource type %s in provider %q", t, typeName), 148 ) 149 } 150 } 151 152 for t, d := range resp.DataSources { 153 s.DataSources[t] = d.Block 154 if d.Version < 0 { 155 // We're not using the version numbers here yet, but we'll check 156 // for validity anyway in case we start using them in future. 157 diags = diags.Append( 158 fmt.Errorf("invalid negative schema version for data source %s in provider %q", t, typeName), 159 ) 160 } 161 } 162 163 schemas[typeName] = s 164 } 165 166 if config != nil { 167 for _, typeName := range config.ProviderTypes() { 168 ensure(typeName) 169 } 170 } 171 172 if state != nil { 173 needed := providers.AddressedTypesAbs(state.ProviderAddrs()) 174 for _, typeName := range needed { 175 ensure(typeName) 176 } 177 } 178 179 return diags 180 } 181 182 func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, components contextComponentFactory) tfdiags.Diagnostics { 183 var diags tfdiags.Diagnostics 184 185 ensure := func(name string) { 186 if _, exists := schemas[name]; exists { 187 return 188 } 189 190 log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name) 191 provisioner, err := components.ResourceProvisioner(name, "early/"+name) 192 if err != nil { 193 // We'll put a stub in the map so we won't re-attempt this on 194 // future calls. 195 schemas[name] = &configschema.Block{} 196 diags = diags.Append( 197 fmt.Errorf("Failed to instantiate provisioner %q to obtain schema: %s", name, err), 198 ) 199 return 200 } 201 defer func() { 202 if closer, ok := provisioner.(ResourceProvisionerCloser); ok { 203 closer.Close() 204 } 205 }() 206 207 resp := provisioner.GetSchema() 208 if resp.Diagnostics.HasErrors() { 209 // We'll put a stub in the map so we won't re-attempt this on 210 // future calls. 211 schemas[name] = &configschema.Block{} 212 diags = diags.Append( 213 fmt.Errorf("Failed to retrieve schema from provisioner %q: %s", name, resp.Diagnostics.Err()), 214 ) 215 return 216 } 217 218 schemas[name] = resp.Provisioner 219 } 220 221 if config != nil { 222 for _, rc := range config.Module.ManagedResources { 223 for _, pc := range rc.Managed.Provisioners { 224 ensure(pc.Type) 225 } 226 } 227 228 // Must also visit our child modules, recursively. 229 for _, cc := range config.Children { 230 childDiags := loadProvisionerSchemas(schemas, cc, components) 231 diags = diags.Append(childDiags) 232 } 233 } 234 235 return diags 236 } 237 238 // ProviderSchema represents the schema for a provider's own configuration 239 // and the configuration for some or all of its resources and data sources. 240 // 241 // The completeness of this structure depends on how it was constructed. 242 // When constructed for a configuration, it will generally include only 243 // resource types and data sources used by that configuration. 244 type ProviderSchema struct { 245 Provider *configschema.Block 246 ResourceTypes map[string]*configschema.Block 247 DataSources map[string]*configschema.Block 248 249 ResourceTypeSchemaVersions map[string]uint64 250 } 251 252 // SchemaForResourceType attempts to find a schema for the given mode and type. 253 // Returns nil if no such schema is available. 254 func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) { 255 switch mode { 256 case addrs.ManagedResourceMode: 257 return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName] 258 case addrs.DataResourceMode: 259 // Data resources don't have schema versions right now, since state is discarded for each refresh 260 return ps.DataSources[typeName], 0 261 default: 262 // Shouldn't happen, because the above cases are comprehensive. 263 return nil, 0 264 } 265 } 266 267 // SchemaForResourceAddr attempts to find a schema for the mode and type from 268 // the given resource address. Returns nil if no such schema is available. 269 func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) { 270 return ps.SchemaForResourceType(addr.Mode, addr.Type) 271 } 272 273 // ProviderSchemaRequest is used to describe to a ResourceProvider which 274 // aspects of schema are required, when calling the GetSchema method. 275 type ProviderSchemaRequest struct { 276 ResourceTypes []string 277 DataSources []string 278 }