-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ds/compute_skus): New data source
data.azurerm_compute_skus
- Loading branch information
Bindewald, André (UIT)
authored and
Bindewald, André (UIT)
committed
Dec 25, 2024
1 parent
021a08d
commit b829cf2
Showing
4 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
103
internal/services/compute/compute_skus_data_source_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |