| package schema |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "sort" |
| "sync" |
| |
| multierror "github.com/hashicorp/go-multierror" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/legacy/terraform" |
| ) |
| |
| var ReservedProviderFields = []string{ |
| "alias", |
| "version", |
| } |
| |
| // Provider represents a resource provider in Terraform, and properly |
| // implements all of the ResourceProvider API. |
| // |
| // By defining a schema for the configuration of the provider, the |
| // map of supporting resources, and a configuration function, the schema |
| // framework takes over and handles all the provider operations for you. |
| // |
| // After defining the provider structure, it is unlikely that you'll require any |
| // of the methods on Provider itself. |
| type Provider struct { |
| // Schema is the schema for the configuration of this provider. If this |
| // provider has no configuration, this can be omitted. |
| // |
| // The keys of this map are the configuration keys, and the value is |
| // the schema describing the value of the configuration. |
| Schema map[string]*Schema |
| |
| // ResourcesMap is the list of available resources that this provider |
| // can manage, along with their Resource structure defining their |
| // own schemas and CRUD operations. |
| // |
| // Provider automatically handles routing operations such as Apply, |
| // Diff, etc. to the proper resource. |
| ResourcesMap map[string]*Resource |
| |
| // DataSourcesMap is the collection of available data sources that |
| // this provider implements, with a Resource instance defining |
| // the schema and Read operation of each. |
| // |
| // Resource instances for data sources must have a Read function |
| // and must *not* implement Create, Update or Delete. |
| DataSourcesMap map[string]*Resource |
| |
| // ProviderMetaSchema is the schema for the configuration of the meta |
| // information for this provider. If this provider has no meta info, |
| // this can be omitted. This functionality is currently experimental |
| // and subject to change or break without warning; it should only be |
| // used by providers that are collaborating on its use with the |
| // Terraform team. |
| ProviderMetaSchema map[string]*Schema |
| |
| // ConfigureFunc is a function for configuring the provider. If the |
| // provider doesn't need to be configured, this can be omitted. |
| // |
| // See the ConfigureFunc documentation for more information. |
| ConfigureFunc ConfigureFunc |
| |
| // MetaReset is called by TestReset to reset any state stored in the meta |
| // interface. This is especially important if the StopContext is stored by |
| // the provider. |
| MetaReset func() error |
| |
| meta interface{} |
| |
| // a mutex is required because TestReset can directly replace the stopCtx |
| stopMu sync.Mutex |
| stopCtx context.Context |
| stopCtxCancel context.CancelFunc |
| stopOnce sync.Once |
| |
| TerraformVersion string |
| } |
| |
| // ConfigureFunc is the function used to configure a Provider. |
| // |
| // The interface{} value returned by this function is stored and passed into |
| // the subsequent resources as the meta parameter. This return value is |
| // usually used to pass along a configured API client, a configuration |
| // structure, etc. |
| type ConfigureFunc func(*ResourceData) (interface{}, error) |
| |
| // InternalValidate should be called to validate the structure |
| // of the provider. |
| // |
| // This should be called in a unit test for any provider to verify |
| // before release that a provider is properly configured for use with |
| // this library. |
| func (p *Provider) InternalValidate() error { |
| if p == nil { |
| return errors.New("provider is nil") |
| } |
| |
| var validationErrors error |
| sm := schemaMap(p.Schema) |
| if err := sm.InternalValidate(sm); err != nil { |
| validationErrors = multierror.Append(validationErrors, err) |
| } |
| |
| // Provider-specific checks |
| for k, _ := range sm { |
| if isReservedProviderFieldName(k) { |
| return fmt.Errorf("%s is a reserved field name for a provider", k) |
| } |
| } |
| |
| for k, r := range p.ResourcesMap { |
| if err := r.InternalValidate(nil, true); err != nil { |
| validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err)) |
| } |
| } |
| |
| for k, r := range p.DataSourcesMap { |
| if err := r.InternalValidate(nil, false); err != nil { |
| validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %s", k, err)) |
| } |
| } |
| |
| return validationErrors |
| } |
| |
| func isReservedProviderFieldName(name string) bool { |
| for _, reservedName := range ReservedProviderFields { |
| if name == reservedName { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // Meta returns the metadata associated with this provider that was |
| // returned by the Configure call. It will be nil until Configure is called. |
| func (p *Provider) Meta() interface{} { |
| return p.meta |
| } |
| |
| // SetMeta can be used to forcefully set the Meta object of the provider. |
| // Note that if Configure is called the return value will override anything |
| // set here. |
| func (p *Provider) SetMeta(v interface{}) { |
| p.meta = v |
| } |
| |
| // Stopped reports whether the provider has been stopped or not. |
| func (p *Provider) Stopped() bool { |
| ctx := p.StopContext() |
| select { |
| case <-ctx.Done(): |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // StopCh returns a channel that is closed once the provider is stopped. |
| func (p *Provider) StopContext() context.Context { |
| p.stopOnce.Do(p.stopInit) |
| |
| p.stopMu.Lock() |
| defer p.stopMu.Unlock() |
| |
| return p.stopCtx |
| } |
| |
| func (p *Provider) stopInit() { |
| p.stopMu.Lock() |
| defer p.stopMu.Unlock() |
| |
| p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) |
| } |
| |
| // Stop implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Stop() error { |
| p.stopOnce.Do(p.stopInit) |
| |
| p.stopMu.Lock() |
| defer p.stopMu.Unlock() |
| |
| p.stopCtxCancel() |
| return nil |
| } |
| |
| // TestReset resets any state stored in the Provider, and will call TestReset |
| // on Meta if it implements the TestProvider interface. |
| // This may be used to reset the schema.Provider at the start of a test, and is |
| // automatically called by resource.Test. |
| func (p *Provider) TestReset() error { |
| p.stopInit() |
| if p.MetaReset != nil { |
| return p.MetaReset() |
| } |
| return nil |
| } |
| |
| // GetSchema implementation of terraform.ResourceProvider interface |
| func (p *Provider) GetSchema(req *terraform.ProviderSchemaRequest) (*terraform.ProviderSchema, error) { |
| resourceTypes := map[string]*configschema.Block{} |
| dataSources := map[string]*configschema.Block{} |
| |
| for _, name := range req.ResourceTypes { |
| if r, exists := p.ResourcesMap[name]; exists { |
| resourceTypes[name] = r.CoreConfigSchema() |
| } |
| } |
| for _, name := range req.DataSources { |
| if r, exists := p.DataSourcesMap[name]; exists { |
| dataSources[name] = r.CoreConfigSchema() |
| } |
| } |
| |
| return &terraform.ProviderSchema{ |
| Provider: schemaMap(p.Schema).CoreConfigSchema(), |
| ResourceTypes: resourceTypes, |
| DataSources: dataSources, |
| }, nil |
| } |
| |
| // Input implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Input( |
| input terraform.UIInput, |
| c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { |
| return schemaMap(p.Schema).Input(input, c) |
| } |
| |
| // Validate implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) { |
| if err := p.InternalValidate(); err != nil { |
| return nil, []error{fmt.Errorf( |
| "Internal validation of the provider failed! This is always a bug\n"+ |
| "with the provider itself, and not a user issue. Please report\n"+ |
| "this bug:\n\n%s", err)} |
| } |
| |
| return schemaMap(p.Schema).Validate(c) |
| } |
| |
| // ValidateResource implementation of terraform.ResourceProvider interface. |
| func (p *Provider) ValidateResource( |
| t string, c *terraform.ResourceConfig) ([]string, []error) { |
| r, ok := p.ResourcesMap[t] |
| if !ok { |
| return nil, []error{fmt.Errorf( |
| "Provider doesn't support resource: %s", t)} |
| } |
| |
| return r.Validate(c) |
| } |
| |
| // Configure implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Configure(c *terraform.ResourceConfig) error { |
| // No configuration |
| if p.ConfigureFunc == nil { |
| return nil |
| } |
| |
| sm := schemaMap(p.Schema) |
| |
| // Get a ResourceData for this configuration. To do this, we actually |
| // generate an intermediary "diff" although that is never exposed. |
| diff, err := sm.Diff(nil, c, nil, p.meta, true) |
| if err != nil { |
| return err |
| } |
| |
| data, err := sm.Data(nil, diff) |
| if err != nil { |
| return err |
| } |
| |
| meta, err := p.ConfigureFunc(data) |
| if err != nil { |
| return err |
| } |
| |
| p.meta = meta |
| return nil |
| } |
| |
| // Apply implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Apply( |
| info *terraform.InstanceInfo, |
| s *terraform.InstanceState, |
| d *terraform.InstanceDiff) (*terraform.InstanceState, error) { |
| r, ok := p.ResourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown resource type: %s", info.Type) |
| } |
| |
| return r.Apply(s, d, p.meta) |
| } |
| |
| // Diff implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Diff( |
| info *terraform.InstanceInfo, |
| s *terraform.InstanceState, |
| c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { |
| r, ok := p.ResourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown resource type: %s", info.Type) |
| } |
| |
| return r.Diff(s, c, p.meta) |
| } |
| |
| // SimpleDiff is used by the new protocol wrappers to get a diff that doesn't |
| // attempt to calculate ignore_changes. |
| func (p *Provider) SimpleDiff( |
| info *terraform.InstanceInfo, |
| s *terraform.InstanceState, |
| c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { |
| r, ok := p.ResourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown resource type: %s", info.Type) |
| } |
| |
| return r.simpleDiff(s, c, p.meta) |
| } |
| |
| // Refresh implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Refresh( |
| info *terraform.InstanceInfo, |
| s *terraform.InstanceState) (*terraform.InstanceState, error) { |
| r, ok := p.ResourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown resource type: %s", info.Type) |
| } |
| |
| return r.Refresh(s, p.meta) |
| } |
| |
| // Resources implementation of terraform.ResourceProvider interface. |
| func (p *Provider) Resources() []terraform.ResourceType { |
| keys := make([]string, 0, len(p.ResourcesMap)) |
| for k := range p.ResourcesMap { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| |
| result := make([]terraform.ResourceType, 0, len(keys)) |
| for _, k := range keys { |
| resource := p.ResourcesMap[k] |
| |
| // This isn't really possible (it'd fail InternalValidate), but |
| // we do it anyways to avoid a panic. |
| if resource == nil { |
| resource = &Resource{} |
| } |
| |
| result = append(result, terraform.ResourceType{ |
| Name: k, |
| Importable: resource.Importer != nil, |
| |
| // Indicates that a provider is compiled against a new enough |
| // version of core to support the GetSchema method. |
| SchemaAvailable: true, |
| }) |
| } |
| |
| return result |
| } |
| |
| func (p *Provider) ImportState( |
| info *terraform.InstanceInfo, |
| id string) ([]*terraform.InstanceState, error) { |
| // Find the resource |
| r, ok := p.ResourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown resource type: %s", info.Type) |
| } |
| |
| // If it doesn't support import, error |
| if r.Importer == nil { |
| return nil, fmt.Errorf("resource %s doesn't support import", info.Type) |
| } |
| |
| // Create the data |
| data := r.Data(nil) |
| data.SetId(id) |
| data.SetType(info.Type) |
| |
| // Call the import function |
| results := []*ResourceData{data} |
| if r.Importer.State != nil { |
| var err error |
| results, err = r.Importer.State(data, p.meta) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| // Convert the results to InstanceState values and return it |
| states := make([]*terraform.InstanceState, len(results)) |
| for i, r := range results { |
| states[i] = r.State() |
| } |
| |
| // Verify that all are non-nil. If there are any nil the error |
| // isn't obvious so we circumvent that with a friendlier error. |
| for _, s := range states { |
| if s == nil { |
| return nil, fmt.Errorf( |
| "nil entry in ImportState results. This is always a bug with\n" + |
| "the resource that is being imported. Please report this as\n" + |
| "a bug to Terraform.") |
| } |
| } |
| |
| return states, nil |
| } |
| |
| // ValidateDataSource implementation of terraform.ResourceProvider interface. |
| func (p *Provider) ValidateDataSource( |
| t string, c *terraform.ResourceConfig) ([]string, []error) { |
| r, ok := p.DataSourcesMap[t] |
| if !ok { |
| return nil, []error{fmt.Errorf( |
| "Provider doesn't support data source: %s", t)} |
| } |
| |
| return r.Validate(c) |
| } |
| |
| // ReadDataDiff implementation of terraform.ResourceProvider interface. |
| func (p *Provider) ReadDataDiff( |
| info *terraform.InstanceInfo, |
| c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { |
| |
| r, ok := p.DataSourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown data source: %s", info.Type) |
| } |
| |
| return r.Diff(nil, c, p.meta) |
| } |
| |
| // RefreshData implementation of terraform.ResourceProvider interface. |
| func (p *Provider) ReadDataApply( |
| info *terraform.InstanceInfo, |
| d *terraform.InstanceDiff) (*terraform.InstanceState, error) { |
| |
| r, ok := p.DataSourcesMap[info.Type] |
| if !ok { |
| return nil, fmt.Errorf("unknown data source: %s", info.Type) |
| } |
| |
| return r.ReadDataApply(d, p.meta) |
| } |
| |
| // DataSources implementation of terraform.ResourceProvider interface. |
| func (p *Provider) DataSources() []terraform.DataSource { |
| keys := make([]string, 0, len(p.DataSourcesMap)) |
| for k, _ := range p.DataSourcesMap { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| |
| result := make([]terraform.DataSource, 0, len(keys)) |
| for _, k := range keys { |
| result = append(result, terraform.DataSource{ |
| Name: k, |
| |
| // Indicates that a provider is compiled against a new enough |
| // version of core to support the GetSchema method. |
| SchemaAvailable: true, |
| }) |
| } |
| |
| return result |
| } |