Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: Bastion Host - fixes merge conflicts on #4096 #4347

Closed
wants to merge 11 commits into from
5 changes: 5 additions & 0 deletions azurerm/internal/services/network/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Client struct {
ApplicationGatewaysClient *network.ApplicationGatewaysClient
ApplicationSecurityGroupsClient *network.ApplicationSecurityGroupsClient
AzureFirewallsClient *network.AzureFirewallsClient
BastionHostsClient *network.BastionHostsClient
ConnectionMonitorsClient *network.ConnectionMonitorsClient
DDOSProtectionPlansClient *network.DdosProtectionPlansClient
ExpressRouteAuthsClient *network.ExpressRouteCircuitAuthorizationsClient
Expand Down Expand Up @@ -46,6 +47,9 @@ func BuildClient(o *common.ClientOptions) *Client {
AzureFirewallsClient := network.NewAzureFirewallsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&AzureFirewallsClient.Client, o.ResourceManagerAuthorizer)

BastionHostsClient := network.NewBastionHostsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&AzureFirewallsClient.Client, o.ResourceManagerAuthorizer)

ConnectionMonitorsClient := network.NewConnectionMonitorsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&ConnectionMonitorsClient.Client, o.ResourceManagerAuthorizer)

Expand Down Expand Up @@ -122,6 +126,7 @@ func BuildClient(o *common.ClientOptions) *Client {
ApplicationGatewaysClient: &ApplicationGatewaysClient,
ApplicationSecurityGroupsClient: &ApplicationSecurityGroupsClient,
AzureFirewallsClient: &AzureFirewallsClient,
BastionHostsClient: &BastionHostsClient,
ConnectionMonitorsClient: &ConnectionMonitorsClient,
DDOSProtectionPlansClient: &DDOSProtectionPlansClient,
ExpressRouteAuthsClient: &ExpressRouteAuthsClient,
Expand Down
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_azuread_application": resourceArmActiveDirectoryApplication(),
"azurerm_azuread_service_principal_password": resourceArmActiveDirectoryServicePrincipalPassword(),
"azurerm_azuread_service_principal": resourceArmActiveDirectoryServicePrincipal(),
"azurerm_bastion_host": resourceArmBastionHost(),
"azurerm_batch_account": resourceArmBatchAccount(),
"azurerm_batch_application": resourceArmBatchApplication(),
"azurerm_batch_certificate": resourceArmBatchCertificate(),
Expand Down
208 changes: 208 additions & 0 deletions azurerm/resource_arm_bastion_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package azurerm

import (
"fmt"
"log"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/hashicorp/terraform/helper/schema"

"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmBastionHost() *schema.Resource {
return &schema.Resource{
Create: resourceArmBastionHostCreateUpdate,
Read: resourceArmBastionHostRead,
Update: resourceArmBastionHostCreateUpdate,
Delete: resourceArmBastionHostDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"location": azure.SchemaLocation(),

// TODO: make this case sensitive once this API bug has been fixed:
// https://github.com/Azure/azure-rest-api-specs/issues/5574
"resource_group_name": azure.SchemaResourceGroupNameDiffSuppress(),

"dns_name": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},

"ip_configuration": {
Type: schema.TypeList,
ForceNew: true,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"subnet_id": {
Type: schema.TypeString,
Required: true,
},
"public_ip_address_id": {
Type: schema.TypeString,
Required: true,
},
},
},
},

"tags": tagsSchema(),
},
}
}

func resourceArmBastionHostCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).network.BastionHostsClient
ctx := meta.(*ArmClient).StopContext

log.Println("[INFO] preparing arguments for Azure Bastion Host creation.")

resourceGroup := d.Get("resource_group_name").(string)
name := d.Get("name").(string)
location := azure.NormalizeLocation(d.Get("location").(string))
dnsName := d.Get("dns_name").(string)
tags := d.Get("tags").(map[string]interface{})

properties := d.Get("ip_configuration").([]interface{})
firstProperty := properties[0].(map[string]interface{})
ipConfName := firstProperty["name"].(string)
subID := firstProperty["subnet_id"].(string)
pipID := firstProperty["public_ip_address_id"].(string)

// Check if resources are to be imported
if requireResourcesToBeImported && d.IsNewResource() {
existing, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing Bastion Host %q (Resource Group %q): %s", name, resourceGroup, err)
}
}

if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_bastion_host", *existing.ID)
}
}

// subnet and public ip resources
subnetID := network.SubResource{
ID: &subID,
}

