Skip to content

Commit

Permalink
New resource: data azurerm_locations (#23324)
Browse files Browse the repository at this point in the history
* NEW data_source_locations

* Update location_data_source.go

Co-authored-by: Tom Harvey <[email protected]>

* Update client.go

Co-authored-by: Tom Harvey <[email protected]>

* FIX imports

---------

Co-authored-by: Tom Harvey <[email protected]>
  • Loading branch information
HappyTobi and tombuildsstuff authored Dec 11, 2023
1 parent 7e23b87 commit 0ec9fd4
Show file tree
Hide file tree
Showing 30 changed files with 1,102 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/labeler-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ service/stream-analytics:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_stream_analytics_((.|\n)*)###'

service/subscription:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(extended_locations|subscription\W+|subscriptions)((.|\n)*)###'
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(extended_locations|location|subscription\W+|subscriptions)((.|\n)*)###'

service/synapse:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_synapse_((.|\n)*)###'
Expand Down
6 changes: 5 additions & 1 deletion internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,11 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error
return fmt.Errorf("building Storage for StorageMover: %+v", err)
}
client.StreamAnalytics = streamAnalytics.NewClient(o)
client.Subscription = subscription.NewClient(o)

if client.Subscription, err = subscription.NewClient(o); err != nil {
return fmt.Errorf("building clients for Subscription: %+v", err)
}

client.Synapse = synapse.NewClient(o)
if client.TrafficManager, err = trafficManager.NewClient(o); err != nil {
return fmt.Errorf("building clients for Traffic Manager: %+v", err)
Expand Down
1 change: 1 addition & 0 deletions internal/provider/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration {
storage.Registration{},
storagemover.Registration{},
signalr.Registration{},
subscription.Registration{},
orbital.Registration{},
streamanalytics.Registration{},
search.Registration{},
Expand Down
25 changes: 18 additions & 7 deletions internal/services/subscription/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,36 @@
package client

import (
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2021-01-01/subscriptions" // nolint: staticcheck
"fmt"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2021-01-01/subscriptions" // nolint: staticcheck
resourceManager "github.com/hashicorp/go-azure-sdk/resource-manager/resources/2022-12-01/subscriptions"
subscriptionAliasPandora "github.com/hashicorp/go-azure-sdk/resource-manager/subscription/2021-10-01/subscriptions" // nolint: staticcheck
"github.com/hashicorp/terraform-provider-azurerm/internal/common"
)

type Client struct {
Client *subscriptions.Client
AliasClient *subscriptionAliasPandora.SubscriptionsClient
Client *subscriptions.Client
AliasClient *subscriptionAliasPandora.SubscriptionsClient
SubscriptionsClient *resourceManager.SubscriptionsClient
}

func NewClient(o *common.ClientOptions) *Client {
func NewClient(o *common.ClientOptions) (*Client, error) {
client := subscriptions.NewClientWithBaseURI(o.ResourceManagerEndpoint)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)

aliasClient := subscriptionAliasPandora.NewSubscriptionsClientWithBaseURI(o.ResourceManagerEndpoint)
o.ConfigureClient(&aliasClient.Client, o.ResourceManagerAuthorizer)

return &Client{
AliasClient: &aliasClient,
Client: &client,
subscriptionsClient, err := resourceManager.NewSubscriptionsClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Subscriptions client: %+v", err)
}
o.Configure(subscriptionsClient.Client, o.Authorizers.ResourceManager)

return &Client{
AliasClient: &aliasClient,
Client: &client,
SubscriptionsClient: subscriptionsClient,
}, nil
}
152 changes: 152 additions & 0 deletions internal/services/subscription/location_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package subscription

import (
"context"
"fmt"
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
resourcesSubscription "github.com/hashicorp/go-azure-sdk/resource-manager/resources/2022-12-01/subscriptions"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

var _ sdk.DataSource = LocationDataSource{}

type LocationDataSource struct{}

type LocationDataSourceModel struct {
Location string `tfschema:"location"`
DisplayName string `tfschema:"display_name"`
ZoneMappings []LocationZoneMapping `tfschema:"zone_mappings"`
}

type LocationZoneMapping struct {
LogicalZone string `tfschema:"logical_zone"`
PhysicalZone string `tfschema:"physical_zone"`
}

func (r LocationDataSource) ResourceType() string {
return "azurerm_location"
}

func (r LocationDataSource) ModelObject() interface{} {
return &LocationDataSourceModel{}
}

func (r LocationDataSource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"location": commonschema.LocationWithoutForceNew(),
}
}

func (r LocationDataSource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"display_name": {
Type: pluginsdk.TypeString,
Computed: true,
},
"zone_mappings": {
Type: pluginsdk.TypeList,
Computed: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"logical_zone": {
Type: pluginsdk.TypeString,
Computed: true,
},
"physical_zone": {
Type: pluginsdk.TypeString,
Computed: true,
},
},
},
},
}
}

