Skip to content

Commit

Permalink
New Resource: azurerm_advisor_suppression (#26177)
Browse files Browse the repository at this point in the history
* New Resource: `azurerm_advisor_suppression`

* Register service in SupportedTypedServices

* apply fixes from review

Signed-off-by: Jan-Otto Kröpke <[email protected]>

* go mod vendor && go mod tidy

---------

Signed-off-by: Jan-Otto Kröpke <[email protected]>
Co-authored-by: kt <[email protected]>
  • Loading branch information
jkroepke and katbyte authored Sep 20, 2024
1 parent 6a37454 commit ccec080
Show file tree
Hide file tree
Showing 20 changed files with 989 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/labeler-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ service/aadb2c:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_aadb2c_directory((.|\n)*)###'

service/advisor:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_advisor_recommendations((.|\n)*)###'
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_advisor_((.|\n)*)###'

service/analysis:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_analysis_services_server((.|\n)*)###'
Expand Down
1 change: 1 addition & 0 deletions internal/provider/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration {
authorization.Registration{},
automanage.Registration{},
automation.Registration{},
advisor.Registration{},
azurestackhci.Registration{},
batch.Registration{},
bot.Registration{},
Expand Down
179 changes: 179 additions & 0 deletions internal/services/advisor/advisor_suppression_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package advisor

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-sdk/resource-manager/advisor/2023-01-01/suppressions"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/advisor/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

var _ sdk.Resource = AdvisorSuppressionResource{}

type AdvisorSuppressionResource struct{}

type AdvisorSuppressionResourceModel struct {
Name string `tfschema:"name"`
SuppressionID string `tfschema:"suppression_id"`
RecommendationID string `tfschema:"recommendation_id"`
ResourceID string `tfschema:"resource_id"`
TTL string `tfschema:"ttl"`
}

func (AdvisorSuppressionResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
},
"recommendation_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
},
"resource_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},
"ttl": {
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validate.Duration,
},
}
}

func (AdvisorSuppressionResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"suppression_id": {
Type: pluginsdk.TypeString,
Computed: true,
},
}
}

func (AdvisorSuppressionResource) ModelObject() interface{} {
return &AdvisorSuppressionResourceModel{}
}

func (AdvisorSuppressionResource) ResourceType() string {
return "azurerm_advisor_suppression"
}

func (r AdvisorSuppressionResource) Create() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Advisor.SuppressionsClient

var model AdvisorSuppressionResourceModel

if err := metadata.Decode(&model); err != nil {
return err
}

id := suppressions.NewScopedSuppressionID(model.ResourceID, model.RecommendationID, model.Name)

existing, err := client.Get(ctx, id)
if err != nil && !response.WasNotFound(existing.HttpResponse) {
return fmt.Errorf("checking for presence of existing %s: %+v", id, err)
}
if !response.WasNotFound(existing.HttpResponse) {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}

param := suppressions.SuppressionContract{
Name: pointer.To(model.Name),
Properties: &suppressions.SuppressionProperties{
SuppressionId: pointer.To(model.SuppressionID),
},
}

if model.TTL != "" {
param.Properties.Ttl = pointer.To(model.TTL)
}

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

metadata.SetID(id)
return nil
},
}
}

func (AdvisorSuppressionResource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Advisor.SuppressionsClient

state := AdvisorSuppressionResourceModel{}

id, err := suppressions.ParseScopedSuppressionID(metadata.ResourceData.Id())
if err != nil {
return err
}

resp, err := client.Get(ctx, *id)
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return metadata.MarkAsGone(id)
}

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

state.Name = id.SuppressionName
state.ResourceID = id.ResourceUri
state.RecommendationID = id.RecommendationId

if model := resp.Model; model != nil {
if props := model.Properties; props != nil {
state.TTL = pointer.From(props.Ttl)
state.SuppressionID = pointer.From(props.SuppressionId)
}
}

return metadata.Encode(&state)
},
}
}

