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