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

azurerm_new_relic_monitor - support monitored_subscription property #28134

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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
support monitored_subscription
teowa committed Nov 28, 2024

Verified

This commit was signed with the committer’s verified signature.
teowa Tao
commit d7978eb733cb19e3ad615751f9e6244f24bc9e3e
18 changes: 14 additions & 4 deletions internal/services/newrelic/client/client.go
Original file line number Diff line number Diff line change
@@ -6,14 +6,16 @@ package client
import (
"fmt"

"github.com/hashicorp/go-azure-sdk/resource-manager/newrelic/2024-03-01/monitoredsubscriptions"
"github.com/hashicorp/go-azure-sdk/resource-manager/newrelic/2024-03-01/monitors"
"github.com/hashicorp/go-azure-sdk/resource-manager/newrelic/2024-03-01/tagrules"
"github.com/hashicorp/terraform-provider-azurerm/internal/common"
)

type Client struct {
MonitorsClient *monitors.MonitorsClient
TagRulesClient *tagrules.TagRulesClient
MonitorsClient *monitors.MonitorsClient
MonitoredSubscriptionsClient *monitoredsubscriptions.MonitoredSubscriptionsClient
TagRulesClient *tagrules.TagRulesClient
}

func NewClient(o *common.ClientOptions) (*Client, error) {
@@ -24,6 +26,13 @@ func NewClient(o *common.ClientOptions) (*Client, error) {

o.Configure(monitorsClient.Client, o.Authorizers.ResourceManager)

monitoredSubscriptionsClient, err := monitoredsubscriptions.NewMonitoredSubscriptionsClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Monitored Subscriptions client: %+v", err)
}

o.Configure(monitoredSubscriptionsClient.Client, o.Authorizers.ResourceManager)

tagRulesClient, err := tagrules.NewTagRulesClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building TagRules client: %+v", err)
@@ -32,7 +41,8 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
o.Configure(tagRulesClient.Client, o.Authorizers.ResourceManager)

return &Client{
MonitorsClient: monitorsClient,
TagRulesClient: tagRulesClient,
MonitorsClient: monitorsClient,
MonitoredSubscriptionsClient: monitoredSubscriptionsClient,
TagRulesClient: tagRulesClient,
}, nil
}
196 changes: 184 additions & 12 deletions internal/services/newrelic/new_relic_monitor_resource.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ package newrelic
import (
"context"
"fmt"
"log"
"regexp"
"strings"
"time"
@@ -15,6 +16,7 @@ import (
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-sdk/resource-manager/newrelic/2024-03-01/monitoredsubscriptions"
"github.com/hashicorp/go-azure-sdk/resource-manager/newrelic/2024-03-01/monitors"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
@@ -27,17 +29,22 @@ const (
)

type NewRelicMonitorModel struct {
Name string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
AccountCreationSource monitors.AccountCreationSource `tfschema:"account_creation_source"`
AccountId string `tfschema:"account_id"`
IngestionKey string `tfschema:"ingestion_key"`
Location string `tfschema:"location"`
OrganizationId string `tfschema:"organization_id"`
OrgCreationSource monitors.OrgCreationSource `tfschema:"org_creation_source"`
PlanData []PlanDataModel `tfschema:"plan"`
UserId string `tfschema:"user_id"`
UserInfo []UserInfoModel `tfschema:"user"`
Name string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
AccountCreationSource monitors.AccountCreationSource `tfschema:"account_creation_source"`
AccountId string `tfschema:"account_id"`
IngestionKey string `tfschema:"ingestion_key"`
Location string `tfschema:"location"`
MonitoredSubscription []NewRelicMonitoredSubscription `tfschema:"monitored_subscription"`
OrganizationId string `tfschema:"organization_id"`
OrgCreationSource monitors.OrgCreationSource `tfschema:"org_creation_source"`
PlanData []PlanDataModel `tfschema:"plan"`
UserId string `tfschema:"user_id"`
UserInfo []UserInfoModel `tfschema:"user"`
}

type NewRelicMonitoredSubscription struct {
SubscriptionId string `tfschema:"subscription_id"`
}

type PlanDataModel struct {
@@ -56,7 +63,10 @@ type UserInfoModel struct {

type NewRelicMonitorResource struct{}

var _ sdk.Resource = NewRelicMonitorResource{}
var (
_ sdk.Resource = NewRelicMonitorResource{}
_ sdk.ResourceWithUpdate = NewRelicMonitorResource{}
)

func (r NewRelicMonitorResource) ResourceType() string {
return "azurerm_new_relic_monitor"
@@ -205,6 +215,21 @@ func (r NewRelicMonitorResource) Arguments() map[string]*pluginsdk.Schema {
ValidateFunc: validation.StringIsNotEmpty,
},

"monitored_subscription": {
Type: pluginsdk.TypeList,
Optional: true,
MinItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"subscription_id": {
Copy link
Contributor Author

@teowa teowa Dec 2, 2024

Choose a reason for hiding this comment

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

This is not flattened because there are other properties in the monitoredSubscription but not supported by API for now and they might be supported in the future.

Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.IsUUID,
},
},
},
},

"organization_id": {
Type: pluginsdk.TypeString,
Optional: true,
@@ -283,6 +308,23 @@ func (r NewRelicMonitorResource) Create() sdk.ResourceFunc {
}

metadata.SetID(id)

monitoredSubscriptionsProperties := monitoredsubscriptions.MonitoredSubscriptionProperties{
Properties: &monitoredsubscriptions.SubscriptionList{
MonitoredSubscriptionList: expandMonitorSubscriptionList(model.MonitoredSubscription),
},
}

monitoredSubscriptionsClient := metadata.Client.NewRelic.MonitoredSubscriptionsClient
monitorId := monitoredsubscriptions.NewMonitorID(id.SubscriptionId, id.ResourceGroupName, id.MonitorName)
if err := metadata.Client.NewRelic.MonitoredSubscriptionsClient.CreateOrUpdateThenPoll(ctx, monitorId, monitoredSubscriptionsProperties); err != nil {
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 still need to create this resource if monitored_subscription is not configured?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed, now it will only create if monitored_subscription is not configured

return fmt.Errorf("creating NewRelic Monitored Subscriptions of %s: %+v", id, err)
}

if err := resourceMonitoredSubscriptionsWaitForAvailable(ctx, monitoredSubscriptionsClient, monitorId, model.MonitoredSubscription); err != nil {
return fmt.Errorf("waiting for the NewRelic Monitored Subscriptions of %s to become available: %+v", id, err)
}

return nil
},
}
@@ -352,11 +394,58 @@ func (r NewRelicMonitorResource) Read() sdk.ResourceFunc {
state.UserInfo = flattenUserInfoModel(properties.UserInfo)
}

monitorId := monitoredsubscriptions.NewMonitorID(id.SubscriptionId, id.ResourceGroupName, id.MonitorName)
monitoredSubscriptionResp, err := metadata.Client.NewRelic.MonitoredSubscriptionsClient.Get(ctx, monitorId)
if err != nil {
return fmt.Errorf("retrieving NewRelic Monitored Subscriptions of %s: %+v", *id, err)
}
if monitoredSubscriptionResp.Model != nil && monitoredSubscriptionResp.Model.Properties != nil {
state.MonitoredSubscription = flattenMonitorSubscriptionList(monitoredSubscriptionResp.Model.Properties.MonitoredSubscriptionList)
}

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

func (r NewRelicMonitorResource) Update() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
id, err := monitors.ParseMonitorID(metadata.ResourceData.Id())
if err != nil {
return err
}

var originalModel NewRelicMonitorModel
if err = metadata.Decode(&originalModel); err != nil {
return fmt.Errorf("decoding: %+v", err)
}

if metadata.ResourceData.HasChange("monitored_subscription") {
monitoredSubscriptionsClient := metadata.Client.NewRelic.MonitoredSubscriptionsClient

monitoredSubscriptionsProperties := monitoredsubscriptions.MonitoredSubscriptionProperties{
Properties: &monitoredsubscriptions.SubscriptionList{
MonitoredSubscriptionList: expandMonitorSubscriptionList(originalModel.MonitoredSubscription),
},
}

monitorId := monitoredsubscriptions.NewMonitorID(id.SubscriptionId, id.ResourceGroupName, id.MonitorName)
if err := metadata.Client.NewRelic.MonitoredSubscriptionsClient.CreateOrUpdateThenPoll(ctx, monitorId, monitoredSubscriptionsProperties); err != nil {
return fmt.Errorf("creating NewRelic Monitored Subscriptions for %s: %+v", id, err)
}

if err := resourceMonitoredSubscriptionsWaitForAvailable(ctx, monitoredSubscriptionsClient, monitorId, originalModel.MonitoredSubscription); err != nil {
return fmt.Errorf("waiting for the NewRelic Monitored Subscriptions for %s to become available: %+v", id, err)
}
}

return nil
},
}
}

