Made instances deployable on already existing services.

This commit is contained in:
aznashwan 2015-06-26 15:48:55 +03:00
parent c1ac72683d
commit 6ea0397e07
4 changed files with 218 additions and 85 deletions

View File

@ -11,7 +11,10 @@ import (
var testAccProviders map[string]terraform.ResourceProvider var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider var testAccProvider *schema.Provider
const testAccSecurityGroupName = "terraform-security-group" const (
testAccSecurityGroupName = "terraform-security-group"
testAccHostedServiceName = "terraform-testing-service"
)
// testAccStorageServiceName is used as the name for the Storage Service // testAccStorageServiceName is used as the name for the Storage Service
// created in all storage-related tests. // created in all storage-related tests.

View File

@ -37,6 +37,13 @@ func resourceAzureInstance() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"hosted_service_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"description": &schema.Schema{ "description": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
@ -197,36 +204,30 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err
return err return err
} }
p := hostedservice.CreateHostedServiceParameters{ var hostedServiceName string
ServiceName: name, // check if hosted service name parameter was given:
Label: base64.StdEncoding.EncodeToString([]byte(name)), if serviceName, ok := d.GetOk("hosted_service_name"); !ok {
Description: fmt.Sprintf("Cloud Service created automatically for instance %s", name), // if not provided; just use the name of the instance to create a new one:
Location: d.Get("location").(string), hostedServiceName = name
ReverseDNSFqdn: d.Get("reverse_dns").(string), d.Set("hosted_service_name", hostedServiceName)
}
log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) p := hostedservice.CreateHostedServiceParameters{
err = hostedServiceClient.CreateHostedService(p) ServiceName: hostedServiceName,
if err != nil { Label: base64.StdEncoding.EncodeToString([]byte(name)),
return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) Description: fmt.Sprintf("Cloud Service created automatically for instance %s", name),
} Location: d.Get("location").(string),
ReverseDNSFqdn: d.Get("reverse_dns").(string),
// Put in this defer here, so we are sure to cleanup already created parts
// when we exit with an error
defer func(mc management.Client) {
if err != nil {
req, err := hostedServiceClient.DeleteHostedService(name, true)
if err != nil {
log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err)
}
// Wait until the Cloud Service is deleted
if err := mc.WaitForOperation(req, nil); err != nil {
log.Printf(
"[DEBUG] Error waiting for Cloud Service of instance %s to be deleted: %s", name, err)
}
} }
}(mc)
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 // Create a new role for the instance
role := vmutils.NewVMConfiguration(name, d.Get("size").(string)) role := vmutils.NewVMConfiguration(name, d.Get("size").(string))
@ -312,7 +313,7 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err
} }
log.Printf("[DEBUG] Creating the new instance...") log.Printf("[DEBUG] Creating the new instance...")
req, err := vmClient.CreateDeployment(role, name, options) req, err := vmClient.CreateDeployment(role, hostedServiceName, options)
if err != nil { if err != nil {
return fmt.Errorf("Error creating instance %s: %s", name, err) return fmt.Errorf("Error creating instance %s: %s", name, err)
} }
@ -333,28 +334,41 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
hostedServiceClient := azureClient.hostedServiceClient hostedServiceClient := azureClient.hostedServiceClient
vmClient := azureClient.vmClient vmClient := azureClient.vmClient
log.Printf("[DEBUG] Retrieving Cloud Service for instance: %s", d.Id()) name := d.Get("name").(string)
cs, err := hostedServiceClient.GetHostedService(d.Id())
// 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 { if err != nil {
return fmt.Errorf("Error retrieving Cloud Service of instance %s: %s", d.Id(), err) return fmt.Errorf("Error retrieving Cloud Service of instance %s (%q): %s", name, hostedServiceName, err)
} }
d.Set("reverse_dns", cs.ReverseDNSFqdn) d.Set("reverse_dns", cs.ReverseDNSFqdn)
d.Set("location", cs.Location) d.Set("location", cs.Location)
log.Printf("[DEBUG] Retrieving instance: %s", d.Id()) log.Printf("[DEBUG] Retrieving instance: %s", name)
dpmt, err := vmClient.GetDeployment(d.Id(), d.Id()) dpmt, err := vmClient.GetDeployment(hostedServiceName, name)
if err != nil { if err != nil {
if management.IsResourceNotFoundError(err) { if management.IsResourceNotFoundError(err) {
d.SetId("") d.SetId("")
return nil return nil
} }
return fmt.Errorf("Error retrieving instance %s: %s", d.Id(), err) return fmt.Errorf("Error retrieving instance %s: %s", name, err)
} }
if len(dpmt.RoleList) != 1 { if len(dpmt.RoleList) != 1 {
return fmt.Errorf( return fmt.Errorf(
"Instance %s has an unexpected number of roles: %d", d.Id(), len(dpmt.RoleList)) "Instance %s has an unexpected number of roles: %d", name, len(dpmt.RoleList))
} }
d.Set("size", dpmt.RoleList[0].RoleSize) d.Set("size", dpmt.RoleList[0].RoleSize)
@ -362,7 +376,7 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
if len(dpmt.RoleInstanceList) != 1 { if len(dpmt.RoleInstanceList) != 1 {
return fmt.Errorf( return fmt.Errorf(
"Instance %s has an unexpected number of role instances: %d", "Instance %s has an unexpected number of role instances: %d",
d.Id(), len(dpmt.RoleInstanceList)) name, len(dpmt.RoleInstanceList))
} }
d.Set("ip_address", dpmt.RoleInstanceList[0].IPAddress) d.Set("ip_address", dpmt.RoleInstanceList[0].IPAddress)
@ -400,7 +414,7 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
default: default:
return fmt.Errorf( return fmt.Errorf(
"Instance %s has an unexpected number of associated subnets %d", "Instance %s has an unexpected number of associated subnets %d",
d.Id(), len(dpmt.RoleInstanceList)) name, len(dpmt.RoleInstanceList))
} }
// Update the security group // Update the security group
@ -434,10 +448,13 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
return nil return nil
} }
name := d.Get("name").(string)
hostedServiceName := d.Get("hosted_service_name").(string)
// Get the current role // Get the current role
role, err := vmClient.GetRole(d.Id(), d.Id(), d.Id()) role, err := vmClient.GetRole(hostedServiceName, name, name)
if err != nil { if err != nil {
return fmt.Errorf("Error retrieving role of instance %s: %s", d.Id(), err) return fmt.Errorf("Error retrieving role of instance %s: %s", name, err)
} }
// Verify if we have all required parameters // Verify if we have all required parameters
@ -473,7 +490,7 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
) )
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error adding endpoint %s for instance %s: %s", m["name"].(string), d.Id(), err) "Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err)
} }
} }
} }
@ -484,19 +501,19 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
err := vmutils.ConfigureWithSecurityGroup(role, sg) err := vmutils.ConfigureWithSecurityGroup(role, sg)
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error associating security group %s with instance %s: %s", sg, d.Id(), err) "Error associating security group %s with instance %s: %s", sg, name, err)
} }
} }
// Update the adjusted role // Update the adjusted role
req, err := vmClient.UpdateRole(d.Id(), d.Id(), d.Id(), *role) req, err := vmClient.UpdateRole(hostedServiceName, name, name, *role)
if err != nil { if err != nil {
return fmt.Errorf("Error updating role of instance %s: %s", d.Id(), err) return fmt.Errorf("Error updating role of instance %s: %s", name, err)
} }
if err := mc.WaitForOperation(req, nil); err != nil { if err := mc.WaitForOperation(req, nil); err != nil {
return fmt.Errorf( return fmt.Errorf(
"Error waiting for role of instance %s to be updated: %s", d.Id(), err) "Error waiting for role of instance %s to be updated: %s", name, err)
} }
return resourceAzureInstanceRead(d, meta) return resourceAzureInstanceRead(d, meta)
@ -505,21 +522,40 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
func resourceAzureInstanceDelete(d *schema.ResourceData, meta interface{}) error { func resourceAzureInstanceDelete(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client) azureClient := meta.(*Client)
mc := azureClient.mgmtClient mc := azureClient.mgmtClient
vmClient := azureClient.vmClient
hostedServiceClient := azureClient.hostedServiceClient hostedServiceClient := azureClient.hostedServiceClient
log.Printf("[DEBUG] Deleting instance: %s", d.Id()) name := d.Get("name").(string)
req, err := hostedServiceClient.DeleteHostedService(d.Id(), true) hostedServiceName := d.Get("hosted_service_name").(string)
if err != nil {
return fmt.Errorf("Error deleting instance %s: %s", d.Id(), err)
}
// Wait until the instance is deleted log.Printf("[DEBUG] Deleting instance: %s", name)
if err := mc.WaitForOperation(req, nil); err != nil {
return fmt.Errorf(
"Error waiting for instance %s to be deleted: %s", d.Id(), err)
}
d.SetId("") // check if the instance had a hosted service created especially for it:
if name == hostedServiceName {
// if so; we must delete the associated hosted service as well:
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)
}
}
return nil return nil
} }

