github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/provider.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 schema 7 8 import ( 9 "context" 10 "errors" 11 "fmt" 12 "sort" 13 "sync" 14 15 multierror "github.com/hashicorp/go-multierror" 16 "github.com/opentofu/opentofu/internal/configs/configschema" 17 "github.com/opentofu/opentofu/internal/legacy/tofu" 18 ) 19 20 var ReservedProviderFields = []string{ 21 "alias", 22 "version", 23 } 24 25 // Provider represents a resource provider in OpenTofu, and properly 26 // implements all of the ResourceProvider API. 27 // 28 // By defining a schema for the configuration of the provider, the 29 // map of supporting resources, and a configuration function, the schema 30 // framework takes over and handles all the provider operations for you. 31 // 32 // After defining the provider structure, it is unlikely that you'll require any 33 // of the methods on Provider itself. 34 type Provider struct { 35 // Schema is the schema for the configuration of this provider. If this 36 // provider has no configuration, this can be omitted. 37 // 38 // The keys of this map are the configuration keys, and the value is 39 // the schema describing the value of the configuration. 40 Schema map[string]*Schema 41 42 // ResourcesMap is the list of available resources that this provider 43 // can manage, along with their Resource structure defining their 44 // own schemas and CRUD operations. 45 // 46 // Provider automatically handles routing operations such as Apply, 47 // Diff, etc. to the proper resource. 48 ResourcesMap map[string]*Resource 49 50 // DataSourcesMap is the collection of available data sources that 51 // this provider implements, with a Resource instance defining 52 // the schema and Read operation of each. 53 // 54 // Resource instances for data sources must have a Read function 55 // and must *not* implement Create, Update or Delete. 56 DataSourcesMap map[string]*Resource 57 58 // ProviderMetaSchema is the schema for the configuration of the meta 59 // information for this provider. If this provider has no meta info, 60 // this can be omitted. This functionality is currently experimental 61 // and subject to change or break without warning; it should only be 62 // used by providers that are collaborating on its use with the 63 // OpenTofu team. 64 ProviderMetaSchema map[string]*Schema 65 66 // ConfigureFunc is a function for configuring the provider. If the 67 // provider doesn't need to be configured, this can be omitted. 68 // 69 // See the ConfigureFunc documentation for more information. 70 ConfigureFunc ConfigureFunc 71 72 // MetaReset is called by TestReset to reset any state stored in the meta 73 // interface. This is especially important if the StopContext is stored by 74 // the provider. 75 MetaReset func() error 76 77 meta interface{} 78 79 // a mutex is required because TestReset can directly replace the stopCtx 80 stopMu sync.Mutex 81 stopCtx context.Context 82 stopCtxCancel context.CancelFunc 83 stopOnce sync.Once 84 85 TerraformVersion string 86 } 87 88 // ConfigureFunc is the function used to configure a Provider. 89 // 90 // The interface{} value returned by this function is stored and passed into 91 // the subsequent resources as the meta parameter. This return value is 92 // usually used to pass along a configured API client, a configuration 93 // structure, etc. 94 type ConfigureFunc func(*ResourceData) (interface{}, error) 95 96 // InternalValidate should be called to validate the structure 97 // of the provider. 98 // 99 // This should be called in a unit test for any provider to verify 100 // before release that a provider is properly configured for use with 101 // this library. 102 func (p *Provider) InternalValidate() error { 103 if p == nil { 104 return errors.New("provider is nil") 105 } 106 107 var validationErrors error 108 sm := schemaMap(p.Schema) 109 if err := sm.InternalValidate(sm); err != nil { 110 validationErrors = multierror.Append(validationErrors, err) 111 } 112 113 // Provider-specific checks 114 for k, _ := range sm { 115 if isReservedProviderFieldName(k) { 116 return fmt.Errorf("%s is a reserved field name for a provider", k) 117 } 118 } 119 120 for k, r := range p.ResourcesMap { 121 if err := r.InternalValidate(nil, true); err != nil { 122 validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %w", k, err)) 123 } 124 } 125 126 for k, r := range p.DataSourcesMap { 127 if err := r.InternalValidate(nil, false); err != nil { 128 validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %w", k, err)) 129 } 130 } 131 132 return validationErrors 133 } 134 135 func isReservedProviderFieldName(name string) bool { 136 for _, reservedName := range ReservedProviderFields { 137 if name == reservedName { 138 return true 139 } 140 } 141 return false 142 } 143 144 // Meta returns the metadata associated with this provider that was 145 // returned by the Configure call. It will be nil until Configure is called. 146 func (p *Provider) Meta() interface{} { 147 return p.meta 148 } 149 150 // SetMeta can be used to forcefully set the Meta object of the provider. 151 // Note that if Configure is called the return value will override anything 152 // set here. 153 func (p *Provider) SetMeta(v interface{}) { 154 p.meta = v 155 } 156 157 // Stopped reports whether the provider has been stopped or not. 158 func (p *Provider) Stopped() bool { 159 ctx := p.StopContext() 160 select { 161 case <-ctx.Done(): 162 return true 163 default: 164 return false 165 } 166 } 167 168 // StopCh returns a channel that is closed once the provider is stopped. 169 func (p *Provider) StopContext() context.Context { 170 p.stopOnce.Do(p.stopInit) 171 172 p.stopMu.Lock() 173 defer p.stopMu.Unlock() 174 175 return p.stopCtx 176 } 177 178 func (p *Provider) stopInit() { 179 p.stopMu.Lock() 180 defer p.stopMu.Unlock() 181 182 p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) 183 } 184 185 // Stop implementation of tofu.ResourceProvider interface. 186 func (p *Provider) Stop() error { 187 p.stopOnce.Do(p.stopInit) 188 189 p.stopMu.Lock() 190 defer p.stopMu.Unlock() 191 192 p.stopCtxCancel() 193 return nil 194 } 195 196 // TestReset resets any state stored in the Provider, and will call TestReset 197 // on Meta if it implements the TestProvider interface. 198 // This may be used to reset the schema.Provider at the start of a test, and is 199 // automatically called by resource.Test. 200 func (p *Provider) TestReset() error { 201 p.stopInit() 202 if p.MetaReset != nil { 203 return p.MetaReset() 204 } 205 return nil 206 } 207 208 // GetSchema implementation of tofu.ResourceProvider interface 209 func (p *Provider) GetSchema(req *tofu.ProviderSchemaRequest) (*tofu.ProviderSchema, error) { 210 resourceTypes := map[string]*configschema.Block{} 211 dataSources := map[string]*configschema.Block{} 212 213 for _, name := range req.ResourceTypes { 214 if r, exists := p.ResourcesMap[name]; exists { 215 resourceTypes[name] = r.CoreConfigSchema() 216 } 217 } 218 for _, name := range req.DataSources { 219 if r, exists := p.DataSourcesMap[name]; exists { 220 dataSources[name] = r.CoreConfigSchema() 221 } 222 } 223 224 return &tofu.ProviderSchema{ 225 Provider: schemaMap(p.Schema).CoreConfigSchema(), 226 ResourceTypes: resourceTypes, 227 DataSources: dataSources, 228 }, nil 229 } 230 231 // Input implementation of tofu.ResourceProvider interface. 232 func (p *Provider) Input( 233 input tofu.UIInput, 234 c *tofu.ResourceConfig) (*tofu.ResourceConfig, error) { 235 return schemaMap(p.Schema).Input(input, c) 236 } 237 238 // Validate implementation of tofu.ResourceProvider interface. 239 func (p *Provider) Validate(c *tofu.ResourceConfig) ([]string, []error) { 240 if err := p.InternalValidate(); err != nil { 241 return nil, []error{fmt.Errorf( 242 "Internal validation of the provider failed! This is always a bug\n"+ 243 "with the provider itself, and not a user issue. Please report\n"+ 244 "this bug:\n\n%w", err)} 245 } 246 247 return schemaMap(p.Schema).Validate(c) 248 } 249 250 // ValidateResource implementation of tofu.ResourceProvider interface. 251 func (p *Provider) ValidateResource( 252 t string, c *tofu.ResourceConfig) ([]string, []error) { 253 r, ok := p.ResourcesMap[t] 254 if !ok { 255 return nil, []error{fmt.Errorf( 256 "Provider doesn't support resource: %s", t)} 257 } 258 259 return r.Validate(c) 260 } 261 262 // Configure implementation of tofu.ResourceProvider interface. 263 func (p *Provider) Configure(c *tofu.ResourceConfig) error { 264 // No configuration 265 if p.ConfigureFunc == nil { 266 return nil 267 } 268 269 sm := schemaMap(p.Schema) 270 271 // Get a ResourceData for this configuration. To do this, we actually 272 // generate an intermediary "diff" although that is never exposed. 273 diff, err := sm.Diff(nil, c, nil, p.meta, true) 274 if err != nil { 275 return err 276 } 277 278 data, err := sm.Data(nil, diff) 279 if err != nil { 280 return err 281 } 282 283 meta, err := p.ConfigureFunc(data) 284 if err != nil { 285 return err 286 } 287 288 p.meta = meta 289 return nil 290 } 291 292 // Apply implementation of tofu.ResourceProvider interface. 293 func (p *Provider) Apply( 294 info *tofu.InstanceInfo, 295 s *tofu.InstanceState, 296 d *tofu.InstanceDiff) (*tofu.InstanceState, error) { 297 r, ok := p.ResourcesMap[info.Type] 298 if !ok { 299 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 300 } 301 302 return r.Apply(s, d, p.meta) 303 } 304 305 // Diff implementation of tofu.ResourceProvider interface. 306 func (p *Provider) Diff( 307 info *tofu.InstanceInfo, 308 s *tofu.InstanceState, 309 c *tofu.ResourceConfig) (*tofu.InstanceDiff, error) { 310 r, ok := p.ResourcesMap[info.Type] 311 if !ok { 312 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 313 } 314 315 return r.Diff(s, c, p.meta) 316 } 317 318 // SimpleDiff is used by the new protocol wrappers to get a diff that doesn't 319 // attempt to calculate ignore_changes. 320 func (p *Provider) SimpleDiff( 321 info *tofu.InstanceInfo, 322 s *tofu.InstanceState, 323 c *tofu.ResourceConfig) (*tofu.InstanceDiff, error) { 324 r, ok := p.ResourcesMap[info.Type] 325 if !ok { 326 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 327 } 328 329 return r.simpleDiff(s, c, p.meta) 330 } 331 332 // Refresh implementation of tofu.ResourceProvider interface. 333 func (p *Provider) Refresh( 334 info *tofu.InstanceInfo, 335 s *tofu.InstanceState) (*tofu.InstanceState, error) { 336 r, ok := p.ResourcesMap[info.Type] 337 if !ok { 338 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 339 } 340 341 return r.Refresh(s, p.meta) 342 } 343 344 // Resources implementation of tofu.ResourceProvider interface. 345 func (p *Provider) Resources() []tofu.ResourceType { 346 keys := make([]string, 0, len(p.ResourcesMap)) 347 for k := range p.ResourcesMap { 348 keys = append(keys, k) 349 } 350 sort.Strings(keys) 351 352 result := make([]tofu.ResourceType, 0, len(keys)) 353 for _, k := range keys { 354 resource := p.ResourcesMap[k] 355 356 // This isn't really possible (it'd fail InternalValidate), but 357 // we do it anyways to avoid a panic. 358 if resource == nil { 359 resource = &Resource{} 360 } 361 362 result = append(result, tofu.ResourceType{ 363 Name: k, 364 Importable: resource.Importer != nil, 365 366 // Indicates that a provider is compiled against a new enough 367 // version of core to support the GetSchema method. 368 SchemaAvailable: true, 369 }) 370 } 371 372 return result 373 } 374 375 func (p *Provider) ImportState( 376 info *tofu.InstanceInfo, 377 id string) ([]*tofu.InstanceState, error) { 378 // Find the resource 379 r, ok := p.ResourcesMap[info.Type] 380 if !ok { 381 return nil, fmt.Errorf("unknown resource type: %s", info.Type) 382 } 383 384 // If it doesn't support import, error 385 if r.Importer == nil { 386 return nil, fmt.Errorf("resource %s doesn't support import", info.Type) 387 } 388 389 // Create the data 390 data := r.Data(nil) 391 data.SetId(id) 392 data.SetType(info.Type) 393 394 // Call the import function 395 results := []*ResourceData{data} 396 if r.Importer.State != nil { 397 var err error 398 results, err = r.Importer.State(data, p.meta) 399 if err != nil { 400 return nil, err 401 } 402 } 403 404 // Convert the results to InstanceState values and return it 405 states := make([]*tofu.InstanceState, len(results)) 406 for i, r := range results { 407 states[i] = r.State() 408 } 409 410 // Verify that all are non-nil. If there are any nil the error 411 // isn't obvious so we circumvent that with a friendlier error. 412 for _, s := range states { 413 if s == nil { 414 return nil, fmt.Errorf( 415 "nil entry in ImportState results. This is always a bug with\n" + 416 "the resource that is being imported. Please report this as\n" + 417 "a bug to OpenTofu.") 418 } 419 } 420 421 return states, nil 422 } 423 424 // ValidateDataSource implementation of tofu.ResourceProvider interface. 425 func (p *Provider) ValidateDataSource( 426 t string, c *tofu.ResourceConfig) ([]string, []error) { 427 r, ok := p.DataSourcesMap[t] 428 if !ok { 429 return nil, []error{fmt.Errorf( 430 "Provider doesn't support data source: %s", t)} 431 } 432 433 return r.Validate(c) 434 } 435 436 // ReadDataDiff implementation of tofu.ResourceProvider interface. 437 func (p *Provider) ReadDataDiff( 438 info *tofu.InstanceInfo, 439 c *tofu.ResourceConfig) (*tofu.InstanceDiff, error) { 440 441 r, ok := p.DataSourcesMap[info.Type] 442 if !ok { 443 return nil, fmt.Errorf("unknown data source: %s", info.Type) 444 } 445 446 return r.Diff(nil, c, p.meta) 447 } 448 449 // RefreshData implementation of tofu.ResourceProvider interface. 450 func (p *Provider) ReadDataApply( 451 info *tofu.InstanceInfo, 452 d *tofu.InstanceDiff) (*tofu.InstanceState, error) { 453 454 r, ok := p.DataSourcesMap[info.Type] 455 if !ok { 456 return nil, fmt.Errorf("unknown data source: %s", info.Type) 457 } 458 459 return r.ReadDataApply(d, p.meta) 460 } 461 462 // DataSources implementation of tofu.ResourceProvider interface. 463 func (p *Provider) DataSources() []tofu.DataSource { 464 keys := make([]string, 0, len(p.DataSourcesMap)) 465 for k, _ := range p.DataSourcesMap { 466 keys = append(keys, k) 467 } 468 sort.Strings(keys) 469 470 result := make([]tofu.DataSource, 0, len(keys)) 471 for _, k := range keys { 472 result = append(result, tofu.DataSource{ 473 Name: k, 474 475 // Indicates that a provider is compiled against a new enough 476 // version of core to support the GetSchema method. 477 SchemaAvailable: true, 478 }) 479 } 480 481 return result 482 }