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