Skip to content

Commit e15a508

Browse files
committed
fix: project filters are now computed
1 parent 2f5c01d commit e15a508

File tree

5 files changed

+147
-107
lines changed

5 files changed

+147
-107
lines changed

internal/provider/data_source_organization.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hashicorp/terraform-plugin-framework/datasource"
88
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
910
"github.com/hashicorp/terraform-plugin-framework/types"
1011
"github.com/jianyuan/terraform-provider-sentry/internal/apiclient"
1112
"github.com/jianyuan/terraform-provider-sentry/internal/diagutils"
@@ -21,13 +22,13 @@ type OrganizationDataSourceModel struct {
2122
InternalId types.String `tfsdk:"internal_id"`
2223
}
2324

24-
func (m *OrganizationDataSourceModel) Fill(org apiclient.Organization) error {
25+
func (m *OrganizationDataSourceModel) Fill(ctx context.Context, org apiclient.Organization) (diags diag.Diagnostics) {
2526
m.Id = types.StringValue(org.Slug)
2627
m.Slug = types.StringValue(org.Slug)
2728
m.Name = types.StringValue(org.Name)
2829
m.InternalId = types.StringValue(org.Id)
2930

30-
return nil
31+
return
3132
}
3233

3334
func NewOrganizationDataSource() datasource.DataSource {
@@ -85,16 +86,13 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe
8586
resp.Diagnostics.Append(diagutils.NewNotFoundError("organization"))
8687
resp.State.RemoveResource(ctx)
8788
return
88-
} else if httpResp.StatusCode() != http.StatusOK {
89+
} else if httpResp.StatusCode() != http.StatusOK || httpResp.JSON200 == nil {
8990
resp.Diagnostics.Append(diagutils.NewClientStatusError("read", httpResp.StatusCode(), httpResp.Body))
9091
return
91-
} else if httpResp.JSON200 == nil {
92-
resp.Diagnostics.Append(diagutils.NewClientError("read", diagutils.ErrEmptyResponse))
93-
return
9492
}
9593

96-
if err := data.Fill(*httpResp.JSON200); err != nil {
97-
resp.Diagnostics.Append(diagutils.NewFillError(err))
94+
resp.Diagnostics.Append(data.Fill(ctx, *httpResp.JSON200)...)
95+
if resp.Diagnostics.HasError() {
9896
return
9997
}
10098

internal/provider/resource_client_key.go

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -378,29 +378,22 @@ func (r *ClientKeyResource) Update(ctx context.Context, req resource.UpdateReque
378378
}
379379

380380
if !plan.JavascriptLoaderScript.Equal(state.JavascriptLoaderScript) {
381-
var javascriptLoaderScriptPlan, javascriptLoaderScriptState ClientKeyJavascriptLoaderScriptResourceModel
382-
resp.Diagnostics.Append(plan.JavascriptLoaderScript.As(ctx, &javascriptLoaderScriptPlan, basetypes.ObjectAsOptions{})...)
383-
resp.Diagnostics.Append(state.JavascriptLoaderScript.As(ctx, &javascriptLoaderScriptState, basetypes.ObjectAsOptions{})...)
381+
var javascriptLoaderScript ClientKeyJavascriptLoaderScriptResourceModel
382+
resp.Diagnostics.Append(plan.JavascriptLoaderScript.As(ctx, &javascriptLoaderScript, basetypes.ObjectAsOptions{})...)
384383
if resp.Diagnostics.HasError() {
385384
return
386385
}
387386

388-
if !javascriptLoaderScriptPlan.BrowserSdkVersion.Equal(javascriptLoaderScriptState.BrowserSdkVersion) {
389-
body.BrowserSdkVersion = javascriptLoaderScriptPlan.BrowserSdkVersion.ValueStringPointer()
390-
}
391-
392-
if !javascriptLoaderScriptPlan.SessionReplayEnabled.Equal(javascriptLoaderScriptState.SessionReplayEnabled) ||
393-
!javascriptLoaderScriptPlan.PerformanceMonitoringEnabled.Equal(javascriptLoaderScriptState.PerformanceMonitoringEnabled) ||
394-
!javascriptLoaderScriptPlan.DebugEnabled.Equal(javascriptLoaderScriptState.DebugEnabled) {
395-
body.DynamicSdkLoaderOptions = &struct {
396-
HasDebug *bool `json:"hasDebug,omitempty"`
397-
HasPerformance *bool `json:"hasPerformance,omitempty"`
398-
HasReplay *bool `json:"hasReplay,omitempty"`
399-
}{
400-
HasReplay: javascriptLoaderScriptPlan.SessionReplayEnabled.ValueBoolPointer(),
401-
HasDebug: javascriptLoaderScriptPlan.DebugEnabled.ValueBoolPointer(),
402-
HasPerformance: javascriptLoaderScriptPlan.PerformanceMonitoringEnabled.ValueBoolPointer(),
403-
}
387+
// NOTE: Both `BrowserSdkVersion` and `DynamicSdkLoaderOptions` must be set together.
388+
body.BrowserSdkVersion = javascriptLoaderScript.BrowserSdkVersion.ValueStringPointer()
389+
body.DynamicSdkLoaderOptions = &struct {
390+
HasDebug *bool `json:"hasDebug,omitempty"`
391+
HasPerformance *bool `json:"hasPerformance,omitempty"`
392+
HasReplay *bool `json:"hasReplay,omitempty"`
393+
}{
394+
HasReplay: javascriptLoaderScript.SessionReplayEnabled.ValueBoolPointer(),
395+
HasDebug: javascriptLoaderScript.DebugEnabled.ValueBoolPointer(),
396+
HasPerformance: javascriptLoaderScript.PerformanceMonitoringEnabled.ValueBoolPointer(),
404397
}
405398
}
406399

internal/provider/resource_client_key_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ func TestAccClientKeyResource(t *testing.T) {
248248
RateLimitCount: ptr.Ptr(2),
249249
Extras: `
250250
javascript_loader_script = {
251-
browser_sdk_version = "7.x"
251+
browser_sdk_version = "7.x"
252252
}
253253
`,
254254
}),

internal/provider/resource_project.go

Lines changed: 88 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ import (
1515
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1616
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
1717
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
18+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
1819
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
20+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
1921
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2022
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
2123
"github.com/hashicorp/terraform-plugin-framework/types"
24+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
2225
"github.com/jianyuan/go-utils/sliceutils"
2326
"github.com/jianyuan/terraform-provider-sentry/internal/apiclient"
2427
"github.com/jianyuan/terraform-provider-sentry/internal/diagutils"
@@ -34,6 +37,14 @@ type ProjectFilterResourceModel struct {
3437
ErrorMessages types.Set `tfsdk:"error_messages"`
3538
}
3639

40+
func (m ProjectFilterResourceModel) AttributeTypes() map[string]attr.Type {
41+
return map[string]attr.Type{
42+
"blacklisted_ips": types.SetType{ElemType: types.StringType},
43+
"releases": types.SetType{ElemType: types.StringType},
44+
"error_messages": types.SetType{ElemType: types.StringType},
45+
}
46+
}
47+
3748
func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) {
3849
if project.Options == nil {
3950
m.BlacklistedIps = types.SetNull(types.StringType)
@@ -45,7 +56,7 @@ func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient
4556

4657
if values, ok := project.Options["filters:blacklisted_ips"].(string); ok {
4758
if values == "" {
48-
m.BlacklistedIps = types.SetNull(types.StringType)
59+
m.BlacklistedIps = types.SetValueMust(types.StringType, []attr.Value{})
4960
} else {
5061
m.BlacklistedIps = types.SetValueMust(types.StringType, sliceutils.Map(func(v string) attr.Value {
5162
return types.StringValue(v)
@@ -57,7 +68,7 @@ func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient
5768

5869
if values, ok := project.Options["filters:releases"].(string); ok {
5970
if values == "" {
60-
m.Releases = types.SetNull(types.StringType)
71+
m.Releases = types.SetValueMust(types.StringType, []attr.Value{})
6172
} else {
6273
m.Releases = types.SetValueMust(types.StringType, sliceutils.Map(func(v string) attr.Value {
6374
return types.StringValue(v)
@@ -69,7 +80,7 @@ func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient
6980

7081
if values, ok := project.Options["filters:error_messages"].(string); ok {
7182
if values == "" {
72-
m.ErrorMessages = types.SetNull(types.StringType)
83+
m.ErrorMessages = types.SetValueMust(types.StringType, []attr.Value{})
7384
} else {
7485
m.ErrorMessages = types.SetValueMust(types.StringType, sliceutils.Map(func(v string) attr.Value {
7586
return types.StringValue(v)
@@ -83,22 +94,22 @@ func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient
8394
}
8495

8596
type ProjectResourceModel struct {
86-
Id types.String `tfsdk:"id"`
87-
Organization types.String `tfsdk:"organization"`
88-
Teams types.Set `tfsdk:"teams"`
89-
Name types.String `tfsdk:"name"`
90-
Slug types.String `tfsdk:"slug"`
91-
Platform types.String `tfsdk:"platform"`
92-
DefaultRules types.Bool `tfsdk:"default_rules"`
93-
DefaultKey types.Bool `tfsdk:"default_key"`
94-
InternalId types.String `tfsdk:"internal_id"`
95-
Features types.Set `tfsdk:"features"`
96-
DigestsMinDelay types.Int64 `tfsdk:"digests_min_delay"`
97-
DigestsMaxDelay types.Int64 `tfsdk:"digests_max_delay"`
98-
ResolveAge types.Int64 `tfsdk:"resolve_age"`
99-
Filters *ProjectFilterResourceModel `tfsdk:"filters"`
100-
FingerprintingRules sentrytypes.TrimmedString `tfsdk:"fingerprinting_rules"`
101-
GroupingEnhancements sentrytypes.TrimmedString `tfsdk:"grouping_enhancements"`
97+
Id types.String `tfsdk:"id"`
98+
Organization types.String `tfsdk:"organization"`
99+
Teams types.Set `tfsdk:"teams"`
100+
Name types.String `tfsdk:"name"`
101+
Slug types.String `tfsdk:"slug"`
102+
Platform types.String `tfsdk:"platform"`
103+
DefaultRules types.Bool `tfsdk:"default_rules"`
104+
DefaultKey types.Bool `tfsdk:"default_key"`
105+
InternalId types.String `tfsdk:"internal_id"`
106+
Features types.Set `tfsdk:"features"`
107+
DigestsMinDelay types.Int64 `tfsdk:"digests_min_delay"`
108+
DigestsMaxDelay types.Int64 `tfsdk:"digests_max_delay"`
109+
ResolveAge types.Int64 `tfsdk:"resolve_age"`
110+
Filters types.Object `tfsdk:"filters"`
111+
FingerprintingRules sentrytypes.TrimmedString `tfsdk:"fingerprinting_rules"`
112+
GroupingEnhancements sentrytypes.TrimmedString `tfsdk:"grouping_enhancements"`
102113
}
103114

104115
func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) {
@@ -125,9 +136,12 @@ func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Proje
125136
m.DigestsMaxDelay = types.Int64Value(project.DigestsMaxDelay)
126137
m.ResolveAge = types.Int64Value(project.ResolveAge)
127138

128-
if m.Filters != nil {
129-
diags.Append(m.Filters.Fill(ctx, project)...)
130-
}
139+
var filters ProjectFilterResourceModel
140+
diags.Append(filters.Fill(ctx, project)...)
141+
142+
var filtersDiags diag.Diagnostics
143+
m.Filters, filtersDiags = types.ObjectValueFrom(ctx, filters.AttributeTypes(), filters)
144+
diags.Append(filtersDiags...)
131145

132146
m.FingerprintingRules = sentrytypes.TrimmedStringValue(project.FingerprintingRules)
133147
m.GroupingEnhancements = sentrytypes.TrimmedStringValue(project.GroupingEnhancements)
@@ -209,6 +223,9 @@ func (r *ProjectResource) Schema(ctx context.Context, req resource.SchemaRequest
209223
"features": schema.SetAttribute{
210224
ElementType: types.StringType,
211225
Computed: true,
226+
PlanModifiers: []planmodifier.Set{
227+
setplanmodifier.UseStateForUnknown(),
228+
},
212229
},
213230
"digests_min_delay": schema.Int64Attribute{
214231
Description: "The minimum amount of time (in seconds) to wait between scheduling digests for delivery after the initial scheduling.",
@@ -237,32 +254,48 @@ func (r *ProjectResource) Schema(ctx context.Context, req resource.SchemaRequest
237254
"filters": schema.SingleNestedAttribute{
238255
Description: "Custom filters for this project.",
239256
Optional: true,
257+
Computed: true,
240258
Attributes: map[string]schema.Attribute{
241259
"blacklisted_ips": schema.SetAttribute{
242260
Description: "Filter events from these IP addresses. (e.g. 127.0.0.1 or 10.0.0.0/8)",
243261
ElementType: types.StringType,
244262
Optional: true,
263+
Computed: true,
245264
Validators: []validator.Set{
246265
setvalidator.SizeAtLeast(1),
247266
},
267+
PlanModifiers: []planmodifier.Set{
268+
setplanmodifier.UseStateForUnknown(),
269+
},
248270
},
249271
"releases": schema.SetAttribute{
250272
MarkdownDescription: "Filter events from these releases. Allows [glob pattern matching](https://en.wikipedia.org/wiki/Glob_(programming)). (e.g. 1.* or [!3].[0-9].*)",
251273
ElementType: types.StringType,
252274
Optional: true,
275+
Computed: true,
253276
Validators: []validator.Set{
254277
setvalidator.SizeAtLeast(1),
255278
},
279+
PlanModifiers: []planmodifier.Set{
280+
setplanmodifier.UseStateForUnknown(),
281+
},
256282
},
257283
"error_messages": schema.SetAttribute{
258284
MarkdownDescription: "Filter events by error messages. Allows [glob pattern matching](https://en.wikipedia.org/wiki/Glob_(programming)). (e.g. TypeError* or *: integer division or modulo by zero)",
259285
ElementType: types.StringType,
260286
Optional: true,
287+
Computed: true,
261288
Validators: []validator.Set{
262289
setvalidator.SizeAtLeast(1),
263290
},
291+
PlanModifiers: []planmodifier.Set{
292+
setplanmodifier.UseStateForUnknown(),
293+
},
264294
},
265295
},
296+
PlanModifiers: []planmodifier.Object{
297+
objectplanmodifier.UseStateForUnknown(),
298+
},
266299
},
267300
"fingerprinting_rules": schema.StringAttribute{
268301
MarkdownDescription: "This can be used to modify the fingerprint rules on the server with custom rules. Rules follow the pattern `matcher:glob -> fingerprint, values`. To learn more about fingerprint rules, [read the docs](https://docs.sentry.io/concepts/data-management/event-grouping/fingerprint-rules/).",
@@ -355,35 +388,35 @@ func (r *ProjectResource) Create(ctx context.Context, req resource.CreateRequest
355388
updateBody.ResolveAge = data.ResolveAge.ValueInt64Pointer()
356389
}
357390

358-
if data.Filters != nil {
391+
if !data.Filters.IsUnknown() {
392+
var filters ProjectFilterResourceModel
393+
resp.Diagnostics.Append(data.Filters.As(ctx, &filters, basetypes.ObjectAsOptions{})...)
394+
if resp.Diagnostics.HasError() {
395+
return
396+
}
397+
359398
options := make(map[string]interface{})
360-
if data.Filters.BlacklistedIps.IsNull() {
361-
options["filters:blacklisted_ips"] = ""
362-
} else {
363-
values := []string{}
364-
resp.Diagnostics.Append(data.Filters.BlacklistedIps.ElementsAs(ctx, &values, false)...)
399+
if !filters.BlacklistedIps.IsUnknown() {
400+
var values []string
401+
resp.Diagnostics.Append(filters.BlacklistedIps.ElementsAs(ctx, &values, false)...)
365402
if resp.Diagnostics.HasError() {
366403
return
367404
}
368405
options["filters:blacklisted_ips"] = strings.Join(values, "\n")
369406
}
370407

371-
if data.Filters.Releases.IsNull() {
372-
options["filters:releases"] = ""
373-
} else {
374-
values := []string{}
375-
resp.Diagnostics.Append(data.Filters.Releases.ElementsAs(ctx, &values, false)...)
408+
if !filters.Releases.IsUnknown() {
409+
var values []string
410+
resp.Diagnostics.Append(filters.Releases.ElementsAs(ctx, &values, false)...)
376411
if resp.Diagnostics.HasError() {
377412
return
378413
}
379414
options["filters:releases"] = strings.Join(values, "\n")
380415
}
381416

382-
if data.Filters.ErrorMessages.IsNull() {
383-
options["filters:error_messages"] = ""
384-
} else {
385-
values := []string{}
386-
resp.Diagnostics.Append(data.Filters.ErrorMessages.ElementsAs(ctx, &values, false)...)
417+
if !filters.ErrorMessages.IsUnknown() {
418+
var values []string
419+
resp.Diagnostics.Append(filters.ErrorMessages.ElementsAs(ctx, &values, false)...)
387420
if resp.Diagnostics.HasError() {
388421
return
389422
}
@@ -538,35 +571,36 @@ func (r *ProjectResource) Update(ctx context.Context, req resource.UpdateRequest
538571
updateBody.ResolveAge = plan.ResolveAge.ValueInt64Pointer()
539572
}
540573

541-
if plan.Filters != nil {
574+
if !plan.Filters.Equal(state.Filters) {
575+
var filtersPlan, filtersState ProjectFilterResourceModel
576+
resp.Diagnostics.Append(plan.Filters.As(ctx, &filtersPlan, basetypes.ObjectAsOptions{})...)
577+
resp.Diagnostics.Append(state.Filters.As(ctx, &filtersState, basetypes.ObjectAsOptions{})...)
578+
if resp.Diagnostics.HasError() {
579+
return
580+
}
581+
542582
options := make(map[string]interface{})
543-
if plan.Filters.BlacklistedIps.IsNull() {
544-
options["filters:blacklisted_ips"] = ""
545-
} else {
546-
values := []string{}
547-
resp.Diagnostics.Append(plan.Filters.BlacklistedIps.ElementsAs(ctx, &values, false)...)
583+
if !filtersPlan.BlacklistedIps.Equal(filtersState.BlacklistedIps) {
584+
var values []string
585+
resp.Diagnostics.Append(filtersPlan.BlacklistedIps.ElementsAs(ctx, &values, false)...)
548586
if resp.Diagnostics.HasError() {
549587
return
550588
}
551589
options["filters:blacklisted_ips"] = strings.Join(values, "\n")
552590
}
553591

554-
if plan.Filters.Releases.IsNull() {
555-
options["filters:releases"] = ""
556-
} else {
557-
values := []string{}
558-
resp.Diagnostics.Append(plan.Filters.Releases.ElementsAs(ctx, &values, false)...)
592+
if !filtersPlan.Releases.Equal(filtersState.Releases) {
593+
var values []string
594+
resp.Diagnostics.Append(filtersPlan.Releases.ElementsAs(ctx, &values, false)...)
559595
if resp.Diagnostics.HasError() {
560596
return
561597
}
562598
options["filters:releases"] = strings.Join(values, "\n")
563599
}
564600

565-
if plan.Filters.ErrorMessages.IsNull() {
566-
options["filters:error_messages"] = ""
567-
} else {
568-
values := []string{}
569-
resp.Diagnostics.Append(plan.Filters.ErrorMessages.ElementsAs(ctx, &values, false)...)
601+
if !filtersPlan.ErrorMessages.Equal(filtersState.ErrorMessages) {
602+
var values []string
603+
resp.Diagnostics.Append(filtersPlan.ErrorMessages.ElementsAs(ctx, &values, false)...)
570604
if resp.Diagnostics.HasError() {
571605
return
572606
}

0 commit comments

Comments
 (0)