Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ fmt:

.PHONY: test
test:
go test -v -cover -timeout=120s -parallel=10 ./...
go test ./... -v -cover -timeout=120s -parallel=10 $(TESTARGS)

.PHONY: testacc
testacc:
TF_ACC=1 go test -v -cover -timeout 120m ./...
TF_ACC=1 go test ./... -v -cover -timeout 120m $(TESTARGS)

.PHONY: sweep
sweep:
Expand Down
13 changes: 13 additions & 0 deletions docs/resources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ resource "sentry_project" "default" {

### Optional

- `client_security` (Attributes) Configure origin URLs which Sentry should accept events from. This is used for communication with clients like [sentry-javascript](https://github.com/getsentry/sentry-javascript). (see [below for nested schema](#nestedatt--client_security))
- `default_key` (Boolean) Whether to create a default key. By default, Sentry will create a key for you. If you wish to manage keys manually, set this to false and create keys using the `sentry_key` resource.
- `default_rules` (Boolean) Whether to create a default issue alert. Defaults to true where the behavior is to alert the user on every new issue.
- `digests_max_delay` (Number) The maximum amount of time (in seconds) to wait between scheduling digests for delivery.
Expand All @@ -75,6 +76,18 @@ resource "sentry_project" "default" {
- `id` (String) The ID of this resource.
- `internal_id` (String) The internal ID for this project.

<a id="nestedatt--client_security"></a>
### Nested Schema for `client_security`

Optional:

- `allowed_domains` (Set of String) A list of allowed domains. Examples: https://example.com, *, *.example.com, *:80.
- `scrape_javascript` (Boolean) Enable JavaScript source fetching. Allow Sentry to scrape missing JavaScript source context when possible.
- `security_token` (String) Security Token. Outbound requests matching Allowed Domains will have the header "{security_token_header}: {security_token}" appended.
- `security_token_header` (String) Security Token Header. Outbound requests matching Allowed Domains will have the header "{security_token_header}: {security_token}" appended.
- `verify_tls_ssl` (Boolean) Verify TLS/SSL. Outbound requests will verify TLS (sometimes known as SSL) connections.


<a id="nestedatt--filters"></a>
### Nested Schema for `filters`

Expand Down
30 changes: 30 additions & 0 deletions internal/apiclient/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ paths:
type: string
options:
type: object
allowedDomains:
type: array
items:
type: string
scrapeJavaScript:
type: boolean
securityToken:
type: string
securityTokenHeader:
type: string
verifySSL:
type: boolean
responses:
"200":
description: OK
Expand Down Expand Up @@ -407,6 +419,11 @@ components:
- resolveAge
- fingerprintingRules
- groupingEnhancements
- allowedDomains
- scrapeJavaScript
- securityToken
- securityTokenHeader
- verifySSL
properties:
organization:
$ref: "#/components/schemas/Organization"
Expand Down Expand Up @@ -449,6 +466,19 @@ components:
type: string
groupingEnhancements:
type: string
allowedDomains:
type: array
items:
type: string
scrapeJavaScript:
type: boolean
securityToken:
type: string
securityTokenHeader:
type: string
nullable: true
verifySSL:
type: boolean
ProjectKey:
type: object
required:
Expand Down
10 changes: 10 additions & 0 deletions internal/apiclient/apiclient.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

164 changes: 164 additions & 0 deletions internal/provider/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,42 @@ func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient
return
}

type ProjectClientSecurityResourceModel struct {
AllowedDomains types.Set `tfsdk:"allowed_domains"`
ScrapeJavascript types.Bool `tfsdk:"scrape_javascript"`
SecurityToken types.String `tfsdk:"security_token"`
SecurityTokenHeader types.String `tfsdk:"security_token_header"`
VerifyTlsSsl types.Bool `tfsdk:"verify_tls_ssl"`
}

func (m ProjectClientSecurityResourceModel) AttributeTypes() map[string]attr.Type {
return map[string]attr.Type{
"allowed_domains": types.SetType{ElemType: types.StringType},
"scrape_javascript": types.BoolType,
"security_token": types.StringType,
"security_token_header": types.StringType,
"verify_tls_ssl": types.BoolType,
}
}

func (m *ProjectClientSecurityResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) {
m.AllowedDomains = types.SetValueMust(types.StringType, sliceutils.Map(func(v string) attr.Value {
return types.StringValue(v)
}, project.AllowedDomains))
m.ScrapeJavascript = types.BoolValue(project.ScrapeJavaScript)
m.SecurityToken = types.StringValue(project.SecurityToken)

if project.SecurityTokenHeader == nil {
m.SecurityTokenHeader = types.StringValue("")
} else {
m.SecurityTokenHeader = types.StringPointerValue(project.SecurityTokenHeader)
}

m.VerifyTlsSsl = types.BoolValue(project.VerifySSL)

return
}

type ProjectResourceModel struct {
Id types.String `tfsdk:"id"`
Organization types.String `tfsdk:"organization"`
Expand All @@ -110,6 +146,7 @@ type ProjectResourceModel struct {
Filters types.Object `tfsdk:"filters"`
FingerprintingRules sentrytypes.TrimmedString `tfsdk:"fingerprinting_rules"`
GroupingEnhancements sentrytypes.TrimmedString `tfsdk:"grouping_enhancements"`
ClientSecurity types.Object `tfsdk:"client_security"`
}

func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) {
Expand Down Expand Up @@ -146,6 +183,13 @@ func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Proje
m.FingerprintingRules = sentrytypes.TrimmedStringValue(project.FingerprintingRules)
m.GroupingEnhancements = sentrytypes.TrimmedStringValue(project.GroupingEnhancements)

var clientSecurity ProjectClientSecurityResourceModel
diags.Append(clientSecurity.Fill(ctx, project)...)

var clientSecurityDiags diag.Diagnostics
m.ClientSecurity, clientSecurityDiags = types.ObjectValueFrom(ctx, clientSecurity.AttributeTypes(), clientSecurity)
diags.Append(clientSecurityDiags...)

return
}

Expand Down Expand Up @@ -315,6 +359,63 @@ func (r *ProjectResource) Schema(ctx context.Context, req resource.SchemaRequest
stringplanmodifier.UseStateForUnknown(),
},
},
"client_security": schema.SingleNestedAttribute{
MarkdownDescription: "Configure origin URLs which Sentry should accept events from. This is used for communication with clients like [sentry-javascript](https://github.com/getsentry/sentry-javascript).",
Optional: true,
Computed: true,
Attributes: map[string]schema.Attribute{
"allowed_domains": schema.SetAttribute{
MarkdownDescription: "A list of allowed domains. Examples: https://example.com, *, *.example.com, *:80.",
ElementType: types.StringType,
Optional: true,
Computed: true,
Validators: []validator.Set{
setvalidator.SizeAtLeast(1),
},
PlanModifiers: []planmodifier.Set{
setplanmodifier.UseStateForUnknown(),
},
},
"scrape_javascript": schema.BoolAttribute{
MarkdownDescription: "Enable JavaScript source fetching. Allow Sentry to scrape missing JavaScript source context when possible.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"security_token": schema.StringAttribute{
MarkdownDescription: "Security Token. Outbound requests matching Allowed Domains will have the header \"{security_token_header}: {security_token}\" appended.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"security_token_header": schema.StringAttribute{
MarkdownDescription: "Security Token Header. Outbound requests matching Allowed Domains will have the header \"{security_token_header}: {security_token}\" appended.",
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.LengthAtMost(20),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"verify_tls_ssl": schema.BoolAttribute{
MarkdownDescription: "Verify TLS/SSL. Outbound requests will verify TLS (sometimes known as SSL) connections.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
},
PlanModifiers: []planmodifier.Object{
objectplanmodifier.UseStateForUnknown(),
},
},
},
}
}
Expand Down Expand Up @@ -436,6 +537,37 @@ func (r *ProjectResource) Create(ctx context.Context, req resource.CreateRequest
updateBody.GroupingEnhancements = data.GroupingEnhancements.ValueStringPointer()
}

