github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/builtin/providers/google/resource_compute_instance_template.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/helper/schema" 8 "google.golang.org/api/compute/v1" 9 "google.golang.org/api/googleapi" 10 ) 11 12 func resourceComputeInstanceTemplate() *schema.Resource { 13 return &schema.Resource{ 14 Create: resourceComputeInstanceTemplateCreate, 15 Read: resourceComputeInstanceTemplateRead, 16 Delete: resourceComputeInstanceTemplateDelete, 17 18 Schema: map[string]*schema.Schema{ 19 "name": &schema.Schema{ 20 Type: schema.TypeString, 21 Required: true, 22 ForceNew: true, 23 }, 24 25 "description": &schema.Schema{ 26 Type: schema.TypeString, 27 Optional: true, 28 ForceNew: true, 29 }, 30 31 "can_ip_forward": &schema.Schema{ 32 Type: schema.TypeBool, 33 Optional: true, 34 Default: false, 35 ForceNew: true, 36 }, 37 38 "instance_description": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 ForceNew: true, 42 }, 43 44 "machine_type": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 ForceNew: true, 48 }, 49 50 "disk": &schema.Schema{ 51 Type: schema.TypeList, 52 Required: true, 53 ForceNew: true, 54 Elem: &schema.Resource{ 55 Schema: map[string]*schema.Schema{ 56 "auto_delete": &schema.Schema{ 57 Type: schema.TypeBool, 58 Optional: true, 59 Default: true, 60 ForceNew: true, 61 }, 62 63 "boot": &schema.Schema{ 64 Type: schema.TypeBool, 65 Optional: true, 66 ForceNew: true, 67 }, 68 69 "device_name": &schema.Schema{ 70 Type: schema.TypeString, 71 Optional: true, 72 ForceNew: true, 73 }, 74 75 "disk_name": &schema.Schema{ 76 Type: schema.TypeString, 77 Optional: true, 78 ForceNew: true, 79 }, 80 81 "disk_size_gb": &schema.Schema{ 82 Type: schema.TypeInt, 83 Optional: true, 84 ForceNew: true, 85 }, 86 87 "disk_type": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 ForceNew: true, 91 }, 92 93 "source_image": &schema.Schema{ 94 Type: schema.TypeString, 95 Optional: true, 96 ForceNew: true, 97 }, 98 99 "interface": &schema.Schema{ 100 Type: schema.TypeString, 101 Optional: true, 102 ForceNew: true, 103 }, 104 105 "mode": &schema.Schema{ 106 Type: schema.TypeString, 107 Optional: true, 108 ForceNew: true, 109 }, 110 111 "source": &schema.Schema{ 112 Type: schema.TypeString, 113 Optional: true, 114 ForceNew: true, 115 }, 116 117 "type": &schema.Schema{ 118 Type: schema.TypeString, 119 Optional: true, 120 ForceNew: true, 121 }, 122 }, 123 }, 124 }, 125 126 "metadata": &schema.Schema{ 127 Type: schema.TypeMap, 128 Optional: true, 129 ForceNew: true, 130 }, 131 132 "network_interface": &schema.Schema{ 133 Type: schema.TypeList, 134 Optional: true, 135 ForceNew: true, 136 Elem: &schema.Resource{ 137 Schema: map[string]*schema.Schema{ 138 "network": &schema.Schema{ 139 Type: schema.TypeString, 140 Optional: true, 141 ForceNew: true, 142 }, 143 144 "subnetwork": &schema.Schema{ 145 Type: schema.TypeString, 146 Optional: true, 147 ForceNew: true, 148 }, 149 150 "access_config": &schema.Schema{ 151 Type: schema.TypeList, 152 Optional: true, 153 Elem: &schema.Resource{ 154 Schema: map[string]*schema.Schema{ 155 "nat_ip": &schema.Schema{ 156 Type: schema.TypeString, 157 Computed: true, 158 Optional: true, 159 }, 160 }, 161 }, 162 }, 163 }, 164 }, 165 }, 166 167 "automatic_restart": &schema.Schema{ 168 Type: schema.TypeBool, 169 Optional: true, 170 Default: true, 171 ForceNew: true, 172 Deprecated: "Please use `scheduling.automatic_restart` instead", 173 }, 174 175 "on_host_maintenance": &schema.Schema{ 176 Type: schema.TypeString, 177 Optional: true, 178 ForceNew: true, 179 Deprecated: "Please use `scheduling.on_host_maintenance` instead", 180 }, 181 182 "region": &schema.Schema{ 183 Type: schema.TypeString, 184 Optional: true, 185 ForceNew: true, 186 }, 187 188 "scheduling": &schema.Schema{ 189 Type: schema.TypeList, 190 Optional: true, 191 ForceNew: true, 192 Elem: &schema.Resource{ 193 Schema: map[string]*schema.Schema{ 194 "preemptible": &schema.Schema{ 195 Type: schema.TypeBool, 196 Optional: true, 197 ForceNew: true, 198 }, 199 200 "automatic_restart": &schema.Schema{ 201 Type: schema.TypeBool, 202 Optional: true, 203 Default: true, 204 ForceNew: true, 205 }, 206 207 "on_host_maintenance": &schema.Schema{ 208 Type: schema.TypeString, 209 Optional: true, 210 ForceNew: true, 211 }, 212 }, 213 }, 214 }, 215 216 "service_account": &schema.Schema{ 217 Type: schema.TypeList, 218 Optional: true, 219 ForceNew: true, 220 Elem: &schema.Resource{ 221 Schema: map[string]*schema.Schema{ 222 "email": &schema.Schema{ 223 Type: schema.TypeString, 224 Computed: true, 225 ForceNew: true, 226 }, 227 228 "scopes": &schema.Schema{ 229 Type: schema.TypeList, 230 Required: true, 231 ForceNew: true, 232 Elem: &schema.Schema{ 233 Type: schema.TypeString, 234 StateFunc: func(v interface{}) string { 235 return canonicalizeServiceScope(v.(string)) 236 }, 237 }, 238 }, 239 }, 240 }, 241 }, 242 243 "tags": &schema.Schema{ 244 Type: schema.TypeSet, 245 Optional: true, 246 ForceNew: true, 247 Elem: &schema.Schema{Type: schema.TypeString}, 248 Set: schema.HashString, 249 }, 250 251 "metadata_fingerprint": &schema.Schema{ 252 Type: schema.TypeString, 253 Computed: true, 254 }, 255 256 "tags_fingerprint": &schema.Schema{ 257 Type: schema.TypeString, 258 Computed: true, 259 }, 260 261 "self_link": &schema.Schema{ 262 Type: schema.TypeString, 263 Computed: true, 264 }, 265 }, 266 } 267 } 268 269 func buildDisks(d *schema.ResourceData, meta interface{}) ([]*compute.AttachedDisk, error) { 270 config := meta.(*Config) 271 272 disksCount := d.Get("disk.#").(int) 273 274 disks := make([]*compute.AttachedDisk, 0, disksCount) 275 for i := 0; i < disksCount; i++ { 276 prefix := fmt.Sprintf("disk.%d", i) 277 278 // Build the disk 279 var disk compute.AttachedDisk 280 disk.Type = "PERSISTENT" 281 disk.Mode = "READ_WRITE" 282 disk.Interface = "SCSI" 283 disk.Boot = i == 0 284 disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) 285 286 if v, ok := d.GetOk(prefix + ".boot"); ok { 287 disk.Boot = v.(bool) 288 } 289 290 if v, ok := d.GetOk(prefix + ".device_name"); ok { 291 disk.DeviceName = v.(string) 292 } 293 294 if v, ok := d.GetOk(prefix + ".source"); ok { 295 disk.Source = v.(string) 296 } else { 297 disk.InitializeParams = &compute.AttachedDiskInitializeParams{} 298 299 if v, ok := d.GetOk(prefix + ".disk_name"); ok { 300 disk.InitializeParams.DiskName = v.(string) 301 } 302 if v, ok := d.GetOk(prefix + ".disk_size_gb"); ok { 303 disk.InitializeParams.DiskSizeGb = int64(v.(int)) 304 } 305 disk.InitializeParams.DiskType = "pd-standard" 306 if v, ok := d.GetOk(prefix + ".disk_type"); ok { 307 disk.InitializeParams.DiskType = v.(string) 308 } 309 310 if v, ok := d.GetOk(prefix + ".source_image"); ok { 311 imageName := v.(string) 312 imageUrl, err := resolveImage(config, imageName) 313 if err != nil { 314 return nil, fmt.Errorf( 315 "Error resolving image name '%s': %s", 316 imageName, err) 317 } 318 disk.InitializeParams.SourceImage = imageUrl 319 } 320 } 321 322 if v, ok := d.GetOk(prefix + ".interface"); ok { 323 disk.Interface = v.(string) 324 } 325 326 if v, ok := d.GetOk(prefix + ".mode"); ok { 327 disk.Mode = v.(string) 328 } 329 330 if v, ok := d.GetOk(prefix + ".type"); ok { 331 disk.Type = v.(string) 332 } 333 334 disks = append(disks, &disk) 335 } 336 337 return disks, nil 338 } 339 340 func buildNetworks(d *schema.ResourceData, meta interface{}) (error, []*compute.NetworkInterface) { 341 // Build up the list of networks 342 config := meta.(*Config) 343 344 networksCount := d.Get("network_interface.#").(int) 345 networkInterfaces := make([]*compute.NetworkInterface, 0, networksCount) 346 for i := 0; i < networksCount; i++ { 347 prefix := fmt.Sprintf("network_interface.%d", i) 348 349 var networkName, subnetworkName string 350 if v, ok := d.GetOk(prefix + ".network"); ok { 351 networkName = v.(string) 352 } 353 if v, ok := d.GetOk(prefix + ".subnetwork"); ok { 354 subnetworkName = v.(string) 355 } 356 357 if networkName == "" && subnetworkName == "" { 358 return fmt.Errorf("network or subnetwork must be provided"), nil 359 } 360 if networkName != "" && subnetworkName != "" { 361 return fmt.Errorf("network or subnetwork must not both be provided"), nil 362 } 363 364 var networkLink, subnetworkLink string 365 if networkName != "" { 366 network, err := config.clientCompute.Networks.Get( 367 config.Project, networkName).Do() 368 if err != nil { 369 return fmt.Errorf( 370 "Error referencing network '%s': %s", 371 networkName, err), nil 372 } 373 networkLink = network.SelfLink 374 } else { 375 // lookup subnetwork link using region and subnetwork name 376 region := d.Get("region").(string) 377 if region == "" { 378 region = config.Region 379 } 380 subnetwork, err := config.clientCompute.Subnetworks.Get( 381 config.Project, region, subnetworkName).Do() 382 if err != nil { 383 return fmt.Errorf( 384 "Error referencing subnetwork '%s' in region '%s': %s", 385 subnetworkName, region, err), nil 386 } 387 subnetworkLink = subnetwork.SelfLink 388 } 389 390 // Build the networkInterface 391 var iface compute.NetworkInterface 392 iface.Network = networkLink 393 iface.Subnetwork = subnetworkLink 394 395 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 396 iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount) 397 for j := 0; j < accessConfigsCount; j++ { 398 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 399 iface.AccessConfigs[j] = &compute.AccessConfig{ 400 Type: "ONE_TO_ONE_NAT", 401 NatIP: d.Get(acPrefix + ".nat_ip").(string), 402 } 403 } 404 405 networkInterfaces = append(networkInterfaces, &iface) 406 } 407 return nil, networkInterfaces 408 } 409 410 func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interface{}) error { 411 config := meta.(*Config) 412 413 instanceProperties := &compute.InstanceProperties{} 414 415 instanceProperties.CanIpForward = d.Get("can_ip_forward").(bool) 416 instanceProperties.Description = d.Get("instance_description").(string) 417 instanceProperties.MachineType = d.Get("machine_type").(string) 418 disks, err := buildDisks(d, meta) 419 if err != nil { 420 return err 421 } 422 instanceProperties.Disks = disks 423 metadata, err := resourceInstanceMetadata(d) 424 if err != nil { 425 return err 426 } 427 instanceProperties.Metadata = metadata 428 err, networks := buildNetworks(d, meta) 429 if err != nil { 430 return err 431 } 432 instanceProperties.NetworkInterfaces = networks 433 434 instanceProperties.Scheduling = &compute.Scheduling{} 435 instanceProperties.Scheduling.OnHostMaintenance = "MIGRATE" 436 437 if v, ok := d.GetOk("automatic_restart"); ok { 438 instanceProperties.Scheduling.AutomaticRestart = v.(bool) 439 } 440 441 if v, ok := d.GetOk("on_host_maintenance"); ok { 442 instanceProperties.Scheduling.OnHostMaintenance = v.(string) 443 } 444 445 if v, ok := d.GetOk("scheduling"); ok { 446 _schedulings := v.([]interface{}) 447 if len(_schedulings) > 1 { 448 return fmt.Errorf("Error, at most one `scheduling` block can be defined") 449 } 450 _scheduling := _schedulings[0].(map[string]interface{}) 451 452 if vp, okp := _scheduling["automatic_restart"]; okp { 453 instanceProperties.Scheduling.AutomaticRestart = vp.(bool) 454 } 455 456 if vp, okp := _scheduling["on_host_maintenance"]; okp { 457 instanceProperties.Scheduling.OnHostMaintenance = vp.(string) 458 } 459 460 if vp, okp := _scheduling["preemptible"]; okp { 461 instanceProperties.Scheduling.Preemptible = vp.(bool) 462 } 463 } 464 465 serviceAccountsCount := d.Get("service_account.#").(int) 466 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 467 for i := 0; i < serviceAccountsCount; i++ { 468 prefix := fmt.Sprintf("service_account.%d", i) 469 470 scopesCount := d.Get(prefix + ".scopes.#").(int) 471 scopes := make([]string, 0, scopesCount) 472 for j := 0; j < scopesCount; j++ { 473 scope := d.Get(fmt.Sprintf(prefix+".scopes.%d", j)).(string) 474 scopes = append(scopes, canonicalizeServiceScope(scope)) 475 } 476 477 serviceAccount := &compute.ServiceAccount{ 478 Email: "default", 479 Scopes: scopes, 480 } 481 482 serviceAccounts = append(serviceAccounts, serviceAccount) 483 } 484 instanceProperties.ServiceAccounts = serviceAccounts 485 486 instanceProperties.Tags = resourceInstanceTags(d) 487 488 instanceTemplate := compute.InstanceTemplate{ 489 Description: d.Get("description").(string), 490 Properties: instanceProperties, 491 Name: d.Get("name").(string), 492 } 493 494 op, err := config.clientCompute.InstanceTemplates.Insert( 495 config.Project, &instanceTemplate).Do() 496 if err != nil { 497 return fmt.Errorf("Error creating instance: %s", err) 498 } 499 500 // Store the ID now 501 d.SetId(instanceTemplate.Name) 502 503 err = computeOperationWaitGlobal(config, op, "Creating Instance Template") 504 if err != nil { 505 return err 506 } 507 508 return resourceComputeInstanceTemplateRead(d, meta) 509 } 510 511 func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{}) error { 512 config := meta.(*Config) 513 514 instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( 515 config.Project, d.Id()).Do() 516 if err != nil { 517 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 518 log.Printf("[WARN] Removing Instance Template %q because it's gone", d.Get("name").(string)) 519 // The resource doesn't exist anymore 520 d.SetId("") 521 522 return nil 523 } 524 525 return fmt.Errorf("Error reading instance template: %s", err) 526 } 527 528 // Set the metadata fingerprint if there is one. 529 if instanceTemplate.Properties.Metadata != nil { 530 d.Set("metadata_fingerprint", instanceTemplate.Properties.Metadata.Fingerprint) 531 } 532 533 // Set the tags fingerprint if there is one. 534 if instanceTemplate.Properties.Tags != nil { 535 d.Set("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint) 536 } 537 d.Set("self_link", instanceTemplate.SelfLink) 538 539 return nil 540 } 541 542 func resourceComputeInstanceTemplateDelete(d *schema.ResourceData, meta interface{}) error { 543 config := meta.(*Config) 544 545 op, err := config.clientCompute.InstanceTemplates.Delete( 546 config.Project, d.Id()).Do() 547 if err != nil { 548 return fmt.Errorf("Error deleting instance template: %s", err) 549 } 550 551 err = computeOperationWaitGlobal(config, op, "Deleting Instance Template") 552 if err != nil { 553 return err 554 } 555 556 d.SetId("") 557 return nil 558 }