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