Merge pull request #2515 from aznashwan/f-azure-instance-services

provider/azure: Made instances deployable on already existing services.
This commit is contained in:
Paul Hinze 2015-06-26 08:54:57 -05:00
commit db11d80947
11 changed files with 242 additions and 110 deletions

View File

@ -11,7 +11,10 @@ import (
var testAccProviders map[string]terraform.ResourceProvider
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
// created in all storage-related tests.

View File

@ -37,6 +37,13 @@ func resourceAzureInstance() *schema.Resource {
ForceNew: true,
},
"hosted_service_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
@ -197,36 +204,30 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err
return err
}
p := hostedservice.CreateHostedServiceParameters{
ServiceName: name,
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),
}
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)
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)
}
// 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)
}
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),
}
}(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
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...")
req, err := vmClient.CreateDeployment(role, name, options)
req, err := vmClient.CreateDeployment(role, hostedServiceName, options)
if err != nil {
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
vmClient := azureClient.vmClient
log.Printf("[DEBUG] Retrieving Cloud Service for instance: %s", d.Id())
cs, err := hostedServiceClient.GetHostedService(d.Id())
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: %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("location", cs.Location)
log.Printf("[DEBUG] Retrieving instance: %s", d.Id())
dpmt, err := vmClient.GetDeployment(d.Id(), d.Id())
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", d.Id(), err)
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", 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)
@ -362,7 +376,7 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
if len(dpmt.RoleInstanceList) != 1 {
return fmt.Errorf(
"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)
@ -400,7 +414,7 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
default:
return fmt.Errorf(
"Instance %s has an unexpected number of associated subnets %d",
d.Id(), len(dpmt.RoleInstanceList))
name, len(dpmt.RoleInstanceList))
}
// Update the security group
@ -434,10 +448,13 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
return nil
}
name := d.Get("name").(string)
hostedServiceName := d.Get("hosted_service_name").(string)
// Get the current role
role, err := vmClient.GetRole(d.Id(), d.Id(), d.Id())
role, err := vmClient.GetRole(hostedServiceName, name, name)
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
@ -473,7 +490,7 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
)
if err != nil {
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)
if err != nil {
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
req, err := vmClient.UpdateRole(d.Id(), d.Id(), d.Id(), *role)
req, err := vmClient.UpdateRole(hostedServiceName, name, name, *role)
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 {
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)
@ -505,21 +522,40 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
func resourceAzureInstanceDelete(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mc := azureClient.mgmtClient
vmClient := azureClient.vmClient
hostedServiceClient := azureClient.hostedServiceClient
log.Printf("[DEBUG] Deleting instance: %s", d.Id())
req, err := hostedServiceClient.DeleteHostedService(d.Id(), true)
if err != nil {
return fmt.Errorf("Error deleting instance %s: %s", d.Id(), err)
}
name := d.Get("name").(string)
hostedServiceName := d.Get("hosted_service_name").(string)
// Wait until the instance is deleted
if err := mc.WaitForOperation(req, nil); err != nil {
return fmt.Errorf(
"Error waiting for instance %s to be deleted: %s", d.Id(), err)
}
log.Printf("[DEBUG] Deleting instance: %s", name)
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
}

View File

@ -16,16 +16,46 @@ func TestAccAzureInstance_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroy,
CheckDestroy: testAccCheckAzureInstanceDestroyed(""),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureInstance_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt),
"azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceBasicAttributes(&dpmt),
resource.TestCheckResourceAttr(
"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(
"azure_instance.foo", "location", "West US"),
resource.TestCheckResourceAttr(
@ -42,16 +72,18 @@ func TestAccAzureInstance_advanced(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroy,
CheckDestroy: testAccCheckAzureInstanceDestroyed(""),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureInstance_advanced,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt),
"azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceAdvancedAttributes(&dpmt),
resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test1"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test1"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "size", "Basic_A1"),
resource.TestCheckResourceAttr(
@ -74,16 +106,18 @@ func TestAccAzureInstance_update(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureInstanceDestroy,
CheckDestroy: testAccCheckAzureInstanceDestroyed(""),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureInstance_advanced,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt),
"azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceAdvancedAttributes(&dpmt),
resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test1"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test1"),
resource.TestCheckResourceAttr(
"azure_instance.foo", "size", "Basic_A1"),
resource.TestCheckResourceAttr(
@ -101,7 +135,7 @@ func TestAccAzureInstance_update(t *testing.T) {
Config: testAccAzureInstance_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureInstanceExists(
"azure_instance.foo", &dpmt),
"azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceUpdatedAttributes(&dpmt),
resource.TestCheckResourceAttr(
"azure_instance.foo", "size", "Basic_A2"),
@ -119,6 +153,7 @@ func TestAccAzureInstance_update(t *testing.T) {
func testAccCheckAzureInstanceExists(
n string,
hostedServiceName string,
dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -130,8 +165,17 @@ func testAccCheckAzureInstanceExists(
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
vm, err := vmClient.GetDeployment(rs.Primary.ID, rs.Primary.ID)
vm, err := vmClient.GetDeployment(serviceName, rs.Primary.ID)
if err != nil {
return err
}
@ -281,29 +325,40 @@ func testAccCheckAzureInstanceUpdatedAttributes(
}
}
func testAccCheckAzureInstanceDestroy(s *terraform.State) error {
hostedServiceClient := testAccProvider.Meta().(*Client).hostedServiceClient
func testAccCheckAzureInstanceDestroyed(hostedServiceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
hostedServiceClient := testAccProvider.Meta().(*Client).hostedServiceClient
for _, rs := range s.RootModule().Resources {
if rs.Type != "azure_instance" {
continue
for _, rs := range s.RootModule().Resources {
if rs.Type != "azure_instance" {
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 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
}
return nil
}
var testAccAzureInstance_basic = fmt.Sprintf(`
@ -324,6 +379,31 @@ resource "azure_instance" "foo" {
}
}`, 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(`
resource "azure_virtual_network" "foo" {
name = "terraform-vnet"

View File

@ -14,10 +14,10 @@ Creates a new hosted service on Azure with its own .cloudapp.net domain.
```
resource "azure_hosted_service" "terraform-service" {
name = "terraform-service"
location = "North Europe"
name = "terraform-service"
location = "North Europe"
ephemeral_contents = false
description = "Hosted service created by Terraform."
description = "Hosted service created by Terraform."
label = "tf-hs-01"
}
```

View File

@ -14,8 +14,17 @@ machine in the deployment based on the specified configuration.
## 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" {
name = "terraform-test"
hosted_service_name = "${azure_hosted_service.terraform-service.name}"
image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1"
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
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.
Changing this forces a new resource to be created (defaults to the instance
name).

View File

@ -23,15 +23,15 @@ resource "azure_security_group" "apps" {
resource "azure_security_group_rule" "ssh_access" {
name = "ssh-access-rule"
security_group_names = ["${azure_security_group.web.name}", "${azure_security_group.apps.name}"]
type = "Inbound"
action = "Allow"
priority = 200
source_address_prefix = "100.0.0.0/32"
source_port_range = "*"
destination_address_prefix = "10.0.0.0/32"
destination_port_range = "22"
protocol = "TCP"
security_group_names = ["${azure_security_group.web.name}", "${azure_security_group.apps.name}"]
type = "Inbound"
action = "Allow"
priority = 200
source_address_prefix = "100.0.0.0/32"
source_port_range = "*"
destination_address_prefix = "10.0.0.0/32"
destination_port_range = "22"
protocol = "TCP"
}
```

View File

@ -21,7 +21,6 @@ resource "azure_sql_database_service" "sql-server" {
max_size_bytes = "5368709120"
service_level_id = "f1173c43-91bd-4aaa-973c-54e79e15235b"
}
```
## Argument Reference

View File

@ -14,10 +14,10 @@ Creates a new storage blob within a given storage container on Azure.
```
resource "azure_storage_blob" "foo" {
name = "tftesting-blob"
storage_service_name = "tfstorserv"
storage_container_name = "terraform-storage-container"
type = "PageBlob"
name = "tftesting-blob"
storage_service_name = "tfstorserv"
storage_container_name = "terraform-storage-container"
type = "PageBlob"
size = 1024
}
````

View File

@ -14,9 +14,9 @@ Creates a new storage container within a given storage service on Azure.
```
resource "azure_storage_container" "stor-cont" {
name = "terraform-storage-container"
container_access_type = "blob"
storage_service_name = "tfstorserv"
name = "terraform-storage-container"
container_access_type = "blob"
storage_service_name = "tfstorserv"
}
````

View File

@ -14,8 +14,8 @@ Creates a new storage queue within a given storage service on Azure.
```
resource "azure_storage_queue" "stor-queue" {
name = "terraform-storage-queue"
storage_service_name = "tfstorserv"
name = "terraform-storage-queue"
storage_service_name = "tfstorserv"
}
````

View File

@ -14,10 +14,10 @@ Creates a new storage service on Azure in which storage containers may be create
```
resource "azure_storage_service" "tfstor" {
name = "tfstor"
location = "West US"
name = "tfstor"
location = "West US"
description = "Made by Terraform."
account_type = "Standard_LRS"
account_type = "Standard_LRS"
}
````