github.com/lamielle/terraform@v0.3.2-0.20141121070651-81f008ba53d5/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 "tags": &schema.Schema{ 128 Type: schema.TypeSet, 129 Optional: true, 130 Elem: &schema.Schema{Type: schema.TypeString}, 131 Set: func(v interface{}) int { 132 return hashcode.String(v.(string)) 133 }, 134 }, 135 136 "metadata_fingerprint": &schema.Schema{ 137 Type: schema.TypeString, 138 Computed: true, 139 }, 140 141 "tags_fingerprint": &schema.Schema{ 142 Type: schema.TypeString, 143 Computed: true, 144 }, 145 }, 146 } 147 } 148 149 func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { 150 config := meta.(*Config) 151 152 // Get the zone 153 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 154 zone, err := config.clientCompute.Zones.Get( 155 config.Project, d.Get("zone").(string)).Do() 156 if err != nil { 157 return fmt.Errorf( 158 "Error loading zone '%s': %s", d.Get("zone").(string), err) 159 } 160 161 // Get the machine type 162 log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string)) 163 machineType, err := config.clientCompute.MachineTypes.Get( 164 config.Project, zone.Name, d.Get("machine_type").(string)).Do() 165 if err != nil { 166 return fmt.Errorf( 167 "Error loading machine type: %s", 168 err) 169 } 170 171 // Build up the list of disks 172 disksCount := d.Get("disk.#").(int) 173 disks := make([]*compute.AttachedDisk, 0, disksCount) 174 for i := 0; i < disksCount; i++ { 175 prefix := fmt.Sprintf("disk.%d", i) 176 177 // var sourceLink string 178 179 // Build the disk 180 var disk compute.AttachedDisk 181 disk.Type = "PERSISTENT" 182 disk.Mode = "READ_WRITE" 183 disk.Boot = i == 0 184 disk.AutoDelete = true 185 186 if v, ok := d.GetOk(prefix + ".auto_delete"); ok { 187 disk.AutoDelete = v.(bool) 188 } 189 190 // Load up the disk for this disk if specified 191 if v, ok := d.GetOk(prefix + ".disk"); ok { 192 diskName := v.(string) 193 diskData, err := config.clientCompute.Disks.Get( 194 config.Project, zone.Name, diskName).Do() 195 if err != nil { 196 return fmt.Errorf( 197 "Error loading disk '%s': %s", 198 diskName, err) 199 } 200 201 disk.Source = diskData.SelfLink 202 } 203 204 // Load up the image for this disk if specified 205 if v, ok := d.GetOk(prefix + ".image"); ok { 206 imageName := v.(string) 207 image, err := readImage(config, imageName) 208 if err != nil { 209 return fmt.Errorf( 210 "Error loading image '%s': %s", 211 imageName, err) 212 } 213 214 disk.InitializeParams = &compute.AttachedDiskInitializeParams{ 215 SourceImage: image.SelfLink, 216 } 217 } 218 219 if v, ok := d.GetOk(prefix + ".type"); ok { 220 diskTypeName := v.(string) 221 diskType, err := readDiskType(config, zone, diskTypeName) 222 if err != nil { 223 return fmt.Errorf( 224 "Error loading disk type '%s': %s", 225 diskTypeName, err) 226 } 227 228 disk.InitializeParams.DiskType = diskType.SelfLink 229 } 230 231 disks = append(disks, &disk) 232 } 233 234 // Build up the list of networks 235 networksCount := d.Get("network.#").(int) 236 networks := make([]*compute.NetworkInterface, 0, networksCount) 237 for i := 0; i < networksCount; i++ { 238 prefix := fmt.Sprintf("network.%d", i) 239 // Load up the name of this network 240 networkName := d.Get(prefix + ".source").(string) 241 network, err := config.clientCompute.Networks.Get( 242 config.Project, networkName).Do() 243 if err != nil { 244 return fmt.Errorf( 245 "Error loading network '%s': %s", 246 networkName, err) 247 } 248 249 // Build the disk 250 var iface compute.NetworkInterface 251 iface.AccessConfigs = []*compute.AccessConfig{ 252 &compute.AccessConfig{ 253 Type: "ONE_TO_ONE_NAT", 254 NatIP: d.Get(prefix + ".address").(string), 255 }, 256 } 257 iface.Network = network.SelfLink 258 259 networks = append(networks, &iface) 260 } 261 262 // Create the instance information 263 instance := compute.Instance{ 264 CanIpForward: d.Get("can_ip_forward").(bool), 265 Description: d.Get("description").(string), 266 Disks: disks, 267 MachineType: machineType.SelfLink, 268 Metadata: resourceInstanceMetadata(d), 269 Name: d.Get("name").(string), 270 NetworkInterfaces: networks, 271 Tags: resourceInstanceTags(d), 272 /* 273 ServiceAccounts: []*compute.ServiceAccount{ 274 &compute.ServiceAccount{ 275 Email: "default", 276 Scopes: []string{ 277 "https://www.googleapis.com/auth/userinfo.email", 278 "https://www.googleapis.com/auth/compute", 279 "https://www.googleapis.com/auth/devstorage.full_control", 280 }, 281 }, 282 }, 283 */ 284 } 285 286 log.Printf("[INFO] Requesting instance creation") 287 op, err := config.clientCompute.Instances.Insert( 288 config.Project, zone.Name, &instance).Do() 289 if err != nil { 290 return fmt.Errorf("Error creating instance: %s", err) 291 } 292 293 // Store the ID now 294 d.SetId(instance.Name) 295 296 // Wait for the operation to complete 297 w := &OperationWaiter{ 298 Service: config.clientCompute, 299 Op: op, 300 Project: config.Project, 301 Zone: zone.Name, 302 Type: OperationWaitZone, 303 } 304 state := w.Conf() 305 state.Delay = 10 * time.Second 306 state.Timeout = 10 * time.Minute 307 state.MinTimeout = 2 * time.Second 308 opRaw, err := state.WaitForState() 309 if err != nil { 310 return fmt.Errorf("Error waiting for instance to create: %s", err) 311 } 312 op = opRaw.(*compute.Operation) 313 if op.Error != nil { 314 // The resource didn't actually create 315 d.SetId("") 316 317 // Return the error 318 return OperationError(*op.Error) 319 } 320 321 return resourceComputeInstanceRead(d, meta) 322 } 323 324 func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { 325 config := meta.(*Config) 326 327 instance, err := config.clientCompute.Instances.Get( 328 config.Project, d.Get("zone").(string), d.Id()).Do() 329 if err != nil { 330 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 331 // The resource doesn't exist anymore 332 d.SetId("") 333 334 return nil 335 } 336 337 return fmt.Errorf("Error reading instance: %s", err) 338 } 339 340 d.Set("can_ip_forward", instance.CanIpForward) 341 342 // Set the networks 343 externalIP := "" 344 for i, iface := range instance.NetworkInterfaces { 345 prefix := fmt.Sprintf("network.%d", i) 346 d.Set(prefix+".name", iface.Name) 347 348 // Use the first external IP found for the default connection info. 349 natIP := resourceInstanceNatIP(iface) 350 if externalIP == "" && natIP != "" { 351 externalIP = natIP 352 } 353 d.Set(prefix+".external_address", natIP) 354 355 d.Set(prefix+".internal_address", iface.NetworkIP) 356 } 357 358 // Initialize the connection info 359 d.SetConnInfo(map[string]string{ 360 "type": "ssh", 361 "host": externalIP, 362 }) 363 364 // Set the metadata fingerprint if there is one. 365 if instance.Metadata != nil { 366 d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) 367 } 368 369 // Set the tags fingerprint if there is one. 370 if instance.Tags != nil { 371 d.Set("tags_fingerprint", instance.Tags.Fingerprint) 372 } 373 374 return nil 375 } 376 377 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 378 config := meta.(*Config) 379 380 // Enable partial mode for the resource since it is possible 381 d.Partial(true) 382 383 // If the Metadata has changed, then update that. 384 if d.HasChange("metadata") { 385 metadata := resourceInstanceMetadata(d) 386 op, err := config.clientCompute.Instances.SetMetadata( 387 config.Project, d.Get("zone").(string), d.Id(), metadata).Do() 388 if err != nil { 389 return fmt.Errorf("Error updating metadata: %s", err) 390 } 391 392 w := &OperationWaiter{ 393 Service: config.clientCompute, 394 Op: op, 395 Project: config.Project, 396 Zone: d.Get("zone").(string), 397 Type: OperationWaitZone, 398 } 399 state := w.Conf() 400 state.Delay = 1 * time.Second 401 state.Timeout = 5 * time.Minute 402 state.MinTimeout = 2 * time.Second 403 opRaw, err := state.WaitForState() 404 if err != nil { 405 return fmt.Errorf("Error waiting for metadata to update: %s", err) 406 } 407 op = opRaw.(*compute.Operation) 408 if op.Error != nil { 409 // Return the error 410 return OperationError(*op.Error) 411 } 412 413 d.SetPartial("metadata") 414 } 415 416 if d.HasChange("tags") { 417 tags := resourceInstanceTags(d) 418 op, err := config.clientCompute.Instances.SetTags( 419 config.Project, d.Get("zone").(string), d.Id(), tags).Do() 420 if err != nil { 421 return fmt.Errorf("Error updating tags: %s", err) 422 } 423 424 w := &OperationWaiter{ 425 Service: config.clientCompute, 426 Op: op, 427 Project: config.Project, 428 Zone: d.Get("zone").(string), 429 Type: OperationWaitZone, 430 } 431 state := w.Conf() 432 state.Delay = 1 * time.Second 433 state.Timeout = 5 * time.Minute 434 state.MinTimeout = 2 * time.Second 435 opRaw, err := state.WaitForState() 436 if err != nil { 437 return fmt.Errorf("Error waiting for tags to update: %s", err) 438 } 439 op = opRaw.(*compute.Operation) 440 if op.Error != nil { 441 // Return the error 442 return OperationError(*op.Error) 443 } 444 445 d.SetPartial("tags") 446 } 447 448 // We made it, disable partial mode 449 d.Partial(false) 450 451 return resourceComputeInstanceRead(d, meta) 452 } 453 454 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 455 config := meta.(*Config) 456 457 op, err := config.clientCompute.Instances.Delete( 458 config.Project, d.Get("zone").(string), d.Id()).Do() 459 if err != nil { 460 return fmt.Errorf("Error deleting instance: %s", err) 461 } 462 463 // Wait for the operation to complete 464 w := &OperationWaiter{ 465 Service: config.clientCompute, 466 Op: op, 467 Project: config.Project, 468 Zone: d.Get("zone").(string), 469 Type: OperationWaitZone, 470 } 471 state := w.Conf() 472 state.Delay = 5 * time.Second 473 state.Timeout = 5 * time.Minute 474 state.MinTimeout = 2 * time.Second 475 opRaw, err := state.WaitForState() 476 if err != nil { 477 return fmt.Errorf("Error waiting for instance to delete: %s", err) 478 } 479 op = opRaw.(*compute.Operation) 480 if op.Error != nil { 481 // Return the error 482 return OperationError(*op.Error) 483 } 484 485 d.SetId("") 486 return nil 487 } 488 489 func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata { 490 var metadata *compute.Metadata 491 if v := d.Get("metadata").([]interface{}); len(v) > 0 { 492 m := new(compute.Metadata) 493 m.Items = make([]*compute.MetadataItems, 0, len(v)) 494 for _, v := range v { 495 for k, v := range v.(map[string]interface{}) { 496 m.Items = append(m.Items, &compute.MetadataItems{ 497 Key: k, 498 Value: v.(string), 499 }) 500 } 501 } 502 503 // Set the fingerprint. If the metadata has never been set before 504 // then this will just be blank. 505 m.Fingerprint = d.Get("metadata_fingerprint").(string) 506 507 metadata = m 508 } 509 510 return metadata 511 } 512 513 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 514 // Calculate the tags 515 var tags *compute.Tags 516 if v := d.Get("tags"); v != nil { 517 vs := v.(*schema.Set) 518 tags = new(compute.Tags) 519 tags.Items = make([]string, vs.Len()) 520 for i, v := range vs.List() { 521 tags.Items[i] = v.(string) 522 } 523 524 tags.Fingerprint = d.Get("tags_fingerprint").(string) 525 } 526 527 return tags 528 } 529 530 // resourceInstanceNatIP acquires the first NatIP with a "ONE_TO_ONE_NAT" type 531 // in the compute.NetworkInterface's AccessConfigs. 532 func resourceInstanceNatIP(iface *compute.NetworkInterface) (natIP string) { 533 for _, config := range iface.AccessConfigs { 534 if config.Type == "ONE_TO_ONE_NAT" { 535 natIP = config.NatIP 536 break 537 } 538 } 539 540 return natIP 541 }