github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/builtin/providers/triton/resource_machine.go (about) 1 package triton 2 3 import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "time" 8 9 "github.com/hashicorp/terraform/helper/hashcode" 10 "github.com/hashicorp/terraform/helper/schema" 11 "github.com/joyent/gosdc/cloudapi" 12 ) 13 14 var ( 15 machineStateRunning = "running" 16 machineStateStopped = "stopped" 17 machineStateDeleted = "deleted" 18 19 machineStateChangeTimeout = 10 * time.Minute 20 machineStateChangeCheckInterval = 10 * time.Second 21 22 resourceMachineMetadataKeys = map[string]string{ 23 // semantics: "schema_name": "metadata_name" 24 "root_authorized_keys": "root_authorized_keys", 25 "user_script": "user-script", 26 "user_data": "user-data", 27 "administrator_pw": "administrator-pw", 28 } 29 ) 30 31 func resourceMachine() *schema.Resource { 32 return &schema.Resource{ 33 Create: resourceMachineCreate, 34 Exists: resourceMachineExists, 35 Read: resourceMachineRead, 36 Update: resourceMachineUpdate, 37 Delete: resourceMachineDelete, 38 39 Schema: map[string]*schema.Schema{ 40 "name": { 41 Description: "friendly name", 42 Type: schema.TypeString, 43 Optional: true, 44 Computed: true, 45 ValidateFunc: resourceMachineValidateName, 46 }, 47 "type": { 48 Description: "machine type (smartmachine or virtualmachine)", 49 Type: schema.TypeString, 50 Computed: true, 51 }, 52 "state": { 53 Description: "current state of the machine", 54 Type: schema.TypeString, 55 Computed: true, 56 }, 57 "dataset": { 58 Description: "dataset URN the machine was provisioned with", 59 Type: schema.TypeString, 60 Computed: true, 61 }, 62 "memory": { 63 Description: "amount of memory the machine has (in Mb)", 64 Type: schema.TypeInt, 65 Computed: true, 66 }, 67 "disk": { 68 Description: "amount of disk the machine has (in Gb)", 69 Type: schema.TypeInt, 70 Computed: true, 71 }, 72 "ips": { 73 Description: "IP addresses the machine has", 74 Type: schema.TypeList, 75 Computed: true, 76 Elem: &schema.Schema{ 77 Type: schema.TypeString, 78 }, 79 }, 80 "tags": { 81 Description: "machine tags", 82 Type: schema.TypeMap, 83 Optional: true, 84 }, 85 "created": { 86 Description: "when the machine was created", 87 Type: schema.TypeString, 88 Computed: true, 89 }, 90 "updated": { 91 Description: "when the machine was update", 92 Type: schema.TypeString, 93 Computed: true, 94 }, 95 "package": { 96 Description: "name of the package to use on provisioning", 97 Type: schema.TypeString, 98 Required: true, 99 }, 100 "image": { 101 Description: "image UUID", 102 Type: schema.TypeString, 103 Required: true, 104 ForceNew: true, 105 // TODO: validate that the UUID is valid 106 }, 107 "primaryip": { 108 Description: "the primary (public) IP address for the machine", 109 Type: schema.TypeString, 110 Computed: true, 111 }, 112 "nic": { 113 Description: "network interface", 114 Type: schema.TypeSet, 115 Computed: true, 116 Optional: true, 117 Set: func(v interface{}) int { 118 m := v.(map[string]interface{}) 119 return hashcode.String(m["network"].(string)) 120 }, 121 Elem: &schema.Resource{ 122 Schema: map[string]*schema.Schema{ 123 "ip": { 124 Description: "NIC's IPv4 address", 125 Computed: true, 126 Type: schema.TypeString, 127 }, 128 "mac": { 129 Description: "NIC's MAC address", 130 Computed: true, 131 Type: schema.TypeString, 132 }, 133 "primary": { 134 Description: "Whether this is the machine's primary NIC", 135 Computed: true, 136 Type: schema.TypeBool, 137 }, 138 "netmask": { 139 Description: "IPv4 netmask", 140 Computed: true, 141 Type: schema.TypeString, 142 }, 143 "gateway": { 144 Description: "IPv4 gateway", 145 Computed: true, 146 Type: schema.TypeString, 147 }, 148 "state": { 149 Description: "describes the state of the NIC (e.g. provisioning, running, or stopped)", 150 Computed: true, 151 Type: schema.TypeString, 152 }, 153 "network": { 154 Description: "Network ID this NIC is attached to", 155 Required: true, 156 Type: schema.TypeString, 157 }, 158 }, 159 }, 160 }, 161 "firewall_enabled": { 162 Description: "enable firewall for this machine", 163 Type: schema.TypeBool, 164 Optional: true, 165 Default: false, 166 }, 167 168 // computed resources from metadata 169 "root_authorized_keys": { 170 Description: "authorized keys for the root user on this machine", 171 Type: schema.TypeString, 172 Optional: true, 173 Computed: true, 174 }, 175 "user_script": { 176 Description: "user script to run on boot (every boot on SmartMachines)", 177 Type: schema.TypeString, 178 Optional: true, 179 Computed: true, 180 }, 181 "user_data": { 182 Description: "copied to machine on boot", 183 Type: schema.TypeString, 184 Optional: true, 185 Computed: true, 186 }, 187 "administrator_pw": { 188 Description: "administrator's initial password (Windows only)", 189 Type: schema.TypeString, 190 Optional: true, 191 Computed: true, 192 }, 193 194 // deprecated fields 195 "networks": { 196 Description: "desired network IDs", 197 Type: schema.TypeList, 198 Optional: true, 199 Computed: true, 200 Deprecated: "Networks is deprecated, please use `nic`", 201 Elem: &schema.Schema{ 202 Type: schema.TypeString, 203 }, 204 }, 205 }, 206 } 207 } 208 209 func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { 210 client := meta.(*cloudapi.Client) 211 212 var networks []string 213 for _, network := range d.Get("networks").([]interface{}) { 214 networks = append(networks, network.(string)) 215 } 216 nics := d.Get("nic").(*schema.Set) 217 for _, nicI := range nics.List() { 218 nic := nicI.(map[string]interface{}) 219 networks = append(networks, nic["network"].(string)) 220 } 221 222 metadata := map[string]string{} 223 for schemaName, metadataKey := range resourceMachineMetadataKeys { 224 if v, ok := d.GetOk(schemaName); ok { 225 metadata[metadataKey] = v.(string) 226 } 227 } 228 229 tags := map[string]string{} 230 for k, v := range d.Get("tags").(map[string]interface{}) { 231 tags[k] = v.(string) 232 } 233 234 machine, err := client.CreateMachine(cloudapi.CreateMachineOpts{ 235 Name: d.Get("name").(string), 236 Package: d.Get("package").(string), 237 Image: d.Get("image").(string), 238 Networks: networks, 239 Metadata: metadata, 240 Tags: tags, 241 FirewallEnabled: d.Get("firewall_enabled").(bool), 242 }) 243 if err != nil { 244 return err 245 } 246 247 err = waitForMachineState(client, machine.Id, machineStateRunning, machineStateChangeTimeout) 248 if err != nil { 249 return err 250 } 251 252 // refresh state after it provisions 253 d.SetId(machine.Id) 254 err = resourceMachineRead(d, meta) 255 if err != nil { 256 return err 257 } 258 259 return nil 260 } 261 262 func resourceMachineExists(d *schema.ResourceData, meta interface{}) (bool, error) { 263 client := meta.(*cloudapi.Client) 264 265 machine, err := client.GetMachine(d.Id()) 266 267 return machine != nil && err == nil, err 268 } 269 270 func resourceMachineRead(d *schema.ResourceData, meta interface{}) error { 271 client := meta.(*cloudapi.Client) 272 273 machine, err := client.GetMachine(d.Id()) 274 if err != nil { 275 return err 276 } 277 278 nics, err := client.ListNICs(d.Id()) 279 if err != nil { 280 return err 281 } 282 283 d.SetId(machine.Id) 284 d.Set("name", machine.Name) 285 d.Set("type", machine.Type) 286 d.Set("state", machine.State) 287 d.Set("dataset", machine.Dataset) 288 d.Set("memory", machine.Memory) 289 d.Set("disk", machine.Disk) 290 d.Set("ips", machine.IPs) 291 d.Set("tags", machine.Tags) 292 d.Set("created", machine.Created) 293 d.Set("updated", machine.Updated) 294 d.Set("package", machine.Package) 295 d.Set("image", machine.Image) 296 d.Set("primaryip", machine.PrimaryIP) 297 d.Set("firewall_enabled", machine.FirewallEnabled) 298 299 // create and update NICs 300 var ( 301 machineNICs []map[string]interface{} 302 networks []string 303 ) 304 for _, nic := range nics { 305 machineNICs = append( 306 machineNICs, 307 map[string]interface{}{ 308 "ip": nic.IP, 309 "mac": nic.MAC, 310 "primary": nic.Primary, 311 "netmask": nic.Netmask, 312 "gateway": nic.Gateway, 313 "state": nic.State, 314 "network": nic.Network, 315 }, 316 ) 317 networks = append(networks, nic.Network) 318 } 319 d.Set("nic", machineNICs) 320 d.Set("networks", networks) 321 322 // computed attributes from metadata 323 for schemaName, metadataKey := range resourceMachineMetadataKeys { 324 d.Set(schemaName, machine.Metadata[metadataKey]) 325 } 326 327 return nil 328 } 329 330 func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { 331 client := meta.(*cloudapi.Client) 332 333 d.Partial(true) 334 335 if d.HasChange("name") { 336 if err := client.RenameMachine(d.Id(), d.Get("name").(string)); err != nil { 337 return err 338 } 339 340 err := waitFor( 341 func() (bool, error) { 342 machine, err := client.GetMachine(d.Id()) 343 return machine.Name == d.Get("name").(string), err 344 }, 345 machineStateChangeCheckInterval, 346 1*time.Minute, 347 ) 348 if err != nil { 349 return err 350 } 351 352 d.SetPartial("name") 353 } 354 355 if d.HasChange("tags") { 356 tags := map[string]string{} 357 for k, v := range d.Get("tags").(map[string]interface{}) { 358 tags[k] = v.(string) 359 } 360 361 var err error 362 if len(tags) == 0 { 363 err = client.DeleteMachineTags(d.Id()) 364 } else { 365 _, err = client.ReplaceMachineTags(d.Id(), tags) 366 } 367 if err != nil { 368 return err 369 } 370 371 err = waitFor( 372 func() (bool, error) { 373 machine, err := client.GetMachine(d.Id()) 374 return reflect.DeepEqual(machine.Tags, tags), err 375 }, 376 machineStateChangeCheckInterval, 377 1*time.Minute, 378 ) 379 if err != nil { 380 return err 381 } 382 383 d.SetPartial("tags") 384 } 385 386 if d.HasChange("package") { 387 if err := client.ResizeMachine(d.Id(), d.Get("package").(string)); err != nil { 388 return err 389 } 390 391 err := waitFor( 392 func() (bool, error) { 393 machine, err := client.GetMachine(d.Id()) 394 return machine.Package == d.Get("package").(string) && machine.State == machineStateRunning, err 395 }, 396 machineStateChangeCheckInterval, 397 machineStateChangeTimeout, 398 ) 399 if err != nil { 400 return err 401 } 402 403 d.SetPartial("package") 404 } 405 406 if d.HasChange("firewall_enabled") { 407 var err error 408 if d.Get("firewall_enabled").(bool) { 409 err = client.EnableFirewallMachine(d.Id()) 410 } else { 411 err = client.DisableFirewallMachine(d.Id()) 412 } 413 if err != nil { 414 return err 415 } 416 417 err = waitFor( 418 func() (bool, error) { 419 machine, err := client.GetMachine(d.Id()) 420 return machine.FirewallEnabled == d.Get("firewall_enabled").(bool), err 421 }, 422 machineStateChangeCheckInterval, 423 machineStateChangeTimeout, 424 ) 425 426 if err != nil { 427 return err 428 } 429 430 d.SetPartial("firewall_enabled") 431 } 432 433 if d.HasChange("nic") { 434 o, n := d.GetChange("nic") 435 if o == nil { 436 o = new(schema.Set) 437 } 438 if n == nil { 439 n = new(schema.Set) 440 } 441 442 oldNICs := o.(*schema.Set) 443 newNICs := o.(*schema.Set) 444 445 // add new NICs that are not in old NICs 446 for _, nicI := range newNICs.Difference(oldNICs).List() { 447 nic := nicI.(map[string]interface{}) 448 fmt.Printf("adding %+v\n", nic) 449 _, err := client.AddNIC(d.Id(), nic["network"].(string)) 450 if err != nil { 451 return err 452 } 453 } 454 455 // remove old NICs that are not in new NICs 456 for _, nicI := range oldNICs.Difference(newNICs).List() { 457 nic := nicI.(map[string]interface{}) 458 fmt.Printf("removing %+v\n", nic) 459 err := client.RemoveNIC(d.Id(), nic["mac"].(string)) 460 if err != nil { 461 return err 462 } 463 } 464 465 d.SetPartial("nic") 466 } 467 468 // metadata stuff 469 metadata := map[string]string{} 470 for schemaName, metadataKey := range resourceMachineMetadataKeys { 471 if d.HasChange(schemaName) { 472 metadata[metadataKey] = d.Get(schemaName).(string) 473 } 474 } 475 if len(metadata) > 0 { 476 _, err := client.UpdateMachineMetadata(d.Id(), metadata) 477 if err != nil { 478 return err 479 } 480 481 err = waitFor( 482 func() (bool, error) { 483 machine, err := client.GetMachine(d.Id()) 484 for k, v := range metadata { 485 if provider_v, ok := machine.Metadata[k]; !ok || v != provider_v { 486 return false, err 487 } 488 } 489 return true, err 490 }, 491 machineStateChangeCheckInterval, 492 1*time.Minute, 493 ) 494 if err != nil { 495 return err 496 } 497 498 for schemaName := range resourceMachineMetadataKeys { 499 if d.HasChange(schemaName) { 500 d.SetPartial(schemaName) 501 } 502 } 503 } 504 505 d.Partial(false) 506 507 err := resourceMachineRead(d, meta) 508 if err != nil { 509 return err 510 } 511 512 return nil 513 } 514 515 func resourceMachineDelete(d *schema.ResourceData, meta interface{}) error { 516 client := meta.(*cloudapi.Client) 517 518 state, err := readMachineState(client, d.Id()) 519 if state != machineStateStopped { 520 err = client.StopMachine(d.Id()) 521 if err != nil { 522 return err 523 } 524 525 waitForMachineState(client, d.Id(), machineStateStopped, machineStateChangeTimeout) 526 } 527 528 err = client.DeleteMachine(d.Id()) 529 if err != nil { 530 return err 531 } 532 533 waitForMachineState(client, d.Id(), machineStateDeleted, machineStateChangeTimeout) 534 return nil 535 } 536 537 func readMachineState(api *cloudapi.Client, id string) (string, error) { 538 machine, err := api.GetMachine(id) 539 if err != nil { 540 return "", err 541 } 542 543 return machine.State, nil 544 } 545 546 // waitForMachineState waits for a machine to be in the desired state (waiting 547 // some seconds between each poll). If it doesn't reach the state within the 548 // duration specified in `timeout`, it returns ErrMachineStateTimeout. 549 func waitForMachineState(api *cloudapi.Client, id, state string, timeout time.Duration) error { 550 return waitFor( 551 func() (bool, error) { 552 currentState, err := readMachineState(api, id) 553 return currentState == state, err 554 }, 555 machineStateChangeCheckInterval, 556 machineStateChangeTimeout, 557 ) 558 } 559 560 func resourceMachineValidateName(value interface{}, name string) (warnings []string, errors []error) { 561 warnings = []string{} 562 errors = []error{} 563 564 r := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\_\.\-]*$`) 565 if !r.Match([]byte(value.(string))) { 566 errors = append(errors, fmt.Errorf(`"%s" is not a valid %s`, value.(string), name)) 567 } 568 569 return warnings, errors 570 }