]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package terraform |
2 | ||
3 | import ( | |
107c1cdb ND |
4 | "fmt" |
5 | "log" | |
6 | ||
7 | "github.com/hashicorp/terraform/addrs" | |
8 | "github.com/hashicorp/terraform/configs" | |
9 | "github.com/hashicorp/terraform/configs/configschema" | |
10 | "github.com/hashicorp/terraform/providers" | |
11 | "github.com/hashicorp/terraform/states" | |
12 | "github.com/hashicorp/terraform/tfdiags" | |
15c0b25d AP |
13 | ) |
14 | ||
107c1cdb ND |
15 | // Schemas is a container for various kinds of schema that Terraform needs |
16 | // during processing. | |
15c0b25d | 17 | type Schemas struct { |
107c1cdb ND |
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] | |
15c0b25d AP |
65 | } |
66 | ||
107c1cdb ND |
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. | |
15c0b25d | 71 | // |
107c1cdb ND |
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 | } | |
15c0b25d AP |
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 | |
107c1cdb ND |
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) | |
15c0b25d AP |
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 | } |