diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index 8685e110217f..49706a2acac5 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -46,7 +46,7 @@ service/automation: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_automation_((.|\n)*)###' service/azure-stack-hci: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_stack_hci_cluster((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_stack_hci_((.|\n)*)###' service/batch: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_batch_((.|\n)*)###' diff --git a/internal/services/azurestackhci/registration.go b/internal/services/azurestackhci/registration.go index 35ed508bbac2..49f65474ad48 100644 --- a/internal/services/azurestackhci/registration.go +++ b/internal/services/azurestackhci/registration.go @@ -10,8 +10,10 @@ import ( type Registration struct{} -var _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{} -var _ sdk.TypedServiceRegistrationWithAGitHubLabel = Registration{} +var ( + _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{} + _ sdk.TypedServiceRegistrationWithAGitHubLabel = Registration{} +) func (r Registration) AssociatedGitHubLabel() string { return "service/azure-stack-hci" @@ -48,5 +50,7 @@ func (r Registration) DataSources() []sdk.DataSource { } func (r Registration) Resources() []sdk.Resource { - return []sdk.Resource{} + return []sdk.Resource{ + StackHCILogicalNetworkResource{}, + } } diff --git a/internal/services/azurestackhci/stack_hci_logical_network_resource.go b/internal/services/azurestackhci/stack_hci_logical_network_resource.go new file mode 100644 index 000000000000..02783a3588e4 --- /dev/null +++ b/internal/services/azurestackhci/stack_hci_logical_network_resource.go @@ -0,0 +1,480 @@ +package azurestackhci + +import ( + "context" + "fmt" + "regexp" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" + "github.com/hashicorp/go-azure-sdk/resource-manager/azurestackhci/2024-01-01/logicalnetworks" + "github.com/hashicorp/go-azure-sdk/resource-manager/extendedlocation/2021-08-15/customlocations" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +var ( + _ sdk.Resource = StackHCILogicalNetworkResource{} + _ sdk.ResourceWithUpdate = StackHCILogicalNetworkResource{} +) + +type StackHCILogicalNetworkResource struct{} + +func (StackHCILogicalNetworkResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return logicalnetworks.ValidateLogicalNetworkID +} + +func (StackHCILogicalNetworkResource) ResourceType() string { + return "azurerm_stack_hci_logical_network" +} + +func (StackHCILogicalNetworkResource) ModelObject() interface{} { + return &StackHCILogicalNetworkResourceModel{} +} + +type StackHCILogicalNetworkResourceModel struct { + Name string `tfschema:"name"` + ResourceGroupName string `tfschema:"resource_group_name"` + Location string `tfschema:"location"` + CustomLocationId string `tfschema:"custom_location_id"` + DNSServers []string `tfschema:"dns_servers"` + Subnet []StackHCISubnetModel `tfschema:"subnet"` + VirtualSwitchName string `tfschema:"virtual_switch_name"` + Tags map[string]interface{} `tfschema:"tags"` +} + +type StackHCISubnetModel struct { + AddressPrefix string `tfschema:"address_prefix"` + IpAllocationMethod string `tfschema:"ip_allocation_method"` + IpPool []StackHCIIPPoolModel `tfschema:"ip_pool"` + Route []StackHCIRouteModel `tfschema:"route"` + VlanId int64 `tfschema:"vlan_id"` +} + +type StackHCIIPPoolModel struct { + Start string `tfschema:"start"` + End string `tfschema:"end"` +} + +type StackHCIRouteModel struct { + Name string `tfschema:"name"` + AddressPrefix string `tfschema:"address_prefix"` + NextHopIpAddress string `tfschema:"next_hop_ip_address"` +} + +func (StackHCILogicalNetworkResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile(`^[a-zA-Z0-9][\-\.\_a-zA-Z0-9]{0,62}[a-zA-Z0-9]$`), + "name must be between 2 and 64 characters and can only contain alphanumberic characters, hyphen, dot and underline", + ), + }, + + "resource_group_name": commonschema.ResourceGroupName(), + + "location": commonschema.Location(), + + "custom_location_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: customlocations.ValidateCustomLocationID, + }, + + "virtual_switch_name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "dns_servers": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsIPv4Address, + }, + }, + + "subnet": { + Type: pluginsdk.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "ip_allocation_method": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(logicalnetworks.PossibleValuesForIPAllocationMethodEnum(), false), + }, + + "address_prefix": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IsCIDR, + }, + + "ip_pool": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "start": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsIPv4Address, + }, + "end": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsIPv4Address, + }, + }, + }, + }, + + "route": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile(`^[a-zA-Z0-9][\-\.\_a-zA-Z0-9]{0,78}[a-zA-Z0-9]$`), + "name must be between 2 and 80 characters and can only contain alphanumberic characters, hyphen, dot and underline", + ), + }, + + "address_prefix": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsCIDR, + }, + + "next_hop_ip_address": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsIPv4Address, + }, + }, + }, + }, + + "vlan_id": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, + }, + + "tags": commonschema.Tags(), + } +} + +func (StackHCILogicalNetworkResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r StackHCILogicalNetworkResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AzureStackHCI.LogicalNetworks + + var config StackHCILogicalNetworkResourceModel + if err := metadata.Decode(&config); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + subscriptionId := metadata.Client.Account.SubscriptionId + id := logicalnetworks.NewLogicalNetworkID(subscriptionId, config.ResourceGroupName, config.Name) + + existing, err := client.Get(ctx, id) + if err != nil && !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + if !response.WasNotFound(existing.HttpResponse) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + payload := logicalnetworks.LogicalNetworks{ + Name: pointer.To(config.Name), + Location: location.Normalize(config.Location), + Tags: tags.Expand(config.Tags), + ExtendedLocation: &logicalnetworks.ExtendedLocation{ + Name: pointer.To(config.CustomLocationId), + Type: pointer.To(logicalnetworks.ExtendedLocationTypesCustomLocation), + }, + Properties: &logicalnetworks.LogicalNetworkProperties{ + VMSwitchName: pointer.To(config.VirtualSwitchName), + Subnets: expandStackHCILogicalNetworkSubnet(config.Subnet), + DhcpOptions: &logicalnetworks.LogicalNetworkPropertiesDhcpOptions{ + DnsServers: pointer.To(config.DNSServers), + }, + }, + } + + if err := client.CreateOrUpdateThenPoll(ctx, id, payload); err != nil { + return fmt.Errorf("performing create %s: %+v", id, err) + } + + metadata.SetID(id) + + return nil + }, + } +} + +func (r StackHCILogicalNetworkResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AzureStackHCI.LogicalNetworks + + id, err := logicalnetworks.ParseLogicalNetworkID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + schema := StackHCILogicalNetworkResourceModel{ + Name: id.LogicalNetworkName, + ResourceGroupName: id.ResourceGroupName, + } + + if model := resp.Model; model != nil { + schema.Location = location.Normalize(model.Location) + schema.Tags = tags.Flatten(model.Tags) + + if model.ExtendedLocation != nil && model.ExtendedLocation.Name != nil { + customLocationId, err := customlocations.ParseCustomLocationIDInsensitively(*model.ExtendedLocation.Name) + if err != nil { + return err + } + + schema.CustomLocationId = customLocationId.ID() + } + + if props := model.Properties; props != nil { + schema.Subnet = flattenStackHCILogicalNetworkSubnet(props.Subnets) + schema.VirtualSwitchName = pointer.From(props.VMSwitchName) + + if props.DhcpOptions != nil { + schema.DNSServers = pointer.From(props.DhcpOptions.DnsServers) + } + } + } + + return metadata.Encode(&schema) + }, + } +} + +func (r StackHCILogicalNetworkResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AzureStackHCI.LogicalNetworks + + id, err := logicalnetworks.ParseLogicalNetworkID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model StackHCILogicalNetworkResourceModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + resp, err := client.Get(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + parameters := resp.Model + if parameters == nil { + return fmt.Errorf("retrieving %s: `model` was nil", *id) + } + + if metadata.ResourceData.HasChange("tags") { + parameters.Tags = tags.Expand(model.Tags) + } + + if err := client.CreateOrUpdateThenPoll(ctx, *id, *parameters); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + return nil + }, + } +} + +func (r StackHCILogicalNetworkResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AzureStackHCI.LogicalNetworks + + id, err := logicalnetworks.ParseLogicalNetworkID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if err := client.DeleteThenPoll(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + return nil + }, + } +} + +func expandStackHCILogicalNetworkSubnet(input []StackHCISubnetModel) *[]logicalnetworks.Subnet { + if len(input) == 0 { + return nil + } + + results := make([]logicalnetworks.Subnet, 0) + for _, v := range input { + results = append(results, logicalnetworks.Subnet{ + Properties: &logicalnetworks.SubnetPropertiesFormat{ + AddressPrefix: pointer.To(v.AddressPrefix), + IPAllocationMethod: pointer.To(logicalnetworks.IPAllocationMethodEnum(v.IpAllocationMethod)), + IPPools: expandStackHCILogicalNetworkIPPool(v.IpPool), + RouteTable: expandStackHCILogicalNetworkRouteTable(v.Route), + Vlan: pointer.To(v.VlanId), + }, + }) + } + + return &results +} + +func flattenStackHCILogicalNetworkSubnet(input *[]logicalnetworks.Subnet) []StackHCISubnetModel { + if input == nil { + return make([]StackHCISubnetModel, 0) + } + + results := make([]StackHCISubnetModel, 0) + for _, v := range *input { + if v.Properties != nil { + results = append(results, StackHCISubnetModel{ + AddressPrefix: pointer.From(v.Properties.AddressPrefix), + IpAllocationMethod: string(pointer.From(v.Properties.IPAllocationMethod)), + IpPool: flattenStackHCILogicalNetworkIPPool(v.Properties.IPPools), + Route: flattenStackHCILogicalNetworkRouteTable(v.Properties.RouteTable), + VlanId: pointer.From(v.Properties.Vlan), + }) + } + } + + return results +} + +func expandStackHCILogicalNetworkIPPool(input []StackHCIIPPoolModel) *[]logicalnetworks.IPPool { + if len(input) == 0 { + return nil + } + + results := make([]logicalnetworks.IPPool, 0) + for _, v := range input { + results = append(results, logicalnetworks.IPPool{ + Start: pointer.To(v.Start), + End: pointer.To(v.End), + }) + } + + return &results +} + +func flattenStackHCILogicalNetworkIPPool(input *[]logicalnetworks.IPPool) []StackHCIIPPoolModel { + if input == nil { + return make([]StackHCIIPPoolModel, 0) + } + + results := make([]StackHCIIPPoolModel, 0) + for _, v := range *input { + results = append(results, StackHCIIPPoolModel{ + Start: pointer.From(v.Start), + End: pointer.From(v.End), + }) + } + + return results +} + +func expandStackHCILogicalNetworkRouteTable(input []StackHCIRouteModel) *logicalnetworks.RouteTable { + if len(input) == 0 { + return nil + } + + routes := make([]logicalnetworks.Route, 0) + for _, v := range input { + routes = append(routes, logicalnetworks.Route{ + Name: pointer.To(v.Name), + Properties: &logicalnetworks.RoutePropertiesFormat{ + AddressPrefix: pointer.To(v.AddressPrefix), + NextHopIPAddress: pointer.To(v.NextHopIpAddress), + }, + }) + } + + return &logicalnetworks.RouteTable{ + Properties: &logicalnetworks.RouteTablePropertiesFormat{ + Routes: pointer.To(routes), + }, + } +} + +func flattenStackHCILogicalNetworkRouteTable(input *logicalnetworks.RouteTable) []StackHCIRouteModel { + if input == nil || input.Properties == nil || input.Properties.Routes == nil { + return make([]StackHCIRouteModel, 0) + } + + results := make([]StackHCIRouteModel, 0) + for _, v := range *input.Properties.Routes { + route := StackHCIRouteModel{ + Name: pointer.From(v.Name), + } + if v.Properties != nil { + route.AddressPrefix = pointer.From(v.Properties.AddressPrefix) + route.NextHopIpAddress = pointer.From(v.Properties.NextHopIPAddress) + } + results = append(results, route) + } + + return results +} diff --git a/internal/services/azurestackhci/stack_hci_logical_network_resource_test.go b/internal/services/azurestackhci/stack_hci_logical_network_resource_test.go new file mode 100644 index 000000000000..6bef504e8c7a --- /dev/null +++ b/internal/services/azurestackhci/stack_hci_logical_network_resource_test.go @@ -0,0 +1,328 @@ +package azurestackhci_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/azurestackhci/2024-01-01/logicalnetworks" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type StackHCILogicalNetworkResource struct{} + +// https://learn.microsoft.com/en-us/azure-stack/hci/manage/create-logical-networks?tabs=azurecli#prerequisites +// The resource can only be created on the customlocation generated after HCI deployment +const ( + customLocationIdEnv = "ARM_TEST_STACK_HCI_CUSTOM_LOCATION_ID" +) + +func TestAccStackHCILogicalNetwork_dynamic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_stack_hci_logical_network", "test") + r := StackHCILogicalNetworkResource{} + + if os.Getenv(customLocationIdEnv) == "" { + t.Skipf("skipping since %q has not been specified", customLocationIdEnv) + } + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.dynamic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccStackHCILogicalNetwork_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_stack_hci_logical_network", "test") + r := StackHCILogicalNetworkResource{} + + if os.Getenv(customLocationIdEnv) == "" { + t.Skipf("skipping since %q has not been specified", customLocationIdEnv) + } + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccStackHCILogicalNetwork_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_stack_hci_logical_network", "test") + r := StackHCILogicalNetworkResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccStackHCILogicalNetwork_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_stack_hci_logical_network", "test") + r := StackHCILogicalNetworkResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r StackHCILogicalNetworkResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + clusterClient := client.AzureStackHCI.LogicalNetworks + id, err := logicalnetworks.ParseLogicalNetworkID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clusterClient.Get(ctx, *id) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (r StackHCILogicalNetworkResource) dynamic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_stack_hci_logical_network" "test" { + name = "acctest-ln-${var.random_string}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + custom_location_id = %q + virtual_switch_name = "ConvergedSwitch(managementcompute)" + + subnet { + ip_allocation_method = "Dynamic" + } +} +`, template, os.Getenv(customLocationIdEnv)) +} + +func (r StackHCILogicalNetworkResource) requiresImport(data acceptance.TestData) string { + config := r.dynamic(data) + + return fmt.Sprintf(` +%s + +resource "azurerm_stack_hci_logical_network" "import" { + name = azurerm_stack_hci_logical_network.test.name + resource_group_name = azurerm_stack_hci_logical_network.test.resource_group_name + location = azurerm_stack_hci_logical_network.test.location + custom_location_id = azurerm_stack_hci_logical_network.test.custom_location_id + virtual_switch_name = azurerm_stack_hci_logical_network.test.virtual_switch_name + + subnet { + ip_allocation_method = azurerm_stack_hci_logical_network.test.subnet.0.ip_allocation_method + } +} +`, config) +} + +func (r StackHCILogicalNetworkResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_stack_hci_logical_network" "test" { + name = "acctest-ln-${var.random_string}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + custom_location_id = %q + virtual_switch_name = "ConvergedSwitch(managementcompute)" + dns_servers = ["10.0.0.7", "10.0.0.8"] + + subnet { + ip_allocation_method = "Static" + address_prefix = "10.0.0.0/24" + vlan_id = 123 + ip_pool { + start = "10.0.0.218" + end = "10.0.0.230" + } + ip_pool { + start = "10.0.0.234" + end = "10.0.0.239" + } + route { + name = "test-route" + address_prefix = "10.0.0.0/28" + next_hop_ip_address = "10.0.20.1" + } + route { + name = "test-route2" + address_prefix = "10.0.0.128/28" + next_hop_ip_address = "10.0.20.2" + } + } +} +`, template, os.Getenv(customLocationIdEnv)) +} + +func (r StackHCILogicalNetworkResource) update(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_stack_hci_logical_network" "test" { + name = "acctest-ln-${var.random_string}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + custom_location_id = %q + virtual_switch_name = "ConvergedSwitch(managementcompute)" + dns_servers = ["10.0.0.7", "10.0.0.8"] + + subnet { + ip_allocation_method = "Static" + address_prefix = "10.0.0.0/24" + vlan_id = 123 + ip_pool { + start = "10.0.0.218" + end = "10.0.0.230" + } + ip_pool { + start = "10.0.0.234" + end = "10.0.0.239" + } + route { + name = "test-route" + address_prefix = "10.0.0.0/28" + next_hop_ip_address = "10.0.20.1" + } + route { + name = "test-route2" + address_prefix = "10.0.0.128/28" + next_hop_ip_address = "10.0.20.2" + } + } + + tags = { + foo = "bar" + } +} +`, template, os.Getenv(customLocationIdEnv)) +} + +func (r StackHCILogicalNetworkResource) complete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_stack_hci_logical_network" "test" { + name = "acctest-ln-${var.random_string}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + custom_location_id = %q + virtual_switch_name = "ConvergedSwitch(managementcompute)" + dns_servers = ["10.0.0.7", "10.0.0.8"] + + subnet { + ip_allocation_method = "Static" + address_prefix = "10.0.0.0/24" + vlan_id = 123 + ip_pool { + start = "10.0.0.218" + end = "10.0.0.230" + } + ip_pool { + start = "10.0.0.234" + end = "10.0.0.239" + } + route { + name = "test-route" + address_prefix = "10.0.0.0/28" + next_hop_ip_address = "10.0.20.1" + } + route { + name = "test-route2" + address_prefix = "10.0.0.128/28" + next_hop_ip_address = "10.0.20.2" + } + } + + tags = { + foo = "bar" + env = "test" + } +} +`, template, os.Getenv(customLocationIdEnv)) +} + +func (r StackHCILogicalNetworkResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +variable "primary_location" { + default = %q +} + +variable "random_string" { + default = %q +} + +resource "azurerm_resource_group" "test" { + name = "acctest-hci-ln-${var.random_string}" + location = var.primary_location +} +`, data.Locations.Primary, data.RandomString) +} diff --git a/website/docs/r/stack_hci_logical_network.html.markdown b/website/docs/r/stack_hci_logical_network.html.markdown new file mode 100644 index 000000000000..1aa18577438e --- /dev/null +++ b/website/docs/r/stack_hci_logical_network.html.markdown @@ -0,0 +1,119 @@ +--- +subcategory: "Azure Stack HCI" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_stack_hci_logical_network" +description: |- + Manages an Azure Stack HCI Logical Network. +--- + +# azurerm_stack_hci_logical_network + +Manages an Azure Stack HCI Logical Network. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "West Europe" +} + +resource "azurerm_stack_hci_logical_network" "example" { + name = "example-hci-ln" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + custom_location_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.ExtendedLocation/customLocations/cl1" + virtual_switch_name = "ConvergedSwitch(managementcompute)" + dns_servers = ["10.0.0.7", "10.0.0.8"] + + subnet { + ip_allocation_method = "Static" + address_prefix = "10.0.0.0/24" + route { + name = "example-route" + address_prefix = "0.0.0.0/0" + next_hop_ip_address = "10.0.20.1" + } + vlan_id = 123 + } + + tags = { + foo = "bar" + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Azure Stack HCI Logical Network. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Azure Stack HCI Logical Network should exist. Changing this forces a new resource to be created. + +* `location` - (Required) The Azure Region where the Azure Stack HCI Logical Network should exist. Changing this forces a new resource to be created. + +* `custom_location_id` - (Required) The ID of Custom Location where the Azure Stack HCI Logical Network should exist. Changing this forces a new resource to be created. + +* `virtual_switch_name` - (Required) The name of the virtual switch on the cluster used to associate with the Azure Stack HCI Logical Network. Possible switch names can be retrieved by following this [Azure guide](https://learn.microsoft.com/azure-stack/hci/manage/create-logical-networks?tabs=azurecli#prerequisites). Changing this forces a new resource to be created. + +* `subnet` - (Required) A `subnet` block as defined below. Changing this forces a new resource to be created. + +* `dns_servers` - (Optional) A list of IPv4 addresses of DNS servers available to VMs deployed in the Logical Networks. Changing this forces a new resource to be created. + +* `tags` - (Optional) A mapping of tags which should be assigned to the Azure Stack HCI Logical Network. + +--- + +A `ip_pool` block supports the following: + +* `start` - (Required) The IPv4 address of the start of the IP address pool. Changing this forces a new resource to be created. + +* `end` - (Required) The IPv4 address of the end of the IP address pool. Changing this forces a new resource to be created. + +--- + +A `route` block supports the following: + +* `name` - (Required) The name of the route. Changing this forces a new resource to be created. + +* `address_prefix` - (Optional) The Address in CIDR notation. Changing this forces a new resource to be created. + +* `next_hop_ip_address` - (Optional) The IPv4 address of the next hop. Changing this forces a new resource to be created. + +--- + +A `subnet` block supports the following: + +* `ip_allocation_method` - (Required) The IP address allocation method for the subnet. Possible values are `Dynamic` and `Static`. Changing this forces a new resource to be created. + +* `address_prefix` - (Optional) The address prefix in CIDR notation. Changing this forces a new resource to be created. + +* `ip_pool` - (Optional) One or more `ip_pool` block as defined above. Changing this forces a new resource to be created. + +* `route` - (Optional) One or more `route` block as defined above. Changing this forces a new resource to be created. + +* `vlan_id` - (Optional) The VLAN ID for the Logical Network. Changing this forces a new resource to be created. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The resource ID of the Azure Stack HCI Logical Network. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Azure Stack HCI Logical Network. +* `read` - (Defaults to 5 minutes) Used when retrieving the Azure Stack HCI Logical Network. +* `update` - (Defaults to 30 minutes) Used when updating the Azure Stack HCI Logical Network. +* `delete` - (Defaults to 30 minutes) Used when deleting the Azure Stack HCI Logical Network. + +## Import + +Azure Stack HCI Logical Networks can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_stack_hci_logical_network.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.AzureStackHCI/logicalNetworks/ln1 +```