func (r NewRelicMonitorResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
@@ -537,3 +626,86 @@ func flattenUserInfoModel(input *monitors.UserInfo) []UserInfoModel {

return append(outputList, output)
}

func expandMonitorSubscriptionList(input []NewRelicMonitoredSubscription) *[]monitoredsubscriptions.MonitoredSubscription {
results := make([]monitoredsubscriptions.MonitoredSubscription, 0)
if len(input) == 0 {
return &results
}

for _, v := range input {
results = append(results, monitoredsubscriptions.MonitoredSubscription{
SubscriptionId: pointer.To(v.SubscriptionId),
})
}

return &results
}

func flattenMonitorSubscriptionList(input *[]monitoredsubscriptions.MonitoredSubscription) []NewRelicMonitoredSubscription {
results := make([]NewRelicMonitoredSubscription, 0)
if input == nil {
return results
}

for _, v := range *input {
results = append(results, NewRelicMonitoredSubscription{
// The returned subscription UUID is in upper case
SubscriptionId: strings.ToLower(pointer.From(v.SubscriptionId)),
})
}

return results
}

func resourceMonitoredSubscriptionsWaitForAvailable(ctx context.Context, client *monitoredsubscriptions.MonitoredSubscriptionsClient, id monitoredsubscriptions.MonitorId, monitoredSubscriptions []NewRelicMonitoredSubscription) error {
deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("internal error: context had no deadline")
}
state := &pluginsdk.StateChangeConf{
MinTimeout: 5 * time.Second,
ContinuousTargetOccurence: 2,
Pending: []string{"Unavailable"},
Target: []string{"Available"},
Refresh: resourceMonitoredSubscriptionsRefresh(ctx, client, id, monitoredSubscriptions),
Timeout: time.Until(deadline),
}

if _, err := state.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for the %s to become available: %+v", id, err)
}

