diff --git a/examples/resources/sentry_project/resource.tf b/examples/resources/sentry_project/resource.tf index 411dafd11..599c3b15a 100644 --- a/examples/resources/sentry_project/resource.tf +++ b/examples/resources/sentry_project/resource.tf @@ -29,4 +29,6 @@ resource "sentry_project" "default" { # mark all functions following a prefix in-app stack.function:mylibrary_* +app EOT + + highlight_tags = ["release", "environment"] } diff --git a/internal/apiclient/api.yaml b/internal/apiclient/api.yaml index 3ed50a882..c40811bf7 100644 --- a/internal/apiclient/api.yaml +++ b/internal/apiclient/api.yaml @@ -398,6 +398,10 @@ paths: type: string verifySSL: type: boolean + highlightTags: + type: array + items: + type: string responses: "200": description: OK @@ -1269,6 +1273,10 @@ components: nullable: true verifySSL: type: boolean + highlightTags: + type: array + items: + type: string ProjectKey: type: object required: diff --git a/internal/apiclient/apiclient.gen.go b/internal/apiclient/apiclient.gen.go index 59d9326ff..7372ebe54 100644 --- a/internal/apiclient/apiclient.gen.go +++ b/internal/apiclient/apiclient.gen.go @@ -314,6 +314,7 @@ type Project struct { Features []string `json:"features"` FingerprintingRules string `json:"fingerprintingRules"` GroupingEnhancements string `json:"groupingEnhancements"` + HighlightTags *[]string `json:"highlightTags,omitempty"` Id string `json:"id"` IsPublic bool `json:"isPublic"` Name string `json:"name"` @@ -880,6 +881,7 @@ type UpdateOrganizationProjectJSONBody struct { DigestsMinDelay *int64 `json:"digestsMinDelay,omitempty"` FingerprintingRules *string `json:"fingerprintingRules,omitempty"` GroupingEnhancements *string `json:"groupingEnhancements,omitempty"` + HighlightTags *[]string `json:"highlightTags,omitempty"` Name *string `json:"name,omitempty"` Options *map[string]interface{} `json:"options,omitempty"` Platform *string `json:"platform,omitempty"` diff --git a/internal/provider/resource_project.go b/internal/provider/resource_project.go index 40d5f0e40..314420205 100644 --- a/internal/provider/resource_project.go +++ b/internal/provider/resource_project.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/jianyuan/go-utils/sliceutils" + "github.com/jianyuan/terraform-provider-sentry/internal/apiclient" "github.com/jianyuan/terraform-provider-sentry/internal/diagutils" "github.com/jianyuan/terraform-provider-sentry/internal/sentryclient" @@ -147,6 +148,7 @@ type ProjectResourceModel struct { FingerprintingRules sentrytypes.TrimmedString `tfsdk:"fingerprinting_rules"` GroupingEnhancements sentrytypes.TrimmedString `tfsdk:"grouping_enhancements"` ClientSecurity types.Object `tfsdk:"client_security"` + HighlightTags types.Set `tfsdk:"highlight_tags"` } func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) { @@ -184,6 +186,14 @@ func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Proje diags.Append(clientSecurity.Fill(ctx, project)...) m.ClientSecurity = tfutils.MergeDiagnostics(types.ObjectValueFrom(ctx, clientSecurity.AttributeTypes(), clientSecurity))(&diags) + if project.HighlightTags != nil { + m.HighlightTags = types.SetValueMust(types.StringType, sliceutils.Map(func(v string) attr.Value { + return types.StringValue(v) + }, *project.HighlightTags)) + } else { + m.HighlightTags = types.SetNull(types.StringType) + } + return } @@ -407,6 +417,15 @@ func (r *ProjectResource) Schema(ctx context.Context, req resource.SchemaRequest objectplanmodifier.UseStateForUnknown(), }, }, + "highlight_tags": schema.SetAttribute{ + MarkdownDescription: "A list of strings with tag keys to highlight on this project's issues. E.g. ['release', 'environment']", + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, }, } } @@ -774,6 +793,15 @@ func (r *ProjectResource) Update(ctx context.Context, req resource.UpdateRequest } } + if !plan.HighlightTags.Equal(state.HighlightTags) { + var highlightTags []string + resp.Diagnostics.Append(plan.HighlightTags.ElementsAs(ctx, &highlightTags, false)...) + if resp.Diagnostics.HasError() { + return + } + updateBody.HighlightTags = &highlightTags + } + httpRespUpdate, err := r.apiClient.UpdateOrganizationProjectWithResponse( ctx, plan.Organization.ValueString(), diff --git a/internal/provider/resource_project_test.go b/internal/provider/resource_project_test.go index 4dc02b27f..57fa3b37b 100644 --- a/internal/provider/resource_project_test.go +++ b/internal/provider/resource_project_test.go @@ -20,6 +20,7 @@ import ( "github.com/jianyuan/go-utils/must" "github.com/jianyuan/go-utils/ptr" "github.com/jianyuan/go-utils/sliceutils" + "github.com/jianyuan/terraform-provider-sentry/internal/acctest" "github.com/jianyuan/terraform-provider-sentry/internal/apiclient" ) @@ -122,6 +123,22 @@ func TestAccProjectResource_basic(t *testing.T) { return fmt.Errorf("unexpected verify tls ssl %v", project.VerifySSL) } + if data.HighlightTags != nil { + if project.HighlightTags == nil { + return fmt.Errorf("highlight tags is nil") + } + + if len(*project.HighlightTags) != len(*data.HighlightTags) { + return fmt.Errorf("unexpected highlight tags %v", *project.HighlightTags) + } + + for _, tag := range *data.HighlightTags { + if !slices.Contains(*project.HighlightTags, tag) { + return fmt.Errorf("highlight tag %v not found", tag) + } + } + } + return nil } } @@ -159,6 +176,14 @@ func TestAccProjectResource_basic(t *testing.T) { "security_token_header": knownvalue.StringExact(ptr.Value(data.SecurityTokenHeader)), "verify_tls_ssl": knownvalue.Bool(ptr.Value(data.VerifyTlsSsl)), })), + func() statecheck.StateCheck { + if data.HighlightTags == nil { + return statecheck.ExpectKnownValue(rn, tfjsonpath.New("highlight_tags"), knownvalue.Null()) + } + return statecheck.ExpectKnownValue(rn, tfjsonpath.New("highlight_tags"), knownvalue.SetExact(sliceutils.Map(func(v string) knownvalue.Check { + return knownvalue.StringExact(v) + }, *data.HighlightTags))) + }(), } } @@ -233,6 +258,7 @@ func TestAccProjectResource_basic(t *testing.T) { Platform: "python", AllowedDomains: ptr.Ptr([]string{"jianyuan.io", "*.jianyuan.io"}), SecurityTokenHeader: ptr.Ptr("x-my-security-token"), + HighlightTags: ptr.Ptr([]string{"release", "environment"}), }), Check: testAccCheckProject(rn, checkProperties(testAccProjectResourceConfig_teamsData{ AllTeamNames: []string{teamName1, teamName2, teamName3}, @@ -243,6 +269,7 @@ func TestAccProjectResource_basic(t *testing.T) { ScrapeJavascript: ptr.Ptr(false), SecurityTokenHeader: ptr.Ptr("x-my-security-token"), VerifyTlsSsl: ptr.Ptr(true), + HighlightTags: ptr.Ptr([]string{"release", "environment"}), })), ConfigStateChecks: configStateChecks(testAccProjectResourceConfig_teamsData{ AllTeamNames: []string{teamName1, teamName2, teamName3}, @@ -253,6 +280,7 @@ func TestAccProjectResource_basic(t *testing.T) { ScrapeJavascript: ptr.Ptr(false), SecurityTokenHeader: ptr.Ptr("x-my-security-token"), VerifyTlsSsl: ptr.Ptr(true), + HighlightTags: ptr.Ptr([]string{"release", "environment"}), }), }, // Remove all optional attributes @@ -839,6 +867,14 @@ resource "sentry_project" "test" { verify_tls_ssl = {{ .VerifyTlsSsl }} {{ end }} } + + {{ if ne .HighlightTags nil }} + highlight_tags = [ + {{ range $i, $tag := .HighlightTags }} + "{{ $tag }}", + {{ end }} + ] + {{ end }} } `)) @@ -852,6 +888,7 @@ type testAccProjectResourceConfig_teamsData struct { SecurityToken *string SecurityTokenHeader *string VerifyTlsSsl *bool + HighlightTags *[]string } func testAccProjectResourceConfig_teams(data testAccProjectResourceConfig_teamsData) string {