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