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

Site recovery support #4003

Merged
merged 23 commits into from Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
63dcc2e
vendor: upgrading to v31.1.0 of github.com/Azure/azure-sdk-for-go
martenbohlin Jul 20, 2019
185e5d0
Added support for creating 'Recovery Services Fabric' in order to sup…
martenbohlin Jul 20, 2019
b66f338
Added support for creating 'Recovery Services Protection Container' i…
martenbohlin Jul 21, 2019
962bb78
Added support for managing 'Recovery Services Replication Policies' i…
martenbohlin Jul 21, 2019
d91c2fe
Added support for managing 'Recovery Services Protection Container Ma…
martenbohlin Jul 21, 2019
063023b
Added support for managing 'Recovery Services Network Mappings' in or…
martenbohlin Jul 22, 2019
c1a8e8a
Added support for replicating VMs in order to support site recovery.
martenbohlin Jul 23, 2019
f43c608
Site recovery: Fixed katbytes initial comments in pull request #4003
martenbohlin Aug 4, 2019
636b0a1
Merge remote-tracking branch 'origin/master' into site-recovery
martenbohlin Aug 5, 2019
4a5510e
Added documentation for azurerm_recovery_network_mapping. Also remove…
martenbohlin Aug 5, 2019
cf1647e
Added documentation for azurerm_recovery_services_fabric
martenbohlin Aug 5, 2019
3288910
Added documentation for azurerm_recovery_services_protection_container.
martenbohlin Aug 5, 2019
6395902
Added documentation for azurerm_recovery_services_protection_containe…
martenbohlin Aug 5, 2019
3865fdc
Added dovcumentation for azurerm_recovery_services_replication_policy
martenbohlin Aug 5, 2019
13230b5
Added documentation for azurerm_recovery_replicated_vm.
martenbohlin Aug 5, 2019
0f1a68a
Merge remote-tracking branch 'origin/master' into site-recovery
martenbohlin Aug 12, 2019
de47d67
Fixed failing integration tests. (#4003)
martenbohlin Aug 12, 2019
4f6b9fc
Removed unnecessary code. (#3480)
martenbohlin Aug 14, 2019
130c96b
Found the correct way to get the location in the sdk-response. (#3480)
martenbohlin Aug 14, 2019
00ac03a
Merge remote-tracking branch 'origin/master' into site-recovery
martenbohlin Aug 14, 2019
8f383a9
Merge remote-tracking branch 'origin/master' into site-recovery
martenbohlin Aug 17, 2019
71fd508
Do not update state with property not in schema. (#3480)
martenbohlin Aug 19, 2019
50492e3
Merge remote-tracking branch 'origin/master' into site-recovery
martenbohlin Aug 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions azurerm/data_source_recovery_services_protection_policy_vm.go
Expand Up @@ -23,9 +23,10 @@ func dataSourceArmRecoveryServicesProtectionPolicyVm() *schema.Resource {
},

"recovery_vault_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateRecoveryServicesVaultName,
},

"resource_group_name": azure.SchemaResourceGroupNameForDataSource(),
Expand Down
21 changes: 21 additions & 0 deletions azurerm/helpers/azure/recovery_services_vault.go
@@ -0,0 +1,21 @@
package azure

import (
"regexp"
"strings"

"github.com/hashicorp/terraform/helper/validation"
)

func ValidateRecoveryServicesVaultName(v interface{}, k string) (warnings []string, errors []error) {
regexpValidator := validation.StringMatch(
regexp.MustCompile("^[a-zA-Z][-a-zA-Z0-9]{1,49}$"),
"Recovery Service Vault name must be 2 - 50 characters long, start with a letter, contain only letters, numbers and hyphens.",
)
return regexpValidator(v, k)
}

// This code is a workaround for this bug https://github.com/Azure/azure-sdk-for-go/issues/2824
func HandleAzureSdkForGoBug2824(id string) string {
return strings.Replace(id, "/Subscriptions/", "/subscriptions/", 1)
}
3 changes: 2 additions & 1 deletion azurerm/helpers/azure/resource_group_test.go
Expand Up @@ -61,7 +61,8 @@ func TestValidateResourceGroupName(t *testing.T) {
_, errors := validateResourceGroupName(tc.Value, "azurerm_resource_group")

if len(errors) != tc.ErrCount {
t.Fatalf("Expected validateResourceGroupName to trigger '%d' errors for '%s' - got '%d'", tc.ErrCount, tc.Value, len(errors))
t.Fatalf("Expected "+
"validateResourceGroupName to trigger '%d' errors for '%s' - got '%d'", tc.ErrCount, tc.Value, len(errors))
}
}
}
61 changes: 55 additions & 6 deletions azurerm/internal/services/recoveryservices/client.go
Expand Up @@ -3,13 +3,20 @@ package recoveryservices
import (
"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2016-06-01/recoveryservices"
"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2017-07-01/backup"
"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common"
)

type Client struct {
ProtectedItemsClient *backup.ProtectedItemsGroupClient
ProtectionPoliciesClient *backup.ProtectionPoliciesClient
VaultsClient *recoveryservices.VaultsClient
ProtectedItemsClient *backup.ProtectedItemsGroupClient
ProtectionPoliciesClient *backup.ProtectionPoliciesClient
VaultsClient *recoveryservices.VaultsClient
FabricClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationFabricsClient
ProtectionContainerClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainersClient
ReplicationPoliciesClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationPoliciesClient
ContainerMappingClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainerMappingsClient
NetworkMappingClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationNetworkMappingsClient
ReplicationMigrationItemsClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectedItemsClient
}

func BuildClient(o *common.ClientOptions) *Client {
Expand All @@ -23,9 +30,51 @@ func BuildClient(o *common.ClientOptions) *Client {
ProtectionPoliciesClient := backup.NewProtectionPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&ProtectionPoliciesClient.Client, o.ResourceManagerAuthorizer)

FabricClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationFabricsClient {
client := siterecovery.NewReplicationFabricsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
return client
}

ProtectionContainerClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainersClient {
client := siterecovery.NewReplicationProtectionContainersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
return client
}

ReplicationPoliciesClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationPoliciesClient {
client := siterecovery.NewReplicationPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
return client
}

ContainerMappingClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainerMappingsClient {
client := siterecovery.NewReplicationProtectionContainerMappingsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
return client
}

NetworkMappingClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationNetworkMappingsClient {
client := siterecovery.NewReplicationNetworkMappingsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
return client
}

ReplicationMigrationItemsClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectedItemsClient {
client := siterecovery.NewReplicationProtectedItemsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
return client
}

return &Client{
ProtectedItemsClient: &ProtectedItemsClient,
ProtectionPoliciesClient: &ProtectionPoliciesClient,
VaultsClient: &VaultsClient,
ProtectedItemsClient: &ProtectedItemsClient,
ProtectionPoliciesClient: &ProtectionPoliciesClient,
VaultsClient: &VaultsClient,
FabricClient: FabricClient,
ProtectionContainerClient: ProtectionContainerClient,
ReplicationPoliciesClient: ReplicationPoliciesClient,
ContainerMappingClient: ContainerMappingClient,
NetworkMappingClient: NetworkMappingClient,
ReplicationMigrationItemsClient: ReplicationMigrationItemsClient,
}
}
6 changes: 6 additions & 0 deletions azurerm/provider.go
Expand Up @@ -396,6 +396,12 @@ func Provider() terraform.ResourceProvider {
"azurerm_virtual_network_peering": resourceArmVirtualNetworkPeering(),
"azurerm_virtual_network": resourceArmVirtualNetwork(),
"azurerm_virtual_wan": resourceArmVirtualWan(),
"azurerm_recovery_services_fabric": resourceArmRecoveryServicesFabric(),
"azurerm_recovery_services_protection_container": resourceArmRecoveryServicesProtectionContainer(),
"azurerm_recovery_services_replication_policy": resourceArmRecoveryServicesReplicationPolicy(),
"azurerm_recovery_services_protection_container_mapping": resourceArmRecoveryServicesProtectionContainerMapping(),
"azurerm_recovery_network_mapping": resourceArmRecoveryServicesNetworkMapping(),
"azurerm_recovery_replicated_vm": resourceArmRecoveryServicesReplicatedVm(),
}

for _, service := range supportedServices {
Expand Down
149 changes: 149 additions & 0 deletions azurerm/resource_arm_recovery_services_fabric.go
@@ -0,0 +1,149 @@
package azurerm

import (
"fmt"

"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery"
"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/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmRecoveryServicesFabric() *schema.Resource {
return &schema.Resource{
Create: resourceArmRecoveryServicesFabricCreate,
Read: resourceArmRecoveryServicesFabricRead,
Update: nil,
Delete: resourceArmRecoveryServicesFabricDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.NoEmptyStrings,
katbyte marked this conversation as resolved.
Show resolved Hide resolved
},
"resource_group_name": azure.SchemaResourceGroupName(),

"recovery_vault_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateRecoveryServicesVaultName,
},
"location": azure.SchemaLocation(),
},
}
}

