github.com/sylr/terraform@v0.11.12-beta1/helper/schema/provider.go (about)

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