diff --git a/azurerm/internal/services/servicefabricmesh/client/client.go b/azurerm/internal/services/servicefabricmesh/client/client.go index 8b5d5095e3ac..1784f99bfbdb 100644 --- a/azurerm/internal/services/servicefabricmesh/client/client.go +++ b/azurerm/internal/services/servicefabricmesh/client/client.go @@ -7,6 +7,7 @@ import ( type Client struct { ApplicationClient *servicefabricmesh.ApplicationClient + NetworkClient *servicefabricmesh.NetworkClient ServiceClient *servicefabricmesh.ServiceClient } @@ -14,11 +15,15 @@ func NewClient(o *common.ClientOptions) *Client { applicationsClient := servicefabricmesh.NewApplicationClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&applicationsClient.Client, o.ResourceManagerAuthorizer) + networksClient := servicefabricmesh.NewNetworkClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&networksClient.Client, o.ResourceManagerAuthorizer) + servicesClient := servicefabricmesh.NewServiceClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&servicesClient.Client, o.ResourceManagerAuthorizer) return &Client{ ApplicationClient: &applicationsClient, + NetworkClient: &networksClient, ServiceClient: &servicesClient, } } diff --git a/azurerm/internal/services/servicefabricmesh/parse/service_fabric_mesh_network.go b/azurerm/internal/services/servicefabricmesh/parse/service_fabric_mesh_network.go new file mode 100644 index 000000000000..6d2170d3a8de --- /dev/null +++ b/azurerm/internal/services/servicefabricmesh/parse/service_fabric_mesh_network.go @@ -0,0 +1,33 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type ServiceFabricMeshNetworkId struct { + ResourceGroup string + Name string +} + +func ServiceFabricMeshNetworkID(input string) (*ServiceFabricMeshNetworkId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse Service Fabric Mesh Network ID %q: %+v", input, err) + } + + network := ServiceFabricMeshNetworkId{ + ResourceGroup: id.ResourceGroup, + } + + if network.Name, err = id.PopSegment("networks"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &network, nil +} diff --git a/azurerm/internal/services/servicefabricmesh/parse/service_fabric_mesh_network_test.go b/azurerm/internal/services/servicefabricmesh/parse/service_fabric_mesh_network_test.go new file mode 100644 index 000000000000..668e51b2ce0d --- /dev/null +++ b/azurerm/internal/services/servicefabricmesh/parse/service_fabric_mesh_network_test.go @@ -0,0 +1,73 @@ +package parse + +import ( + "testing" +) + +func TestServiceFabricMeshNetworkId(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *ServiceFabricMeshNetworkId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing Networks Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ServiceFabricMesh/networks/", + Expected: nil, + }, + { + Name: "Service Fabric Mesh Network ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ServiceFabricMesh/networks/Network1", + Expected: &ServiceFabricMeshNetworkId{ + Name: "Network1", + ResourceGroup: "resGroup1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ServiceFabricMesh/Networks/Network1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := ServiceFabricMeshNetworkID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/servicefabricmesh/registration.go b/azurerm/internal/services/servicefabricmesh/registration.go index 8de8aa184f03..24fc81bd4a26 100644 --- a/azurerm/internal/services/servicefabricmesh/registration.go +++ b/azurerm/internal/services/servicefabricmesh/registration.go @@ -24,6 +24,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_service_fabric_mesh_application": resourceArmServiceFabricMeshApplication(), + "azurerm_service_fabric_mesh_application": resourceArmServiceFabricMeshApplication(), + "azurerm_service_fabric_mesh_local_network": resourceArmServiceFabricMeshLocalNetwork(), } } diff --git a/azurerm/internal/services/servicefabricmesh/resource_arm_service_fabric_mesh_application.go b/azurerm/internal/services/servicefabricmesh/service_fabric_mesh_application_resource.go similarity index 100% rename from azurerm/internal/services/servicefabricmesh/resource_arm_service_fabric_mesh_application.go rename to azurerm/internal/services/servicefabricmesh/service_fabric_mesh_application_resource.go diff --git a/azurerm/internal/services/servicefabricmesh/service_fabric_mesh_local_network_resource.go b/azurerm/internal/services/servicefabricmesh/service_fabric_mesh_local_network_resource.go new file mode 100644 index 000000000000..5b504cefda90 --- /dev/null +++ b/azurerm/internal/services/servicefabricmesh/service_fabric_mesh_local_network_resource.go @@ -0,0 +1,175 @@ +package servicefabricmesh + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/servicefabricmesh/mgmt/2018-09-01-preview/servicefabricmesh" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "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/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/servicefabricmesh/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmServiceFabricMeshLocalNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceArmServiceFabricMeshLocalNetworkCreateUpdate, + Read: resourceArmServiceFabricMeshLocalNetworkRead, + Update: resourceArmServiceFabricMeshLocalNetworkCreateUpdate, + Delete: resourceArmServiceFabricMeshLocalNetworkDelete, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ServiceFabricMeshNetworkID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + // Follow casing issue here https://github.com/Azure/azure-rest-api-specs/issues/9330 + "resource_group_name": azure.SchemaResourceGroupNameDiffSuppress(), + + "location": azure.SchemaLocation(), + + "network_address_prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmServiceFabricMeshLocalNetworkCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).ServiceFabricMesh.NetworkClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + location := location.Normalize(d.Get("location").(string)) + t := d.Get("tags").(map[string]interface{}) + + if d.IsNewResource() { + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Service Fabric Mesh Local Network: %+v", err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_service_fabric_mesh_local_network", *existing.ID) + } + } + + parameters := servicefabricmesh.NetworkResourceDescription{ + Properties: &servicefabricmesh.LocalNetworkResourceProperties{ + Description: utils.String(d.Get("description").(string)), + Kind: servicefabricmesh.KindLocal, + NetworkAddressPrefix: utils.String(d.Get("network_address_prefix").(string)), + }, + Location: utils.String(location), + Tags: tags.Expand(t), + } + + if _, err := client.Create(ctx, resourceGroup, name, parameters); err != nil { + return fmt.Errorf("creating Service Fabric Mesh Local Network %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("retrieving Service Fabric Mesh Local Network %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("client returned a nil ID for Service Fabric Mesh Local Network %q", name) + } + + d.SetId(*resp.ID) + + return resourceArmServiceFabricMeshLocalNetworkRead(d, meta) +} + +func resourceArmServiceFabricMeshLocalNetworkRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).ServiceFabricMesh.NetworkClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ServiceFabricMeshNetworkID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Unable to find Service Fabric Mesh Local Network %q - removing from state", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("reading Service Fabric Mesh Local Network: %+v", err) + } + + props, ok := resp.Properties.AsLocalNetworkResourceProperties() + if !ok { + return fmt.Errorf("classifiying Service Fabric Mesh Local Network %q (Resource Group %q): Expected: %q Received: %q", id.Name, id.ResourceGroup, servicefabricmesh.KindNetworkResourceProperties, props.Kind) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("location", location.NormalizeNilable(resp.Location)) + d.Set("network_address_prefix", props.NetworkAddressPrefix) + d.Set("description", props.Description) + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmServiceFabricMeshLocalNetworkDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).ServiceFabricMesh.NetworkClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ServiceFabricMeshNetworkID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Delete(ctx, id.ResourceGroup, id.Name) + if err != nil { + if !response.WasNotFound(resp.Response) { + return fmt.Errorf("deleting Service Fabric Mesh Local Network %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + } + + return nil +} diff --git a/azurerm/internal/services/servicefabricmesh/tests/resource_arm_service_fabric_mesh_application_test.go b/azurerm/internal/services/servicefabricmesh/tests/service_fabric_mesh_application_resource_test.go similarity index 100% rename from azurerm/internal/services/servicefabricmesh/tests/resource_arm_service_fabric_mesh_application_test.go rename to azurerm/internal/services/servicefabricmesh/tests/service_fabric_mesh_application_resource_test.go diff --git a/azurerm/internal/services/servicefabricmesh/tests/service_fabric_mesh_local_network_resource_test.go b/azurerm/internal/services/servicefabricmesh/tests/service_fabric_mesh_local_network_resource_test.go new file mode 100644 index 000000000000..c4a1a74d2aff --- /dev/null +++ b/azurerm/internal/services/servicefabricmesh/tests/service_fabric_mesh_local_network_resource_test.go @@ -0,0 +1,169 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/servicefabricmesh/parse" +) + +func TestAccAzureRMServiceFabricMeshLocalNetwork_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_service_fabric_mesh_local_network", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMServiceFabricMeshLocalNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceFabricMeshLocalNetwork_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceFabricMeshLocalNetworkExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMServiceFabricMeshLocalNetwork_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_service_fabric_mesh_local_network", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMServiceFabricMeshLocalNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceFabricMeshLocalNetwork_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceFabricMeshLocalNetworkExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMServiceFabricMeshLocalNetwork_update(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceFabricMeshLocalNetworkExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMServiceFabricMeshLocalNetwork_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceFabricMeshLocalNetworkExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMServiceFabricMeshLocalNetworkDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).ServiceFabricMesh.NetworkClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_service_fabric_mesh_local_network" { + continue + } + + id, err := parse.ServiceFabricMeshNetworkID(rs.Primary.ID) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Service Fabric Mesh Local Network still exists:\n%+v", resp) + } + } + + return nil +} + +func testCheckAzureRMServiceFabricMeshLocalNetworkExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).ServiceFabricMesh.NetworkClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + id, err := parse.ServiceFabricMeshNetworkID(rs.Primary.ID) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("Bad: Get on serviceFabricMeshNetworksClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Service Fabric Mesh Local Network %q (Resource Group: %q) does not exist", id.Name, id.ResourceGroup) + } + + return nil + } +} + +func testAccAzureRMServiceFabricMeshLocalNetwork_basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-sfm-%d" + location = "%s" +} + +resource "azurerm_service_fabric_mesh_local_network" "test" { + name = "accTest-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + network_address_prefix = "10.0.0.0/22" + + description = "Test Description" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func testAccAzureRMServiceFabricMeshLocalNetwork_update(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-sfm-%d" + location = "%s" +} + +resource "azurerm_service_fabric_mesh_local_network" "test" { + name = "accTest-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + network_address_prefix = "10.1.0.0/22" + description = "Test Description" + + tags = { + Hello = "World" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 9444d2d9f164..0a787dbe25ef 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2689,6 +2689,9 @@