func resourceArmRecoveryServicesFabricCreate(d *schema.ResourceData, meta interface{}) error {
resGroup := d.Get("resource_group_name").(string)
vaultName := d.Get("recovery_vault_name").(string)
location := azure.NormalizeLocation(d.Get("location").(string))
name := d.Get("name").(string)

client := meta.(*ArmClient).recoveryServices.FabricClient(resGroup, vaultName)
ctx := meta.(*ArmClient).StopContext

if requireResourcesToBeImported && d.IsNewResource() {
existing, err := client.Get(ctx, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing rec overy services fabric %s (vault %s): %+v", name, vaultName, err)
}
}

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

parameters := siterecovery.FabricCreationInput{
Properties: &siterecovery.FabricCreationInputProperties{
CustomDetails: siterecovery.AzureFabricCreationInput{
InstanceType: "Azure",
Location: &location,
},
},
}

future, err := client.Create(ctx, name, parameters)
if err != nil {
return fmt.Errorf("Error creating recovery services fabric %s (vault %s): %+v", name, vaultName, err)
}
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error creating recovery services fabric %s (vault %s): %+v", name, vaultName, err)
}

resp, err := client.Get(ctx, name)
if err != nil {
return fmt.Errorf("Error retrieving recovery services fabric %s (vault %s): %+v", name, vaultName, err)
}