View File

@ -16,16 +16,46 @@ func TestAccAzureInstance_basic(t *testing.T) {
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroy, CheckDestroy: testAccCheckAzureInstanceDestroyed(""),
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccAzureInstance_basic, Config: testAccAzureInstance_basic,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists( testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt), "azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceBasicAttributes(&dpmt), testAccCheckAzureInstanceBasicAttributes(&dpmt),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test"), "azure_instance.foo", "name", "terraform-test"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "location", "West US"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "endpoint.2462817782.public_port", "22"),
),
},
},
})
}
func TestAccAzureInstance_separateHostedService(t *testing.T) {
var dpmt virtualmachine.DeploymentResponse
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroyed(testAccHostedServiceName),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureInstance_seperateHostedService,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists(
"azure_instance.foo", testAccHostedServiceName, &dpmt),
testAccCheckAzureInstanceBasicAttributes(&dpmt),
resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-testing-service"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "location", "West US"), "azure_instance.foo", "location", "West US"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
@ -42,16 +72,18 @@ func TestAccAzureInstance_advanced(t *testing.T) {
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroy, CheckDestroy: testAccCheckAzureInstanceDestroyed(""),
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccAzureInstance_advanced, Config: testAccAzureInstance_advanced,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists( testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt), "azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceAdvancedAttributes(&dpmt), testAccCheckAzureInstanceAdvancedAttributes(&dpmt),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test1"), "azure_instance.foo", "name", "terraform-test1"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "size", "Basic_A1"), "azure_instance.foo", "size", "Basic_A1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
@ -74,16 +106,18 @@ func TestAccAzureInstance_update(t *testing.T) {
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroy, CheckDestroy: testAccCheckAzureInstanceDestroyed(""),
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccAzureInstance_advanced, Config: testAccAzureInstance_advanced,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists( testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt), "azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceAdvancedAttributes(&dpmt), testAccCheckAzureInstanceAdvancedAttributes(&dpmt),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test1"), "azure_instance.foo", "name", "terraform-test1"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "size", "Basic_A1"), "azure_instance.foo", "size", "Basic_A1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
@ -101,7 +135,7 @@ func TestAccAzureInstance_update(t *testing.T) {
Config: testAccAzureInstance_update, Config: testAccAzureInstance_update,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists( testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt), "azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceUpdatedAttributes(&dpmt), testAccCheckAzureInstanceUpdatedAttributes(&dpmt),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "size", "Basic_A2"), "azure_instance.foo", "size", "Basic_A2"),
@ -119,6 +153,7 @@ func TestAccAzureInstance_update(t *testing.T) {
func testAccCheckAzureInstanceExists( func testAccCheckAzureInstanceExists(
n string, n string,
hostedServiceName string,
dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc { dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
@ -130,8 +165,17 @@ func testAccCheckAzureInstanceExists(
return fmt.Errorf("No instance ID is set") return fmt.Errorf("No instance ID is set")
} }
// if not hosted service was provided; it means that we expect it
// to be identical with the name of the instance; which is in the ID.
var serviceName string
if hostedServiceName == "" {
serviceName = rs.Primary.ID
} else {
serviceName = hostedServiceName
}
vmClient := testAccProvider.Meta().(*Client).vmClient vmClient := testAccProvider.Meta().(*Client).vmClient
vm, err := vmClient.GetDeployment(rs.Primary.ID, rs.Primary.ID) vm, err := vmClient.GetDeployment(serviceName, rs.Primary.ID)
if err != nil { if err != nil {
return err return err
} }
@ -281,29 +325,40 @@ func testAccCheckAzureInstanceUpdatedAttributes(
} }
} }
func testAccCheckAzureInstanceDestroy(s *terraform.State) error { func testAccCheckAzureInstanceDestroyed(hostedServiceName string) resource.TestCheckFunc {
hostedServiceClient := testAccProvider.Meta().(*Client).hostedServiceClient return func(s *terraform.State) error {
hostedServiceClient := testAccProvider.Meta().(*Client).hostedServiceClient
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
if rs.Type != "azure_instance" { if rs.Type != "azure_instance" {
continue continue
}
if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}
// if not hosted service was provided; it means that we expect it
// to be identical with the name of the instance; which is in the ID.
var serviceName string
if hostedServiceName == "" {
serviceName = rs.Primary.ID
} else {
serviceName = hostedServiceName
}
_, err := hostedServiceClient.GetHostedService(serviceName)
if err == nil {
return fmt.Errorf("Instance %s still exists", rs.Primary.ID)
}
if !management.IsResourceNotFoundError(err) {
return err
}
} }
if rs.Primary.ID == "" { return nil
return fmt.Errorf("No instance ID is set")
}
_, err := hostedServiceClient.GetHostedService(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Instance %s still exists", rs.Primary.ID)
}
if !management.IsResourceNotFoundError(err) {
return err
}
} }
return nil
} }
var testAccAzureInstance_basic = fmt.Sprintf(` var testAccAzureInstance_basic = fmt.Sprintf(`
@ -324,6 +379,31 @@ resource "azure_instance" "foo" {
} }
}`, testAccStorageServiceName) }`, testAccStorageServiceName)
var testAccAzureInstance_seperateHostedService = fmt.Sprintf(`
resource "azure_hosted_service" "foo" {
name = "%s"
location = "West US"
ephemeral_contents = true
}
resource "azure_instance" "foo" {
name = "terraform-test"
hosted_service_name = "${azure_hosted_service.foo.name}"
image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1"
storage_service_name = "%s"
location = "West US"
username = "terraform"
password = "Pass!admin123"
endpoint {
name = "SSH"
protocol = "tcp"
public_port = 22
private_port = 22
}
}`, testAccHostedServiceName, testAccStorageServiceName)
var testAccAzureInstance_advanced = fmt.Sprintf(` var testAccAzureInstance_advanced = fmt.Sprintf(`
resource "azure_virtual_network" "foo" { resource "azure_virtual_network" "foo" {
name = "terraform-vnet" name = "terraform-vnet"

View File

@ -8,14 +8,23 @@ description: |-
# azure\_instance # azure\_instance
Creates a hosted service, role and deployment and then creates a virtual Creates a hosted service, role and deployment and then creates a virtual
machine in the deployment based on the specified configuration. machine in the deployment based on the specified configuration.
## Example Usage ## Example Usage
``` ```
resource "azure_hosted_service" "terraform-service" {
name = "terraform-service"
location = "North Europe"
ephemeral_contents = false
description = "Hosted service created by Terraform."
label = "tf-hs-01"
}
resource "azure_instance" "web" { resource "azure_instance" "web" {
name = "terraform-test" name = "terraform-test"
hosted_service_name = "${azure_hosted_service.terraform-service.name}"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage_service_name = "yourstorage" storage_service_name = "yourstorage"
@ -39,6 +48,11 @@ The following arguments are supported:
* `name` - (Required) The name of the instance. Changing this forces a new * `name` - (Required) The name of the instance. Changing this forces a new
resource to be created. resource to be created.
* `hosted_service_name` - (Optional) The name of the hosted service the
instance should be deployed under. If not provided; it will default to the
value of `name`. Changes to this parameter forces the creation of a new
resource.
* `description` - (Optional) The description for the associated hosted service. * `description` - (Optional) The description for the associated hosted service.
Changing this forces a new resource to be created (defaults to the instance Changing this forces a new resource to be created (defaults to the instance
name). name).