Skip to content

Commit

Permalink
feat(ds/compute_skus): New data source data.azurerm_compute_skus
Browse files Browse the repository at this point in the history
  • Loading branch information
Bindewald, André (UIT) authored and Bindewald, André (UIT) committed Dec 25, 2024
1 parent 021a08d commit b829cf2
Show file tree
Hide file tree
Showing 4 changed files with 373 additions and 0 deletions.
183 changes: 183 additions & 0 deletions internal/services/compute/compute_skus_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package compute

import (
"fmt"
"slices"
"strings"
"time"

"github.com/google/uuid"
"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/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
)

func dataSourceComputeSkus() *pluginsdk.Resource {
return &pluginsdk.Resource{
Read: dataSourceComputeSkusRead,

Timeouts: &pluginsdk.ResourceTimeout{
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
},

Schema: 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,
},
"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,
Optional: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
},
},
"zones": commonschema.ZonesMultipleComputed(),
},
},
},
},
}
}

func dataSourceComputeSkusRead(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.SkusClient
subscriptionId := meta.(*clients.Client).Account.SubscriptionId

ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

resp, err := client.ResourceSkusList(ctx, commonids.NewSubscriptionID(subscriptionId), skus.DefaultResourceSkusListOperationOptions())
if err != nil {
return fmt.Errorf("retrieving SKUs: %+v", err)
}

name := d.Get("name").(string)
loc := location.Normalize(d.Get("location").(string))
availableSkus := make([]map[string]interface{}, 0)

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 d.Get("include_capabilities").(bool) {
if sku.Capabilities != nil && len(*sku.Capabilities) > 0 {
for _, capability := range *sku.Capabilities {
capabilities[*capability.Name] = *capability.Value
}
}
}

availableSkus = append(availableSkus, map[string]interface{}{
"name": sku.Name,
"resource_type": sku.ResourceType,
"size": sku.Size,
"tier": sku.Tier,
"location_restrictions": locationRestrictions,
"zone_restrictions": zoneRestrictions,
"zones": zones,
"capabilities": capabilities,
})
}
d.SetId(uuid.New().String())
d.Set("skus", availableSkus)
}

return nil
}
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)
}
1 change: 1 addition & 0 deletions internal/services/compute/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func (r Registration) WebsiteCategories() []string {
func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource {
return map[string]*pluginsdk.Resource{
"azurerm_availability_set": dataSourceAvailabilitySet(),
"azurerm_compute_skus": dataSourceComputeSkus(),
"azurerm_dedicated_host": dataSourceDedicatedHost(),
"azurerm_dedicated_host_group": dataSourceDedicatedHostGroup(),
"azurerm_disk_encryption_set": dataSourceDiskEncryptionSet(),
Expand Down
86 changes: 86 additions & 0 deletions website/docs/d/compute_skus.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
subcategory: "Compute"
layout: "azurerm"
page_title: "Azure Resource Manager: azurerm_compute_skus"
description: |-
Lists available Compute SKUs
---

# Data Source: azurerm_compute_skus

This data source lists available Azure Compute SKUs.

This can be used together with a `precondition` to check if a Virtual Machine SKU is available before the `apply` phase.

## Example Usage

```hcl
data "azurerm_compute_skus" "available" {
name = "Standard_D2s_v3"
location = "westus"
}
output "available_skus" {
value = {
for sku in data.azurerm_compute_skus.available.skus : sku.name => sku
}
}
# Changes to Outputs:
# + available_skus = {
# + Standard_D2s_v3 = {
# + capabilities = {}
# + location_restrictions = []
# + name = "Standard_D2s_v3"
# + resource_type = "virtualMachines"
# + size = "D2s_v3"
# + tier = "Standard"
# + zone_restrictions = []
# + zones = [
# + "2",
# + "1",
# + "3",
# ]
# }
# }
```

## Argument Reference

~> **Note:** Due to API limitations this data source will always get **ALL** available SKUs, regardless of any set filters.

* `location` - (Required) The Azure location of the SKU.

* `name` - (Optional) The name of the SKU, like `Standard_DS2_v2`.

* `include_capabilities` - (Optional) Set to `true` if the SKUs capabilities should be included in the result.

## Attributes Reference

* `skus` - One or more `sku` blocks as defined below.

---

The `sku` block exports the following:

* `name` - The name of the SKU.

* `resource_type` - The resource type of the SKU, like `virtualMachines` or `disks`.

* `tier` - The tier of the SKU.

* `size` - The size of the SKU.

* `capabilities` - If included, this provides a map of the SKUs capabilities.

* `zones` - If the SKU supports Availability Zones, this list contains the IDs of the zones at which the SKU is normally available.

* `location_restrictions` - A list of locations at which the SKU is currently not available. The availability is tied to your Azure subscription.

* `zone_restrictions` - A list of zones in which the SKU is currently not available. The availability is tied to your Azure subscription.

## Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions:

* `read` - (Defaults to 5 minutes) Used when retrieving the SKUs.

0 comments on commit b829cf2

Please sign in to comment.