func (AdvisorSuppressionResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Advisor.SuppressionsClient

id, err := suppressions.ParseScopedSuppressionID(metadata.ResourceData.Id())
if err != nil {
return err
}

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

return nil
},
}
}

func (AdvisorSuppressionResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return suppressions.ValidateScopedSuppressionID
}
72 changes: 72 additions & 0 deletions internal/services/advisor/advisor_suppression_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package advisor_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-sdk/resource-manager/advisor/2023-01-01/suppressions"
"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/tf/pluginsdk"
)

type AdvisorSuppressionResource struct{}

func TestAccAnalysisServicesServer_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_advisor_suppression", "test")
r := AdvisorSuppressionResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (AdvisorSuppressionResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := suppressions.ParseScopedSuppressionID(state.ID)
if err != nil {
return nil, err
}

resp, err := clients.Advisor.SuppressionsClient.Get(ctx, *id)
if err != nil {
return nil, fmt.Errorf("retrieving %s: %+v", *id, err)
}

return pointer.To(resp.Model != nil && resp.Model.Id != nil), nil
}

func (t AdvisorSuppressionResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
data "azurerm_client_config" "current" {}
data "azurerm_advisor_recommendations" "test" {}
# The recommendation_names local variable is used to sort the recommendation names.
locals {
recommendation_names = sort(data.azurerm_advisor_recommendations.test.recommendations[*].recommendation_name)
}
resource "azurerm_advisor_suppression" "test" {
name = "acctest%d"
recommendation_id = local.recommendation_names[0]
resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}"
ttl = "00:30:00"
}
`, data.RandomInteger)
}
11 changes: 10 additions & 1 deletion internal/services/advisor/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,30 @@ import (
"fmt"

"github.com/hashicorp/go-azure-sdk/resource-manager/advisor/2023-01-01/getrecommendations"
"github.com/hashicorp/go-azure-sdk/resource-manager/advisor/2023-01-01/suppressions"
"github.com/hashicorp/terraform-provider-azurerm/internal/common"
)

type Client struct {
RecommendationsClient *getrecommendations.GetRecommendationsClient
SuppressionsClient *suppressions.SuppressionsClient
}

func NewClient(o *common.ClientOptions) (*Client, error) {
recommendationsClient, err := getrecommendations.NewGetRecommendationsClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Recommendations client: %+v", err)
return nil, fmt.Errorf("building recommendations client: %+v", err)
}
o.Configure(recommendationsClient.Client, o.Authorizers.ResourceManager)

suppressionsClient, err := suppressions.NewSuppressionsClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building suppressions client: %+v", err)
}
o.Configure(suppressionsClient.Client, o.Authorizers.ResourceManager)

return &Client{
RecommendationsClient: recommendationsClient,
SuppressionsClient: suppressionsClient,
}, nil
}
17 changes: 16 additions & 1 deletion internal/services/advisor/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (

type Registration struct{}

var _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{}
var (
_ sdk.TypedServiceRegistrationWithAGitHubLabel = Registration{}
_ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{}
)

func (r Registration) AssociatedGitHubLabel() string {
return "service/advisor"
Expand Down Expand Up @@ -39,3 +42,15 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource {
func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
return map[string]*pluginsdk.Resource{}
}

// DataSources returns a list of Data Sources supported by this Service
func (r Registration) DataSources() []sdk.DataSource {
return []sdk.DataSource{}
}

// Resources returns a list of Resources supported by this Service
func (r Registration) Resources() []sdk.Resource {
return []sdk.Resource{
AdvisorSuppressionResource{},
}
}
19 changes: 19 additions & 0 deletions internal/services/advisor/validate/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validate

import (
"fmt"
"regexp"
)

func Duration(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)

if !regexp.MustCompile(`^(?:[0-9]{1,2}:)?[0-9]{2}:[0-9]{2}:[0-9]{2}$`).Match([]byte(value)) {
errors = append(errors, fmt.Errorf("%q must be in format DD:HH:MM:SS. If DD is 00, it has to be omit", k))
}

return warnings, errors
}
Loading

0 comments on commit ccec080

Please sign in to comment.