diff --git a/.changelog/47466.txt b/.changelog/47466.txt new file mode 100644 index 000000000000..c42d8a7ad05b --- /dev/null +++ b/.changelog/47466.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_prometheus_scraper_logging_configuration +``` \ No newline at end of file diff --git a/.ci/semgrep/framework/flex.yml b/.ci/semgrep/framework/flex.yml index 7685660ba832..6089011a8ee5 100644 --- a/.ci/semgrep/framework/flex.yml +++ b/.ci/semgrep/framework/flex.yml @@ -39,6 +39,7 @@ rules: - "/internal/service/ssoadmin/trusted_token_issuer.go" - "/internal/service/verifiedpermissions/schema.go" - "/internal/service/bedrockagentcore/gateway_target.go" + - "/internal/service/amp/scraper_logging_configuration.go" patterns: - pattern: func $FUNC(ctx context.Context, ...) - metavariable-comparison: diff --git a/internal/service/amp/exports_test.go b/internal/service/amp/exports_test.go index a39aeefe59fb..b06b56263b00 100644 --- a/internal/service/amp/exports_test.go +++ b/internal/service/amp/exports_test.go @@ -5,18 +5,20 @@ package amp // Exports for use in tests only. var ( - ResourceAlertManagerDefinition = resourceAlertManagerDefinition - ResourceQueryLoggingConfiguration = newQueryLoggingConfigurationResource - ResourceRuleGroupNamespace = resourceRuleGroupNamespace - ResourceScraper = newScraperResource - ResourceWorkspace = resourceWorkspace - ResourceResourcePolicy = newResourcePolicyResource + ResourceAlertManagerDefinition = resourceAlertManagerDefinition + ResourceQueryLoggingConfiguration = newQueryLoggingConfigurationResource + ResourceRuleGroupNamespace = resourceRuleGroupNamespace + ResourceScraper = newScraperResource + ResourceScraperLoggingConfiguration = newScraperLoggingConfigurationResource + ResourceWorkspace = resourceWorkspace + ResourceResourcePolicy = newResourcePolicyResource - FindAlertManagerDefinitionByID = findAlertManagerDefinitionByID - FindQueryLoggingConfigurationByID = findQueryLoggingConfigurationByID - FindResourcePolicyByWorkspaceID = findResourcePolicyByWorkspaceID - FindRuleGroupNamespaceByARN = findRuleGroupNamespaceByARN - FindScraperByID = findScraperByID - FindWorkspaceByID = findWorkspaceByID - FindWorkspaceConfigurationByID = findWorkspaceConfigurationByID + FindAlertManagerDefinitionByID = findAlertManagerDefinitionByID + FindQueryLoggingConfigurationByID = findQueryLoggingConfigurationByID + FindResourcePolicyByWorkspaceID = findResourcePolicyByWorkspaceID + FindRuleGroupNamespaceByARN = findRuleGroupNamespaceByARN + FindScraperByID = findScraperByID + FindScraperLoggingConfigurationByID = findScraperLoggingConfigurationByID + FindWorkspaceByID = findWorkspaceByID + FindWorkspaceConfigurationByID = findWorkspaceConfigurationByID ) diff --git a/internal/service/amp/scraper_logging_configuration.go b/internal/service/amp/scraper_logging_configuration.go new file mode 100644 index 000000000000..6de7c4ae7664 --- /dev/null +++ b/internal/service/amp/scraper_logging_configuration.go @@ -0,0 +1,487 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package amp + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/amp" + awstypes "github.com/aws/aws-sdk-go-v2/service/amp/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_prometheus_scraper_logging_configuration", name="ScraperLoggingConfiguration") +// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/amp;amp.DescribeScraperLoggingConfigurationOutput") +func newScraperLoggingConfigurationResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &scraperLoggingConfigurationResource{} + + r.SetDefaultCreateTimeout(5 * time.Minute) + r.SetDefaultUpdateTimeout(5 * time.Minute) + r.SetDefaultDeleteTimeout(5 * time.Minute) + + return r, nil +} + +type scraperLoggingConfigurationResource struct { + framework.ResourceWithModel[scraperLoggingConfigurationResourceModel] + framework.WithTimeouts +} + +func (r *scraperLoggingConfigurationResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "scraper_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "scraper_components": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[scraperComponentModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrType: schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ScraperComponentType](), + Required: true, + }, + "options": schema.MapAttribute{ + CustomType: fwtypes.MapOfStringType, + ElementType: types.StringType, + Optional: true, + }, + }, + }, + }, + "logging_destination": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[scraperLoggingDestinationModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + names.AttrCloudWatchLogs: schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[scraperCloudWatchLogDestinationModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "log_group_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile(`:\*$`), "ARN must end with `:*`"), + }, + }, + }, + }, + }, + }, + }, + }, + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *scraperLoggingConfigurationResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data scraperLoggingConfigurationResourceModel + smerr.AddEnrich(ctx, &response.Diagnostics, request.Plan.Get(ctx, &data)) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().AMPClient(ctx) + + scraperID := fwflex.StringValueFromFramework(ctx, data.ScraperID) + var input amp.UpdateScraperLoggingConfigurationInput + smerr.AddEnrich(ctx, &response.Diagnostics, fwflex.Expand(ctx, data, &input)) + if response.Diagnostics.HasError() { + return + } + input.ScraperComponents = expandScraperComponents(ctx, data.ScraperComponents) + + _, err := conn.UpdateScraperLoggingConfiguration(ctx, &input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("creating Prometheus Scraper Logging Configuration (%s)", scraperID), err.Error()) + + return + } + + if _, err := waitScraperLoggingConfigurationCreated(ctx, conn, scraperID, r.CreateTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Prometheus Scraper Logging Configuration (%s) create", scraperID), err.Error()) + + return + } + + smerr.AddEnrich(ctx, &response.Diagnostics, response.State.Set(ctx, data)) +} + +func (r *scraperLoggingConfigurationResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data scraperLoggingConfigurationResourceModel + smerr.AddEnrich(ctx, &response.Diagnostics, request.State.Get(ctx, &data)) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().AMPClient(ctx) + + scraperID := fwflex.StringValueFromFramework(ctx, data.ScraperID) + output, err := findScraperLoggingConfigurationByID(ctx, conn, scraperID) + + if retry.NotFound(err) { + smerr.AddOne(ctx, &response.Diagnostics, fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading Prometheus Scraper Logging Configuration (%s)", scraperID), err.Error()) + + return + } + + smerr.AddEnrich(ctx, &response.Diagnostics, r.flattenIntoModel(ctx, output, &data)) + if response.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &response.Diagnostics, response.State.Set(ctx, &data)) +} + +func (r *scraperLoggingConfigurationResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var new scraperLoggingConfigurationResourceModel + smerr.AddEnrich(ctx, &response.Diagnostics, request.Plan.Get(ctx, &new)) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().AMPClient(ctx) + + scraperID := fwflex.StringValueFromFramework(ctx, new.ScraperID) + var input amp.UpdateScraperLoggingConfigurationInput + smerr.AddEnrich(ctx, &response.Diagnostics, fwflex.Expand(ctx, new, &input)) + if response.Diagnostics.HasError() { + return + } + input.ScraperComponents = expandScraperComponents(ctx, new.ScraperComponents) + + _, err := conn.UpdateScraperLoggingConfiguration(ctx, &input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating Prometheus Scraper Logging Configuration (%s)", scraperID), err.Error()) + + return + } + + if _, err := waitScraperLoggingConfigurationUpdated(ctx, conn, scraperID, r.UpdateTimeout(ctx, new.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Prometheus Scraper Logging Configuration (%s) update", scraperID), err.Error()) + + return + } + + smerr.AddEnrich(ctx, &response.Diagnostics, response.State.Set(ctx, new)) +} + +func (r *scraperLoggingConfigurationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data scraperLoggingConfigurationResourceModel + smerr.AddEnrich(ctx, &response.Diagnostics, request.State.Get(ctx, &data)) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().AMPClient(ctx) + + scraperID := fwflex.StringValueFromFramework(ctx, data.ScraperID) + var input amp.DeleteScraperLoggingConfigurationInput + input.ScraperId = aws.String(scraperID) + input.ClientToken = aws.String(create.UniqueId(ctx)) + _, err := conn.DeleteScraperLoggingConfiguration(ctx, &input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting Prometheus Scraper Logging Configuration (%s)", scraperID), err.Error()) + + return + } + + if _, err := waitScraperLoggingConfigurationDeleted(ctx, conn, scraperID, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Prometheus Scraper Logging Configuration (%s) delete", scraperID), err.Error()) + + return + } +} + +func (r *scraperLoggingConfigurationResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("scraper_id"), request, response) +} + +// flattenIntoModel fills data from the API output, handling the autoflex-incompatible scraper_components field. +func (r *scraperLoggingConfigurationResource) flattenIntoModel(ctx context.Context, output *amp.DescribeScraperLoggingConfigurationOutput, data *scraperLoggingConfigurationResourceModel) diag.Diagnostics { + var diags diag.Diagnostics + + diags.Append(fwflex.Flatten(ctx, output, data)...) + if diags.HasError() { + return diags + } + + // Only flatten scraper_components if they were already in state (user-configured). + // The API returns default components even when none were configured by the user. + if !data.ScraperComponents.IsNull() && len(output.ScraperComponents) > 0 { + componentPtrs := make([]*scraperComponentModel, len(output.ScraperComponents)) + for i, c := range output.ScraperComponents { + component := scraperComponentModel{ + Type: fwtypes.StringEnumValue(c.Type), + } + + if c.Config != nil && c.Config.Options != nil { + elements := make(map[string]attr.Value, len(c.Config.Options)) + for k, v := range c.Config.Options { + elements[k] = basetypes.NewStringValue(v) + } + optionsMap, d := fwtypes.NewMapValueOf[basetypes.StringValue](ctx, elements) + diags.Append(d...) + if !diags.HasError() { + component.Options = optionsMap + } + } else { + component.Options = fwtypes.NewMapValueOfNull[basetypes.StringValue](ctx) + } + componentPtrs[i] = &component + } + + data.ScraperComponents = fwtypes.NewListNestedObjectValueOfSliceMust(ctx, componentPtrs) + } + + return diags +} + +func findScraperLoggingConfigurationByID(ctx context.Context, conn *amp.Client, id string) (*amp.DescribeScraperLoggingConfigurationOutput, error) { + var input amp.DescribeScraperLoggingConfigurationInput + input.ScraperId = aws.String(id) + + output, err := conn.DescribeScraperLoggingConfiguration(ctx, &input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Status == nil { + return nil, tfresource.NewEmptyResultError() + } + + return output, nil +} + +func statusScraperLoggingConfiguration(conn *amp.Client, id string) retry.StateRefreshFunc { + return func(ctx context.Context) (any, string, error) { + output, err := findScraperLoggingConfigurationByID(ctx, conn, id) + + if retry.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status.StatusCode), nil + } +} + +func waitScraperLoggingConfigurationCreated(ctx context.Context, conn *amp.Client, id string, timeout time.Duration) (*amp.DescribeScraperLoggingConfigurationOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ScraperLoggingConfigurationStatusCodeCreating, awstypes.ScraperLoggingConfigurationStatusCodeUpdating), + Target: enum.Slice(awstypes.ScraperLoggingConfigurationStatusCodeActive), + Refresh: statusScraperLoggingConfiguration(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*amp.DescribeScraperLoggingConfigurationOutput); ok { + retry.SetLastError(err, errors.New(aws.ToString(output.Status.StatusReason))) + + return output, err + } + + return nil, err +} + +func waitScraperLoggingConfigurationUpdated(ctx context.Context, conn *amp.Client, id string, timeout time.Duration) (*amp.DescribeScraperLoggingConfigurationOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ScraperLoggingConfigurationStatusCodeUpdating), + Target: enum.Slice(awstypes.ScraperLoggingConfigurationStatusCodeActive), + Refresh: statusScraperLoggingConfiguration(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*amp.DescribeScraperLoggingConfigurationOutput); ok { + retry.SetLastError(err, errors.New(aws.ToString(output.Status.StatusReason))) + + return output, err + } + + return nil, err +} + +func waitScraperLoggingConfigurationDeleted(ctx context.Context, conn *amp.Client, id string, timeout time.Duration) (*amp.DescribeScraperLoggingConfigurationOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ScraperLoggingConfigurationStatusCodeDeleting, awstypes.ScraperLoggingConfigurationStatusCodeActive), + Target: []string{}, + Refresh: statusScraperLoggingConfiguration(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*amp.DescribeScraperLoggingConfigurationOutput); ok { + retry.SetLastError(err, errors.New(aws.ToString(output.Status.StatusReason))) + + return output, err + } + + return nil, err +} + +type scraperLoggingConfigurationResourceModel struct { + framework.WithRegionModel + LoggingDestination fwtypes.ListNestedObjectValueOf[scraperLoggingDestinationModel] `tfsdk:"logging_destination"` + ScraperComponents fwtypes.ListNestedObjectValueOf[scraperComponentModel] `tfsdk:"scraper_components" autoflex:"-"` + ScraperID types.String `tfsdk:"scraper_id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type scraperLoggingDestinationModel struct { + CloudwatchLogs fwtypes.ListNestedObjectValueOf[scraperCloudWatchLogDestinationModel] `tfsdk:"cloudwatch_logs"` +} + +type scraperComponentModel struct { + Type fwtypes.StringEnum[awstypes.ScraperComponentType] `tfsdk:"type"` + Options fwtypes.MapOfString `tfsdk:"options"` +} + +type scraperCloudWatchLogDestinationModel struct { + LogGroupARN fwtypes.ARN `tfsdk:"log_group_arn"` +} + +var ( + _ fwflex.Expander = scraperLoggingDestinationModel{} + _ fwflex.Flattener = &scraperLoggingDestinationModel{} +) + +func (m scraperLoggingDestinationModel) Expand(ctx context.Context) (any, diag.Diagnostics) { + var diags diag.Diagnostics + + cwData, d := m.CloudwatchLogs.ToPtr(ctx) + diags.Append(d...) + if diags.HasError() { + return nil, diags + } + + if cwData == nil { + return nil, diags + } + + return &awstypes.ScraperLoggingDestinationMemberCloudWatchLogs{ + Value: awstypes.CloudWatchLogDestination{ + LogGroupArn: cwData.LogGroupARN.ValueStringPointer(), + }, + }, diags +} + +func (m *scraperLoggingDestinationModel) Flatten(ctx context.Context, v any) diag.Diagnostics { + var diags diag.Diagnostics + + switch t := v.(type) { + case awstypes.ScraperLoggingDestinationMemberCloudWatchLogs: + var data scraperCloudWatchLogDestinationModel + diags.Append(fwflex.Flatten(ctx, t.Value, &data)...) + if diags.HasError() { + return diags + } + m.CloudwatchLogs = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &data) + } + + return diags +} + +func expandScraperComponents(ctx context.Context, src fwtypes.ListNestedObjectValueOf[scraperComponentModel]) []awstypes.ScraperComponent { + if src.IsNull() || src.IsUnknown() { + return nil + } + + components, diags := src.ToSlice(ctx) + if diags.HasError() { + return nil + } + + result := make([]awstypes.ScraperComponent, len(components)) + for i, component := range components { + result[i] = awstypes.ScraperComponent{ + Type: component.Type.ValueEnum(), + } + + if !component.Options.IsNull() && !component.Options.IsUnknown() { + optionsMap := make(map[string]string) + component.Options.ElementsAs(ctx, &optionsMap, false) + result[i].Config = &awstypes.ComponentConfig{ + Options: optionsMap, + } + } + } + + return result +} diff --git a/internal/service/amp/scraper_logging_configuration_test.go b/internal/service/amp/scraper_logging_configuration_test.go new file mode 100644 index 000000000000..efcb9c007c6b --- /dev/null +++ b/internal/service/amp/scraper_logging_configuration_test.go @@ -0,0 +1,227 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package amp_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/amp" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/retry" + tfamp "github.com/hashicorp/terraform-provider-aws/internal/service/amp" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccPreCheckScraperLoggingConfiguration(ctx context.Context, t *testing.T) { + conn := acctest.ProviderMeta(ctx, t).AMPClient(ctx) + + var input amp.ListScrapersInput + _, err := conn.ListScrapers(ctx, &input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func TestAccAMPScraperLoggingConfiguration_basic(t *testing.T) { + ctx := acctest.Context(t) + var v amp.DescribeScraperLoggingConfigurationOutput + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + resourceName := "aws_prometheus_scraper_logging_configuration.test" + scraperResourceName := "aws_prometheus_scraper.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckScraperLoggingConfiguration(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckScraperLoggingConfigurationDestroy(ctx, t), + Steps: []resource.TestStep{ + { + Config: testAccScraperLoggingConfigurationConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckScraperLoggingConfigurationExists(ctx, t, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "scraper_id", scraperResourceName, names.AttrID), + resource.TestCheckResourceAttr(resourceName, "logging_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "logging_destination.0.cloudwatch_logs.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "logging_destination.0.cloudwatch_logs.0.log_group_arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "scraper_id"), + ImportStateVerifyIdentifierAttribute: "scraper_id", + }, + }, + }) +} + +func TestAccAMPScraperLoggingConfiguration_scraperComponents(t *testing.T) { + ctx := acctest.Context(t) + var v amp.DescribeScraperLoggingConfigurationOutput + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + resourceName := "aws_prometheus_scraper_logging_configuration.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckScraperLoggingConfiguration(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckScraperLoggingConfigurationDestroy(ctx, t), + Steps: []resource.TestStep{ + { + Config: testAccScraperLoggingConfigurationConfig_scraperComponents(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckScraperLoggingConfigurationExists(ctx, t, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "scraper_components.#", "2"), + resource.TestCheckResourceAttr(resourceName, "scraper_components.0.type", "COLLECTOR"), + resource.TestCheckResourceAttr(resourceName, "scraper_components.1.type", "EXPORTER"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "scraper_id"), + ImportStateVerifyIdentifierAttribute: "scraper_id", + ImportStateVerifyIgnore: []string{"scraper_components"}, + }, + }, + }) +} + +func TestAccAMPScraperLoggingConfiguration_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v amp.DescribeScraperLoggingConfigurationOutput + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + resourceName := "aws_prometheus_scraper_logging_configuration.test" + + acctest.ParallelTest(ctx, t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckScraperLoggingConfiguration(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AMPServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckScraperLoggingConfigurationDestroy(ctx, t), + Steps: []resource.TestStep{ + { + Config: testAccScraperLoggingConfigurationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckScraperLoggingConfigurationExists(ctx, t, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, t, tfamp.ResourceScraperLoggingConfiguration, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckScraperLoggingConfigurationDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.ProviderMeta(ctx, t).AMPClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_prometheus_scraper_logging_configuration" { + continue + } + + _, err := tfamp.FindScraperLoggingConfigurationByID(ctx, conn, rs.Primary.Attributes["scraper_id"]) + + if retry.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Prometheus Scraper Logging Configuration %s still exists", rs.Primary.Attributes["scraper_id"]) + } + + return nil + } +} + +func testAccCheckScraperLoggingConfigurationExists(ctx context.Context, t *testing.T, n string, v *amp.DescribeScraperLoggingConfigurationOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.ProviderMeta(ctx, t).AMPClient(ctx) + + output, err := tfamp.FindScraperLoggingConfigurationByID(ctx, conn, rs.Primary.Attributes["scraper_id"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccScraperLoggingConfigurationConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccScraperConfig_basic(rName), + fmt.Sprintf(` +resource "aws_cloudwatch_log_group" "test" { + name = "/aws/prometheus/scraper-logs/%[1]s" +} + +resource "aws_prometheus_scraper_logging_configuration" "test" { + scraper_id = aws_prometheus_scraper.test.id + + logging_destination { + cloudwatch_logs { + log_group_arn = "${aws_cloudwatch_log_group.test.arn}:*" + } + } +} +`, rName)) +} + +func testAccScraperLoggingConfigurationConfig_scraperComponents(rName string) string { + return acctest.ConfigCompose( + testAccScraperConfig_basic(rName), + fmt.Sprintf(` +resource "aws_cloudwatch_log_group" "test" { + name = "/aws/prometheus/scraper-logs/%[1]s" +} + +resource "aws_prometheus_scraper_logging_configuration" "test" { + scraper_id = aws_prometheus_scraper.test.id + + scraper_components { + type = "COLLECTOR" + } + + scraper_components { + type = "EXPORTER" + } + + logging_destination { + cloudwatch_logs { + log_group_arn = "${aws_cloudwatch_log_group.test.arn}:*" + } + } +} +`, rName)) +} diff --git a/internal/service/amp/service_package_gen.go b/internal/service/amp/service_package_gen.go index 1ae5098e95a6..dfddac94e263 100644 --- a/internal/service/amp/service_package_gen.go +++ b/internal/service/amp/service_package_gen.go @@ -54,6 +54,12 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser }), Region: inttypes.ResourceRegionDefault(), }, + { + Factory: newScraperLoggingConfigurationResource, + TypeName: "aws_prometheus_scraper_logging_configuration", + Name: "ScraperLoggingConfiguration", + Region: inttypes.ResourceRegionDefault(), + }, { Factory: newWorkspaceConfigurationResource, TypeName: "aws_prometheus_workspace_configuration", diff --git a/website/docs/r/prometheus_scraper_logging_configuration.html.markdown b/website/docs/r/prometheus_scraper_logging_configuration.html.markdown new file mode 100644 index 000000000000..a519ed00412d --- /dev/null +++ b/website/docs/r/prometheus_scraper_logging_configuration.html.markdown @@ -0,0 +1,136 @@ +--- +subcategory: "AMP (Managed Prometheus)" +layout: "aws" +page_title: "AWS: aws_prometheus_scraper_logging_configuration" +description: |- + Manages an Amazon Managed Service for Prometheus (AMP) Scraper Logging Configuration. +--- + +# Resource: aws_prometheus_scraper_logging_configuration + +Manages an Amazon Managed Service for Prometheus (AMP) Scraper Logging Configuration. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_prometheus_scraper" "example" { + source { + eks { + cluster_arn = aws_eks_cluster.example.arn + subnet_ids = aws_subnet.example[*].id + } + } + + destination { + amp { + workspace_arn = aws_prometheus_workspace.example.arn + } + } + + scrape_configuration = <