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 data source: azurerm_compute_skus #28382

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/labeler-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ service/cognitive-services:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(ai_services|cognitive_)((.|\n)*)###'

service/communication:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(communication_service|email_communication_service|gallery_application|orchestrated_virtual_machine_scale_set\W+|restore_point_collection|virtual_machine_gallery_application_assignment\W+|virtual_machine_implicit_data_disk_from_source\W+|virtual_machine_restore_point\W+|virtual_machine_restore_point_collection\W+|virtual_machine_run_command\W+)((.|\n)*)###'
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(communication_service|compute_skus|email_communication_service|gallery_application|orchestrated_virtual_machine_scale_set\W+|restore_point_collection|virtual_machine_gallery_application_assignment\W+|virtual_machine_implicit_data_disk_from_source\W+|virtual_machine_restore_point\W+|virtual_machine_restore_point_collection\W+|virtual_machine_run_command\W+)((.|\n)*)###'

service/connections:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(api_connection|managed_api)((.|\n)*)###'
Expand Down
217 changes: 217 additions & 0 deletions internal/services/compute/compute_skus_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compute

import (
"context"
"slices"
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-sdk/resource-manager/compute/2021-07-01/skus"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
)

type ComputeSkusDataSource struct{}

var _ sdk.DataSource = ComputeSkusDataSource{}

type ComputeSkusDataSourceModel struct {
Name string `tfschema:"name"`
Location string `tfschema:"location"`
IncludeCapabilities bool `tfschema:"include_capabilities"`
Skus []ComputeSkusSkuModel `tfschema:"skus"`
}

type ComputeSkusSkuModel struct {
Name string `tfschema:"name"`
ResourceType string `tfschema:"resource_type"`
Size string `tfschema:"size"`
Tier string `tfschema:"tier"`
LocationRestrictions []string `tfschema:"location_restrictions"`
ZoneRestrictions []string `tfschema:"zone_restrictions"`
Capabilities map[string]string `tfschema:"capabilities"`
Zones []string `tfschema:"zones"`
}

func (ds ComputeSkusDataSource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"location": commonschema.Location(),
"include_capabilities": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
},
}
}

func (ds ComputeSkusDataSource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"skus": {
Type: pluginsdk.TypeList,
Computed: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Computed: true,
},
"resource_type": {
Type: pluginsdk.TypeString,
Computed: true,
},
"size": {
Type: pluginsdk.TypeString,
Computed: true,
},
"tier": {
Type: pluginsdk.TypeString,
Computed: true,
},
"location_restrictions": {
Type: pluginsdk.TypeList,
Computed: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
},
},
"zone_restrictions": {
Type: pluginsdk.TypeList,
Computed: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
},
},
"capabilities": {
Type: pluginsdk.TypeMap,
Computed: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
},
},
"zones": commonschema.ZonesMultipleComputed(),
},
},
},
}
}

func (ds ComputeSkusDataSource) ModelObject() interface{} {
return &ComputeSkusDataSourceModel{}
}

func (ds ComputeSkusDataSource) ResourceType() string {
return "azurerm_compute_skus"
}

func (ds ComputeSkusDataSource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
var state ComputeSkusDataSourceModel
if err := metadata.Decode(&state); err != nil {
return err
}

subscriptionId := metadata.Client.Account.SubscriptionId
name := state.Name
loc := location.Normalize(state.Location)
availableSkus := make([]ComputeSkusSkuModel, 0)
id := parse.NewSkusID(subscriptionId)

resp, err := metadata.Client.Compute.SkusClient.ResourceSkusList(ctx, commonids.NewSubscriptionID(subscriptionId), skus.DefaultResourceSkusListOperationOptions())
if err != nil {
return err
}

if model := resp.Model; model != nil {
for _, sku := range *model {
// the API does not allow filtering by name
if name != "" {
if !strings.EqualFold(*sku.Name, name) {
continue
}
}

// while the API accepts OData filters, the location filter is currently
// not working, thus we need to filter the results manually
locationsNormalized := make([]string, len(*sku.Locations))
for _, v := range *sku.Locations {
locationsNormalized = append(locationsNormalized, location.Normalize(v))
}
if !slices.Contains(locationsNormalized, loc) {
continue
}

var zones []string
var locationRestrictions []string
var zoneRestrictions []string
capabilities := make(map[string]string)

if sku.Restrictions != nil && len(*sku.Restrictions) > 0 {
for _, restriction := range *sku.Restrictions {
restrictionType := *restriction.Type

switch restrictionType {
case skus.ResourceSkuRestrictionsTypeLocation:
restrictedLocationsNormalized := make([]string, 0)
for _, v := range *restriction.RestrictionInfo.Locations {
restrictedLocationsNormalized = append(restrictedLocationsNormalized, location.Normalize(v))
}
locationRestrictions = restrictedLocationsNormalized

case skus.ResourceSkuRestrictionsTypeZone:
zoneRestrictions = *restriction.RestrictionInfo.Zones
}
}
}

if sku.LocationInfo != nil && len(*sku.LocationInfo) > 0 {
for _, locationInfo := range *sku.LocationInfo {
if location.Normalize(*locationInfo.Location) == loc {
zones = *locationInfo.Zones
}
}
}

if state.IncludeCapabilities {
if sku.Capabilities != nil && len(*sku.Capabilities) > 0 {
for _, capability := range *sku.Capabilities {
capabilities[*capability.Name] = *capability.Value
}
}
}

availableSkus = append(availableSkus, ComputeSkusSkuModel{
Name: pointer.From(sku.Name),
ResourceType: pointer.From(sku.ResourceType),
Size: pointer.From(sku.Size),
Tier: pointer.From(sku.Tier),
LocationRestrictions: locationRestrictions,
ZoneRestrictions: zoneRestrictions,
Zones: zones,
Capabilities: capabilities,
})
}

state.Skus = availableSkus
}

metadata.SetID(id)
return metadata.Encode(&state)
},
}
}
103 changes: 103 additions & 0 deletions internal/services/compute/compute_skus_data_source_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compute_test

