github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/azure/resource_azure_instance.go (about) 1 package azure 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "fmt" 7 "log" 8 "strings" 9 "time" 10 11 "github.com/Azure/azure-sdk-for-go/management" 12 "github.com/Azure/azure-sdk-for-go/management/hostedservice" 13 "github.com/Azure/azure-sdk-for-go/management/osimage" 14 "github.com/Azure/azure-sdk-for-go/management/virtualmachine" 15 "github.com/Azure/azure-sdk-for-go/management/virtualmachineimage" 16 "github.com/Azure/azure-sdk-for-go/management/vmutils" 17 "github.com/hashicorp/terraform/helper/hashcode" 18 "github.com/hashicorp/terraform/helper/resource" 19 "github.com/hashicorp/terraform/helper/schema" 20 ) 21 22 const ( 23 linux = "Linux" 24 windows = "Windows" 25 storageContainterName = "vhds" 26 osDiskBlobNameFormat = "%s.vhd" 27 osDiskBlobStorageURL = "http://%s.blob.core.windows.net/" + storageContainterName + "/" + osDiskBlobNameFormat 28 ) 29 30 func resourceAzureInstance() *schema.Resource { 31 return &schema.Resource{ 32 Create: resourceAzureInstanceCreate, 33 Read: resourceAzureInstanceRead, 34 Update: resourceAzureInstanceUpdate, 35 Delete: resourceAzureInstanceDelete, 36 37 Schema: map[string]*schema.Schema{ 38 "name": &schema.Schema{ 39 Type: schema.TypeString, 40 Required: true, 41 ForceNew: true, 42 }, 43 44 "hosted_service_name": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 Computed: true, 48 ForceNew: true, 49 }, 50 51 // in order to prevent an unintentional delete of a containing 52 // hosted service in the case the same name are given to both the 53 // service and the instance despite their being created separately, 54 // we must maintain a flag to definitively denote whether this 55 // instance had a hosted service created for it or not: 56 "has_dedicated_service": &schema.Schema{ 57 Type: schema.TypeBool, 58 Computed: true, 59 }, 60 61 "description": &schema.Schema{ 62 Type: schema.TypeString, 63 Optional: true, 64 Computed: true, 65 ForceNew: true, 66 }, 67 68 "image": &schema.Schema{ 69 Type: schema.TypeString, 70 Required: true, 71 ForceNew: true, 72 }, 73 74 "size": &schema.Schema{ 75 Type: schema.TypeString, 76 Required: true, 77 }, 78 79 "subnet": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 Computed: true, 83 ForceNew: true, 84 }, 85 86 "virtual_network": &schema.Schema{ 87 Type: schema.TypeString, 88 Optional: true, 89 ForceNew: true, 90 }, 91 92 "storage_service_name": &schema.Schema{ 93 Type: schema.TypeString, 94 Optional: true, 95 ForceNew: true, 96 }, 97 98 "reverse_dns": &schema.Schema{ 99 Type: schema.TypeString, 100 Optional: true, 101 ForceNew: true, 102 }, 103 104 "location": &schema.Schema{ 105 Type: schema.TypeString, 106 Required: true, 107 ForceNew: true, 108 }, 109 110 "automatic_updates": &schema.Schema{ 111 Type: schema.TypeBool, 112 Optional: true, 113 Default: false, 114 ForceNew: true, 115 }, 116 117 "time_zone": &schema.Schema{ 118 Type: schema.TypeString, 119 Optional: true, 120 ForceNew: true, 121 }, 122 123 "username": &schema.Schema{ 124 Type: schema.TypeString, 125 Required: true, 126 ForceNew: true, 127 }, 128 129 "password": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 ForceNew: true, 133 }, 134 135 "ssh_key_thumbprint": &schema.Schema{ 136 Type: schema.TypeString, 137 Optional: true, 138 ForceNew: true, 139 }, 140 141 "endpoint": &schema.Schema{ 142 Type: schema.TypeSet, 143 Optional: true, 144 Computed: true, 145 Elem: &schema.Resource{ 146 Schema: map[string]*schema.Schema{ 147 "name": &schema.Schema{ 148 Type: schema.TypeString, 149 Required: true, 150 }, 151 152 "protocol": &schema.Schema{ 153 Type: schema.TypeString, 154 Optional: true, 155 Default: "tcp", 156 }, 157 158 "public_port": &schema.Schema{ 159 Type: schema.TypeInt, 160 Required: true, 161 }, 162 163 "private_port": &schema.Schema{ 164 Type: schema.TypeInt, 165 Required: true, 166 }, 167 }, 168 }, 169 Set: resourceAzureEndpointHash, 170 }, 171 172 "security_group": &schema.Schema{ 173 Type: schema.TypeString, 174 Optional: true, 175 Computed: true, 176 }, 177 178 "ip_address": &schema.Schema{ 179 Type: schema.TypeString, 180 Computed: true, 181 }, 182 183 "vip_address": &schema.Schema{ 184 Type: schema.TypeString, 185 Computed: true, 186 }, 187 188 "domain_name": &schema.Schema{ 189 Type: schema.TypeString, 190 Optional: true, 191 ForceNew: true, 192 }, 193 194 "domain_username": &schema.Schema{ 195 Type: schema.TypeString, 196 Optional: true, 197 ForceNew: true, 198 }, 199 200 "domain_password": &schema.Schema{ 201 Type: schema.TypeString, 202 Optional: true, 203 ForceNew: true, 204 }, 205 206 "domain_ou": &schema.Schema{ 207 Type: schema.TypeString, 208 Optional: true, 209 ForceNew: true, 210 }, 211 }, 212 } 213 } 214 215 func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) { 216 azureClient := meta.(*Client) 217 mc := azureClient.mgmtClient 218 hostedServiceClient := azureClient.hostedServiceClient 219 vmClient := azureClient.vmClient 220 221 name := d.Get("name").(string) 222 223 // Compute/set the description 224 description := d.Get("description").(string) 225 if description == "" { 226 description = name 227 } 228 229 // Retrieve the needed details of the image 230 configureForImage, osType, err := retrieveImageDetails( 231 meta, 232 d.Get("image").(string), 233 name, 234 d.Get("storage_service_name").(string), 235 ) 236 if err != nil { 237 return err 238 } 239 240 // Verify if we have all required parameters 241 if err := verifyInstanceParameters(d, osType); err != nil { 242 return err 243 } 244 245 var hostedServiceName string 246 // check if hosted service name parameter was given: 247 if serviceName, ok := d.GetOk("hosted_service_name"); !ok { 248 // if not provided; just use the name of the instance to create a new one: 249 hostedServiceName = name 250 d.Set("hosted_service_name", hostedServiceName) 251 d.Set("has_dedicated_service", true) 252 253 p := hostedservice.CreateHostedServiceParameters{ 254 ServiceName: hostedServiceName, 255 Label: base64.StdEncoding.EncodeToString([]byte(name)), 256 Description: fmt.Sprintf("Cloud Service created automatically for instance %s", name), 257 Location: d.Get("location").(string), 258 ReverseDNSFqdn: d.Get("reverse_dns").(string), 259 } 260 261 log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) 262 err = hostedServiceClient.CreateHostedService(p) 263 if err != nil { 264 return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) 265 } 266 } else { 267 // else; use the provided hosted service name: 268 hostedServiceName = serviceName.(string) 269 } 270 271 // Create a new role for the instance 272 role := vmutils.NewVMConfiguration(name, d.Get("size").(string)) 273 274 log.Printf("[DEBUG] Configuring deployment from image...") 275 err = configureForImage(&role) 276 if err != nil { 277 return fmt.Errorf("Error configuring the deployment for %s: %s", name, err) 278 } 279 280 if osType == linux { 281 // This is pretty ugly, but the Azure SDK leaves me no other choice... 282 if tp, ok := d.GetOk("ssh_key_thumbprint"); ok { 283 err = vmutils.ConfigureForLinux( 284 &role, 285 name, 286 d.Get("username").(string), 287 d.Get("password").(string), 288 tp.(string), 289 ) 290 } else { 291 err = vmutils.ConfigureForLinux( 292 &role, 293 name, 294 d.Get("username").(string), 295 d.Get("password").(string), 296 ) 297 } 298 if err != nil { 299 return fmt.Errorf("Error configuring %s for Linux: %s", name, err) 300 } 301 } 302 303 if osType == windows { 304 err = vmutils.ConfigureForWindows( 305 &role, 306 name, 307 d.Get("username").(string), 308 d.Get("password").(string), 309 d.Get("automatic_updates").(bool), 310 d.Get("time_zone").(string), 311 ) 312 if err != nil { 313 return fmt.Errorf("Error configuring %s for Windows: %s", name, err) 314 } 315 316 if domain_name, ok := d.GetOk("domain_name"); ok { 317 err = vmutils.ConfigureWindowsToJoinDomain( 318 &role, 319 d.Get("domain_username").(string), 320 d.Get("domain_password").(string), 321 domain_name.(string), 322 d.Get("domain_ou").(string), 323 ) 324 if err != nil { 325 return fmt.Errorf("Error configuring %s for WindowsToJoinDomain: %s", name, err) 326 } 327 } 328 } 329 330 if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 { 331 for _, v := range s.List() { 332 m := v.(map[string]interface{}) 333 err := vmutils.ConfigureWithExternalPort( 334 &role, 335 m["name"].(string), 336 m["private_port"].(int), 337 m["public_port"].(int), 338 endpointProtocol(m["protocol"].(string)), 339 ) 340 if err != nil { 341 return fmt.Errorf( 342 "Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err) 343 } 344 } 345 } 346 347 if subnet, ok := d.GetOk("subnet"); ok { 348 err = vmutils.ConfigureWithSubnet(&role, subnet.(string)) 349 if err != nil { 350 return fmt.Errorf( 351 "Error associating subnet %s with instance %s: %s", d.Get("subnet").(string), name, err) 352 } 353 } 354 355 if sg, ok := d.GetOk("security_group"); ok { 356 err = vmutils.ConfigureWithSecurityGroup(&role, sg.(string)) 357 if err != nil { 358 return fmt.Errorf( 359 "Error associating security group %s with instance %s: %s", sg.(string), name, err) 360 } 361 } 362 363 options := virtualmachine.CreateDeploymentOptions{ 364 VirtualNetworkName: d.Get("virtual_network").(string), 365 } 366 367 log.Printf("[DEBUG] Creating the new instance...") 368 req, err := vmClient.CreateDeployment(role, hostedServiceName, options) 369 if err != nil { 370 return fmt.Errorf("Error creating instance %s: %s", name, err) 371 } 372 373 log.Printf("[DEBUG] Waiting for the new instance to be created...") 374 if err := mc.WaitForOperation(req, nil); err != nil { 375 return fmt.Errorf( 376 "Error waiting for instance %s to be created: %s", name, err) 377 } 378 379 d.SetId(name) 380 381 return resourceAzureInstanceRead(d, meta) 382 } 383 384 func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { 385 azureClient := meta.(*Client) 386 hostedServiceClient := azureClient.hostedServiceClient 387 vmClient := azureClient.vmClient 388 389 name := d.Get("name").(string) 390 391 // check if the instance belongs to an independent hosted service 392 // or it had one created for it. 393 var hostedServiceName string 394 if serviceName, ok := d.GetOk("hosted_service_name"); ok { 395 // if independent; use that hosted service name: 396 hostedServiceName = serviceName.(string) 397 } else { 398 // else; suppose it's the instance's name: 399 hostedServiceName = name 400 } 401 402 log.Printf("[DEBUG] Retrieving Cloud Service for instance: %s", name) 403 cs, err := hostedServiceClient.GetHostedService(hostedServiceName) 404 if err != nil { 405 return fmt.Errorf("Error retrieving Cloud Service of instance %s (%q): %s", name, hostedServiceName, err) 406 } 407 408 d.Set("reverse_dns", cs.ReverseDNSFqdn) 409 d.Set("location", cs.Location) 410 411 log.Printf("[DEBUG] Retrieving instance: %s", name) 412 dpmt, err := vmClient.GetDeployment(hostedServiceName, name) 413 if err != nil { 414 if management.IsResourceNotFoundError(err) { 415 d.SetId("") 416 return nil 417 } 418 return fmt.Errorf("Error retrieving instance %s: %s", name, err) 419 } 420 421 if len(dpmt.RoleList) != 1 { 422 return fmt.Errorf( 423 "Instance %s has an unexpected number of roles: %d", name, len(dpmt.RoleList)) 424 } 425 426 d.Set("size", dpmt.RoleList[0].RoleSize) 427 428 if len(dpmt.RoleInstanceList) != 1 { 429 return fmt.Errorf( 430 "Instance %s has an unexpected number of role instances: %d", 431 name, len(dpmt.RoleInstanceList)) 432 } 433 d.Set("ip_address", dpmt.RoleInstanceList[0].IPAddress) 434 435 if len(dpmt.RoleInstanceList[0].InstanceEndpoints) > 0 { 436 d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip) 437 } 438 439 // Find the network configuration set 440 for _, c := range dpmt.RoleList[0].ConfigurationSets { 441 if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { 442 // Create a new set to hold all configured endpoints 443 endpoints := &schema.Set{ 444 F: resourceAzureEndpointHash, 445 } 446 447 // Loop through all endpoints 448 for _, ep := range c.InputEndpoints { 449 endpoint := map[string]interface{}{} 450 451 // Update the values 452 endpoint["name"] = ep.Name 453 endpoint["protocol"] = string(ep.Protocol) 454 endpoint["public_port"] = ep.Port 455 endpoint["private_port"] = ep.LocalPort 456 endpoints.Add(endpoint) 457 } 458 d.Set("endpoint", endpoints) 459 460 // Update the subnet 461 switch len(c.SubnetNames) { 462 case 1: 463 d.Set("subnet", c.SubnetNames[0]) 464 case 0: 465 d.Set("subnet", "") 466 default: 467 return fmt.Errorf( 468 "Instance %s has an unexpected number of associated subnets %d", 469 name, len(dpmt.RoleInstanceList)) 470 } 471 472 // Update the security group 473 d.Set("security_group", c.NetworkSecurityGroup) 474 } 475 } 476 477 connType := "ssh" 478 if dpmt.RoleList[0].OSVirtualHardDisk.OS == windows { 479 connType = "winrm" 480 } 481 482 // Set the connection info for any configured provisioners 483 d.SetConnInfo(map[string]string{ 484 "type": connType, 485 "host": dpmt.VirtualIPs[0].Address, 486 "user": d.Get("username").(string), 487 "password": d.Get("password").(string), 488 }) 489 490 return nil 491 } 492 493 func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 494 azureClient := meta.(*Client) 495 mc := azureClient.mgmtClient 496 vmClient := azureClient.vmClient 497 498 // First check if anything we can update changed, and if not just return 499 if !d.HasChange("size") && !d.HasChange("endpoint") && !d.HasChange("security_group") { 500 return nil 501 } 502 503 name := d.Get("name").(string) 504 hostedServiceName := d.Get("hosted_service_name").(string) 505 506 // Get the current role 507 role, err := vmClient.GetRole(hostedServiceName, name, name) 508 if err != nil { 509 return fmt.Errorf("Error retrieving role of instance %s: %s", name, err) 510 } 511 512 // Verify if we have all required parameters 513 if err := verifyInstanceParameters(d, role.OSVirtualHardDisk.OS); err != nil { 514 return err 515 } 516 517 if d.HasChange("size") { 518 role.RoleSize = d.Get("size").(string) 519 } 520 521 if d.HasChange("endpoint") { 522 _, n := d.GetChange("endpoint") 523 524 // Delete the existing endpoints 525 for i, c := range role.ConfigurationSets { 526 if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { 527 c.InputEndpoints = nil 528 role.ConfigurationSets[i] = c 529 } 530 } 531 532 // And add the ones we still want 533 if s := n.(*schema.Set); s.Len() > 0 { 534 for _, v := range s.List() { 535 m := v.(map[string]interface{}) 536 err := vmutils.ConfigureWithExternalPort( 537 role, 538 m["name"].(string), 539 m["private_port"].(int), 540 m["public_port"].(int), 541 endpointProtocol(m["protocol"].(string)), 542 ) 543 if err != nil { 544 return fmt.Errorf( 545 "Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err) 546 } 547 } 548 } 549 } 550 551 if d.HasChange("security_group") { 552 sg := d.Get("security_group").(string) 553 err := vmutils.ConfigureWithSecurityGroup(role, sg) 554 if err != nil { 555 return fmt.Errorf( 556 "Error associating security group %s with instance %s: %s", sg, name, err) 557 } 558 } 559 560 // Update the adjusted role 561 req, err := vmClient.UpdateRole(hostedServiceName, name, name, *role) 562 if err != nil { 563 return fmt.Errorf("Error updating role of instance %s: %s", name, err) 564 } 565 566 if err := mc.WaitForOperation(req, nil); err != nil { 567 return fmt.Errorf( 568 "Error waiting for role of instance %s to be updated: %s", name, err) 569 } 570 571 return resourceAzureInstanceRead(d, meta) 572 } 573 574 func resourceAzureInstanceDelete(d *schema.ResourceData, meta interface{}) error { 575 azureClient := meta.(*Client) 576 mc := azureClient.mgmtClient 577 vmClient := azureClient.vmClient 578 579 name := d.Get("name").(string) 580 hostedServiceName := d.Get("hosted_service_name").(string) 581 582 log.Printf("[DEBUG] Deleting instance: %s", name) 583 584 // check if the instance had a hosted service created especially for it: 585 if d.Get("has_dedicated_service").(bool) { 586 // if so; we must delete the associated hosted service as well: 587 hostedServiceClient := azureClient.hostedServiceClient 588 req, err := hostedServiceClient.DeleteHostedService(name, true) 589 if err != nil { 590 return fmt.Errorf("Error deleting instance and hosted service %s: %s", name, err) 591 } 592 593 // Wait until the hosted service and the instance it contains is deleted: 594 if err := mc.WaitForOperation(req, nil); err != nil { 595 return fmt.Errorf( 596 "Error waiting for instance %s to be deleted: %s", name, err) 597 } 598 } else { 599 // else; just delete the instance: 600 reqID, err := vmClient.DeleteDeployment(hostedServiceName, name) 601 if err != nil { 602 return fmt.Errorf("Error deleting instance %s off hosted service %s: %s", name, hostedServiceName, err) 603 } 604 605 // and wait for the deletion: 606 if err := mc.WaitForOperation(reqID, nil); err != nil { 607 return fmt.Errorf("Error waiting for intance %s to be deleted off the hosted service %s: %s", 608 name, hostedServiceName, err) 609 } 610 } 611 612 log.Printf("[INFO] Waiting for the deletion of instance '%s''s disk blob.", name) 613 614 // in order to avoid `terraform taint`-like scenarios in which the instance 615 // is deleted and re-created so fast the previous storage blob which held 616 // the image doesn't manage to get deleted (despite it being in a 617 // 'deleting' state) and a lease conflict occurs over it, we must ensure 618 // the blob got completely deleted as well: 619 storName := d.Get("storage_service_name").(string) 620 blobClient, err := azureClient.getStorageServiceBlobClient(storName) 621 if err != nil { 622 return err 623 } 624 625 err = resource.Retry(5*time.Minute, func() error { 626 exists, err := blobClient.BlobExists( 627 storageContainterName, fmt.Sprintf(osDiskBlobNameFormat, name), 628 ) 629 if err != nil { 630 return resource.RetryError{Err: err} 631 } 632 633 if exists { 634 return fmt.Errorf("Instance '%s''s disk storage blob still exists.", name) 635 } 636 637 return nil 638 }) 639 640 return err 641 } 642 643 func resourceAzureEndpointHash(v interface{}) int { 644 var buf bytes.Buffer 645 m := v.(map[string]interface{}) 646 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 647 buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) 648 buf.WriteString(fmt.Sprintf("%d-", m["public_port"].(int))) 649 buf.WriteString(fmt.Sprintf("%d-", m["private_port"].(int))) 650 651 return hashcode.String(buf.String()) 652 } 653 654 func retrieveImageDetails( 655 meta interface{}, 656 label string, 657 name string, 658 storage string) (func(*virtualmachine.Role) error, string, error) { 659 660 azureClient := meta.(*Client) 661 vmImageClient := azureClient.vmImageClient 662 osImageClient := azureClient.osImageClient 663 664 configureForImage, osType, VMLabels, err := retrieveVMImageDetails(vmImageClient, label) 665 if err == nil { 666 return configureForImage, osType, nil 667 } 668 669 configureForImage, osType, OSLabels, err := retrieveOSImageDetails(osImageClient, label, name, storage) 670 if err == nil { 671 return configureForImage, osType, nil 672 } 673 674 if err == PlatformStorageError { 675 return nil, "", err 676 } 677 678 return nil, "", fmt.Errorf("Could not find image with label '%s'. Available images are: %s", 679 label, strings.Join(append(VMLabels, OSLabels...), ", ")) 680 } 681 682 func retrieveVMImageDetails( 683 vmImageClient virtualmachineimage.Client, 684 label string) (func(*virtualmachine.Role) error, string, []string, error) { 685 imgs, err := vmImageClient.ListVirtualMachineImages() 686 if err != nil { 687 return nil, "", nil, fmt.Errorf("Error retrieving image details: %s", err) 688 } 689 690 var labels []string 691 for _, img := range imgs.VMImages { 692 if img.Label == label { 693 if img.OSDiskConfiguration.OS != linux && img.OSDiskConfiguration.OS != windows { 694 return nil, "", nil, fmt.Errorf("Unsupported image OS: %s", img.OSDiskConfiguration.OS) 695 } 696 697 configureForImage := func(role *virtualmachine.Role) error { 698 return vmutils.ConfigureDeploymentFromVMImage( 699 role, 700 img.Name, 701 "", 702 true, 703 ) 704 } 705 706 return configureForImage, img.OSDiskConfiguration.OS, nil, nil 707 } 708 709 labels = append(labels, img.Label) 710 } 711 712 return nil, "", labels, fmt.Errorf("Could not find image with label '%s'", label) 713 } 714 715 func retrieveOSImageDetails( 716 osImageClient osimage.OSImageClient, 717 label string, 718 name string, 719 storage string) (func(*virtualmachine.Role) error, string, []string, error) { 720 721 imgs, err := osImageClient.ListOSImages() 722 if err != nil { 723 return nil, "", nil, fmt.Errorf("Error retrieving image details: %s", err) 724 } 725 726 var labels []string 727 for _, img := range imgs.OSImages { 728 if img.Label == label { 729 if img.OS != linux && img.OS != windows { 730 return nil, "", nil, fmt.Errorf("Unsupported image OS: %s", img.OS) 731 } 732 if img.MediaLink == "" { 733 if storage == "" { 734 return nil, "", nil, PlatformStorageError 735 } 736 img.MediaLink = fmt.Sprintf(osDiskBlobStorageURL, storage, name) 737 } 738 739 configureForImage := func(role *virtualmachine.Role) error { 740 return vmutils.ConfigureDeploymentFromPlatformImage( 741 role, 742 img.Name, 743 img.MediaLink, 744 label, 745 ) 746 } 747 748 return configureForImage, img.OS, nil, nil 749 } 750 751 labels = append(labels, img.Label) 752 } 753 754 return nil, "", labels, fmt.Errorf("Could not find image with label '%s'", label) 755 } 756 757 func endpointProtocol(p string) virtualmachine.InputEndpointProtocol { 758 if p == "tcp" { 759 return virtualmachine.InputEndpointProtocolTCP 760 } 761 762 return virtualmachine.InputEndpointProtocolUDP 763 } 764 765 func verifyInstanceParameters(d *schema.ResourceData, osType string) error { 766 if osType == linux { 767 _, pass := d.GetOk("password") 768 _, key := d.GetOk("ssh_key_thumbprint") 769 770 if !pass && !key { 771 return fmt.Errorf( 772 "You must supply a 'password' and/or a 'ssh_key_thumbprint' when using a Linux image") 773 } 774 } 775 776 if osType == windows { 777 if _, ok := d.GetOk("password"); !ok { 778 return fmt.Errorf("You must supply a 'password' when using a Windows image") 779 } 780 781 if _, ok := d.GetOk("time_zone"); !ok { 782 return fmt.Errorf("You must supply a 'time_zone' when using a Windows image") 783 } 784 } 785 786 if _, ok := d.GetOk("subnet"); ok { 787 if _, ok := d.GetOk("virtual_network"); !ok { 788 return fmt.Errorf("You must also supply a 'virtual_network' when supplying a 'subnet'") 789 } 790 } 791 792 if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 { 793 for _, v := range s.List() { 794 protocol := v.(map[string]interface{})["protocol"].(string) 795 796 if protocol != "tcp" && protocol != "udp" { 797 return fmt.Errorf( 798 "Invalid endpoint protocol %s! Valid options are 'tcp' and 'udp'.", protocol) 799 } 800 } 801 } 802 803 return nil 804 }