github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/builtin/providers/google/resource_compute_instance_template.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/hashicorp/terraform/helper/hashcode" 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 Required: true, 23 ForceNew: true, 24 }, 25 26 "description": &schema.Schema{ 27 Type: schema.TypeString, 28 Optional: true, 29 ForceNew: true, 30 }, 31 32 "can_ip_forward": &schema.Schema{ 33 Type: schema.TypeBool, 34 Optional: true, 35 Default: false, 36 ForceNew: true, 37 }, 38 39 "instance_description": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 ForceNew: true, 43 }, 44 45 "machine_type": &schema.Schema{ 46 Type: schema.TypeString, 47 Required: true, 48 ForceNew: true, 49 }, 50 51 // TODO: Constraint either source or other disk params 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 "metadata": &schema.Schema{ 129 Type: schema.TypeList, 130 Optional: true, 131 ForceNew: true, 132 Elem: &schema.Schema{ 133 Type: schema.TypeMap, 134 }, 135 }, 136 137 "network": &schema.Schema{ 138 Type: schema.TypeList, 139 Required: true, 140 ForceNew: true, 141 Elem: &schema.Resource{ 142 Schema: map[string]*schema.Schema{ 143 "source": &schema.Schema{ 144 Type: schema.TypeString, 145 ForceNew: true, 146 Required: true, 147 }, 148 149 "address": &schema.Schema{ 150 Type: schema.TypeString, 151 ForceNew: true, 152 Optional: true, 153 }, 154 }, 155 }, 156 }, 157 158 "automatic_restart": &schema.Schema{ 159 Type: schema.TypeBool, 160 Optional: true, 161 Default: true, 162 ForceNew: true, 163 }, 164 165 "on_host_maintenance": &schema.Schema{ 166 Type: schema.TypeString, 167 Optional: true, 168 ForceNew: true, 169 }, 170 171 "service_account": &schema.Schema{ 172 Type: schema.TypeList, 173 Optional: true, 174 ForceNew: true, 175 Elem: &schema.Resource{ 176 Schema: map[string]*schema.Schema{ 177 "email": &schema.Schema{ 178 Type: schema.TypeString, 179 Computed: true, 180 ForceNew: true, 181 }, 182 183 "scopes": &schema.Schema{ 184 Type: schema.TypeList, 185 Required: true, 186 ForceNew: true, 187 Elem: &schema.Schema{ 188 Type: schema.TypeString, 189 StateFunc: func(v interface{}) string { 190 return canonicalizeServiceScope(v.(string)) 191 }, 192 }, 193 }, 194 }, 195 }, 196 }, 197 198 "tags": &schema.Schema{ 199 Type: schema.TypeSet, 200 Optional: true, 201 ForceNew: true, 202 Elem: &schema.Schema{Type: schema.TypeString}, 203 Set: func(v interface{}) int { 204 return hashcode.String(v.(string)) 205 }, 206 }, 207 208 "metadata_fingerprint": &schema.Schema{ 209 Type: schema.TypeString, 210 Computed: true, 211 }, 212 213 "tags_fingerprint": &schema.Schema{ 214 Type: schema.TypeString, 215 Computed: true, 216 }, 217 218 "self_link": &schema.Schema{ 219 Type: schema.TypeString, 220 Computed: true, 221 }, 222 }, 223 } 224 } 225 226 func buildDisks(d *schema.ResourceData, meta interface{}) []*compute.AttachedDisk { 227 disksCount := d.Get("disk.#").(int) 228 229 disks := make([]*compute.AttachedDisk, 0, disksCount) 230 for i := 0; i < disksCount; i++ { 231 prefix := fmt.Sprintf("disk.%d", i) 232 233 // Build the disk 234 var disk compute.AttachedDisk 235 disk.Type = "PERSISTENT" 236 disk.Mode = "READ_WRITE" 237 disk.Interface = "SCSI" 238 disk.Boot = i == 0 239 disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) 240 241 if v, ok := d.GetOk(prefix + ".boot"); ok { 242 disk.Boot = v.(bool) 243 } 244 245 if v, ok := d.GetOk(prefix + ".device_name"); ok { 246 disk.DeviceName = v.(string) 247 } 248 249 if v, ok := d.GetOk(prefix + ".source"); ok { 250 disk.Source = v.(string) 251 } else { 252 disk.InitializeParams = &compute.AttachedDiskInitializeParams{} 253 254 if v, ok := d.GetOk(prefix + ".disk_name"); ok { 255 disk.InitializeParams.DiskName = v.(string) 256 } 257 if v, ok := d.GetOk(prefix + ".disk_size_gb"); ok { 258 disk.InitializeParams.DiskSizeGb = v.(int64) 259 } 260 disk.InitializeParams.DiskType = "pd-standard" 261 if v, ok := d.GetOk(prefix + ".disk_type"); ok { 262 disk.InitializeParams.DiskType = v.(string) 263 } 264 265 if v, ok := d.GetOk(prefix + ".source_image"); ok { 266 disk.InitializeParams.SourceImage = v.(string) 267 } 268 } 269 270 if v, ok := d.GetOk(prefix + ".interface"); ok { 271 disk.Interface = v.(string) 272 } 273 274 if v, ok := d.GetOk(prefix + ".mode"); ok { 275 disk.Mode = v.(string) 276 } 277 278 if v, ok := d.GetOk(prefix + ".type"); ok { 279 disk.Type = v.(string) 280 } 281 282 disks = append(disks, &disk) 283 } 284 285 return disks 286 } 287 288 func buildNetworks(d *schema.ResourceData, meta interface{}) (error, []*compute.NetworkInterface) { 289 // Build up the list of networks 290 networksCount := d.Get("network.#").(int) 291 networks := make([]*compute.NetworkInterface, 0, networksCount) 292 for i := 0; i < networksCount; i++ { 293 prefix := fmt.Sprintf("network.%d", i) 294 295 source := "global/networks/default" 296 if v, ok := d.GetOk(prefix + ".source"); ok { 297 if v.(string) != "default" { 298 source = v.(string) 299 } 300 } 301 302 // Build the interface 303 var iface compute.NetworkInterface 304 iface.AccessConfigs = []*compute.AccessConfig{ 305 &compute.AccessConfig{ 306 Type: "ONE_TO_ONE_NAT", 307 NatIP: d.Get(prefix + ".address").(string), 308 }, 309 } 310 iface.Network = source 311 312 networks = append(networks, &iface) 313 } 314 return nil, networks 315 } 316 317 func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interface{}) error { 318 config := meta.(*Config) 319 320 instanceProperties := &compute.InstanceProperties{} 321 322 instanceProperties.CanIpForward = d.Get("can_ip_forward").(bool) 323 instanceProperties.Description = d.Get("instance_description").(string) 324 instanceProperties.MachineType = d.Get("machine_type").(string) 325 instanceProperties.Disks = buildDisks(d, meta) 326 instanceProperties.Metadata = resourceInstanceMetadata(d) 327 err, networks := buildNetworks(d, meta) 328 if err != nil { 329 return err 330 } 331 instanceProperties.NetworkInterfaces = networks 332 333 instanceProperties.Scheduling = &compute.Scheduling{ 334 AutomaticRestart: d.Get("automatic_restart").(bool), 335 } 336 instanceProperties.Scheduling.OnHostMaintenance = "MIGRATE" 337 if v, ok := d.GetOk("on_host_maintenance"); ok { 338 instanceProperties.Scheduling.OnHostMaintenance = v.(string) 339 } 340 341 serviceAccountsCount := d.Get("service_account.#").(int) 342 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 343 for i := 0; i < serviceAccountsCount; i++ { 344 prefix := fmt.Sprintf("service_account.%d", i) 345 346 scopesCount := d.Get(prefix + ".scopes.#").(int) 347 scopes := make([]string, 0, scopesCount) 348 for j := 0; j < scopesCount; j++ { 349 scope := d.Get(fmt.Sprintf(prefix+".scopes.%d", j)).(string) 350 scopes = append(scopes, canonicalizeServiceScope(scope)) 351 } 352 353 serviceAccount := &compute.ServiceAccount{ 354 Email: "default", 355 Scopes: scopes, 356 } 357 358 serviceAccounts = append(serviceAccounts, serviceAccount) 359 } 360 instanceProperties.ServiceAccounts = serviceAccounts 361 362 instanceProperties.Tags = resourceInstanceTags(d) 363 364 instanceTemplate := compute.InstanceTemplate{ 365 Description: d.Get("description").(string), 366 Properties: instanceProperties, 367 Name: d.Get("name").(string), 368 } 369 370 op, err := config.clientCompute.InstanceTemplates.Insert( 371 config.Project, &instanceTemplate).Do() 372 if err != nil { 373 return fmt.Errorf("Error creating instance: %s", err) 374 } 375 376 // Store the ID now 377 d.SetId(instanceTemplate.Name) 378 379 // Wait for the operation to complete 380 w := &OperationWaiter{ 381 Service: config.clientCompute, 382 Op: op, 383 Project: config.Project, 384 Type: OperationWaitGlobal, 385 } 386 state := w.Conf() 387 state.Delay = 10 * time.Second 388 state.Timeout = 10 * time.Minute 389 state.MinTimeout = 2 * time.Second 390 opRaw, err := state.WaitForState() 391 if err != nil { 392 return fmt.Errorf("Error waiting for instance template to create: %s", err) 393 } 394 op = opRaw.(*compute.Operation) 395 if op.Error != nil { 396 // The resource didn't actually create 397 d.SetId("") 398 399 // Return the error 400 return OperationError(*op.Error) 401 } 402 403 return resourceComputeInstanceTemplateRead(d, meta) 404 } 405 406 func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{}) error { 407 config := meta.(*Config) 408 409 instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( 410 config.Project, d.Id()).Do() 411 if err != nil { 412 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 413 // The resource doesn't exist anymore 414 d.SetId("") 415 416 return nil 417 } 418 419 return fmt.Errorf("Error reading instance template: %s", err) 420 } 421 422 // Set the metadata fingerprint if there is one. 423 if instanceTemplate.Properties.Metadata != nil { 424 d.Set("metadata_fingerprint", instanceTemplate.Properties.Metadata.Fingerprint) 425 } 426 427 // Set the tags fingerprint if there is one. 428 if instanceTemplate.Properties.Tags != nil { 429 d.Set("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint) 430 } 431 d.Set("self_link", instanceTemplate.SelfLink) 432 433 return nil 434 } 435 436 func resourceComputeInstanceTemplateDelete(d *schema.ResourceData, meta interface{}) error { 437 config := meta.(*Config) 438 439 op, err := config.clientCompute.InstanceTemplates.Delete( 440 config.Project, d.Id()).Do() 441 if err != nil { 442 return fmt.Errorf("Error deleting instance template: %s", err) 443 } 444 445 // Wait for the operation to complete 446 w := &OperationWaiter{ 447 Service: config.clientCompute, 448 Op: op, 449 Project: config.Project, 450 Type: OperationWaitGlobal, 451 } 452 state := w.Conf() 453 state.Delay = 5 * time.Second 454 state.Timeout = 5 * time.Minute 455 state.MinTimeout = 2 * time.Second 456 opRaw, err := state.WaitForState() 457 if err != nil { 458 return fmt.Errorf("Error waiting for instance template to delete: %s", err) 459 } 460 op = opRaw.(*compute.Operation) 461 if op.Error != nil { 462 // Return the error 463 return OperationError(*op.Error) 464 } 465 466 d.SetId("") 467 return nil 468 }