github.com/tomaszheflik/terraform@v0.7.3-0.20160827060421-32f990b41594/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 ) 12 13 // ResourceAddress is a way of identifying an individual resource (or, 14 // eventually, a subset of resources) within the state. It is used for Targets. 15 type ResourceAddress struct { 16 // Addresses a resource falling somewhere in the module path 17 // When specified alone, addresses all resources within a module path 18 Path []string 19 20 // Addresses a specific resource that occurs in a list 21 Index int 22 23 InstanceType InstanceType 24 InstanceTypeSet bool 25 Name string 26 Type string 27 Mode config.ResourceMode // significant only if InstanceTypeSet 28 } 29 30 // Copy returns a copy of this ResourceAddress 31 func (r *ResourceAddress) Copy() *ResourceAddress { 32 n := &ResourceAddress{ 33 Path: make([]string, 0, len(r.Path)), 34 Index: r.Index, 35 InstanceType: r.InstanceType, 36 Name: r.Name, 37 Type: r.Type, 38 Mode: r.Mode, 39 } 40 for _, p := range r.Path { 41 n.Path = append(n.Path, p) 42 } 43 return n 44 } 45 46 // String outputs the address that parses into this address. 47 func (r *ResourceAddress) String() string { 48 var result []string 49 for _, p := range r.Path { 50 result = append(result, "module", p) 51 } 52 53 switch r.Mode { 54 case config.ManagedResourceMode: 55 // nothing to do 56 case config.DataResourceMode: 57 result = append(result, "data") 58 default: 59 panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) 60 } 61 62 if r.Type != "" { 63 result = append(result, r.Type) 64 } 65 66 if r.Name != "" { 67 name := r.Name 68 if r.InstanceTypeSet { 69 switch r.InstanceType { 70 case TypePrimary: 71 name += ".primary" 72 case TypeDeposed: 73 name += ".deposed" 74 case TypeTainted: 75 name += ".tainted" 76 } 77 } 78 79 if r.Index >= 0 { 80 name += fmt.Sprintf("[%d]", r.Index) 81 } 82 result = append(result, name) 83 } 84 85 return strings.Join(result, ".") 86 } 87 88 func ParseResourceAddress(s string) (*ResourceAddress, error) { 89 matches, err := tokenizeResourceAddress(s) 90 if err != nil { 91 return nil, err 92 } 93 mode := config.ManagedResourceMode 94 if matches["data_prefix"] != "" { 95 mode = config.DataResourceMode 96 } 97 resourceIndex, err := ParseResourceIndex(matches["index"]) 98 if err != nil { 99 return nil, err 100 } 101 instanceType, err := ParseInstanceType(matches["instance_type"]) 102 if err != nil { 103 return nil, err 104 } 105 path := ParseResourcePath(matches["path"]) 106 107 // not allowed to say "data." without a type following 108 if mode == config.DataResourceMode && matches["type"] == "" { 109 return nil, fmt.Errorf("must target specific data instance") 110 } 111 112 return &ResourceAddress{ 113 Path: path, 114 Index: resourceIndex, 115 InstanceType: instanceType, 116 InstanceTypeSet: matches["instance_type"] != "", 117 Name: matches["name"], 118 Type: matches["type"], 119 Mode: mode, 120 }, nil 121 } 122 123 func (addr *ResourceAddress) Equals(raw interface{}) bool { 124 other, ok := raw.(*ResourceAddress) 125 if !ok { 126 return false 127 } 128 129 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || 130 reflect.DeepEqual(addr.Path, other.Path) 131 132 indexMatch := addr.Index == -1 || 133 other.Index == -1 || 134 addr.Index == other.Index 135 136 nameMatch := addr.Name == "" || 137 other.Name == "" || 138 addr.Name == other.Name 139 140 typeMatch := addr.Type == "" || 141 other.Type == "" || 142 addr.Type == other.Type 143 144 // mode is significant only when type is set 145 modeMatch := addr.Type == "" || 146 other.Type == "" || 147 addr.Mode == other.Mode 148 149 return pathMatch && 150 indexMatch && 151 addr.InstanceType == other.InstanceType && 152 nameMatch && 153 typeMatch && 154 modeMatch 155 } 156 157 func ParseResourceIndex(s string) (int, error) { 158 if s == "" { 159 return -1, nil 160 } 161 return strconv.Atoi(s) 162 } 163 164 func ParseResourcePath(s string) []string { 165 if s == "" { 166 return nil 167 } 168 parts := strings.Split(s, ".") 169 path := make([]string, 0, len(parts)) 170 for _, s := range parts { 171 // Due to the limitations of the regexp match below, the path match has 172 // some noise in it we have to filter out :| 173 if s == "" || s == "module" { 174 continue 175 } 176 path = append(path, s) 177 } 178 return path 179 } 180 181 func ParseInstanceType(s string) (InstanceType, error) { 182 switch s { 183 case "", "primary": 184 return TypePrimary, nil 185 case "deposed": 186 return TypeDeposed, nil 187 case "tainted": 188 return TypeTainted, nil 189 default: 190 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) 191 } 192 } 193 194 func tokenizeResourceAddress(s string) (map[string]string, error) { 195 // Example of portions of the regexp below using the 196 // string "aws_instance.web.tainted[1]" 197 re := regexp.MustCompile(`\A` + 198 // "module.foo.module.bar" (optional) 199 `(?P<path>(?:module\.[^.]+\.?)*)` + 200 // possibly "data.", if targeting is a data resource 201 `(?P<data_prefix>(?:data\.)?)` + 202 // "aws_instance.web" (optional when module path specified) 203 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + 204 // "tainted" (optional, omission implies: "primary") 205 `(?:\.(?P<instance_type>\w+))?` + 206 // "1" (optional, omission implies: "0") 207 `(?:\[(?P<index>\d+)\])?` + 208 `\z`) 209 groupNames := re.SubexpNames() 210 rawMatches := re.FindAllStringSubmatch(s, -1) 211 if len(rawMatches) != 1 { 212 return nil, fmt.Errorf("Problem parsing address: %q", s) 213 } 214 matches := make(map[string]string) 215 for i, m := range rawMatches[0] { 216 matches[groupNames[i]] = m 217 } 218 return matches, nil 219 }