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  }