func (r LocationDataSource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Subscription.SubscriptionsClient
subscriptionId := metadata.Client.Account.SubscriptionId

id := commonids.NewSubscriptionID(subscriptionId)
resp, err := client.ListLocations(ctx, id, resourcesSubscription.DefaultListLocationsOperationOptions())
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return fmt.Errorf("%s was not found", id)
}

return fmt.Errorf("retrieving %s: %+v", id, err)
}

if resp.Model == nil {
return fmt.Errorf("retrieving %s: model was nil", id)
}

if resp.Model.Value == nil {
return fmt.Errorf("retrieving %s: model value was nil", id)
}

var model LocationDataSourceModel
if err := metadata.Decode(&model); err != nil {
return fmt.Errorf("decoding: %+v", err)
}

normalizedLocation := location.Normalize(model.Location)

locationValue, err := getLocation(normalizedLocation, resp.Model.Value)
if err != nil {
return err
}

var state LocationDataSourceModel
state.ZoneMappings = flattenZonesMapping(locationValue)
state.DisplayName = pointer.From(locationValue.DisplayName)
state.Location = normalizedLocation

metadata.ResourceData.SetId(fmt.Sprintf("%s/locations/%s", id.ID(), normalizedLocation))

return metadata.Encode(&state)
},
}
}

func getLocation(location string, input *[]resourcesSubscription.Location) (*resourcesSubscription.Location, error) {
for _, item := range *input {
if pointer.From(item.Name) == location && strings.EqualFold(string(pointer.From(item.Metadata.RegionType)), "Physical") {
return &item, nil
}
}

return nil, fmt.Errorf("no location was found for %q", location)
}

func flattenZonesMapping(location *resourcesSubscription.Location) (zoneMappings []LocationZoneMapping) {
zoneMappings = make([]LocationZoneMapping, 0)

if location == nil || location.AvailabilityZoneMappings == nil {
return zoneMappings
}

for _, zoneMapping := range *location.AvailabilityZoneMappings {
locationZoneMapping := LocationZoneMapping{
LogicalZone: pointer.From(zoneMapping.LogicalZone),
PhysicalZone: pointer.From(zoneMapping.PhysicalZone),
}

zoneMappings = append(zoneMappings, locationZoneMapping)
}

return zoneMappings
}
57 changes: 57 additions & 0 deletions internal/services/subscription/location_data_source_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package subscription_test

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
)

type LocationsDataSource struct{}

func TestAccLocationDataSource_NonExistingRegion(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_location", "test")

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: LocationsDataSource{}.basic("not-existing-region"),
ExpectError: regexp.MustCompile("\"not-existing-region\" was not found in the list of supported Azure Locations"),
},
})
}

func TestAccLocationDataSource_westUS(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_location", "test")

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: LocationsDataSource{}.basic("westus"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("display_name").HasValue("West US"),
check.That(data.ResourceName).Key("zone_mappings.0.logical_zone").HasValue("1"),
check.That(data.ResourceName).Key("zone_mappings.1.logical_zone").HasValue("2"),
check.That(data.ResourceName).Key("zone_mappings.2.logical_zone").HasValue("3"),
check.That(data.ResourceName).Key("zone_mappings.0.physical_zone").IsNotEmpty(),
check.That(data.ResourceName).Key("zone_mappings.1.physical_zone").IsNotEmpty(),
check.That(data.ResourceName).Key("zone_mappings.2.physical_zone").IsNotEmpty(),
),
},
})
}

func (d LocationsDataSource) basic(location string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
data "azurerm_location" "test" {
location = "%s"
}
`, location)
}
15 changes: 14 additions & 1 deletion internal/services/subscription/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (

type Registration struct{}

var _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{}
var (
_ sdk.TypedServiceRegistrationWithAGitHubLabel = Registration{}
_ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{}
)

func (r Registration) AssociatedGitHubLabel() string {
return "service/subscription"
Expand Down Expand Up @@ -43,3 +46,13 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
"azurerm_subscription": resourceSubscription(),
}
}

func (r Registration) DataSources() []sdk.DataSource {
return []sdk.DataSource{
LocationDataSource{},
}
}

func (r Registration) Resources() []sdk.Resource {
return []sdk.Resource{}
}
Loading

0 comments on commit 0ec9fd4

Please sign in to comment.