github.com/memsql/terraform@v0.7.0-rc2.0.20160706152241-21e2173e0a32/terraform/resource.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform/config"
    10  )
    11  
    12  // ResourceProvisionerConfig is used to pair a provisioner
    13  // with its provided configuration. This allows us to use singleton
    14  // instances of each ResourceProvisioner and to keep the relevant
    15  // configuration instead of instantiating a new Provisioner for each
    16  // resource.
    17  type ResourceProvisionerConfig struct {
    18  	Type        string
    19  	Provisioner ResourceProvisioner
    20  	Config      *ResourceConfig
    21  	RawConfig   *config.RawConfig
    22  	ConnInfo    *config.RawConfig
    23  }
    24  
    25  // Resource encapsulates a resource, its configuration, its provider,
    26  // its current state, and potentially a desired diff from the state it
    27  // wants to reach.
    28  type Resource struct {
    29  	// These are all used by the new EvalNode stuff.
    30  	Name       string
    31  	Type       string
    32  	CountIndex int
    33  
    34  	// These aren't really used anymore anywhere, but we keep them around
    35  	// since we haven't done a proper cleanup yet.
    36  	Id           string
    37  	Info         *InstanceInfo
    38  	Config       *ResourceConfig
    39  	Dependencies []string
    40  	Diff         *InstanceDiff
    41  	Provider     ResourceProvider
    42  	State        *InstanceState
    43  	Provisioners []*ResourceProvisionerConfig
    44  	Flags        ResourceFlag
    45  }
    46  
    47  // ResourceKind specifies what kind of instance we're working with, whether
    48  // its a primary instance, a tainted instance, or an orphan.
    49  type ResourceFlag byte
    50  
    51  const (
    52  	FlagPrimary ResourceFlag = 1 << iota
    53  	FlagTainted
    54  	FlagOrphan
    55  	FlagReplacePrimary
    56  	FlagDeposed
    57  )
    58  
    59  // InstanceInfo is used to hold information about the instance and/or
    60  // resource being modified.
    61  type InstanceInfo struct {
    62  	// Id is a unique name to represent this instance. This is not related
    63  	// to InstanceState.ID in any way.
    64  	Id string
    65  
    66  	// ModulePath is the complete path of the module containing this
    67  	// instance.
    68  	ModulePath []string
    69  
    70  	// Type is the resource type of this instance
    71  	Type string
    72  }
    73  
    74  // HumanId is a unique Id that is human-friendly and useful for UI elements.
    75  func (i *InstanceInfo) HumanId() string {
    76  	if len(i.ModulePath) <= 1 {
    77  		return i.Id
    78  	}
    79  
    80  	return fmt.Sprintf(
    81  		"module.%s.%s",
    82  		strings.Join(i.ModulePath[1:], "."),
    83  		i.Id)
    84  }
    85  
    86  // ResourceConfig holds the configuration given for a resource. This is
    87  // done instead of a raw `map[string]interface{}` type so that rich
    88  // methods can be added to it to make dealing with it easier.
    89  type ResourceConfig struct {
    90  	ComputedKeys []string
    91  	Raw          map[string]interface{}
    92  	Config       map[string]interface{}
    93  
    94  	raw *config.RawConfig
    95  }
    96  
    97  // NewResourceConfig creates a new ResourceConfig from a config.RawConfig.
    98  func NewResourceConfig(c *config.RawConfig) *ResourceConfig {
    99  	result := &ResourceConfig{raw: c}
   100  	result.interpolateForce()
   101  	return result
   102  }
   103  
   104  // CheckSet checks that the given list of configuration keys is
   105  // properly set. If not, errors are returned for each unset key.
   106  //
   107  // This is useful to be called in the Validate method of a ResourceProvider.
   108  func (c *ResourceConfig) CheckSet(keys []string) []error {
   109  	var errs []error
   110  
   111  	for _, k := range keys {
   112  		if !c.IsSet(k) {
   113  			errs = append(errs, fmt.Errorf("%s must be set", k))
   114  		}
   115  	}
   116  
   117  	return errs
   118  }
   119  
   120  // Get looks up a configuration value by key and returns the value.
   121  //
   122  // The second return value is true if the get was successful. Get will
   123  // not succeed if the value is being computed.
   124  func (c *ResourceConfig) Get(k string) (interface{}, bool) {
   125  	// First try to get it from c.Config since that has interpolated values
   126  	result, ok := c.get(k, c.Config)
   127  	if ok {
   128  		return result, ok
   129  	}
   130  
   131  	// Otherwise, just get it from the raw config
   132  	return c.get(k, c.Raw)
   133  }
   134  
   135  // GetRaw looks up a configuration value by key and returns the value,
   136  // from the raw, uninterpolated config.
   137  //
   138  // The second return value is true if the get was successful. Get will
   139  // not succeed if the value is being computed.
   140  func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
   141  	return c.get(k, c.Raw)
   142  }
   143  
   144  // IsComputed returns whether the given key is computed or not.
   145  func (c *ResourceConfig) IsComputed(k string) bool {
   146  	_, ok := c.get(k, c.Config)
   147  	_, okRaw := c.get(k, c.Raw)
   148  	return !ok && okRaw
   149  }
   150  
   151  // IsSet checks if the key in the configuration is set. A key is set if
   152  // it has a value or the value is being computed (is unknown currently).
   153  //
   154  // This function should be used rather than checking the keys of the
   155  // raw configuration itself, since a key may be omitted from the raw
   156  // configuration if it is being computed.
   157  func (c *ResourceConfig) IsSet(k string) bool {
   158  	if c == nil {
   159  		return false
   160  	}
   161  
   162  	for _, ck := range c.ComputedKeys {
   163  		if ck == k {
   164  			return true
   165  		}
   166  	}
   167  
   168  	if _, ok := c.Get(k); ok {
   169  		return true
   170  	}
   171  
   172  	return false
   173  }
   174  
   175  func (c *ResourceConfig) get(
   176  	k string, raw map[string]interface{}) (interface{}, bool) {
   177  	parts := strings.Split(k, ".")
   178  	if len(parts) == 1 && parts[0] == "" {
   179  		parts = nil
   180  	}
   181  
   182  	var current interface{} = raw
   183  	for _, part := range parts {
   184  		if current == nil {
   185  			return nil, false
   186  		}
   187  
   188  		cv := reflect.ValueOf(current)
   189  		switch cv.Kind() {
   190  		case reflect.Map:
   191  			v := cv.MapIndex(reflect.ValueOf(part))
   192  			if !v.IsValid() {
   193  				return nil, false
   194  			}
   195  			current = v.Interface()
   196  		case reflect.Slice:
   197  			if part == "#" {
   198  				current = cv.Len()
   199  			} else {
   200  				i, err := strconv.ParseInt(part, 0, 0)
   201  				if err != nil {
   202  					return nil, false
   203  				}
   204  				if i >= int64(cv.Len()) {
   205  					return nil, false
   206  				}
   207  				current = cv.Index(int(i)).Interface()
   208  			}
   209  		default:
   210  			panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
   211  		}
   212  	}
   213  
   214  	return current, true
   215  }
   216  
   217  // interpolateForce is a temporary thing. We want to get rid of interpolate
   218  // above and likewise this, but it can only be done after the f-ast-graph
   219  // refactor is complete.
   220  func (c *ResourceConfig) interpolateForce() {
   221  	if c.raw == nil {
   222  		var err error
   223  		c.raw, err = config.NewRawConfig(make(map[string]interface{}))
   224  		if err != nil {
   225  			panic(err)
   226  		}
   227  	}
   228  
   229  	c.ComputedKeys = c.raw.UnknownKeys()
   230  	c.Raw = c.raw.Raw
   231  	c.Config = c.raw.Config()
   232  }