From 30066eebb03acdacf8d64d949dd1ba05d49ce10a Mon Sep 17 00:00:00 2001 From: Jiawei Tao Date: Wed, 25 Dec 2024 15:23:09 +0800 Subject: [PATCH 1/3] New data source: azurerm_dynatrace_monitor --- .../dynatrace_monitors_data_source.go | 217 ++++++++++++++++++ .../dynatrace_monitors_data_source_test.go | 33 +++ .../dynatrace_monitors_resource_test.go | 12 +- internal/services/dynatrace/helper.go | 26 +++ internal/services/dynatrace/registration.go | 4 +- .../docs/d/dynatrace_monitors.html.markdown | 87 +++++++ 6 files changed, 372 insertions(+), 7 deletions(-) create mode 100644 internal/services/dynatrace/dynatrace_monitors_data_source.go create mode 100644 internal/services/dynatrace/dynatrace_monitors_data_source_test.go create mode 100644 website/docs/d/dynatrace_monitors.html.markdown diff --git a/internal/services/dynatrace/dynatrace_monitors_data_source.go b/internal/services/dynatrace/dynatrace_monitors_data_source.go new file mode 100644 index 000000000000..d4d9d86e623f --- /dev/null +++ b/internal/services/dynatrace/dynatrace_monitors_data_source.go @@ -0,0 +1,217 @@ +package dynatrace + +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-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" + "github.com/hashicorp/go-azure-sdk/resource-manager/dynatrace/2023-04-27/monitors" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "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" +) + +type MonitorsDataSource struct{} + +type MonitorsDataSourceModel struct { + Name string `tfschema:"name"` + ResourceGroup string `tfschema:"resource_group_name"` + Location string `tfschema:"location"` + MonitoringStatus bool `tfschema:"monitoring_enabled"` + MarketplaceSubscriptionStatus string `tfschema:"marketplace_subscription"` + Identity []identity.ModelSystemAssigned `tfschema:"identity"` + EnvironmentProperties []EnvironmentProperties `tfschema:"environment_properties"` + PlanData []PlanData `tfschema:"plan"` + UserInfo []UserInfo `tfschema:"user"` + Tags map[string]string `tfschema:"tags"` +} + +type EnvironmentProperties struct { + EnvironmentInfo []EnvironmentInfo `tfschema:"environment_info"` +} + +type EnvironmentInfo struct { + EnvironmentId string `tfschema:"environment_id"` +} + +var _ sdk.DataSource = MonitorsDataSource{} + +func (d MonitorsDataSource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "resource_group_name": commonschema.ResourceGroupNameForDataSource(), + } +} + +func (d MonitorsDataSource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "location": commonschema.LocationComputed(), + + "monitoring_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "marketplace_subscription": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "identity": commonschema.SystemAssignedIdentityComputed(), + + "plan": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "billing_cycle": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "plan": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "usage_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "effective_date": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "user": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "country": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "email": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "first_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "last_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "phone_number": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "environment_properties": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "environment_info": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "environment_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + + "tags": tags.SchemaDataSource(), + } +} + +func (d MonitorsDataSource) ModelObject() interface{} { + return &MonitorsDataSourceModel{} +} + +func (d MonitorsDataSource) ResourceType() string { + return "azurerm_dynatrace_monitor" +} + +func (d MonitorsDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Dynatrace.MonitorsClient + subscriptionId := metadata.Client.Account.SubscriptionId + + var monitor MonitorsDataSourceModel + if err := metadata.Decode(&monitor); err != nil { + return err + } + id := monitors.NewMonitorID(subscriptionId, monitor.ResourceGroup, monitor.Name) + + existing, err := client.Get(ctx, id) + if err != nil { + if response.WasNotFound(existing.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("reading %s: %+v", id, err) + } + + if model := existing.Model; model != nil { + props := model.Properties + identityProps, err := flattenDynatraceIdentity(model.Identity) + if err != nil { + return fmt.Errorf("flattening identity: %+v", err) + } + monitoringStatus := true + if pointer.From(props.MonitoringStatus) == monitors.MonitoringStatusDisabled { + monitoringStatus = false + } + + monitorResource := MonitorsDataSourceModel{ + Name: id.MonitorName, + ResourceGroup: id.ResourceGroupName, + Location: model.Location, + MonitoringStatus: monitoringStatus, + MarketplaceSubscriptionStatus: string(pointer.From(props.MarketplaceSubscriptionStatus)), + Identity: identityProps, + EnvironmentProperties: FlattenDynatraceEnvironmentProperties(props.DynatraceEnvironmentProperties), + PlanData: FlattenDynatracePlanData(props.PlanData), + UserInfo: FlattenDynatraceUserInfo(metadata.ResourceData.Get("user").([]interface{})), + } + if model.Tags != nil { + monitorResource.Tags = pointer.From(model.Tags) + } + return metadata.Encode(&monitorResource) + } + return nil + }, + } +} diff --git a/internal/services/dynatrace/dynatrace_monitors_data_source_test.go b/internal/services/dynatrace/dynatrace_monitors_data_source_test.go new file mode 100644 index 000000000000..4e5a36af14b5 --- /dev/null +++ b/internal/services/dynatrace/dynatrace_monitors_data_source_test.go @@ -0,0 +1,33 @@ +package dynatrace_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" +) + +type MonitorsDataSource struct{} + +func TestAccDynatraceMonitorsDataSource_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_dynatrace_monitor", "test") + r := MonitorsDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc(), + }, + }) +} + +func (d MonitorsDataSource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +data "azurerm_dynatrace_monitor" "test" { + name = azurerm_dynatrace_monitor.test.name + resource_group_name = azurerm_resource_group.test.name +} +`, MonitorsResource{}.basic(data)) +} diff --git a/internal/services/dynatrace/dynatrace_monitors_resource_test.go b/internal/services/dynatrace/dynatrace_monitors_resource_test.go index 5e4c31e98765..10d786fcf873 100644 --- a/internal/services/dynatrace/dynatrace_monitors_resource_test.go +++ b/internal/services/dynatrace/dynatrace_monitors_resource_test.go @@ -155,11 +155,11 @@ resource "azurerm_dynatrace_monitor" "test" { } user { - first_name = "%s" - last_name = "%s" - email = "%s" - phone_number = "%s" - country = "%s" + first_name = "Alice" + last_name = "Bobab" + email = "agarwald@microsoft.com" + phone_number = "123456" + country = "westus" } plan { @@ -172,7 +172,7 @@ resource "azurerm_dynatrace_monitor" "test" { environment = "Dev" } } -`, template, data.RandomInteger, r.dynatraceInfo.UserFirstName, r.dynatraceInfo.UserLastName, r.dynatraceInfo.UserEmail, r.dynatraceInfo.UserPhoneNumber, r.dynatraceInfo.UserCountry) +`, template, data.RandomInteger) } func (r MonitorsResource) updated(data acceptance.TestData) string { diff --git a/internal/services/dynatrace/helper.go b/internal/services/dynatrace/helper.go index 951ec681e0fd..68b08723f6bf 100644 --- a/internal/services/dynatrace/helper.go +++ b/internal/services/dynatrace/helper.go @@ -36,6 +36,32 @@ func ExpandDynatraceUserInfo(input []UserInfo) *monitors.UserInfo { }) } +func FlattenDynatraceEnvironmentProperties(input *monitors.DynatraceEnvironmentProperties) []EnvironmentProperties { + if input == nil { + return []EnvironmentProperties{} + } + + environmentInfo := FlattenDynatraceEnvironmentInfo(input.EnvironmentInfo) + + return []EnvironmentProperties{ + { + EnvironmentInfo: environmentInfo, + }, + } +} + +func FlattenDynatraceEnvironmentInfo(input *monitors.EnvironmentInfo) []EnvironmentInfo { + if input == nil { + return []EnvironmentInfo{} + } + + return []EnvironmentInfo{ + { + EnvironmentId: pointer.From(input.EnvironmentId), + }, + } +} + func FlattenDynatracePlanData(input *monitors.PlanData) []PlanData { if input == nil { return []PlanData{} diff --git a/internal/services/dynatrace/registration.go b/internal/services/dynatrace/registration.go index 001d19bbde01..9b1a5ff2983a 100644 --- a/internal/services/dynatrace/registration.go +++ b/internal/services/dynatrace/registration.go @@ -18,7 +18,9 @@ func (r Registration) Name() string { } func (r Registration) DataSources() []sdk.DataSource { - return []sdk.DataSource{} + return []sdk.DataSource{ + MonitorsDataSource{}, + } } func (r Registration) Resources() []sdk.Resource { diff --git a/website/docs/d/dynatrace_monitors.html.markdown b/website/docs/d/dynatrace_monitors.html.markdown new file mode 100644 index 000000000000..9b39a5708d0e --- /dev/null +++ b/website/docs/d/dynatrace_monitors.html.markdown @@ -0,0 +1,87 @@ +--- +subcategory: "Dynatrace" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_dynatrace_monitor" +description: |- + Gets information about an existing Dynatrace monitors. +--- + +# Data Source: azurerm_dynatrace_monitor. + +Use this data source to access information about an existing Dynatrace Monitor. + +## Example Usage + +```hcl + +data "azurerm_dynatrace_monitor" "example" { + name = "example-dynatracemonitor" + resource_group_name = "example-resources" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) Name of the Dynatrace monitor. + +* `resource_group_name` - (Required) The name of the Resource Group where the Dynatrace monitor should exist. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Dynatrace monitor. + +* `location` - The Azure Region where the Dynatrace monitor should exist. + +* `identity` - The kind of managed identity assigned to this resource. A `identity` block as defined below. + +* `marketplace_subscription` - Flag specifying the Marketplace Subscription Status of the resource. If payment is not made in time, the resource will go in Suspended state. + +* `plan` - Billing plan information. A `plan` block as defined below. + +* `user` - User's information. A `user` block as defined below. + +* `monitoring_enabled` - Flag specifying if the resource monitoring is enabled or disabled. + +* `tags` - A mapping of tags to assign to the resource. + +--- + +A `identity` block supports the following: + +* `type` - The type of identity used for the resource. Only possible value is `SystemAssigned`. + +--- + +A `plan` block supports the following: + +* `billing_cycle` - Different billing cycles. + +* `effective_date` - Date when plan was applied. + +* `plan` - Plan id as published by Dynatrace. + +* `usage_type` - Different usage type. + +--- + +A `user` block supports the following: + +* `country` - Country of the user. + +* `email` - Email of the user used by Dynatrace for contacting them if needed. + +* `first_name` - First name of the user. + +* `last_name` - Last name of the user. + +* `phone_number` - phone number of the user by Dynatrace for contacting them if needed. + +## 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 Dynatrace monitor. From 36e729f7d4353994afd1d41764bfc778e50428fe Mon Sep 17 00:00:00 2001 From: Jiawei Tao Date: Wed, 25 Dec 2024 15:35:58 +0800 Subject: [PATCH 2/3] Remove deprecated method --- internal/services/dynatrace/dynatrace_monitors_data_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/dynatrace/dynatrace_monitors_data_source.go b/internal/services/dynatrace/dynatrace_monitors_data_source.go index d4d9d86e623f..38b0b9678ee9 100644 --- a/internal/services/dynatrace/dynatrace_monitors_data_source.go +++ b/internal/services/dynatrace/dynatrace_monitors_data_source.go @@ -179,7 +179,7 @@ func (d MonitorsDataSource) Read() sdk.ResourceFunc { existing, err := client.Get(ctx, id) if err != nil { if response.WasNotFound(existing.HttpResponse) { - return metadata.MarkAsGone(id) + return fmt.Errorf("%s was not found", id) } return fmt.Errorf("reading %s: %+v", id, err) } From 2e1095c22385550704f615cfe032dee2e0000084 Mon Sep 17 00:00:00 2001 From: Jiawei Tao Date: Tue, 14 Jan 2025 13:56:49 +0800 Subject: [PATCH 3/3] address comments --- .../dynatrace/dynatrace_monitors_data_source.go | 2 +- .../dynatrace/dynatrace_monitors_resource.go | 2 +- .../dynatrace/dynatrace_monitors_resource_test.go | 12 ++++++------ internal/services/dynatrace/helper.go | 15 +++++++-------- website/docs/d/dynatrace_monitors.html.markdown | 8 ++++---- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/services/dynatrace/dynatrace_monitors_data_source.go b/internal/services/dynatrace/dynatrace_monitors_data_source.go index 38b0b9678ee9..d7f2196599a5 100644 --- a/internal/services/dynatrace/dynatrace_monitors_data_source.go +++ b/internal/services/dynatrace/dynatrace_monitors_data_source.go @@ -204,7 +204,7 @@ func (d MonitorsDataSource) Read() sdk.ResourceFunc { Identity: identityProps, EnvironmentProperties: FlattenDynatraceEnvironmentProperties(props.DynatraceEnvironmentProperties), PlanData: FlattenDynatracePlanData(props.PlanData), - UserInfo: FlattenDynatraceUserInfo(metadata.ResourceData.Get("user").([]interface{})), + UserInfo: FlattenDynatraceUserInfo(props.UserInfo), } if model.Tags != nil { monitorResource.Tags = pointer.From(model.Tags) diff --git a/internal/services/dynatrace/dynatrace_monitors_resource.go b/internal/services/dynatrace/dynatrace_monitors_resource.go index 4298172e9238..66a89b8aac91 100644 --- a/internal/services/dynatrace/dynatrace_monitors_resource.go +++ b/internal/services/dynatrace/dynatrace_monitors_resource.go @@ -277,7 +277,7 @@ func (r MonitorsResource) Read() sdk.ResourceFunc { MarketplaceSubscriptionStatus: string(*props.MarketplaceSubscriptionStatus), Identity: identityProps, PlanData: FlattenDynatracePlanData(props.PlanData), - UserInfo: FlattenDynatraceUserInfo(metadata.ResourceData.Get("user").([]interface{})), + UserInfo: FlattenDynatraceUserInfo(props.UserInfo), } if model.Tags != nil { diff --git a/internal/services/dynatrace/dynatrace_monitors_resource_test.go b/internal/services/dynatrace/dynatrace_monitors_resource_test.go index 10d786fcf873..5e4c31e98765 100644 --- a/internal/services/dynatrace/dynatrace_monitors_resource_test.go +++ b/internal/services/dynatrace/dynatrace_monitors_resource_test.go @@ -155,11 +155,11 @@ resource "azurerm_dynatrace_monitor" "test" { } user { - first_name = "Alice" - last_name = "Bobab" - email = "agarwald@microsoft.com" - phone_number = "123456" - country = "westus" + first_name = "%s" + last_name = "%s" + email = "%s" + phone_number = "%s" + country = "%s" } plan { @@ -172,7 +172,7 @@ resource "azurerm_dynatrace_monitor" "test" { environment = "Dev" } } -`, template, data.RandomInteger) +`, template, data.RandomInteger, r.dynatraceInfo.UserFirstName, r.dynatraceInfo.UserLastName, r.dynatraceInfo.UserEmail, r.dynatraceInfo.UserPhoneNumber, r.dynatraceInfo.UserCountry) } func (r MonitorsResource) updated(data acceptance.TestData) string { diff --git a/internal/services/dynatrace/helper.go b/internal/services/dynatrace/helper.go index 68b08723f6bf..7af87dd5970a 100644 --- a/internal/services/dynatrace/helper.go +++ b/internal/services/dynatrace/helper.go @@ -98,19 +98,18 @@ func FlattenDynatracePlanData(input *monitors.PlanData) []PlanData { } } -func FlattenDynatraceUserInfo(input []interface{}) []UserInfo { - if len(input) == 0 { +func FlattenDynatraceUserInfo(input *monitors.UserInfo) []UserInfo { + if input == nil { return []UserInfo{} } - v := input[0].(map[string]interface{}) return []UserInfo{ { - Country: v["country"].(string), - EmailAddress: v["email"].(string), - FirstName: v["first_name"].(string), - LastName: v["last_name"].(string), - PhoneNumber: v["phone_number"].(string), + Country: pointer.From(input.Country), + EmailAddress: pointer.From(input.EmailAddress), + FirstName: pointer.From(input.FirstName), + LastName: pointer.From(input.LastName), + PhoneNumber: pointer.From(input.PhoneNumber), }, } } diff --git a/website/docs/d/dynatrace_monitors.html.markdown b/website/docs/d/dynatrace_monitors.html.markdown index 9b39a5708d0e..d88854b5831e 100644 --- a/website/docs/d/dynatrace_monitors.html.markdown +++ b/website/docs/d/dynatrace_monitors.html.markdown @@ -50,13 +50,13 @@ In addition to the Arguments listed above - the following Attributes are exporte --- -A `identity` block supports the following: +An `identity` block exports the following: -* `type` - The type of identity used for the resource. Only possible value is `SystemAssigned`. +* `type` - The type of identity used for the resource. --- -A `plan` block supports the following: +A `plan` block exports the following: * `billing_cycle` - Different billing cycles. @@ -68,7 +68,7 @@ A `plan` block supports the following: --- -A `user` block supports the following: +A `user` block exports the following: * `country` - Country of the user.