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