return nil
}

func resourceMonitoredSubscriptionsRefresh(ctx context.Context, client *monitoredsubscriptions.MonitoredSubscriptionsClient, id monitoredsubscriptions.MonitorId, monitoredSubscriptions []NewRelicMonitoredSubscription) pluginsdk.StateRefreshFunc {
return func() (interface{}, string, error) {
log.Printf("[DEBUG] Checking to see if NewRelic Monitored Subscriptions of %s is available ..", id)

resp, err := client.Get(ctx, id)
Copy link
Contributor

Choose a reason for hiding this comment

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

will it return error if the resource is deleted?

Copy link
Contributor Author

@teowa teowa Dec 3, 2024

Choose a reason for hiding this comment

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

updated, now the waiting is removed as tested it is not necessary

if err != nil {
return resp, "Error", fmt.Errorf("retrieving NewRelic Monitored Subscriptions of %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.

The id is created by monitoredsubscriptions , not monitors, other logs also have the same problem

Copy link
Contributor Author

@teowa teowa Dec 2, 2024

Choose a reason for hiding this comment

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

updated to use monitors.MonitorId for error message

}

if resp.Model == nil || resp.Model.Properties == nil || resp.Model.Properties.MonitoredSubscriptionList == nil {
return resp, "Error", fmt.Errorf("unexpected nil model of NewRelic Monitored Subscriptions of %s", id)
}

matchCount := 0
for _, v := range monitoredSubscriptions {
for _, u := range *resp.Model.Properties.MonitoredSubscriptionList {
if u.SubscriptionId != nil && strings.EqualFold(v.SubscriptionId, *u.SubscriptionId) {
matchCount++
break
}
}
}

if len(monitoredSubscriptions) != len(*resp.Model.Properties.MonitoredSubscriptionList) || matchCount != len(monitoredSubscriptions) {
return resp, "Unavailable", nil
}

return resp, "Available", nil
}
}
78 changes: 78 additions & 0 deletions internal/services/newrelic/new_relic_monitor_resource_test.go
Original file line number Diff line number Diff line change
@@ -98,6 +98,52 @@ func TestAccNewRelicMonitor_identity(t *testing.T) {
})
}

func TestAccNewRelicMonitor_monitoredSubscription(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_new_relic_monitor", "test")
r := NewRelicMonitorResource{}
effectiveDate := time.Now().Add(time.Hour * 7).Format(time.RFC3339)
email := "7021d2ab-5eed-45e5-9582-d1f8a90b4477@example.com"
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.monitoredSubscription(data, effectiveDate, email),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccNewRelicMonitor_monitoredSubscriptionUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_new_relic_monitor", "test")
r := NewRelicMonitorResource{}
effectiveDate := time.Now().Add(time.Hour * 7).Format(time.RFC3339)
email := "4c82ab21-3697-4578-bd51-6428867ea12a@example.com"
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data, effectiveDate, email),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.monitoredSubscription(data, effectiveDate, email),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basic(data, effectiveDate, email),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (r NewRelicMonitorResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := monitors.ParseMonitorID(state.ID)
if err != nil {
@@ -247,3 +293,35 @@ resource "azurerm_role_assignment" "test" {
}
`, template, data.RandomInteger, data.Locations.Primary, effectiveDate, email)
}

func (r NewRelicMonitorResource) monitoredSubscription(data acceptance.TestData, effectiveDate string, email string) string {
template := r.template(data)
return fmt.Sprintf(`
%s

data "azurerm_subscription" "test" {
subscription_id = "%s"
}

resource "azurerm_new_relic_monitor" "test" {
name = "acctest-nrm-%d"
resource_group_name = azurerm_resource_group.test.name
location = "%s"
plan {
effective_date = "%s"
}
user {
email = "%s"
first_name = "first"
last_name = "last"
phone_number = "123456"
}
identity {
type = "SystemAssigned"
}
monitored_subscription {
subscription_id = data.azurerm_subscription.test.subscription_id
}
}
`, template, data.Subscriptions.Secondary, data.RandomInteger, data.Locations.Primary, effectiveDate, email)
}
Loading