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