github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/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  }