if !data.ClientSecurity.IsUnknown() {
var clientSecurity ProjectClientSecurityResourceModel
resp.Diagnostics.Append(data.ClientSecurity.As(ctx, &clientSecurity, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}

if !clientSecurity.AllowedDomains.IsUnknown() {
resp.Diagnostics.Append(clientSecurity.AllowedDomains.ElementsAs(ctx, &updateBody.AllowedDomains, false)...)
if resp.Diagnostics.HasError() {
return
}
}

if !clientSecurity.ScrapeJavascript.IsUnknown() {
updateBody.ScrapeJavaScript = clientSecurity.ScrapeJavascript.ValueBoolPointer()
}

if !clientSecurity.SecurityToken.IsUnknown() {
updateBody.SecurityToken = clientSecurity.SecurityToken.ValueStringPointer()
}

if !clientSecurity.SecurityTokenHeader.IsUnknown() {
updateBody.SecurityTokenHeader = clientSecurity.SecurityTokenHeader.ValueStringPointer()
}

if !clientSecurity.VerifyTlsSsl.IsUnknown() {
updateBody.VerifySSL = clientSecurity.VerifyTlsSsl.ValueBoolPointer()
}
}

httpRespUpdate, err := r.apiClient.UpdateOrganizationProjectWithResponse(
ctx,
data.Organization.ValueString(),
Expand Down Expand Up @@ -620,6 +752,38 @@ func (r *ProjectResource) Update(ctx context.Context, req resource.UpdateRequest
updateBody.GroupingEnhancements = plan.GroupingEnhancements.ValueStringPointer()
}

if !plan.ClientSecurity.Equal(state.ClientSecurity) {
var clientSecurityPlan, clientSecurityState ProjectClientSecurityResourceModel
resp.Diagnostics.Append(plan.ClientSecurity.As(ctx, &clientSecurityPlan, basetypes.ObjectAsOptions{})...)
resp.Diagnostics.Append(state.ClientSecurity.As(ctx, &clientSecurityState, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}

if !clientSecurityPlan.AllowedDomains.Equal(clientSecurityState.AllowedDomains) {
resp.Diagnostics.Append(clientSecurityPlan.AllowedDomains.ElementsAs(ctx, &updateBody.AllowedDomains, false)...)
if resp.Diagnostics.HasError() {
return
}
}

if !clientSecurityPlan.ScrapeJavascript.Equal(clientSecurityState.ScrapeJavascript) {
updateBody.ScrapeJavaScript = clientSecurityPlan.ScrapeJavascript.ValueBoolPointer()
}

if !clientSecurityPlan.SecurityToken.Equal(clientSecurityState.SecurityToken) {
updateBody.SecurityToken = clientSecurityPlan.SecurityToken.ValueStringPointer()
}

if !clientSecurityPlan.SecurityTokenHeader.Equal(clientSecurityState.SecurityTokenHeader) {
updateBody.SecurityTokenHeader = clientSecurityPlan.SecurityTokenHeader.ValueStringPointer()
}

if !clientSecurityPlan.VerifyTlsSsl.Equal(clientSecurityState.VerifyTlsSsl) {
updateBody.VerifySSL = clientSecurityPlan.VerifyTlsSsl.ValueBoolPointer()
}
}

httpRespUpdate, err := r.apiClient.UpdateOrganizationProjectWithResponse(
ctx,
plan.Organization.ValueString(),
Expand Down
Loading
Loading