Skip to content

Commit

Permalink
feat: sentry_keys data source
Browse files Browse the repository at this point in the history
  • Loading branch information
jianyuan committed May 3, 2024
1 parent b99e6ad commit 21ed1b9
Show file tree
Hide file tree
Showing 12 changed files with 372 additions and 13 deletions.
4 changes: 2 additions & 2 deletions docs/data-sources/key.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ data "sentry_key" "first" {

### Read-Only

- `dsn_csp` (String) DSN for the Content Security Policy (CSP) for the key.
- `dsn_public` (String) DSN for the key.
- `dsn_csp` (String) Security header endpoint for features like CSP and Expect-CT reports.
- `dsn_public` (String) The DSN tells the SDK where to send the events to.
- `dsn_secret` (String, Deprecated)
- `id` (String) The ID of this resource.
- `is_active` (Boolean) Flag indicating the key is active.
Expand Down
55 changes: 55 additions & 0 deletions docs/data-sources/keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "sentry_keys Data Source - terraform-provider-sentry"
subcategory: ""
description: |-
List a Project's Client Keys.
---

# sentry_keys (Data Source)

List a Project's Client Keys.

## Example Usage

```terraform
# List a Project's Client Keys
data "sentry_keys" "all" {
organization = "my-organization"
project = "web-app"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `organization` (String) The slug of the organization the resource belongs to.
- `project` (String) The slug of the project the resource belongs to.

### Optional

- `filter_status` (String) Filter client keys by `active` or `inactive`. Defaults to returning all keys if not specified.

### Read-Only

- `keys` (Attributes List) The list of client keys. (see [below for nested schema](#nestedatt--keys))

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

Read-Only:

- `dsn_csp` (String) Security header endpoint for features like CSP and Expect-CT reports.
- `dsn_public` (String) The DSN tells the SDK where to send the events to.
- `dsn_secret` (String) Deprecated DSN includes a secret which is no longer required by newer SDK versions. If you are unsure which to use, follow installation instructions for your language.
- `id` (String) The ID of this resource.
- `name` (String) The name of the client key.
- `organization` (String) The slug of the organization the resource belongs to.
- `project` (String) The slug of the project the resource belongs to.
- `project_id` (String) The ID of the project that the key belongs to.
- `public` (String) The public key.
- `rate_limit_count` (Number) Number of events that can be reported within the rate limit window.
- `rate_limit_window` (Number) Length of time that will be considered when checking the rate limit.
- `secret` (String) The secret key.
4 changes: 2 additions & 2 deletions docs/resources/key.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ resource "sentry_key" "default" {

### Read-Only

- `dsn_csp` (String) DSN for the Content Security Policy (CSP) for the key.
- `dsn_public` (String) DSN for the key.
- `dsn_csp` (String) Security header endpoint for features like CSP and Expect-CT reports.
- `dsn_public` (String) The DSN tells the SDK where to send the events to.
- `dsn_secret` (String, Deprecated)
- `id` (String) The ID of this resource.
- `is_active` (Boolean) Flag indicating the key is active.
Expand Down
5 changes: 5 additions & 0 deletions examples/data-sources/sentry_keys/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# List a Project's Client Keys
data "sentry_keys" "all" {
organization = "my-organization"
project = "web-app"
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.15.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503214907-7f50dfa4cc2a
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503222850-164dd3150cfc
golang.org/x/oauth2 v0.19.0
golang.org/x/sync v0.7.0
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503214907-7f50dfa4cc2a h1:9nZlgnMXCYUuFG87na//GR/l1wLRiRSj0xe3L4dynfw=
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503214907-7f50dfa4cc2a/go.mod h1:ZMSSWRuXbIwtoH4jg2ka9ZA8x3o3zC8nkTeeqq97G7g=
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503221254-88630f735765 h1:x+eL4zCNru9EgYBBa0/kWigpcy+Ly6h6hSQGd6R4uyQ=
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503221254-88630f735765/go.mod h1:ZMSSWRuXbIwtoH4jg2ka9ZA8x3o3zC8nkTeeqq97G7g=
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503222850-164dd3150cfc h1:5iurCN+F7ETY/Wx6eCkA4yrs+8oYigRSdjtshaifiUs=
github.com/jianyuan/go-sentry/v2 v2.7.1-0.20240503222850-164dd3150cfc/go.mod h1:ZMSSWRuXbIwtoH4jg2ka9ZA8x3o3zC8nkTeeqq97G7g=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down
224 changes: 224 additions & 0 deletions internal/provider/data_source_client_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package provider

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/jianyuan/go-sentry/v2/sentry"
)

var _ datasource.DataSource = &ClientKeysDataSource{}

func NewClientKeysDataSource() datasource.DataSource {
return &ClientKeysDataSource{}
}

type ClientKeysDataSource struct {
client *sentry.Client
}

type ClientKeysDataSourceKeyModel struct {
Id types.String `tfsdk:"id"`
Organization types.String `tfsdk:"organization"`
Project types.String `tfsdk:"project"`
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
Public types.String `tfsdk:"public"`
Secret types.String `tfsdk:"secret"`
RateLimitWindow types.Int64 `tfsdk:"rate_limit_window"`
RateLimitCount types.Int64 `tfsdk:"rate_limit_count"`
DsnPublic types.String `tfsdk:"dsn_public"`
DsnSecret types.String `tfsdk:"dsn_secret"`
DsnCsp types.String `tfsdk:"dsn_csp"`
}

func (m *ClientKeysDataSourceKeyModel) Fill(organization string, project string, d sentry.ProjectKey) error {
m.Id = types.StringValue(d.ID)
m.Organization = types.StringValue(organization)
m.Project = types.StringValue(project)
m.ProjectId = types.StringValue(d.ProjectID.String())
m.Name = types.StringValue(d.Name)
m.Public = types.StringValue(d.Public)
m.Secret = types.StringValue(d.Secret)

if d.RateLimit != nil {
m.RateLimitWindow = types.Int64Value(int64(d.RateLimit.Window))
m.RateLimitCount = types.Int64Value(int64(d.RateLimit.Count))
}

m.DsnPublic = types.StringValue(d.DSN.Public)
m.DsnSecret = types.StringValue(d.DSN.Secret)
m.DsnCsp = types.StringValue(d.DSN.CSP)

return nil
}

type ClientKeysDataSourceModel struct {
Organization types.String `tfsdk:"organization"`
Project types.String `tfsdk:"project"`
FilterStatus types.String `tfsdk:"filter_status"`
Keys []ClientKeysDataSourceKeyModel `tfsdk:"keys"`
}

func (m *ClientKeysDataSourceModel) Fill(organization string, project string, keys []*sentry.ProjectKey) error {
m.Organization = types.StringValue(organization)
m.Project = types.StringValue(project)

for _, key := range keys {
var model ClientKeysDataSourceKeyModel
if err := model.Fill(organization, project, *key); err != nil {
return err
}

m.Keys = append(m.Keys, model)
}

return nil
}

func (d *ClientKeysDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_keys"
}

func (d *ClientKeysDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "List a Project's Client Keys.",

Attributes: map[string]schema.Attribute{
"organization": schema.StringAttribute{
MarkdownDescription: "The slug of the organization the resource belongs to.",
Required: true,
},
"project": schema.StringAttribute{
MarkdownDescription: "The slug of the project the resource belongs to.",
Required: true,
},
"filter_status": schema.StringAttribute{
MarkdownDescription: "Filter client keys by `active` or `inactive`. Defaults to returning all keys if not specified.",
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf(
"active",
"inactive",
),
},
},
"keys": schema.ListNestedAttribute{
MarkdownDescription: "The list of client keys.",
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
MarkdownDescription: "The ID of this resource.",
Computed: true,
},
"organization": schema.StringAttribute{
MarkdownDescription: "The slug of the organization the resource belongs to.",
Computed: true,
},
"project": schema.StringAttribute{
MarkdownDescription: "The slug of the project the resource belongs to.",
Computed: true,
},
"project_id": schema.StringAttribute{
MarkdownDescription: "The ID of the project that the key belongs to.",
Computed: true,
},
"name": schema.StringAttribute{
MarkdownDescription: "The name of the client key.",
Computed: true,
},
"public": schema.StringAttribute{
MarkdownDescription: "The public key.",
Computed: true,
},
"secret": schema.StringAttribute{
MarkdownDescription: "The secret key.",
Computed: true,
},
"rate_limit_window": schema.NumberAttribute{
MarkdownDescription: "Length of time that will be considered when checking the rate limit.",
Computed: true,
},
"rate_limit_count": schema.NumberAttribute{
MarkdownDescription: "Number of events that can be reported within the rate limit window.",
Computed: true,
},
"dsn_public": schema.StringAttribute{
MarkdownDescription: "The DSN tells the SDK where to send the events to.",
Computed: true,
},
"dsn_secret": schema.StringAttribute{
MarkdownDescription: "Deprecated DSN includes a secret which is no longer required by newer SDK versions. If you are unsure which to use, follow installation instructions for your language.",
Computed: true,
},
"dsn_csp": schema.StringAttribute{
MarkdownDescription: "Security header endpoint for features like CSP and Expect-CT reports.",
Computed: true,
},
},
},
},
},
}
}

func (d *ClientKeysDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*sentry.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *sentry.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

d.client = client
}

func (d *ClientKeysDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data ClientKeysDataSourceModel

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

var allKeys []*sentry.ProjectKey
params := &sentry.ListProjectKeysParams{
Status: data.FilterStatus.ValueStringPointer(),
}
for {
keys, apiResp, err := d.client.ProjectKeys.List(ctx, data.Organization.ValueString(), data.Project.ValueString(), params)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read client keys, got error: %s", err))
return
}

allKeys = append(allKeys, keys...)

if apiResp.Cursor == "" {
break
}
params.Cursor = apiResp.Cursor
}

if err := data.Fill(data.Organization.ValueString(), data.Project.ValueString(), allKeys); err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error filling client keys: %s", err.Error()))
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
70 changes: 70 additions & 0 deletions internal/provider/data_source_client_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package provider

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
"github.com/jianyuan/terraform-provider-sentry/internal/acctest"
)

func TestAccClientKeysDataSource(t *testing.T) {
dn := "data.sentry_keys.test"
team := acctest.RandomWithPrefix("tf-team")
project := acctest.RandomWithPrefix("tf-project")

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccClientKeysDataSourceConfig(team, project),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(dn, tfjsonpath.New("organization"), knownvalue.StringExact(acctest.TestOrganization)),
statecheck.ExpectKnownValue(dn, tfjsonpath.New("project"), knownvalue.StringExact(project)),
statecheck.ExpectKnownValue(dn, tfjsonpath.New("keys"), knownvalue.ListExact([]knownvalue.Check{
knownvalue.MapExact(map[string]knownvalue.Check{
"id": knownvalue.NotNull(),
"organization": knownvalue.StringExact(acctest.TestOrganization),
"project": knownvalue.StringExact(project),
"project_id": knownvalue.NotNull(),
"name": knownvalue.StringExact("Default"),
"public": knownvalue.NotNull(),
"secret": knownvalue.NotNull(),
"rate_limit_window": knownvalue.Null(),
"rate_limit_count": knownvalue.Null(),
"dsn_public": knownvalue.NotNull(),
"dsn_secret": knownvalue.NotNull(),
"dsn_csp": knownvalue.NotNull(),
}),
})),
},
},
},
})
}

func testAccClientKeysDataSourceConfig(teamName string, projectName string) string {
return testAccOrganizationDataSourceConfig + fmt.Sprintf(`
resource "sentry_team" "test" {
organization = data.sentry_organization.test.id
name = "%[1]s"
slug = "%[1]s"
}
resource "sentry_project" "test" {
organization = sentry_team.test.organization
teams = [sentry_team.test.id]
name = "%[2]s"
platform = "go"
}
data "sentry_keys" "test" {
organization = sentry_project.test.organization
project = sentry_project.test.id
}
`, teamName, projectName)
}
Loading

0 comments on commit 21ed1b9

Please sign in to comment.