github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/resource_address.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/config"
    11  	"github.com/hashicorp/terraform/config/module"
    12  )
    13  
    14  // ResourceAddress is a way of identifying an individual resource (or,
    15  // eventually, a subset of resources) within the state. It is used for Targets.
    16  type ResourceAddress struct {
    17  	// Addresses a resource falling somewhere in the module path
    18  	// When specified alone, addresses all resources within a module path
    19  	Path []string
    20  
    21  	// Addresses a specific resource that occurs in a list
    22  	Index int
    23  
    24  	InstanceType    InstanceType
    25  	InstanceTypeSet bool
    26  	Name            string
    27  	Type            string
    28  	Mode            config.ResourceMode // significant only if InstanceTypeSet
    29  }
    30  
    31  // Copy returns a copy of this ResourceAddress
    32  func (r *ResourceAddress) Copy() *ResourceAddress {
    33  	if r == nil {
    34  		return nil
    35  	}
    36  
    37  	n := &ResourceAddress{
    38  		Path:         make([]string, 0, len(r.Path)),
    39  		Index:        r.Index,
    40  		InstanceType: r.InstanceType,
    41  		Name:         r.Name,
    42  		Type:         r.Type,
    43  		Mode:         r.Mode,
    44  	}
    45  	for _, p := range r.Path {
    46  		n.Path = append(n.Path, p)
    47  	}
    48  	return n
    49  }
    50  
    51  // String outputs the address that parses into this address.
    52  func (r *ResourceAddress) String() string {
    53  	var result []string
    54  	for _, p := range r.Path {
    55  		result = append(result, "module", p)
    56  	}
    57  
    58  	switch r.Mode {
    59  	case config.ManagedResourceMode:
    60  		// nothing to do
    61  	case config.DataResourceMode:
    62  		result = append(result, "data")
    63  	default:
    64  		panic(fmt.Errorf("unsupported resource mode %s", r.Mode))
    65  	}
    66  
    67  	if r.Type != "" {
    68  		result = append(result, r.Type)
    69  	}
    70  
    71  	if r.Name != "" {
    72  		name := r.Name
    73  		if r.InstanceTypeSet {
    74  			switch r.InstanceType {
    75  			case TypePrimary:
    76  				name += ".primary"
    77  			case TypeDeposed:
    78  				name += ".deposed"
    79  			case TypeTainted:
    80  				name += ".tainted"
    81  			}
    82  		}
    83  
    84  		if r.Index >= 0 {
    85  			name += fmt.Sprintf("[%d]", r.Index)
    86  		}
    87  		result = append(result, name)
    88  	}
    89  
    90  	return strings.Join(result, ".")
    91  }
    92  
    93  // HasResourceSpec returns true if the address has a resource spec, as
    94  // defined in the documentation:
    95  //    https://www.terraform.io/docs/internals/resource-addressing.html
    96  // In particular, this returns false if the address contains only
    97  // a module path, thus addressing the entire module.
    98  func (r *ResourceAddress) HasResourceSpec() bool {
    99  	return r.Type != "" && r.Name != ""
   100  }
   101  
   102  // WholeModuleAddress returns the resource address that refers to all
   103  // resources in the same module as the receiver address.
   104  func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress {
   105  	return &ResourceAddress{
   106  		Path:            r.Path,
   107  		Index:           -1,
   108  		InstanceTypeSet: false,
   109  	}
   110  }
   111  
   112  // MatchesConfig returns true if the receiver matches the given
   113  // configuration resource within the given configuration module.
   114  //
   115  // Since resource configuration blocks represent all of the instances of
   116  // a multi-instance resource, the index of the address (if any) is not
   117  // considered.
   118  func (r *ResourceAddress) MatchesConfig(mod *module.Tree, rc *config.Resource) bool {
   119  	if r.HasResourceSpec() {
   120  		if r.Mode != rc.Mode || r.Type != rc.Type || r.Name != rc.Name {
   121  			return false
   122  		}
   123  	}
   124  
   125  	addrPath := r.Path
   126  	cfgPath := mod.Path()
   127  
   128  	// normalize
   129  	if len(addrPath) == 0 {
   130  		addrPath = nil
   131  	}
   132  	if len(cfgPath) == 0 {
   133  		cfgPath = nil
   134  	}
   135  	return reflect.DeepEqual(addrPath, cfgPath)
   136  }
   137  
   138  // stateId returns the ID that this resource should be entered with
   139  // in the state. This is also used for diffs. In the future, we'd like to
   140  // move away from this string field so I don't export this.
   141  func (r *ResourceAddress) stateId() string {
   142  	result := fmt.Sprintf("%s.%s", r.Type, r.Name)
   143  	switch r.Mode {
   144  	case config.ManagedResourceMode:
   145  		// Done
   146  	case config.DataResourceMode:
   147  		result = fmt.Sprintf("data.%s", result)
   148  	default:
   149  		panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
   150  	}
   151  	if r.Index >= 0 {
   152  		result += fmt.Sprintf(".%d", r.Index)
   153  	}
   154  
   155  	return result
   156  }
   157  
   158  // parseResourceAddressConfig creates a resource address from a config.Resource
   159  func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
   160  	return &ResourceAddress{
   161  		Type:         r.Type,
   162  		Name:         r.Name,
   163  		Index:        -1,
   164  		InstanceType: TypePrimary,
   165  		Mode:         r.Mode,
   166  	}, nil
   167  }
   168  
   169  // parseResourceAddressInternal parses the somewhat bespoke resource
   170  // identifier used in states and diffs, such as "instance.name.0".
   171  func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
   172  	// Split based on ".". Every resource address should have at least two
   173  	// elements (type and name).
   174  	parts := strings.Split(s, ".")
   175  	if len(parts) < 2 || len(parts) > 4 {
   176  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   177  	}
   178  
   179  	// Data resource if we have at least 3 parts and the first one is data
   180  	mode := config.ManagedResourceMode
   181  	if len(parts) > 2 && parts[0] == "data" {
   182  		mode = config.DataResourceMode
   183  		parts = parts[1:]
   184  	}
   185  
   186  	// If we're not a data resource and we have more than 3, then it is an error
   187  	if len(parts) > 3 && mode != config.DataResourceMode {
   188  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   189  	}
   190  
   191  	// Build the parts of the resource address that are guaranteed to exist
   192  	addr := &ResourceAddress{
   193  		Type:         parts[0],
   194  		Name:         parts[1],
   195  		Index:        -1,
   196  		InstanceType: TypePrimary,
   197  		Mode:         mode,
   198  	}
   199  
   200  	// If we have more parts, then we have an index. Parse that.
   201  	if len(parts) > 2 {
   202  		idx, err := strconv.ParseInt(parts[2], 0, 0)
   203  		if err != nil {
   204  			return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
   205  		}
   206  
   207  		addr.Index = int(idx)
   208  	}
   209  
   210  	return addr, nil
   211  }
   212  
   213  func ParseResourceAddress(s string) (*ResourceAddress, error) {
   214  	matches, err := tokenizeResourceAddress(s)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	mode := config.ManagedResourceMode
   219  	if matches["data_prefix"] != "" {
   220  		mode = config.DataResourceMode
   221  	}
   222  	resourceIndex, err := ParseResourceIndex(matches["index"])
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	instanceType, err := ParseInstanceType(matches["instance_type"])
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	path := ParseResourcePath(matches["path"])
   231  
   232  	// not allowed to say "data." without a type following
   233  	if mode == config.DataResourceMode && matches["type"] == "" {
   234  		return nil, fmt.Errorf(
   235  			"invalid resource address %q: must target specific data instance",
   236  			s,
   237  		)
   238  	}
   239  
   240  	return &ResourceAddress{
   241  		Path:            path,
   242  		Index:           resourceIndex,
   243  		InstanceType:    instanceType,
   244  		InstanceTypeSet: matches["instance_type"] != "",
   245  		Name:            matches["name"],
   246  		Type:            matches["type"],
   247  		Mode:            mode,
   248  	}, nil
   249  }
   250  
   251  func (addr *ResourceAddress) Equals(raw interface{}) bool {
   252  	other, ok := raw.(*ResourceAddress)
   253  	if !ok {
   254  		return false
   255  	}
   256  
   257  	pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
   258  		reflect.DeepEqual(addr.Path, other.Path)
   259  
   260  	indexMatch := addr.Index == -1 ||
   261  		other.Index == -1 ||
   262  		addr.Index == other.Index
   263  
   264  	nameMatch := addr.Name == "" ||
   265  		other.Name == "" ||
   266  		addr.Name == other.Name
   267  
   268  	typeMatch := addr.Type == "" ||
   269  		other.Type == "" ||
   270  		addr.Type == other.Type
   271  
   272  	// mode is significant only when type is set
   273  	modeMatch := addr.Type == "" ||
   274  		other.Type == "" ||
   275  		addr.Mode == other.Mode
   276  
   277  	return pathMatch &&
   278  		indexMatch &&
   279  		addr.InstanceType == other.InstanceType &&
   280  		nameMatch &&
   281  		typeMatch &&
   282  		modeMatch
   283  }
   284  
   285  func ParseResourceIndex(s string) (int, error) {
   286  	if s == "" {
   287  		return -1, nil
   288  	}
   289  	return strconv.Atoi(s)
   290  }
   291  
   292  func ParseResourcePath(s string) []string {
   293  	if s == "" {
   294  		return nil
   295  	}
   296  	parts := strings.Split(s, ".")
   297  	path := make([]string, 0, len(parts))
   298  	for _, s := range parts {
   299  		// Due to the limitations of the regexp match below, the path match has
   300  		// some noise in it we have to filter out :|
   301  		if s == "" || s == "module" {
   302  			continue
   303  		}
   304  		path = append(path, s)
   305  	}
   306  	return path
   307  }
   308  
   309  func ParseInstanceType(s string) (InstanceType, error) {
   310  	switch s {
   311  	case "", "primary":
   312  		return TypePrimary, nil
   313  	case "deposed":
   314  		return TypeDeposed, nil
   315  	case "tainted":
   316  		return TypeTainted, nil
   317  	default:
   318  		return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
   319  	}
   320  }
   321  
   322  func tokenizeResourceAddress(s string) (map[string]string, error) {
   323  	// Example of portions of the regexp below using the
   324  	// string "aws_instance.web.tainted[1]"
   325  	re := regexp.MustCompile(`\A` +
   326  		// "module.foo.module.bar" (optional)
   327  		`(?P<path>(?:module\.[^.]+\.?)*)` +
   328  		// possibly "data.", if targeting is a data resource
   329  		`(?P<data_prefix>(?:data\.)?)` +
   330  		// "aws_instance.web" (optional when module path specified)
   331  		`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
   332  		// "tainted" (optional, omission implies: "primary")
   333  		`(?:\.(?P<instance_type>\w+))?` +
   334  		// "1" (optional, omission implies: "0")
   335  		`(?:\[(?P<index>\d+)\])?` +
   336  		`\z`)
   337  
   338  	groupNames := re.SubexpNames()
   339  	rawMatches := re.FindAllStringSubmatch(s, -1)
   340  	if len(rawMatches) != 1 {
   341  		return nil, fmt.Errorf("invalid resource address %q", s)
   342  	}
   343  
   344  	matches := make(map[string]string)
   345  	for i, m := range rawMatches[0] {
   346  		matches[groupNames[i]] = m
   347  	}
   348  
   349  	return matches, nil
   350  }