847 lines
22 KiB
Go
847 lines
22 KiB
Go
package azure
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/management"
|
|
"github.com/Azure/azure-sdk-for-go/management/hostedservice"
|
|
"github.com/Azure/azure-sdk-for-go/management/osimage"
|
|
"github.com/Azure/azure-sdk-for-go/management/virtualmachine"
|
|
"github.com/Azure/azure-sdk-for-go/management/virtualmachineimage"
|
|
"github.com/Azure/azure-sdk-for-go/management/vmutils"
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
const (
|
|
linux = "Linux"
|
|
windows = "Windows"
|
|
storageContainterName = "vhds"
|
|
osDiskBlobNameFormat = "%s.vhd"
|
|
osDiskBlobStorageURL = "http://%s.blob.core.windows.net/" + storageContainterName + "/" + osDiskBlobNameFormat
|
|
)
|
|
|
|
func resourceAzureInstance() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceAzureInstanceCreate,
|
|
Read: resourceAzureInstanceRead,
|
|
Update: resourceAzureInstanceUpdate,
|
|
Delete: resourceAzureInstanceDelete,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"hosted_service_name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
// in order to prevent an unintentional delete of a containing
|
|
// hosted service in the case the same name are given to both the
|
|
// service and the instance despite their being created separately,
|
|
// we must maintain a flag to definitively denote whether this
|
|
// instance had a hosted service created for it or not:
|
|
"has_dedicated_service": &schema.Schema{
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
|
|
"description": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"image": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"size": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
|
|
"subnet": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"virtual_network": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"storage_service_name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"reverse_dns": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"location": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"automatic_updates": &schema.Schema{
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: false,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"time_zone": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"username": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"password": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"ssh_key_thumbprint": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"endpoint": &schema.Schema{
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Computed: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
|
|
"protocol": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: "tcp",
|
|
},
|
|
|
|
"public_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
|
|
"private_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
Set: resourceAzureEndpointHash,
|
|
},
|
|
|
|
"security_group": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
|
|
"ip_address": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
|
|
"vip_address": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
|
|
"domain_name": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"domain_username": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"domain_password": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"domain_ou": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"custom_data": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
StateFunc: func(v interface{}) string {
|
|
if s, ok := v.(string); ok && s != "" {
|
|
hash := sha1.Sum([]byte(s))
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
return ""
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) {
|
|
azureClient := meta.(*Client)
|
|
mc := azureClient.mgmtClient
|
|
hostedServiceClient := azureClient.hostedServiceClient
|
|
vmClient := azureClient.vmClient
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
// Compute/set the description
|
|
description := d.Get("description").(string)
|
|
if description == "" {
|
|
description = name
|
|
}
|
|
|
|
// Retrieve the needed details of the image
|
|
configureForImage, osType, err := retrieveImageDetails(
|
|
meta,
|
|
d.Get("image").(string),
|
|
name,
|
|
d.Get("storage_service_name").(string),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Verify if we have all required parameters
|
|
if err := verifyInstanceParameters(d, osType); err != nil {
|
|
return err
|
|
}
|
|
|
|
var hostedServiceName string
|
|
// check if hosted service name parameter was given:
|
|
if serviceName, ok := d.GetOk("hosted_service_name"); !ok {
|
|
// if not provided; just use the name of the instance to create a new one:
|
|
hostedServiceName = name
|
|
d.Set("hosted_service_name", hostedServiceName)
|
|
d.Set("has_dedicated_service", true)
|
|
|
|
p := hostedservice.CreateHostedServiceParameters{
|
|
ServiceName: hostedServiceName,
|
|
Label: base64.StdEncoding.EncodeToString([]byte(name)),
|
|
Description: fmt.Sprintf("Cloud Service created automatically for instance %s", name),
|
|
Location: d.Get("location").(string),
|
|
ReverseDNSFqdn: d.Get("reverse_dns").(string),
|
|
}
|
|
|
|
log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name)
|
|
err = hostedServiceClient.CreateHostedService(p)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err)
|
|
}
|
|
} else {
|
|
// else; use the provided hosted service name:
|
|
hostedServiceName = serviceName.(string)
|
|
}
|
|
|
|
// Create a new role for the instance
|
|
role := vmutils.NewVMConfiguration(name, d.Get("size").(string))
|
|
|
|
log.Printf("[DEBUG] Configuring deployment from image...")
|
|
err = configureForImage(&role)
|
|
if err != nil {
|
|
return fmt.Errorf("Error configuring the deployment for %s: %s", name, err)
|
|
}
|
|
|
|
var customData string
|
|
if data, ok := d.GetOk("custom_data"); ok {
|
|
data := data.(string)
|
|
|
|
// Ensure the custom_data is not double-encoded.
|
|
if _, err := base64.StdEncoding.DecodeString(data); err != nil {
|
|
customData = base64.StdEncoding.EncodeToString([]byte(data))
|
|
} else {
|
|
customData = data
|
|
}
|
|
}
|
|
|
|
if osType == linux {
|
|
// This is pretty ugly, but the Azure SDK leaves me no other choice...
|
|
if tp, ok := d.GetOk("ssh_key_thumbprint"); ok {
|
|
err = vmutils.ConfigureForLinux(
|
|
&role,
|
|
name,
|
|
d.Get("username").(string),
|
|
d.Get("password").(string),
|
|
tp.(string),
|
|
)
|
|
} else {
|
|
err = vmutils.ConfigureForLinux(
|
|
&role,
|
|
name,
|
|
d.Get("username").(string),
|
|
d.Get("password").(string),
|
|
)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("Error configuring %s for Linux: %s", name, err)
|
|
}
|
|
|
|
if customData != "" {
|
|
err = vmutils.ConfigureWithCustomDataForLinux(&role, customData)
|
|
if err != nil {
|
|
return fmt.Errorf("Error configuring custom data for %s: %s", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if osType == windows {
|
|
err = vmutils.ConfigureForWindows(
|
|
&role,
|
|
name,
|
|
d.Get("username").(string),
|
|
d.Get("password").(string),
|
|
d.Get("automatic_updates").(bool),
|
|
d.Get("time_zone").(string),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("Error configuring %s for Windows: %s", name, err)
|
|
}
|
|
|
|
if domain_name, ok := d.GetOk("domain_name"); ok {
|
|
err = vmutils.ConfigureWindowsToJoinDomain(
|
|
&role,
|
|
d.Get("domain_username").(string),
|
|
d.Get("domain_password").(string),
|
|
domain_name.(string),
|
|
d.Get("domain_ou").(string),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("Error configuring %s for WindowsToJoinDomain: %s", name, err)
|
|
}
|
|
}
|
|
|
|
if customData != "" {
|
|
err = vmutils.ConfigureWithCustomDataForWindows(&role, customData)
|
|
if err != nil {
|
|
return fmt.Errorf("Error configuring custom data for %s: %s", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 {
|
|
for _, v := range s.List() {
|
|
m := v.(map[string]interface{})
|
|
err := vmutils.ConfigureWithExternalPort(
|
|
&role,
|
|
m["name"].(string),
|
|
m["private_port"].(int),
|
|
m["public_port"].(int),
|
|
endpointProtocol(m["protocol"].(string)),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if subnet, ok := d.GetOk("subnet"); ok {
|
|
err = vmutils.ConfigureWithSubnet(&role, subnet.(string))
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Error associating subnet %s with instance %s: %s", d.Get("subnet").(string), name, err)
|
|
}
|
|
}
|
|
|
|
if sg, ok := d.GetOk("security_group"); ok {
|
|
err = vmutils.ConfigureWithSecurityGroup(&role, sg.(string))
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Error associating security group %s with instance %s: %s", sg.(string), name, err)
|
|
}
|
|
}
|
|
|
|
options := virtualmachine.CreateDeploymentOptions{
|
|
VirtualNetworkName: d.Get("virtual_network").(string),
|
|
}
|
|
|
|
log.Printf("[DEBUG] Creating the new instance...")
|
|
req, err := vmClient.CreateDeployment(role, hostedServiceName, options)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating instance %s: %s", name, err)
|
|
}
|
|
|
|
log.Printf("[DEBUG] Waiting for the new instance to be created...")
|
|
if err := mc.WaitForOperation(req, nil); err != nil {
|
|
return fmt.Errorf(
|
|
"Error waiting for instance %s to be created: %s", name, err)
|
|
}
|
|
|
|
d.SetId(name)
|
|
|
|
return resourceAzureInstanceRead(d, meta)
|
|
}
|
|
|
|
func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
|
azureClient := meta.(*Client)
|
|
hostedServiceClient := azureClient.hostedServiceClient
|
|
vmClient := azureClient.vmClient
|
|
|
|
name := d.Get("name").(string)
|
|
|
|
// check if the instance belongs to an independent hosted service
|
|
// or it had one created for it.
|
|
var hostedServiceName string
|
|
if serviceName, ok := d.GetOk("hosted_service_name"); ok {
|
|
// if independent; use that hosted service name:
|
|
hostedServiceName = serviceName.(string)
|
|
} else {
|
|
// else; suppose it's the instance's name:
|
|
hostedServiceName = name
|
|
}
|
|
|
|
log.Printf("[DEBUG] Retrieving Cloud Service for instance: %s", name)
|
|
cs, err := hostedServiceClient.GetHostedService(hostedServiceName)
|
|
if err != nil {
|
|
return fmt.Errorf("Error retrieving Cloud Service of instance %s (%q): %s", name, hostedServiceName, err)
|
|
}
|
|
|
|
d.Set("reverse_dns", cs.ReverseDNSFqdn)
|
|
d.Set("location", cs.Location)
|
|
|
|
log.Printf("[DEBUG] Retrieving instance: %s", name)
|
|
dpmt, err := vmClient.GetDeployment(hostedServiceName, name)
|
|
if err != nil {
|
|
if management.IsResourceNotFoundError(err) {
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
return fmt.Errorf("Error retrieving instance %s: %s", name, err)
|
|
}
|
|
|
|
if len(dpmt.RoleList) != 1 {
|
|
return fmt.Errorf(
|
|
"Instance %s has an unexpected number of roles: %d", name, len(dpmt.RoleList))
|
|
}
|
|
|
|
d.Set("size", dpmt.RoleList[0].RoleSize)
|
|
|
|
if len(dpmt.RoleInstanceList) != 1 {
|
|
return fmt.Errorf(
|
|
"Instance %s has an unexpected number of role instances: %d",
|
|
name, len(dpmt.RoleInstanceList))
|
|
}
|
|
d.Set("ip_address", dpmt.RoleInstanceList[0].IPAddress)
|
|
|
|
if len(dpmt.RoleInstanceList[0].InstanceEndpoints) > 0 {
|
|
d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip)
|
|
}
|
|
|
|
// Find the network configuration set
|
|
for _, c := range dpmt.RoleList[0].ConfigurationSets {
|
|
if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork {
|
|
// Create a new set to hold all configured endpoints
|
|
endpoints := &schema.Set{
|
|
F: resourceAzureEndpointHash,
|
|
}
|
|
|
|
// Loop through all endpoints
|
|
for _, ep := range c.InputEndpoints {
|
|
endpoint := map[string]interface{}{}
|
|
|
|
// Update the values
|
|
endpoint["name"] = ep.Name
|
|
endpoint["protocol"] = string(ep.Protocol)
|
|
endpoint["public_port"] = ep.Port
|
|
endpoint["private_port"] = ep.LocalPort
|
|
endpoints.Add(endpoint)
|
|
}
|
|
d.Set("endpoint", endpoints)
|
|
|
|
// Update the subnet
|
|
switch len(c.SubnetNames) {
|
|
case 1:
|
|
d.Set("subnet", c.SubnetNames[0])
|
|
case 0:
|
|
d.Set("subnet", "")
|
|
default:
|
|
return fmt.Errorf(
|
|
"Instance %s has an unexpected number of associated subnets %d",
|
|
name, len(dpmt.RoleInstanceList))
|
|
}
|
|
|
|
// Update the security group
|
|
d.Set("security_group", c.NetworkSecurityGroup)
|
|
}
|
|
}
|
|
|
|
connType := "ssh"
|
|
if dpmt.RoleList[0].OSVirtualHardDisk.OS == windows {
|
|
connType = "winrm"
|
|
}
|
|
|
|
// Set the connection info for any configured provisioners
|
|
d.SetConnInfo(map[string]string{
|
|
"type": connType,
|
|
"host": dpmt.VirtualIPs[0].Address,
|
|
"user": d.Get("username").(string),
|
|
"password": d.Get("password").(string),
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
azureClient := meta.(*Client)
|
|
mc := azureClient.mgmtClient
|
|
vmClient := azureClient.vmClient
|
|
|
|
// First check if anything we can update changed, and if not just return
|
|
if !d.HasChange("size") && !d.HasChange("endpoint") && !d.HasChange("security_group") {
|
|
return nil
|
|
}
|
|
|
|
name := d.Get("name").(string)
|
|
hostedServiceName := d.Get("hosted_service_name").(string)
|
|
|
|
// Get the current role
|
|
role, err := vmClient.GetRole(hostedServiceName, name, name)
|
|
if err != nil {
|
|
return fmt.Errorf("Error retrieving role of instance %s: %s", name, err)
|
|
}
|
|
|
|
// Verify if we have all required parameters
|
|
if err := verifyInstanceParameters(d, role.OSVirtualHardDisk.OS); err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.HasChange("size") {
|
|
role.RoleSize = d.Get("size").(string)
|
|
}
|
|
|
|
if d.HasChange("endpoint") {
|
|
_, n := d.GetChange("endpoint")
|
|
|
|
// Delete the existing endpoints
|
|
for i, c := range role.ConfigurationSets {
|
|
if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork {
|
|
c.InputEndpoints = nil
|
|
role.ConfigurationSets[i] = c
|
|
}
|
|
}
|
|
|
|
// And add the ones we still want
|
|
if s := n.(*schema.Set); s.Len() > 0 {
|
|
for _, v := range s.List() {
|
|
m := v.(map[string]interface{})
|
|
err := vmutils.ConfigureWithExternalPort(
|
|
role,
|
|
m["name"].(string),
|
|
m["private_port"].(int),
|
|
m["public_port"].(int),
|
|
endpointProtocol(m["protocol"].(string)),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if d.HasChange("security_group") {
|
|
sg := d.Get("security_group").(string)
|
|
err := vmutils.ConfigureWithSecurityGroup(role, sg)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Error associating security group %s with instance %s: %s", sg, name, err)
|
|
}
|
|
}
|
|
|
|
// Update the adjusted role
|
|
req, err := vmClient.UpdateRole(hostedServiceName, name, name, *role)
|
|
if err != nil {
|
|
return fmt.Errorf("Error updating role of instance %s: %s", name, err)
|
|
}
|
|
|
|
if err := mc.WaitForOperation(req, nil); err != nil {
|
|
return fmt.Errorf(
|
|
"Error waiting for role of instance %s to be updated: %s", name, err)
|
|
}
|
|
|
|
return resourceAzureInstanceRead(d, meta)
|
|
}
|
|
|
|
func resourceAzureInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
|
azureClient := meta.(*Client)
|
|
mc := azureClient.mgmtClient
|
|
vmClient := azureClient.vmClient
|
|
|
|
name := d.Get("name").(string)
|
|
hostedServiceName := d.Get("hosted_service_name").(string)
|
|
|
|
log.Printf("[DEBUG] Deleting instance: %s", name)
|
|
|
|
// check if the instance had a hosted service created especially for it:
|
|
if d.Get("has_dedicated_service").(bool) {
|
|
// if so; we must delete the associated hosted service as well:
|
|
hostedServiceClient := azureClient.hostedServiceClient
|
|
req, err := hostedServiceClient.DeleteHostedService(name, true)
|
|
if err != nil {
|
|
return fmt.Errorf("Error deleting instance and hosted service %s: %s", name, err)
|
|
}
|
|
|
|
// Wait until the hosted service and the instance it contains is deleted:
|
|
if err := mc.WaitForOperation(req, nil); err != nil {
|
|
return fmt.Errorf(
|
|
"Error waiting for instance %s to be deleted: %s", name, err)
|
|
}
|
|
} else {
|
|
// else; just delete the instance:
|
|
reqID, err := vmClient.DeleteDeployment(hostedServiceName, name)
|
|
if err != nil {
|
|
return fmt.Errorf("Error deleting instance %s off hosted service %s: %s", name, hostedServiceName, err)
|
|
}
|
|
|
|
// and wait for the deletion:
|
|
if err := mc.WaitForOperation(reqID, nil); err != nil {
|
|
return fmt.Errorf("Error waiting for intance %s to be deleted off the hosted service %s: %s",
|
|
name, hostedServiceName, err)
|
|
}
|
|
}
|
|
|
|
log.Printf("[INFO] Waiting for the deletion of instance '%s''s disk blob.", name)
|
|
|
|
// in order to avoid `terraform taint`-like scenarios in which the instance
|
|
// is deleted and re-created so fast the previous storage blob which held
|
|
// the image doesn't manage to get deleted (despite it being in a
|
|
// 'deleting' state) and a lease conflict occurs over it, we must ensure
|
|
// the blob got completely deleted as well:
|
|
storName := d.Get("storage_service_name").(string)
|
|
blobClient, err := azureClient.getStorageServiceBlobClient(storName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = resource.Retry(15*time.Minute, func() *resource.RetryError {
|
|
exists, err := blobClient.BlobExists(
|
|
storageContainterName, fmt.Sprintf(osDiskBlobNameFormat, name),
|
|
)
|
|
if err != nil {
|
|
return resource.NonRetryableError(err)
|
|
}
|
|
|
|
if exists {
|
|
return resource.RetryableError(
|
|
fmt.Errorf("Instance '%s''s disk storage blob still exists.", name))
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func resourceAzureEndpointHash(v interface{}) int {
|
|
var buf bytes.Buffer
|
|
m := v.(map[string]interface{})
|
|
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
|
|
buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
|
|
buf.WriteString(fmt.Sprintf("%d-", m["public_port"].(int)))
|
|
buf.WriteString(fmt.Sprintf("%d-", m["private_port"].(int)))
|
|
|
|
return hashcode.String(buf.String())
|
|
}
|
|
|
|
func retrieveImageDetails(
|
|
meta interface{},
|
|
label string,
|
|
name string,
|
|
storage string) (func(*virtualmachine.Role) error, string, error) {
|
|
|
|
azureClient := meta.(*Client)
|
|
vmImageClient := azureClient.vmImageClient
|
|
osImageClient := azureClient.osImageClient
|
|
|
|
configureForImage, osType, VMLabels, err := retrieveVMImageDetails(vmImageClient, label)
|
|
if err == nil {
|
|
return configureForImage, osType, nil
|
|
}
|
|
|
|
configureForImage, osType, OSLabels, err := retrieveOSImageDetails(osImageClient, label, name, storage)
|
|
if err == nil {
|
|
return configureForImage, osType, nil
|
|
}
|
|
|
|
if err == PlatformStorageError {
|
|
return nil, "", err
|
|
}
|
|
|
|
return nil, "", fmt.Errorf("Could not find image with label '%s'. Available images are: %s",
|
|
label, strings.Join(append(VMLabels, OSLabels...), ", "))
|
|
}
|
|
|
|
func retrieveVMImageDetails(
|
|
vmImageClient virtualmachineimage.Client,
|
|
label string) (func(*virtualmachine.Role) error, string, []string, error) {
|
|
imgs, err := vmImageClient.ListVirtualMachineImages(virtualmachineimage.ListParameters{})
|
|
if err != nil {
|
|
return nil, "", nil, fmt.Errorf("Error retrieving image details: %s", err)
|
|
}
|
|
|
|
var labels []string
|
|
for _, img := range imgs.VMImages {
|
|
if img.Label == label {
|
|
if img.OSDiskConfiguration.OS != linux && img.OSDiskConfiguration.OS != windows {
|
|
return nil, "", nil, fmt.Errorf("Unsupported image OS: %s", img.OSDiskConfiguration.OS)
|
|
}
|
|
|
|
configureForImage := func(role *virtualmachine.Role) error {
|
|
return vmutils.ConfigureDeploymentFromPublishedVMImage(
|
|
role,
|
|
img.Name,
|
|
"",
|
|
true,
|
|
)
|
|
}
|
|
|
|
return configureForImage, img.OSDiskConfiguration.OS, nil, nil
|
|
}
|
|
|
|
labels = append(labels, img.Label)
|
|
}
|
|
|
|
return nil, "", labels, fmt.Errorf("Could not find image with label '%s'", label)
|
|
}
|
|
|
|
func retrieveOSImageDetails(
|
|
osImageClient osimage.OSImageClient,
|
|
label string,
|
|
name string,
|
|
storage string) (func(*virtualmachine.Role) error, string, []string, error) {
|
|
|
|
imgs, err := osImageClient.ListOSImages()
|
|
if err != nil {
|
|
return nil, "", nil, fmt.Errorf("Error retrieving image details: %s", err)
|
|
}
|
|
|
|
var labels []string
|
|
for _, img := range imgs.OSImages {
|
|
if img.Label == label {
|
|
if img.OS != linux && img.OS != windows {
|
|
return nil, "", nil, fmt.Errorf("Unsupported image OS: %s", img.OS)
|
|
}
|
|
if img.MediaLink == "" {
|
|
if storage == "" {
|
|
return nil, "", nil, PlatformStorageError
|
|
}
|
|
img.MediaLink = fmt.Sprintf(osDiskBlobStorageURL, storage, name)
|
|
}
|
|
|
|
configureForImage := func(role *virtualmachine.Role) error {
|
|
return vmutils.ConfigureDeploymentFromPlatformImage(
|
|
role,
|
|
img.Name,
|
|
img.MediaLink,
|
|
label,
|
|
)
|
|
}
|
|
|
|
return configureForImage, img.OS, nil, nil
|
|
}
|
|
|
|
labels = append(labels, img.Label)
|
|
}
|
|
|
|
return nil, "", labels, fmt.Errorf("Could not find image with label '%s'", label)
|
|
}
|
|
|
|
func endpointProtocol(p string) virtualmachine.InputEndpointProtocol {
|
|
if p == "tcp" {
|
|
return virtualmachine.InputEndpointProtocolTCP
|
|
}
|
|
|
|
return virtualmachine.InputEndpointProtocolUDP
|
|
}
|
|
|
|
func verifyInstanceParameters(d *schema.ResourceData, osType string) error {
|
|
if osType == linux {
|
|
_, pass := d.GetOk("password")
|
|
_, key := d.GetOk("ssh_key_thumbprint")
|
|
|
|
if !pass && !key {
|
|
return fmt.Errorf(
|
|
"You must supply a 'password' and/or a 'ssh_key_thumbprint' when using a Linux image")
|
|
}
|
|
}
|
|
|
|
if osType == windows {
|
|
if _, ok := d.GetOk("password"); !ok {
|
|
return fmt.Errorf("You must supply a 'password' when using a Windows image")
|
|
}
|
|
|
|
if _, ok := d.GetOk("time_zone"); !ok {
|
|
return fmt.Errorf("You must supply a 'time_zone' when using a Windows image")
|
|
}
|
|
}
|
|
|
|
if _, ok := d.GetOk("subnet"); ok {
|
|
if _, ok := d.GetOk("virtual_network"); !ok {
|
|
return fmt.Errorf("You must also supply a 'virtual_network' when supplying a 'subnet'")
|
|
}
|
|
}
|
|
|
|
if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 {
|
|
for _, v := range s.List() {
|
|
protocol := v.(map[string]interface{})["protocol"].(string)
|
|
|
|
if protocol != "tcp" && protocol != "udp" {
|
|
return fmt.Errorf(
|
|
"Invalid endpoint protocol %s! Valid options are 'tcp' and 'udp'.", protocol)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|