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