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