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