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