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 1 commit
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
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.
Loading