github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  	meta interface{}
    54  
    55  	stopCtx       context.Context
    56  	stopCtxCancel context.CancelFunc
    57  	stopOnce      sync.Once
    58  }
    59  
    60  // ConfigureFunc is the function used to configure a Provider.
    61  //
    62  // The interface{} value returned by this function is stored and passed into
    63  // the subsequent resources as the meta parameter. This return value is
    64  // usually used to pass along a configured API client, a configuration
    65  // structure, etc.
    66  type ConfigureFunc func(*ResourceData) (interface{}, error)
    67  
    68  // InternalValidate should be called to validate the structure
    69  // of the provider.
    70  //
    71  // This should be called in a unit test for any provider to verify
    72  // before release that a provider is properly configured for use with
    73  // this library.
    74  func (p *Provider) InternalValidate() error {
    75  	if p == nil {
    76  		return errors.New("provider is nil")
    77  	}
    78  
    79  	var validationErrors error
    80  	sm := schemaMap(p.Schema)
    81  	if err := sm.InternalValidate(sm); err != nil {
    82  		validationErrors = multierror.Append(validationErrors, err)
    83  	}
    84  
    85  	for k, r := range p.ResourcesMap {
    86  		if err := r.InternalValidate(nil, true); err != nil {
    87  			validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err))
    88  		}
    89  	}
    90  
    91  	for k, r := range p.DataSourcesMap {
    92  		if err := r.InternalValidate(nil, false); err != nil {
    93  			validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %s", k, err))
    94  		}
    95  	}
    96  
    97  	return validationErrors
    98  }
    99  
   100  // Meta returns the metadata associated with this provider that was
   101  // returned by the Configure call. It will be nil until Configure is called.
   102  func (p *Provider) Meta() interface{} {
   103  	return p.meta
   104  }
   105  
   106  // SetMeta can be used to forcefully set the Meta object of the provider.
   107  // Note that if Configure is called the return value will override anything
   108  // set here.
   109  func (p *Provider) SetMeta(v interface{}) {
   110  	p.meta = v
   111  }
   112  
   113  // Stopped reports whether the provider has been stopped or not.
   114  func (p *Provider) Stopped() bool {
   115  	ctx := p.StopContext()
   116  	select {
   117  	case <-ctx.Done():
   118  		return true
   119  	default:
   120  		return false
   121  	}
   122  }
   123  
   124  // StopCh returns a channel that is closed once the provider is stopped.
   125  func (p *Provider) StopContext() context.Context {
   126  	p.stopOnce.Do(p.stopInit)
   127  	return p.stopCtx
   128  }
   129  
   130  func (p *Provider) stopInit() {
   131  	p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background())
   132  }
   133  
   134  // Stop implementation of terraform.ResourceProvider interface.
   135  func (p *Provider) Stop() error {
   136  	p.stopOnce.Do(p.stopInit)
   137  	p.stopCtxCancel()
   138  	return nil
   139  }
   140  
   141  // Input implementation of terraform.ResourceProvider interface.
   142  func (p *Provider) Input(
   143  	input terraform.UIInput,
   144  	c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
   145  	return schemaMap(p.Schema).Input(input, c)
   146  }
   147  
   148  // Validate implementation of terraform.ResourceProvider interface.
   149  func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
   150  	if err := p.InternalValidate(); err != nil {
   151  		return nil, []error{fmt.Errorf(
   152  			"Internal validation of the provider failed! This is always a bug\n"+
   153  				"with the provider itself, and not a user issue. Please report\n"+
   154  				"this bug:\n\n%s", err)}
   155  	}
   156  
   157  	return schemaMap(p.Schema).Validate(c)
   158  }
   159  
   160  // ValidateResource implementation of terraform.ResourceProvider interface.
   161  func (p *Provider) ValidateResource(
   162  	t string, c *terraform.ResourceConfig) ([]string, []error) {
   163  	r, ok := p.ResourcesMap[t]
   164  	if !ok {
   165  		return nil, []error{fmt.Errorf(
   166  			"Provider doesn't support resource: %s", t)}
   167  	}
   168  
   169  	return r.Validate(c)
   170  }
   171  
   172  // Configure implementation of terraform.ResourceProvider interface.
   173  func (p *Provider) Configure(c *terraform.ResourceConfig) error {
   174  	// No configuration
   175  	if p.ConfigureFunc == nil {
   176  		return nil
   177  	}
   178  
   179  	sm := schemaMap(p.Schema)
   180  
   181  	// Get a ResourceData for this configuration. To do this, we actually
   182  	// generate an intermediary "diff" although that is never exposed.
   183  	diff, err := sm.Diff(nil, c)
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	data, err := sm.Data(nil, diff)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	meta, err := p.ConfigureFunc(data)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	p.meta = meta
   199  	return nil
   200  }
   201  
   202  // Apply implementation of terraform.ResourceProvider interface.
   203  func (p *Provider) Apply(
   204  	info *terraform.InstanceInfo,
   205  	s *terraform.InstanceState,
   206  	d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   207  	r, ok := p.ResourcesMap[info.Type]
   208  	if !ok {
   209  		return nil, fmt.Errorf("unknown resource type: %s", info.Type)
   210  	}
   211  
   212  	return r.Apply(s, d, p.meta)
   213  }
   214  
   215  // Diff implementation of terraform.ResourceProvider interface.
   216  func (p *Provider) Diff(
   217  	info *terraform.InstanceInfo,
   218  	s *terraform.InstanceState,
   219  	c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   220  	r, ok := p.ResourcesMap[info.Type]
   221  	if !ok {
   222  		return nil, fmt.Errorf("unknown resource type: %s", info.Type)
   223  	}
   224  
   225  	return r.Diff(s, c)
   226  }
   227  
   228  // Refresh implementation of terraform.ResourceProvider interface.
   229  func (p *Provider) Refresh(
   230  	info *terraform.InstanceInfo,
   231  	s *terraform.InstanceState) (*terraform.InstanceState, error) {
   232  	r, ok := p.ResourcesMap[info.Type]
   233  	if !ok {
   234  		return nil, fmt.Errorf("unknown resource type: %s", info.Type)
   235  	}
   236  
   237  	return r.Refresh(s, p.meta)
   238  }
   239  
   240  // Resources implementation of terraform.ResourceProvider interface.
   241  func (p *Provider) Resources() []terraform.ResourceType {
   242  	keys := make([]string, 0, len(p.ResourcesMap))
   243  	for k, _ := range p.ResourcesMap {
   244  		keys = append(keys, k)
   245  	}
   246  	sort.Strings(keys)
   247  
   248  	result := make([]terraform.ResourceType, 0, len(keys))
   249  	for _, k := range keys {
   250  		resource := p.ResourcesMap[k]
   251  
   252  		// This isn't really possible (it'd fail InternalValidate), but
   253  		// we do it anyways to avoid a panic.
   254  		if resource == nil {
   255  			resource = &Resource{}
   256  		}
   257  
   258  		result = append(result, terraform.ResourceType{
   259  			Name:       k,
   260  			Importable: resource.Importer != nil,
   261  		})
   262  	}
   263  
   264  	return result
   265  }
   266  
   267  func (p *Provider) ImportState(
   268  	info *terraform.InstanceInfo,
   269  	id string) ([]*terraform.InstanceState, error) {
   270  	// Find the resource
   271  	r, ok := p.ResourcesMap[info.Type]
   272  	if !ok {
   273  		return nil, fmt.Errorf("unknown resource type: %s", info.Type)
   274  	}
   275  
   276  	// If it doesn't support import, error
   277  	if r.Importer == nil {
   278  		return nil, fmt.Errorf("resource %s doesn't support import", info.Type)
   279  	}
   280  
   281  	// Create the data
   282  	data := r.Data(nil)
   283  	data.SetId(id)
   284  	data.SetType(info.Type)
   285  
   286  	// Call the import function
   287  	results := []*ResourceData{data}
   288  	if r.Importer.State != nil {
   289  		var err error
   290  		results, err = r.Importer.State(data, p.meta)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  	}
   295  
   296  	// Convert the results to InstanceState values and return it
   297  	states := make([]*terraform.InstanceState, len(results))
   298  	for i, r := range results {
   299  		states[i] = r.State()
   300  	}
   301  
   302  	// Verify that all are non-nil. If there are any nil the error
   303  	// isn't obvious so we circumvent that with a friendlier error.
   304  	for _, s := range states {
   305  		if s == nil {
   306  			return nil, fmt.Errorf(
   307  				"nil entry in ImportState results. This is always a bug with\n" +
   308  					"the resource that is being imported. Please report this as\n" +
   309  					"a bug to Terraform.")
   310  		}
   311  	}
   312  
   313  	return states, nil
   314  }
   315  
   316  // ValidateDataSource implementation of terraform.ResourceProvider interface.
   317  func (p *Provider) ValidateDataSource(
   318  	t string, c *terraform.ResourceConfig) ([]string, []error) {
   319  	r, ok := p.DataSourcesMap[t]
   320  	if !ok {
   321  		return nil, []error{fmt.Errorf(
   322  			"Provider doesn't support data source: %s", t)}
   323  	}
   324  
   325  	return r.Validate(c)
   326  }
   327  
   328  // ReadDataDiff implementation of terraform.ResourceProvider interface.
   329  func (p *Provider) ReadDataDiff(
   330  	info *terraform.InstanceInfo,
   331  	c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
   332  
   333  	r, ok := p.DataSourcesMap[info.Type]
   334  	if !ok {
   335  		return nil, fmt.Errorf("unknown data source: %s", info.Type)
   336  	}
   337  
   338  	return r.Diff(nil, c)
   339  }
   340  
   341  // RefreshData implementation of terraform.ResourceProvider interface.
   342  func (p *Provider) ReadDataApply(
   343  	info *terraform.InstanceInfo,
   344  	d *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   345  
   346  	r, ok := p.DataSourcesMap[info.Type]
   347  	if !ok {
   348  		return nil, fmt.Errorf("unknown data source: %s", info.Type)
   349  	}
   350  
   351  	return r.ReadDataApply(d, p.meta)
   352  }
   353  
   354  // DataSources implementation of terraform.ResourceProvider interface.
   355  func (p *Provider) DataSources() []terraform.DataSource {
   356  	keys := make([]string, 0, len(p.DataSourcesMap))
   357  	for k, _ := range p.DataSourcesMap {
   358  		keys = append(keys, k)
   359  	}
   360  	sort.Strings(keys)
   361  
   362  	result := make([]terraform.DataSource, 0, len(keys))
   363  	for _, k := range keys {
   364  		result = append(result, terraform.DataSource{
   365  			Name: k,
   366  		})
   367  	}
   368  
   369  	return result
   370  }