publicIPAddressID := network.SubResource{
ID: &pipID,
}

bastionHostIPConfigurationPropertiesFormat := network.BastionHostIPConfigurationPropertiesFormat{
Subnet: &subnetID,
PublicIPAddress: &publicIPAddressID,
}

bastionHostIPConfiguration := []network.BastionHostIPConfiguration{
{
Name: &ipConfName,
BastionHostIPConfigurationPropertiesFormat: &bastionHostIPConfigurationPropertiesFormat,
},
}

bastionHostProperties := network.BastionHostPropertiesFormat{
IPConfigurations: &bastionHostIPConfiguration,
DNSName: &dnsName,
}

parameters := network.BastionHost{
Location: &location,
BastionHostPropertiesFormat: &bastionHostProperties,
Tags: expandTags(tags),
}

// creation
future, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters)
if err != nil {
return fmt.Errorf("Error creating/updating Bastion Host %q (Resource Group %q): %+v", name, resourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for creation/update of Bastion Host %q (Resource Group %q): %+v", name, resourceGroup, err)
}

// check presence
read, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error retrieving Bastion Host %q (Resource Group %q): %+v", name, resourceGroup, err)
}

d.SetId(*read.ID)

return resourceArmBastionHostRead(d, meta)
}

func resourceArmBastionHostRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).network.BastionHostsClient
ctx := meta.(*ArmClient).StopContext

id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
name := id.Path["bastionHosts"]
resourceGroup := id.ResourceGroup

resp, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
d.SetId("")
log.Printf("[DEBUG] Bastion Host %q was not found in Resource Group %q - removing from state!", name, resourceGroup)
return nil
}
return fmt.Errorf("Error reading the state of Bastion Host %q: %+v", name, err)
}

flattenAndSetTags(d, resp.Tags)

return nil
}

func resourceArmBastionHostDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).network.BastionHostsClient
ctx := meta.(*ArmClient).StopContext

id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}

name := id.Path["bastionHosts"]
resourceGroup := id.ResourceGroup

future, err := client.Delete(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("Error deleting Bastion Host %q (Resource Group %q): %+v", name, resourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
if !response.WasNotFound(future.Response()) {
return fmt.Errorf("Error waiting for deletion of Bastion Host %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}

return nil
}
126 changes: 126 additions & 0 deletions azurerm/resource_arm_bastion_host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package azurerm

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func TestAccAzureRMBastionHost_basic(t *testing.T) {
resourceName := "azurerm_bastion_host.test"
ri := tf.AccRandTimeInt()

config := testAccAzureRMBastionHost_basic(ri, testLocation())

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMBastionHostDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMBastionHostExists(resourceName),
),
},
},
})
}

func testAccAzureRMBastionHost_basic(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "testvnet"
address_space = ["192.168.1.0/24"]
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
}

resource "azurerm_subnet" "test" {
name = "AzureBastionSubnet"
resource_group_name = "${azurerm_resource_group.test.name}"
virtual_network_name = "${azurerm_virtual_network.test.name}"
address_prefix = "192.168.1.224/27"
}

resource "azurerm_public_ip" "test" {
name = "testpip"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
allocation_method = "Static"
sku = "Standard"
}

resource "azurerm_bastion_host" "test" {
name = "testbastion%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"

ip_configuration {
name = "configuration"
subnet_id = "${azurerm_subnet.test.id}"
public_ip_address_id = "${azurerm_public_ip.test.id}"
}
}
`, rInt, location, rInt)
}

func testCheckAzureRMBastionHostExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*ArmClient).network.BastionHostsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %q", resourceName)
}

name := rs.Primary.Attributes["name"]
resourceGroup := rs.Primary.Attributes["resource_group_name"]

resp, err := client.Get(ctx, resourceGroup, name)

if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return fmt.Errorf("Bad: Azure Bastion Host %q does not exist", rs.Primary.ID)
}
return fmt.Errorf("Bad: Get on Azure Bastion Host Client: %+v", err)
}

return nil
}
}

func testCheckAzureRMBastionHostDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*ArmClient).network.BastionHostsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext

for _, rs := range s.RootModule().Resources {
if rs.Type != "azurerm_bastion_host" {
continue
}

name := rs.Primary.Attributes["name"]
resourceGroup := rs.Primary.Attributes["resource_group_name"]

resp, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if !utils.ResponseWasNotFound(resp.Response) {
return err
}
}

return nil
}

return nil
}
Loading