import (
"fmt"
"testing"

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

type ComputeSkusDataSource struct{}

func TestAccDataSourceComputeSkus_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_compute_skus", "test")
r := ComputeSkusDataSource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("skus.#").HasValue("1"),
check.That(data.ResourceName).Key("skus.1").DoesNotExist(),
check.That(data.ResourceName).Key("skus.0.name").HasValue("Standard_DS2_v2"),
check.That(data.ResourceName).Key("skus.0.capabilities.#").HasValue("0"),
),
},
})
}

func TestAccDataSourceComputeSkus_withCapabilities(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_compute_skus", "test")
r := ComputeSkusDataSource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.withCapabilities(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("skus.#").HasValue("1"),
check.That(data.ResourceName).Key("skus.1").DoesNotExist(),
check.That(data.ResourceName).Key("skus.0.name").HasValue("Standard_DS2_v2"),
check.That(data.ResourceName).Key("skus.0.capabilities.%").Exists(),
),
},
})
}

func TestAccDataSourceComputeSkus_allSkus(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_compute_skus", "test")
r := ComputeSkusDataSource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.allSkus(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("skus.0.name").Exists(),
check.That(data.ResourceName).Key("skus.1.name").Exists(),
check.That(data.ResourceName).Key("skus.2.name").Exists(),
),
},
})
}

func (ComputeSkusDataSource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

data "azurerm_compute_skus" "test" {
name = "Standard_DS2_v2"
location = "%s"
}
`, data.Locations.Primary)
}

func (ComputeSkusDataSource) withCapabilities(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

data "azurerm_compute_skus" "test" {
name = "Standard_DS2_v2"
location = "%s"
include_capabilities = true
}
`, data.Locations.Primary)
}

func (ComputeSkusDataSource) allSkus(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

data "azurerm_compute_skus" "test" {
location = "%s"
}
`, data.Locations.Primary)
}
57 changes: 57 additions & 0 deletions internal/services/compute/parse/skus.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 parse

// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten

import (
"errors"
"fmt"
"strings"

"github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids"
)

type SkusId struct {
SubscriptionId string
}

func NewSkusID(subscriptionId string) SkusId {
return SkusId{
SubscriptionId: subscriptionId,
}
}

func (id SkusId) String() string {
segments := []string{}
segmentsStr := strings.Join(segments, " / ")
return fmt.Sprintf("%s: (%s)", "Skus", segmentsStr)
}

func (id SkusId) ID() string {
fmtString := "/subscriptions/%s"
return fmt.Sprintf(fmtString, id.SubscriptionId)
}

// SkusID parses a Skus ID into an SkusId struct
func SkusID(input string) (*SkusId, error) {
id, err := resourceids.ParseAzureResourceID(input)
if err != nil {
return nil, fmt.Errorf("parsing %q as an Skus ID: %+v", input, err)
}

resourceId := SkusId{
SubscriptionId: id.SubscriptionID,
}

if resourceId.SubscriptionId == "" {
return nil, errors.New("ID was missing the 'subscriptions' element")
}

if err := id.ValidateNoEmptySegments(input); err != nil {
return nil, err
}

return &resourceId, nil
}
Loading
Loading