d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID))

return resourceArmRecoveryServicesFabricRead(d, meta)
}

func resourceArmRecoveryServicesFabricRead(d *schema.ResourceData, meta interface{}) error {
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}

resGroup := id.ResourceGroup
vaultName := id.Path["vaults"]
name := id.Path["replicationFabrics"]

client := meta.(*ArmClient).recoveryServices.FabricClient(resGroup, vaultName)
ctx := meta.(*ArmClient).StopContext

resp, err := client.Get(ctx, name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
d.SetId("")
return nil
}
return fmt.Errorf("Error making Read request on recovery services fabric %s (vault %s): %+v", name, vaultName, err)
}

d.Set("name", resp.Name)
d.Set("resource_group_name", resGroup)
if props := resp.Properties; props != nil {
if azureDetails, isAzureDetails := props.CustomDetails.AsAzureFabricSpecificDetails(); isAzureDetails {
d.Set("location", azureDetails.Location)
}
}
d.Set("recovery_vault_name", vaultName)
return nil
}

func resourceArmRecoveryServicesFabricDelete(d *schema.ResourceData, meta interface{}) error {
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}

resGroup := id.ResourceGroup
vaultName := id.Path["vaults"]
name := id.Path["replicationFabrics"]

client := meta.(*ArmClient).recoveryServices.FabricClient(resGroup, vaultName)
ctx := meta.(*ArmClient).StopContext

future, err := client.Delete(ctx, name)
if err != nil {
return fmt.Errorf("Error deleting recovery services fabric %s (vault %s): %+v", name, vaultName, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for deletion of recovery services fabric %s (vault %s): %+v", name, vaultName, err)
}

return nil
}
97 changes: 97 additions & 0 deletions azurerm/resource_arm_recovery_services_fabric_test.go
@@ -0,0 +1,97 @@
package azurerm

import (
"fmt"
"net/http"
"testing"

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

func TestAccAzureRMRecoveryFabric_basic(t *testing.T) {
resourceGroupName := "azurerm_resource_group.test"
vaultName := "azurerm_recovery_services_vault.test"
resourceName := "azurerm_recovery_services_fabric.test"
ri := tf.AccRandTimeInt()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMResourceGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAzureRMRecoveryFabric_basic(ri, testLocation()),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMRecoveryFabricExists(resourceGroupName, vaultName, resourceName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

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

resource "azurerm_recovery_services_vault" "test" {
name = "acctest-vault-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
sku = "Standard"
}

resource "azurerm_recovery_services_fabric" "test" {
resource_group_name = "${azurerm_resource_group.test.name}"
recovery_vault_name = "${azurerm_recovery_services_vault.test.name}"
name = "acctest-fabric-%d"
location = "${azurerm_resource_group.test.location}"
}
`, rInt, location, rInt, rInt)
}

func testCheckAzureRMRecoveryFabricExists(resourceGroupStateName, vaultStateName string, resourceStateName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// Ensure we have enough information in state to look up in API
resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName]
if !ok {
return fmt.Errorf("Not found: %s", resourceGroupStateName)
}
vaultState, ok := s.RootModule().Resources[vaultStateName]
if !ok {
return fmt.Errorf("Not found: %s", vaultStateName)
}
fabricState, ok := s.RootModule().Resources[resourceStateName]
if !ok {
return fmt.Errorf("Not found: %s", resourceStateName)
}

resourceGroupName := resourceGroupState.Primary.Attributes["name"]
vaultName := vaultState.Primary.Attributes["name"]
fabricName := fabricState.Primary.Attributes["name"]

// Ensure fabric exists in API
client := testAccProvider.Meta().(*ArmClient).recoveryServices.FabricClient(resourceGroupName, vaultName)
ctx := testAccProvider.Meta().(*ArmClient).StopContext

resp, err := client.Get(ctx, fabricName)
if err != nil {
return fmt.Errorf("Bad: Get on fabricClient: %+v", err)
}

if resp.Response.StatusCode == http.StatusNotFound {
return fmt.Errorf("Bad: fabric: %q does not exist", fabricName)
}

return nil
}
}