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 resources azurerm_capacity_reservation_group and azurerm_capacity_reservation #16464

Merged
merged 6 commits into from
Jun 8, 2022
Merged
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
Next Next commit
New resources capacity_reservation_group and capacity_reservation
myc2h6o committed Apr 20, 2022
commit 827c2c2fcea6337497bec9fc26bd9af40f816ce1
163 changes: 163 additions & 0 deletions internal/services/compute/capacity_reservation_group_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package compute

import (
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-helpers/resourcemanager/zones"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tags"
"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"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

func resourceCapacityReservationGroup() *pluginsdk.Resource {
return &pluginsdk.Resource{
Create: resourceCapacityReservationGroupCreate,
Read: resourceCapacityReservationGroupRead,
Update: resourceCapacityReservationGroupUpdate,
Delete: resourceCapacityReservationGroupDelete,

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

Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
_, err := parse.CapacityReservationGroupID(id)
return err
}),

Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have more specific validation requirements for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added the validation for name

},

"resource_group_name": azure.SchemaResourceGroupName(),

"location": azure.SchemaLocation(),

"zones": commonschema.ZonesMultipleOptionalForceNew(),

"tags": tags.Schema(),
},
}
}

func resourceCapacityReservationGroupCreate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.CapacityReservationGroupsClient
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)
id := parse.NewCapacityReservationGroupID(subscriptionId, resourceGroup, name)
existing, err := client.Get(ctx, id.ResourceGroup, id.Name, "")
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("checking for existing %s: %+v", id, err)
}
}
if !utils.ResponseWasNotFound(existing.Response) {
return tf.ImportAsExistsError("azurerm_capacity_reservation_group", id.ID())
}

parameters := compute.CapacityReservationGroup{
Name: utils.String(name),
Location: utils.String(location.Normalize(d.Get("location").(string))),
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

zones := zones.Expand(d.Get("zones").(*schema.Set).List())
if len(zones) > 0 {
parameters.Zones = &zones
}
Comment on lines +88 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be using the standard Expand functions for Zones

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

different from the other resource in this change, in line 87 (before the new commit), already used the standard zones.Expand


if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters); err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}

d.SetId(id.ID())
return resourceCapacityReservationGroupRead(d, meta)
}

func resourceCapacityReservationGroupRead(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.CapacityReservationGroupsClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.CapacityReservationGroupID(d.Id())
if err != nil {
return err
}

resp, err := client.Get(ctx, id.ResourceGroup, id.Name, "")
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[INFO] Capacity Reservation Group %q does not exist - removing from state", d.Id())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.Printf("[INFO] Capacity Reservation Group %q does not exist - removing from state", d.Id())
log.Printf("[INFO] %s was not found - removing from state", *id)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

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

d.Set("name", resp.Name)
d.Set("resource_group_name", id.ResourceGroup)
d.Set("location", location.NormalizeNilable(resp.Location))
d.Set("zones", utils.FlattenStringSlice(resp.Zones))
return tags.FlattenAndSet(d, resp.Tags)
}

func resourceCapacityReservationGroupUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.CapacityReservationGroupsClient
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.CapacityReservationGroupID(d.Id())
if err != nil {
return err
}

parameters := compute.CapacityReservationGroupUpdate{}

if d.HasChange("tags") {
parameters.Tags = tags.Expand(d.Get("tags").(map[string]interface{}))
}

if _, err := client.Update(ctx, id.ResourceGroup, id.Name, parameters); err != nil {
return fmt.Errorf("updating %s: %+v", id, err)
}
return resourceCapacityReservationGroupRead(d, meta)
}

func resourceCapacityReservationGroupDelete(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.CapacityReservationGroupsClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.CapacityReservationGroupID(d.Id())
if err != nil {
return err
}

if _, err := client.Delete(ctx, id.ResourceGroup, id.Name); err != nil {
return fmt.Errorf("deleting %s: %+v", id, err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this is gone straight away? given the other API has issues, are there any API errors when deleting multiple reservations within a parent group here?

Copy link
Contributor Author

@myc2h6o myc2h6o Apr 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This api only has the non-future version: https://github.com/Azure/azure-sdk-for-go/blob/bf144f61fde04d2f29dd52d84f25e931cea67fd8/services/compute/mgmt/2021-11-01/compute/capacityreservationgroups.go#L120.
The api issue is that, although capacity reservation is deleted and not present in the Get response, capacity reservation group still needs some additional wait (about 5 seconds) to realize that, so added the wait in the capacity reservation deletion. Have put the detail in spec repo Azure/azure-rest-api-specs#18767

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't see any wait? we souldn't be waiting and instead poll to check until its finished

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @katbyte The deletion for this resource capacity_reservation_group is synchrounous per sdk.

And the wait and poll is for the other resource capaicty_reservation below in this change, where the asynchronous sdk has bug

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just took another thought. The actual issue Azure/azure-rest-api-specs#18767 happens within deleting the parent resource capacity_reservation_group due to cache refresh issue. So instead of adding a wait on capacity_reservation, probably it's better to add a retry logic on the parent resource here itself. I'll update the pr and let you know once it's ready to be reviewed again

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package compute_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

type CapacityReservationGroupResource struct{}

func TestAccCapacityReservationGroup_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_capacity_reservation_group", "test")
r := CapacityReservationGroupResource{}
data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccCapacityReservationGroup_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_capacity_reservation_group", "test")
r := CapacityReservationGroupResource{}
data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.RequiresImportErrorStep(r.requiresImport),
})
}

func TestAccCapacityReservationGroup_zones(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_capacity_reservation_group", "test")
r := CapacityReservationGroupResource{}
data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.zones(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccCapacityReservationGroup_tags(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_capacity_reservation_group", "test")
r := CapacityReservationGroupResource{}
data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.tags(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.tagsUpdated(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (r CapacityReservationGroupResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) {
id, err := parse.CapacityReservationGroupID(state.ID)
if err != nil {
return nil, err
}

resp, err := client.Compute.CapacityReservationGroupsClient.Get(ctx, id.ResourceGroup, id.Name, "")
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return utils.Bool(false), nil
}
return nil, fmt.Errorf("retrieving %s: %+v", id, err)
}

return utils.Bool(resp.ID != nil), nil
}

func (r CapacityReservationGroupResource) template(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctest-compute-%d"
location = "%s"
}
`, data.RandomInteger, data.Locations.Primary)
}

func (r CapacityReservationGroupResource) basic(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_capacity_reservation_group" "test" {
name = "acctest-crg-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
}
`, template, data.RandomInteger)
}

func (r CapacityReservationGroupResource) requiresImport(data acceptance.TestData) string {
config := r.basic(data)
return fmt.Sprintf(`
%s

resource "azurerm_capacity_reservation_group" "import" {
name = azurerm_capacity_reservation_group.test.name
resource_group_name = azurerm_capacity_reservation_group.test.resource_group_name
location = azurerm_capacity_reservation_group.test.location
}
`, config)
}

func (r CapacityReservationGroupResource) zones(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_capacity_reservation_group" "test" {
name = "acctest-ccrg-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
zones = ["1", "2"]
}
`, template, data.RandomInteger)
}

func (r CapacityReservationGroupResource) tags(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_capacity_reservation_group" "test" {
name = "acctest-ccrg-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
tags = {
ENV = "Test"
}
}
`, template, data.RandomInteger)
}

func (r CapacityReservationGroupResource) tagsUpdated(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_capacity_reservation_group" "test" {
name = "acctest-ccrg-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
tags = {
ENV2 = "Test2"
}
}
`, template, data.RandomInteger)
}
Loading