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