github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/builtin/providers/google/resource_compute_instance.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "code.google.com/p/google-api-go-client/compute/v1" 9 "code.google.com/p/google-api-go-client/googleapi" 10 "github.com/hashicorp/terraform/helper/hashcode" 11 "github.com/hashicorp/terraform/helper/schema" 12 ) 13 14 func resourceComputeInstance() *schema.Resource { 15 return &schema.Resource{ 16 Create: resourceComputeInstanceCreate, 17 Read: resourceComputeInstanceRead, 18 Update: resourceComputeInstanceUpdate, 19 Delete: resourceComputeInstanceDelete, 20 21 Schema: map[string]*schema.Schema{ 22 "name": &schema.Schema{ 23 Type: schema.TypeString, 24 Required: true, 25 ForceNew: true, 26 }, 27 28 "description": &schema.Schema{ 29 Type: schema.TypeString, 30 Optional: true, 31 ForceNew: true, 32 }, 33 34 "machine_type": &schema.Schema{ 35 Type: schema.TypeString, 36 Required: true, 37 ForceNew: true, 38 }, 39 40 "zone": &schema.Schema{ 41 Type: schema.TypeString, 42 Required: true, 43 ForceNew: true, 44 }, 45 46 "disk": &schema.Schema{ 47 Type: schema.TypeList, 48 Required: true, 49 ForceNew: true, 50 Elem: &schema.Resource{ 51 Schema: map[string]*schema.Schema{ 52 // TODO(mitchellh): one of image or disk is required 53 54 "disk": &schema.Schema{ 55 Type: schema.TypeString, 56 Optional: true, 57 }, 58 59 "image": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 }, 63 64 "type": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 ForceNew: true, 68 }, 69 70 "auto_delete": &schema.Schema{ 71 Type: schema.TypeBool, 72 Optional: true, 73 }, 74 }, 75 }, 76 }, 77 78 "network": &schema.Schema{ 79 Type: schema.TypeList, 80 Required: true, 81 ForceNew: true, 82 Elem: &schema.Resource{ 83 Schema: map[string]*schema.Schema{ 84 "source": &schema.Schema{ 85 Type: schema.TypeString, 86 Required: true, 87 }, 88 89 "address": &schema.Schema{ 90 Type: schema.TypeString, 91 Optional: true, 92 }, 93 94 "name": &schema.Schema{ 95 Type: schema.TypeString, 96 Computed: true, 97 }, 98 99 "internal_address": &schema.Schema{ 100 Type: schema.TypeString, 101 Computed: true, 102 }, 103 104 "external_address": &schema.Schema{ 105 Type: schema.TypeString, 106 Computed: true, 107 }, 108 }, 109 }, 110 }, 111 112 "can_ip_forward": &schema.Schema{ 113 Type: schema.TypeBool, 114 Optional: true, 115 Default: false, 116 ForceNew: true, 117 }, 118 119 "metadata": &schema.Schema{ 120 Type: schema.TypeList, 121 Optional: true, 122 Elem: &schema.Schema{ 123 Type: schema.TypeMap, 124 }, 125 }, 126 127 "service_account": &schema.Schema{ 128 Type: schema.TypeList, 129 Optional: true, 130 ForceNew: true, 131 Elem: &schema.Resource{ 132 Schema: map[string]*schema.Schema{ 133 "email": &schema.Schema{ 134 Type: schema.TypeString, 135 Computed: true, 136 ForceNew: true, 137 }, 138 139 "scopes": &schema.Schema{ 140 Type: schema.TypeList, 141 Required: true, 142 ForceNew: true, 143 Elem: &schema.Schema{ 144 Type: schema.TypeString, 145 StateFunc: func(v interface{}) string { 146 return canonicalizeServiceScope(v.(string)) 147 }, 148 }, 149 }, 150 }, 151 }, 152 }, 153 154 "tags": &schema.Schema{ 155 Type: schema.TypeSet, 156 Optional: true, 157 Elem: &schema.Schema{Type: schema.TypeString}, 158 Set: func(v interface{}) int { 159 return hashcode.String(v.(string)) 160 }, 161 }, 162 163 "metadata_fingerprint": &schema.Schema{ 164 Type: schema.TypeString, 165 Computed: true, 166 }, 167 168 "tags_fingerprint": &schema.Schema{ 169 Type: schema.TypeString, 170 Computed: true, 171 }, 172 }, 173 } 174 } 175 176 func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { 177 config := meta.(*Config) 178 179 // Get the zone 180 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 181 zone, err := config.clientCompute.Zones.Get( 182 config.Project, d.Get("zone").(string)).Do() 183 if err != nil { 184 return fmt.Errorf( 185 "Error loading zone '%s': %s", d.Get("zone").(string), err) 186 } 187 188 // Get the machine type 189 log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string)) 190 machineType, err := config.clientCompute.MachineTypes.Get( 191 config.Project, zone.Name, d.Get("machine_type").(string)).Do() 192 if err != nil { 193 return fmt.Errorf( 194 "Error loading machine type: %s", 195 err) 196 } 197 198 // Build up the list of disks 199 disksCount := d.Get("disk.#").(int) 200 disks := make([]*compute.AttachedDisk, 0, disksCount) 201 for i := 0; i < disksCount; i++ { 202 prefix := fmt.Sprintf("disk.%d", i) 203 204 // var sourceLink string 205 206 // Build the disk 207 var disk compute.AttachedDisk 208 disk.Type = "PERSISTENT" 209 disk.Mode = "READ_WRITE" 210 disk.Boot = i == 0 211 disk.AutoDelete = true 212 213 if v, ok := d.GetOk(prefix + ".auto_delete"); ok { 214 disk.AutoDelete = v.(bool) 215 } 216 217 // Load up the disk for this disk if specified 218 if v, ok := d.GetOk(prefix + ".disk"); ok { 219 diskName := v.(string) 220 diskData, err := config.clientCompute.Disks.Get( 221 config.Project, zone.Name, diskName).Do() 222 if err != nil { 223 return fmt.Errorf( 224 "Error loading disk '%s': %s", 225 diskName, err) 226 } 227 228 disk.Source = diskData.SelfLink 229 } 230 231 // Load up the image for this disk if specified 232 if v, ok := d.GetOk(prefix + ".image"); ok { 233 imageName := v.(string) 234 image, err := readImage(config, imageName) 235 if err != nil { 236 return fmt.Errorf( 237 "Error loading image '%s': %s", 238 imageName, err) 239 } 240 241 disk.InitializeParams = &compute.AttachedDiskInitializeParams{ 242 SourceImage: image.SelfLink, 243 } 244 } 245 246 if v, ok := d.GetOk(prefix + ".type"); ok { 247 diskTypeName := v.(string) 248 diskType, err := readDiskType(config, zone, diskTypeName) 249 if err != nil { 250 return fmt.Errorf( 251 "Error loading disk type '%s': %s", 252 diskTypeName, err) 253 } 254 255 disk.InitializeParams.DiskType = diskType.SelfLink 256 } 257 258 disks = append(disks, &disk) 259 } 260 261 // Build up the list of networks 262 networksCount := d.Get("network.#").(int) 263 networks := make([]*compute.NetworkInterface, 0, networksCount) 264 for i := 0; i < networksCount; i++ { 265 prefix := fmt.Sprintf("network.%d", i) 266 // Load up the name of this network 267 networkName := d.Get(prefix + ".source").(string) 268 network, err := config.clientCompute.Networks.Get( 269 config.Project, networkName).Do() 270 if err != nil { 271 return fmt.Errorf( 272 "Error loading network '%s': %s", 273 networkName, err) 274 } 275 276 // Build the disk 277 var iface compute.NetworkInterface 278 iface.AccessConfigs = []*compute.AccessConfig{ 279 &compute.AccessConfig{ 280 Type: "ONE_TO_ONE_NAT", 281 NatIP: d.Get(prefix + ".address").(string), 282 }, 283 } 284 iface.Network = network.SelfLink 285 286 networks = append(networks, &iface) 287 } 288 289 serviceAccountsCount := d.Get("service_account.#").(int) 290 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 291 for i := 0; i < serviceAccountsCount; i++ { 292 prefix := fmt.Sprintf("service_account.%d", i) 293 294 scopesCount := d.Get(prefix + ".scopes.#").(int) 295 scopes := make([]string, 0, scopesCount) 296 for j := 0; j < scopesCount; j++ { 297 scope := d.Get(fmt.Sprintf(prefix + ".scopes.%d", j)).(string) 298 scopes = append(scopes, canonicalizeServiceScope(scope)) 299 } 300 301 serviceAccount := &compute.ServiceAccount { 302 Email: "default", 303 Scopes: scopes, 304 } 305 306 serviceAccounts = append(serviceAccounts, serviceAccount) 307 } 308 309 // Create the instance information 310 instance := compute.Instance{ 311 CanIpForward: d.Get("can_ip_forward").(bool), 312 Description: d.Get("description").(string), 313 Disks: disks, 314 MachineType: machineType.SelfLink, 315 Metadata: resourceInstanceMetadata(d), 316 Name: d.Get("name").(string), 317 NetworkInterfaces: networks, 318 Tags: resourceInstanceTags(d), 319 ServiceAccounts: serviceAccounts, 320 } 321 322 log.Printf("[INFO] Requesting instance creation") 323 op, err := config.clientCompute.Instances.Insert( 324 config.Project, zone.Name, &instance).Do() 325 if err != nil { 326 return fmt.Errorf("Error creating instance: %s", err) 327 } 328 329 // Store the ID now 330 d.SetId(instance.Name) 331 332 // Wait for the operation to complete 333 w := &OperationWaiter{ 334 Service: config.clientCompute, 335 Op: op, 336 Project: config.Project, 337 Zone: zone.Name, 338 Type: OperationWaitZone, 339 } 340 state := w.Conf() 341 state.Delay = 10 * time.Second 342 state.Timeout = 10 * time.Minute 343 state.MinTimeout = 2 * time.Second 344 opRaw, err := state.WaitForState() 345 if err != nil { 346 return fmt.Errorf("Error waiting for instance to create: %s", err) 347 } 348 op = opRaw.(*compute.Operation) 349 if op.Error != nil { 350 // The resource didn't actually create 351 d.SetId("") 352 353 // Return the error 354 return OperationError(*op.Error) 355 } 356 357 return resourceComputeInstanceRead(d, meta) 358 } 359 360 func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { 361 config := meta.(*Config) 362 363 instance, err := config.clientCompute.Instances.Get( 364 config.Project, d.Get("zone").(string), d.Id()).Do() 365 if err != nil { 366 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 367 // The resource doesn't exist anymore 368 d.SetId("") 369 370 return nil 371 } 372 373 return fmt.Errorf("Error reading instance: %s", err) 374 } 375 376 d.Set("can_ip_forward", instance.CanIpForward) 377 378 // Set the service accounts 379 for i, serviceAccount := range instance.ServiceAccounts { 380 prefix := fmt.Sprintf("service_account.%d", i) 381 d.Set(prefix + ".email", serviceAccount.Email) 382 d.Set(prefix + ".scopes.#", len(serviceAccount.Scopes)) 383 for j, scope := range serviceAccount.Scopes { 384 d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope) 385 } 386 } 387 388 // Set the networks 389 externalIP := "" 390 for i, iface := range instance.NetworkInterfaces { 391 prefix := fmt.Sprintf("network.%d", i) 392 d.Set(prefix+".name", iface.Name) 393 394 // Use the first external IP found for the default connection info. 395 natIP := resourceInstanceNatIP(iface) 396 if externalIP == "" && natIP != "" { 397 externalIP = natIP 398 } 399 d.Set(prefix+".external_address", natIP) 400 401 d.Set(prefix+".internal_address", iface.NetworkIP) 402 } 403 404 // Initialize the connection info 405 d.SetConnInfo(map[string]string{ 406 "type": "ssh", 407 "host": externalIP, 408 }) 409 410 // Set the metadata fingerprint if there is one. 411 if instance.Metadata != nil { 412 d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) 413 } 414 415 // Set the tags fingerprint if there is one. 416 if instance.Tags != nil { 417 d.Set("tags_fingerprint", instance.Tags.Fingerprint) 418 } 419 420 return nil 421 } 422 423 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 424 config := meta.(*Config) 425 426 // Enable partial mode for the resource since it is possible 427 d.Partial(true) 428 429 // If the Metadata has changed, then update that. 430 if d.HasChange("metadata") { 431 metadata := resourceInstanceMetadata(d) 432 op, err := config.clientCompute.Instances.SetMetadata( 433 config.Project, d.Get("zone").(string), d.Id(), metadata).Do() 434 if err != nil { 435 return fmt.Errorf("Error updating metadata: %s", err) 436 } 437 438 w := &OperationWaiter{ 439 Service: config.clientCompute, 440 Op: op, 441 Project: config.Project, 442 Zone: d.Get("zone").(string), 443 Type: OperationWaitZone, 444 } 445 state := w.Conf() 446 state.Delay = 1 * time.Second 447 state.Timeout = 5 * time.Minute 448 state.MinTimeout = 2 * time.Second 449 opRaw, err := state.WaitForState() 450 if err != nil { 451 return fmt.Errorf("Error waiting for metadata to update: %s", err) 452 } 453 op = opRaw.(*compute.Operation) 454 if op.Error != nil { 455 // Return the error 456 return OperationError(*op.Error) 457 } 458 459 d.SetPartial("metadata") 460 } 461 462 if d.HasChange("tags") { 463 tags := resourceInstanceTags(d) 464 op, err := config.clientCompute.Instances.SetTags( 465 config.Project, d.Get("zone").(string), d.Id(), tags).Do() 466 if err != nil { 467 return fmt.Errorf("Error updating tags: %s", err) 468 } 469 470 w := &OperationWaiter{ 471 Service: config.clientCompute, 472 Op: op, 473 Project: config.Project, 474 Zone: d.Get("zone").(string), 475 Type: OperationWaitZone, 476 } 477 state := w.Conf() 478 state.Delay = 1 * time.Second 479 state.Timeout = 5 * time.Minute 480 state.MinTimeout = 2 * time.Second 481 opRaw, err := state.WaitForState() 482 if err != nil { 483 return fmt.Errorf("Error waiting for tags to update: %s", err) 484 } 485 op = opRaw.(*compute.Operation) 486 if op.Error != nil { 487 // Return the error 488 return OperationError(*op.Error) 489 } 490 491 d.SetPartial("tags") 492 } 493 494 // We made it, disable partial mode 495 d.Partial(false) 496 497 return resourceComputeInstanceRead(d, meta) 498 } 499 500 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 501 config := meta.(*Config) 502 503 op, err := config.clientCompute.Instances.Delete( 504 config.Project, d.Get("zone").(string), d.Id()).Do() 505 if err != nil { 506 return fmt.Errorf("Error deleting instance: %s", err) 507 } 508 509 // Wait for the operation to complete 510 w := &OperationWaiter{ 511 Service: config.clientCompute, 512 Op: op, 513 Project: config.Project, 514 Zone: d.Get("zone").(string), 515 Type: OperationWaitZone, 516 } 517 state := w.Conf() 518 state.Delay = 5 * time.Second 519 state.Timeout = 5 * time.Minute 520 state.MinTimeout = 2 * time.Second 521 opRaw, err := state.WaitForState() 522 if err != nil { 523 return fmt.Errorf("Error waiting for instance to delete: %s", err) 524 } 525 op = opRaw.(*compute.Operation) 526 if op.Error != nil { 527 // Return the error 528 return OperationError(*op.Error) 529 } 530 531 d.SetId("") 532 return nil 533 } 534 535 func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata { 536 var metadata *compute.Metadata 537 if v := d.Get("metadata").([]interface{}); len(v) > 0 { 538 m := new(compute.Metadata) 539 m.Items = make([]*compute.MetadataItems, 0, len(v)) 540 for _, v := range v { 541 for k, v := range v.(map[string]interface{}) { 542 m.Items = append(m.Items, &compute.MetadataItems{ 543 Key: k, 544 Value: v.(string), 545 }) 546 } 547 } 548 549 // Set the fingerprint. If the metadata has never been set before 550 // then this will just be blank. 551 m.Fingerprint = d.Get("metadata_fingerprint").(string) 552 553 metadata = m 554 } 555 556 return metadata 557 } 558 559 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 560 // Calculate the tags 561 var tags *compute.Tags 562 if v := d.Get("tags"); v != nil { 563 vs := v.(*schema.Set) 564 tags = new(compute.Tags) 565 tags.Items = make([]string, vs.Len()) 566 for i, v := range vs.List() { 567 tags.Items[i] = v.(string) 568 } 569 570 tags.Fingerprint = d.Get("tags_fingerprint").(string) 571 } 572 573 return tags 574 } 575 576 // resourceInstanceNatIP acquires the first NatIP with a "ONE_TO_ONE_NAT" type 577 // in the compute.NetworkInterface's AccessConfigs. 578 func resourceInstanceNatIP(iface *compute.NetworkInterface) (natIP string) { 579 for _, config := range iface.AccessConfigs { 580 if config.Type == "ONE_TO_ONE_NAT" { 581 natIP = config.NatIP 582 break 583 } 584 } 585 586 return natIP 587 }