github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/builtin/providers/digitalocean/resource_digitalocean_droplet.go (about) 1 package digitalocean 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/terraform/flatmap" 10 "github.com/hashicorp/terraform/helper/config" 11 "github.com/hashicorp/terraform/helper/diff" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/terraform" 14 "github.com/pearkes/digitalocean" 15 ) 16 17 func resource_digitalocean_droplet_create( 18 s *terraform.ResourceState, 19 d *terraform.ResourceDiff, 20 meta interface{}) (*terraform.ResourceState, error) { 21 p := meta.(*ResourceProvider) 22 client := p.client 23 24 // Merge the diff into the state so that we have all the attributes 25 // properly. 26 rs := s.MergeDiff(d) 27 28 // Build up our creation options 29 opts := digitalocean.CreateDroplet{ 30 Backups: rs.Attributes["backups"], 31 Image: rs.Attributes["image"], 32 IPV6: rs.Attributes["ipv6"], 33 Name: rs.Attributes["name"], 34 PrivateNetworking: rs.Attributes["private_networking"], 35 Region: rs.Attributes["region"], 36 Size: rs.Attributes["size"], 37 } 38 39 // Only expand ssh_keys if we have them 40 if _, ok := rs.Attributes["ssh_keys.#"]; ok { 41 v := flatmap.Expand(rs.Attributes, "ssh_keys").([]interface{}) 42 if len(v) > 0 { 43 vs := make([]string, 0, len(v)) 44 45 // here we special case the * expanded lists. For example: 46 // 47 // ssh_keys = ["${digitalocean_key.foo.*.id}"] 48 // 49 if len(v) == 1 && strings.Contains(v[0].(string), ",") { 50 vs = strings.Split(v[0].(string), ",") 51 } 52 53 for _, v := range v { 54 vs = append(vs, v.(string)) 55 } 56 57 opts.SSHKeys = vs 58 } 59 } 60 61 log.Printf("[DEBUG] Droplet create configuration: %#v", opts) 62 63 id, err := client.CreateDroplet(&opts) 64 65 if err != nil { 66 return nil, fmt.Errorf("Error creating Droplet: %s", err) 67 } 68 69 // Assign the droplets id 70 rs.ID = id 71 72 log.Printf("[INFO] Droplet ID: %s", id) 73 74 dropletRaw, err := WaitForDropletAttribute(id, "active", []string{"new"}, "status", client) 75 76 if err != nil { 77 return rs, fmt.Errorf( 78 "Error waiting for droplet (%s) to become ready: %s", 79 id, err) 80 } 81 82 droplet := dropletRaw.(*digitalocean.Droplet) 83 84 // Initialize the connection info 85 rs.ConnInfo["type"] = "ssh" 86 rs.ConnInfo["host"] = droplet.IPV4Address() 87 88 return resource_digitalocean_droplet_update_state(rs, droplet) 89 } 90 91 func resource_digitalocean_droplet_update( 92 s *terraform.ResourceState, 93 d *terraform.ResourceDiff, 94 meta interface{}) (*terraform.ResourceState, error) { 95 p := meta.(*ResourceProvider) 96 client := p.client 97 rs := s.MergeDiff(d) 98 99 var err error 100 101 if attr, ok := d.Attributes["size"]; ok { 102 err = client.PowerOff(rs.ID) 103 104 if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") { 105 return s, err 106 } 107 108 // Wait for power off 109 _, err = WaitForDropletAttribute( 110 rs.ID, "off", []string{"active"}, "status", client) 111 112 err = client.Resize(rs.ID, attr.New) 113 114 if err != nil { 115 return rs, err 116 } 117 118 // Wait for the size to change 119 _, err = WaitForDropletAttribute( 120 rs.ID, attr.New, []string{"", attr.Old}, "size", client) 121 122 if err != nil { 123 return s, err 124 } 125 126 err = client.PowerOn(rs.ID) 127 128 if err != nil { 129 return s, err 130 } 131 132 // Wait for power off 133 _, err = WaitForDropletAttribute( 134 rs.ID, "active", []string{"off"}, "status", client) 135 136 if err != nil { 137 return s, err 138 } 139 } 140 141 if attr, ok := d.Attributes["name"]; ok { 142 err = client.Rename(rs.ID, attr.New) 143 144 if err != nil { 145 return s, err 146 } 147 148 // Wait for the name to change 149 _, err = WaitForDropletAttribute( 150 rs.ID, attr.New, []string{"", attr.Old}, "name", client) 151 } 152 153 if attr, ok := d.Attributes["private_networking"]; ok { 154 err = client.Rename(rs.ID, attr.New) 155 156 if err != nil { 157 return s, err 158 } 159 160 // Wait for the private_networking to turn on/off 161 _, err = WaitForDropletAttribute( 162 rs.ID, attr.New, []string{"", attr.Old}, "private_networking", client) 163 } 164 165 if attr, ok := d.Attributes["ipv6"]; ok { 166 err = client.Rename(rs.ID, attr.New) 167 168 if err != nil { 169 return s, err 170 } 171 172 // Wait for ipv6 to turn on/off 173 _, err = WaitForDropletAttribute( 174 rs.ID, attr.New, []string{"", attr.Old}, "ipv6", client) 175 } 176 177 droplet, err := resource_digitalocean_droplet_retrieve(rs.ID, client) 178 179 if err != nil { 180 return s, err 181 } 182 183 return resource_digitalocean_droplet_update_state(rs, droplet) 184 } 185 186 func resource_digitalocean_droplet_destroy( 187 s *terraform.ResourceState, 188 meta interface{}) error { 189 p := meta.(*ResourceProvider) 190 client := p.client 191 192 log.Printf("[INFO] Deleting Droplet: %s", s.ID) 193 194 // Destroy the droplet 195 err := client.DestroyDroplet(s.ID) 196 197 // Handle remotely destroyed droplets 198 if err != nil && strings.Contains(err.Error(), "404 Not Found") { 199 return nil 200 } 201 202 if err != nil { 203 return fmt.Errorf("Error deleting Droplet: %s", err) 204 } 205 206 return nil 207 } 208 209 func resource_digitalocean_droplet_refresh( 210 s *terraform.ResourceState, 211 meta interface{}) (*terraform.ResourceState, error) { 212 p := meta.(*ResourceProvider) 213 client := p.client 214 215 droplet, err := resource_digitalocean_droplet_retrieve(s.ID, client) 216 217 // Handle remotely destroyed droplets 218 if err != nil && strings.Contains(err.Error(), "404 Not Found") { 219 return nil, nil 220 } 221 222 if err != nil { 223 return nil, err 224 } 225 226 return resource_digitalocean_droplet_update_state(s, droplet) 227 } 228 229 func resource_digitalocean_droplet_diff( 230 s *terraform.ResourceState, 231 c *terraform.ResourceConfig, 232 meta interface{}) (*terraform.ResourceDiff, error) { 233 234 b := &diff.ResourceBuilder{ 235 Attrs: map[string]diff.AttrType{ 236 "backups": diff.AttrTypeUpdate, 237 "image": diff.AttrTypeCreate, 238 "ipv6": diff.AttrTypeUpdate, 239 "name": diff.AttrTypeUpdate, 240 "private_networking": diff.AttrTypeUpdate, 241 "region": diff.AttrTypeCreate, 242 "size": diff.AttrTypeUpdate, 243 "ssh_keys": diff.AttrTypeCreate, 244 }, 245 246 ComputedAttrs: []string{ 247 "backups", 248 "ipv4_address", 249 "ipv6", 250 "ipv6_address", 251 "locked", 252 "private_networking", 253 "status", 254 }, 255 } 256 257 return b.Diff(s, c) 258 } 259 260 func resource_digitalocean_droplet_update_state( 261 s *terraform.ResourceState, 262 droplet *digitalocean.Droplet) (*terraform.ResourceState, error) { 263 264 s.Attributes["name"] = droplet.Name 265 s.Attributes["region"] = droplet.RegionSlug() 266 267 if droplet.ImageSlug() == "" && droplet.ImageId() != "" { 268 s.Attributes["image"] = droplet.ImageId() 269 } else { 270 s.Attributes["image"] = droplet.ImageSlug() 271 } 272 273 if droplet.IPV6Address() != "" { 274 s.Attributes["ipv6"] = "true" 275 s.Attributes["ipv6_address"] = droplet.IPV6Address() 276 } 277 278 s.Attributes["ipv4_address"] = droplet.IPV4Address() 279 s.Attributes["locked"] = droplet.IsLocked() 280 s.Attributes["private_networking"] = droplet.NetworkingType() 281 s.Attributes["size"] = droplet.SizeSlug() 282 s.Attributes["status"] = droplet.Status 283 284 return s, nil 285 } 286 287 // retrieves an ELB by it's ID 288 func resource_digitalocean_droplet_retrieve(id string, client *digitalocean.Client) (*digitalocean.Droplet, error) { 289 // Retrieve the ELB properties for updating the state 290 droplet, err := client.RetrieveDroplet(id) 291 292 if err != nil { 293 return nil, fmt.Errorf("Error retrieving droplet: %s", err) 294 } 295 296 return &droplet, nil 297 } 298 299 func resource_digitalocean_droplet_validation() *config.Validator { 300 return &config.Validator{ 301 Required: []string{ 302 "image", 303 "name", 304 "region", 305 "size", 306 }, 307 Optional: []string{ 308 "backups", 309 "ipv6", 310 "private_networking", 311 "ssh_keys.*", 312 }, 313 } 314 } 315 316 func WaitForDropletAttribute(id string, target string, pending []string, attribute string, client *digitalocean.Client) (interface{}, error) { 317 // Wait for the droplet so we can get the networking attributes 318 // that show up after a while 319 log.Printf( 320 "[INFO] Waiting for Droplet (%s) to have %s of %s", 321 id, attribute, target) 322 323 stateConf := &resource.StateChangeConf{ 324 Pending: pending, 325 Target: target, 326 Refresh: new_droplet_state_refresh_func(id, attribute, client), 327 Timeout: 10 * time.Minute, 328 Delay: 10 * time.Second, 329 MinTimeout: 3 * time.Second, 330 } 331 332 return stateConf.WaitForState() 333 } 334 335 func new_droplet_state_refresh_func(id string, attribute string, client *digitalocean.Client) resource.StateRefreshFunc { 336 return func() (interface{}, string, error) { 337 // Retrieve the ELB properties for updating the state 338 droplet, err := client.RetrieveDroplet(id) 339 340 if err != nil { 341 log.Printf("Error on retrieving droplet when waiting: %s", err) 342 return nil, "", err 343 } 344 345 // If the droplet is locked, continue waiting. We can 346 // only perform actions on unlocked droplets, so it's 347 // pointless to look at that status 348 if droplet.IsLocked() == "true" { 349 log.Println("[DEBUG] Droplet is locked, skipping status check and retrying") 350 return nil, "", nil 351 } 352 353 // Use our mapping to get back a map of the 354 // droplet properties 355 resourceMap, err := resource_digitalocean_droplet_update_state( 356 &terraform.ResourceState{Attributes: map[string]string{}}, &droplet) 357 358 if err != nil { 359 log.Printf("Error creating map from droplet: %s", err) 360 return nil, "", err 361 } 362 363 // See if we can access our attribute 364 if attr, ok := resourceMap.Attributes[attribute]; ok { 365 return &droplet, attr, nil 366 } 367 368 return nil, "", nil 369 } 370 }