diff --git a/docs/data-sources/connection.md b/docs/data-sources/connection.md
new file mode 100644
index 0000000..6611144
--- /dev/null
+++ b/docs/data-sources/connection.md
@@ -0,0 +1,124 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "hookdeck_connection Data Source - terraform-provider-hookdeck"
+subcategory: ""
+description: |-
+ Connection Data Source
+---
+
+# hookdeck_connection (Data Source)
+
+Connection Data Source
+
+
+
+
+## Schema
+
+### Required
+
+- `id` (String) ID of the connection
+
+### Read-Only
+
+- `created_at` (String) Date the connection was created
+- `description` (String) Description for the connection
+- `destination_id` (String) ID of a destination to bind to the connection
+- `disabled_at` (String) Date the connection was disabled
+- `name` (String) A unique, human-friendly name for the connection
+- `paused_at` (String) Date the connection was paused
+- `rules` (Attributes Set) (see [below for nested schema](#nestedatt--rules))
+- `source_id` (String) ID of a source to bind to the connection
+- `team_id` (String) ID of the workspace
+- `updated_at` (String) Date the connection was last updated
+
+
+### Nested Schema for `rules`
+
+Read-Only:
+
+- `delay_rule` (Attributes) (see [below for nested schema](#nestedatt--rules--delay_rule))
+- `filter_rule` (Attributes) (see [below for nested schema](#nestedatt--rules--filter_rule))
+- `retry_rule` (Attributes) (see [below for nested schema](#nestedatt--rules--retry_rule))
+- `transform_rule` (Attributes) (see [below for nested schema](#nestedatt--rules--transform_rule))
+
+
+### Nested Schema for `rules.delay_rule`
+
+Read-Only:
+
+- `delay` (Number) Delay to introduce in MS
+
+
+
+### Nested Schema for `rules.filter_rule`
+
+Read-Only:
+
+- `body` (Attributes) (see [below for nested schema](#nestedatt--rules--filter_rule--body))
+- `headers` (Attributes) (see [below for nested schema](#nestedatt--rules--filter_rule--headers))
+- `path` (Attributes) (see [below for nested schema](#nestedatt--rules--filter_rule--path))
+- `query` (Attributes) (see [below for nested schema](#nestedatt--rules--filter_rule--query))
+
+
+### Nested Schema for `rules.filter_rule.body`
+
+Read-Only:
+
+- `boolean` (Boolean)
+- `json` (String) Stringied JSON using our filter syntax to filter on request headers
+- `number` (Number)
+- `string` (String)
+
+
+
+### Nested Schema for `rules.filter_rule.headers`
+
+Read-Only:
+
+- `boolean` (Boolean)
+- `json` (String) Stringied JSON using our filter syntax to filter on request headers
+- `number` (Number)
+- `string` (String)
+
+
+
+### Nested Schema for `rules.filter_rule.path`
+
+Read-Only:
+
+- `boolean` (Boolean)
+- `json` (String) Stringied JSON using our filter syntax to filter on request headers
+- `number` (Number)
+- `string` (String)
+
+
+
+### Nested Schema for `rules.filter_rule.query`
+
+Read-Only:
+
+- `boolean` (Boolean)
+- `json` (String) Stringied JSON using our filter syntax to filter on request headers
+- `number` (Number)
+- `string` (String)
+
+
+
+
+### Nested Schema for `rules.retry_rule`
+
+Read-Only:
+
+- `count` (Number) Maximum number of retries to attempt
+- `interval` (Number) Time in MS between each retry
+- `strategy` (String) must be one of ["linear", "exponential"]
+Algorithm to use when calculating delay between retries
+
+
+
+### Nested Schema for `rules.transform_rule`
+
+Read-Only:
+
+- `transformation_id` (String) ID of the attached transformation object.
diff --git a/docs/data-sources/destination.md b/docs/data-sources/destination.md
new file mode 100644
index 0000000..142f449
--- /dev/null
+++ b/docs/data-sources/destination.md
@@ -0,0 +1,136 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "hookdeck_destination Data Source - terraform-provider-hookdeck"
+subcategory: ""
+description: |-
+ Destination Data Source
+---
+
+# hookdeck_destination (Data Source)
+
+Destination Data Source
+
+
+
+
+## Schema
+
+### Required
+
+- `id` (String) ID of the destination
+
+### Read-Only
+
+- `auth_method` (Attributes) Config for the destination's auth method (see [below for nested schema](#nestedatt--auth_method))
+- `cli_path` (String) Path for the CLI destination
+- `created_at` (String) Date the destination was created
+- `description` (String) Description for the destination
+- `disabled_at` (String) Date the destination was disabled
+- `http_method` (String) must be one of ["GET", "POST", "PUT", "PATCH", "DELETE"]
+HTTP method used on requests sent to the destination, overrides the method used on requests sent to the source.
+- `name` (String) A unique, human-friendly name for the destination
+- `path_forwarding_disabled` (Boolean)
+- `rate_limit` (Attributes) Rate limit (see [below for nested schema](#nestedatt--rate_limit))
+- `team_id` (String) ID of the workspace
+- `updated_at` (String) Date the destination was last updated
+- `url` (String) HTTP endpoint of the destination
+
+
+### Nested Schema for `auth_method`
+
+Read-Only:
+
+- `api_key` (Attributes) API Key (see [below for nested schema](#nestedatt--auth_method--api_key))
+- `aws_signature` (Attributes) AWS Signature (see [below for nested schema](#nestedatt--auth_method--aws_signature))
+- `basic_auth` (Attributes) Basic Auth (see [below for nested schema](#nestedatt--auth_method--basic_auth))
+- `bearer_token` (Attributes) Bearer Token (see [below for nested schema](#nestedatt--auth_method--bearer_token))
+- `custom_signature` (Attributes) Custom Signature (see [below for nested schema](#nestedatt--auth_method--custom_signature))
+- `hookdeck_signature` (Attributes) Hookdeck Signature (see [below for nested schema](#nestedatt--auth_method--hookdeck_signature))
+- `json` (String, Sensitive) Stringified JSON value for destination payload, used when Terraform provider hasn't supported the destination method on Hookdeck yet
+- `oauth2_authorization_code` (Attributes) OAuth2 Client Credentials (see [below for nested schema](#nestedatt--auth_method--oauth2_authorization_code))
+- `oauth2_client_credentials` (Attributes) OAuth2 Client Credentials (see [below for nested schema](#nestedatt--auth_method--oauth2_client_credentials))
+
+
+### Nested Schema for `auth_method.api_key`
+
+Read-Only:
+
+- `api_key` (String, Sensitive) API key for the API key auth
+- `key` (String) Key for the API key auth
+- `to` (String) must be one of ["header", "query"]
+Whether the API key should be sent as a header or a query parameter
+
+
+
+### Nested Schema for `auth_method.aws_signature`
+
+Read-Only:
+
+- `access_key_id` (String, Sensitive) AWS access key id
+- `secret_access_key` (String, Sensitive) AWS secret access key
+
+
+
+### Nested Schema for `auth_method.basic_auth`
+
+Read-Only:
+
+- `password` (String, Sensitive) Password for basic auth
+- `username` (String) Username for basic auth
+
+
+
+### Nested Schema for `auth_method.bearer_token`
+
+Read-Only:
+
+- `token` (String, Sensitive) Token for the bearer token auth
+
+
+
+### Nested Schema for `auth_method.custom_signature`
+
+Read-Only:
+
+- `key` (String) Key for the custom signature auth
+- `signing_secret` (String, Sensitive) Signing secret for the custom signature auth. If left empty a secret will be generated for you.
+
+
+
+### Nested Schema for `auth_method.hookdeck_signature`
+
+
+
+### Nested Schema for `auth_method.oauth2_authorization_code`
+
+Read-Only:
+
+- `auth_server` (String) URL of the auth server
+- `client_id` (String) Client id in the auth server
+- `client_secret` (String, Sensitive) Client secret in the auth server
+- `refresh_token` (String, Sensitive) Refresh token already returned by the auth server
+- `scope` (String) Scope to access
+
+
+
+### Nested Schema for `auth_method.oauth2_client_credentials`
+
+Read-Only:
+
+- `auth_server` (String) URL of the auth server
+- `authentication_type` (String) must be one of [basic, bearer]
+Basic (default) or Bearer Authentication
+- `client_id` (String) Client id in the auth server
+- `client_secret` (String, Sensitive) Client secret in the auth server
+- `scope` (String) Scope to access
+
+
+
+
+### Nested Schema for `rate_limit`
+
+Read-Only:
+
+- `limit` (Number) Limit event attempts to receive per period. Max value is workspace plan's max attempts thoughput.
+- `period` (String) must be one of ["second", "minute", "hour"]
+Period to rate limit attempts
diff --git a/docs/data-sources/source.md b/docs/data-sources/source.md
new file mode 100644
index 0000000..04576fd
--- /dev/null
+++ b/docs/data-sources/source.md
@@ -0,0 +1,41 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "hookdeck_source Data Source - terraform-provider-hookdeck"
+subcategory: ""
+description: |-
+ Source Data Source
+---
+
+# hookdeck_source (Data Source)
+
+Source Data Source
+
+
+
+
+## Schema
+
+### Required
+
+- `id` (String) ID of the source
+
+### Read-Only
+
+- `allowed_http_methods` (List of String) List of allowed HTTP methods. Defaults to PUT, POST, PATCH, DELETE.
+- `created_at` (String) Date the source was created
+- `custom_response` (Attributes) Custom response object (see [below for nested schema](#nestedatt--custom_response))
+- `description` (String) Description for the source
+- `disabled_at` (String) Date the source was disabled
+- `name` (String) A unique, human-friendly name for the source
+- `team_id` (String) ID of the workspace
+- `updated_at` (String) Date the source was last updated
+- `url` (String) A unique URL that must be supplied to your webhook's provider
+
+
+### Nested Schema for `custom_response`
+
+Read-Only:
+
+- `body` (String) Body of the custom response
+- `content_type` (String) must be one of [json, text, xml]
+Content type of the custom response
diff --git a/docs/resources/webhook_registration.md b/docs/resources/webhook_registration.md
index 2449596..bcdfe61 100644
--- a/docs/resources/webhook_registration.md
+++ b/docs/resources/webhook_registration.md
@@ -3,12 +3,12 @@
page_title: "hookdeck_webhook_registration Resource - terraform-provider-hookdeck"
subcategory: ""
description: |-
- Webhook Resource
+ Webhook Registration Resource
---
# hookdeck_webhook_registration (Resource)
-Webhook Resource
+Webhook Registration Resource
## Example Usage
diff --git a/internal/provider/connection/datasource.go b/internal/provider/connection/datasource.go
new file mode 100644
index 0000000..6729b52
--- /dev/null
+++ b/internal/provider/connection/datasource.go
@@ -0,0 +1,81 @@
+package connection
+
+import (
+ "context"
+ "fmt"
+ schemaHelpers "terraform-provider-hookdeck/internal/schemahelpers"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &connectionDataSource{}
+ _ datasource.DataSourceWithConfigure = &connectionDataSource{}
+)
+
+// NewConnectionDataSource is a helper function to simplify the provider implementation.
+func NewConnectionDataSource() datasource.DataSource {
+ return &connectionDataSource{}
+}
+
+// connectionDataSource is the datasource implementation.
+type connectionDataSource struct {
+ client hookdeckClient.Client
+}
+
+// Metadata returns the datasource type name.
+func (r *connectionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_connection"
+}
+
+// Schema returns the data source schema.
+func (r *connectionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Connection Data Source",
+ Attributes: schemaHelpers.DataSourceSchemaFromResourceSchema(schemaAttributes(), "id"),
+ }
+}
+
+// Configure adds the provider configured client to the datasource.
+func (r *connectionDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*hookdeckClient.Client)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *hookdeckClient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = *client
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (r *connectionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ // Get data from Terraform state
+ var data *connectionResourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get refreshed datasource value
+ connection, err := r.client.Connection.Retrieve(context.Background(), data.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading connection", err.Error())
+ return
+ }
+
+ // Save refreshed data into Terraform state
+ data.Refresh(connection)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/connection/resource.go b/internal/provider/connection/resource.go
index ddd3a98..49616cb 100644
--- a/internal/provider/connection/resource.go
+++ b/internal/provider/connection/resource.go
@@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
)
@@ -31,6 +32,14 @@ func (r *connectionResource) Metadata(_ context.Context, req resource.MetadataRe
resp.TypeName = req.ProviderTypeName + "_connection"
}
+// Schema returns the resource schema.
+func (r *connectionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Connection Resource",
+ Attributes: schemaAttributes(),
+ }
+}
+
// Configure adds the provider configured client to the resource.
func (r *connectionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
diff --git a/internal/provider/connection/schema.go b/internal/provider/connection/schema.go
index 17cfd14..9908d8f 100644
--- a/internal/provider/connection/schema.go
+++ b/internal/provider/connection/schema.go
@@ -1,19 +1,16 @@
package connection
import (
- "context"
-
"terraform-provider-hookdeck/internal/validators"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
- "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"
)
-func (r *connectionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+func schemaAttributes() map[string]schema.Attribute {
filterRulePropertySchema := schema.SingleNestedAttribute{
Optional: true,
Attributes: map[string]schema.Attribute{
@@ -36,148 +33,144 @@ func (r *connectionResource) Schema(_ context.Context, _ resource.SchemaRequest,
},
}
- resp.Schema = schema.Schema{
- MarkdownDescription: "Connection Resource",
-
- Attributes: map[string]schema.Attribute{
- "created_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "Date the connection was created",
+ return map[string]schema.Attribute{
+ "created_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "description": schema.StringAttribute{
- Optional: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "Description for the connection",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "destination_id": schema.StringAttribute{
- Required: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.RequiresReplace(),
- },
- Description: "ID of a destination to bind to the connection",
+ Description: "Date the connection was created",
+ },
+ "description": schema.StringAttribute{
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "disabled_at": schema.StringAttribute{
- Computed: true,
- Optional: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: "Date the connection was disabled",
+ Description: "Description for the connection",
+ },
+ "destination_id": schema.StringAttribute{
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
},
- "id": schema.StringAttribute{
- Computed: true,
- Description: `ID of the connection`,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
+ Description: "ID of a destination to bind to the connection",
+ },
+ "disabled_at": schema.StringAttribute{
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "name": schema.StringAttribute{
- Optional: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: `A unique, human-friendly name for the connection`,
+ Description: "Date the connection was disabled",
+ },
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: `ID of the connection`,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "paused_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: "Date the connection was paused",
+ },
+ "name": schema.StringAttribute{
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "rules": schema.SetNestedAttribute{
- Optional: true,
- NestedObject: schema.NestedAttributeObject{
- Attributes: map[string]schema.Attribute{
- "delay_rule": schema.SingleNestedAttribute{
- Optional: true,
- Attributes: map[string]schema.Attribute{
- "delay": schema.Int64Attribute{
- Required: true,
- Description: `Delay to introduce in MS`,
- },
+ Description: `A unique, human-friendly name for the connection`,
+ },
+ "paused_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
+ },
+ Description: "Date the connection was paused",
+ },
+ "rules": schema.SetNestedAttribute{
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "delay_rule": schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "delay": schema.Int64Attribute{
+ Required: true,
+ Description: `Delay to introduce in MS`,
},
},
- "filter_rule": schema.SingleNestedAttribute{
- Optional: true,
- Attributes: map[string]schema.Attribute{
- "body": filterRulePropertySchema,
- "headers": filterRulePropertySchema,
- "path": filterRulePropertySchema,
- "query": filterRulePropertySchema,
+ },
+ "filter_rule": schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "body": filterRulePropertySchema,
+ "headers": filterRulePropertySchema,
+ "path": filterRulePropertySchema,
+ "query": filterRulePropertySchema,
+ },
+ Validators: []validator.Object{
+ validators.AtLeastOneChild(),
+ },
+ },
+ "retry_rule": schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "count": schema.Int64Attribute{
+ Optional: true,
+ Description: `Maximum number of retries to attempt`,
},
- Validators: []validator.Object{
- validators.AtLeastOneChild(),
+ "interval": schema.Int64Attribute{
+ Optional: true,
+ Description: `Time in MS between each retry`,
},
- },
- "retry_rule": schema.SingleNestedAttribute{
- Optional: true,
- Attributes: map[string]schema.Attribute{
- "count": schema.Int64Attribute{
- Optional: true,
- Description: `Maximum number of retries to attempt`,
- },
- "interval": schema.Int64Attribute{
- Optional: true,
- Description: `Time in MS between each retry`,
- },
- "strategy": schema.StringAttribute{
- Required: true,
- Validators: []validator.String{
- stringvalidator.OneOf(
- "linear",
- "exponential",
- ),
- },
- MarkdownDescription: `must be one of ["linear", "exponential"]` + "\n" +
- `Algorithm to use when calculating delay between retries`,
+ "strategy": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(
+ "linear",
+ "exponential",
+ ),
},
+ MarkdownDescription: `must be one of ["linear", "exponential"]` + "\n" +
+ `Algorithm to use when calculating delay between retries`,
},
},
- "transform_rule": schema.SingleNestedAttribute{
- Optional: true,
- Attributes: map[string]schema.Attribute{
- "transformation_id": schema.StringAttribute{
- Required: true,
- Description: `ID of the attached transformation object.`,
- },
+ },
+ "transform_rule": schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "transformation_id": schema.StringAttribute{
+ Required: true,
+ Description: `ID of the attached transformation object.`,
},
},
},
- Validators: []validator.Object{
- validators.ExactlyOneChild(),
- },
},
- },
- "source_id": schema.StringAttribute{
- Required: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.RequiresReplace(),
+ Validators: []validator.Object{
+ validators.ExactlyOneChild(),
},
- Description: `ID of a source to bind to the connection`,
},
- "team_id": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: `ID of the workspace`,
+ },
+ "source_id": schema.StringAttribute{
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
},
- "updated_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: `Date the connection was last updated`,
+ Description: `ID of a source to bind to the connection`,
+ },
+ "team_id": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ Description: `ID of the workspace`,
+ },
+ "updated_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
+ Description: `Date the connection was last updated`,
},
}
}
diff --git a/internal/provider/destination/datasource.go b/internal/provider/destination/datasource.go
new file mode 100644
index 0000000..3bc54cc
--- /dev/null
+++ b/internal/provider/destination/datasource.go
@@ -0,0 +1,81 @@
+package destination
+
+import (
+ "context"
+ "fmt"
+ schemaHelpers "terraform-provider-hookdeck/internal/schemahelpers"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &destinationDataSource{}
+ _ datasource.DataSourceWithConfigure = &destinationDataSource{}
+)
+
+// NewDestinationDataSource is a helper function to simplify the provider implementation.
+func NewDestinationDataSource() datasource.DataSource {
+ return &destinationDataSource{}
+}
+
+// destinationDataSource is the datasource implementation.
+type destinationDataSource struct {
+ client hookdeckClient.Client
+}
+
+// Metadata returns the datasource type name.
+func (r *destinationDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_destination"
+}
+
+// Schema returns the data source schema.
+func (r *destinationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Destination Data Source",
+ Attributes: schemaHelpers.DataSourceSchemaFromResourceSchema(schemaAttributes(), "id"),
+ }
+}
+
+// Configure adds the provider configured client to the datasource.
+func (r *destinationDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*hookdeckClient.Client)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *hookdeckClient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = *client
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (r *destinationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ // Get data from Terraform state
+ var data *destinationResourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get refreshed datasource value
+ destination, err := r.client.Destination.Retrieve(context.Background(), data.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading destination", err.Error())
+ return
+ }
+
+ // Save refreshed data into Terraform state
+ data.Refresh(destination)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/destination/resource.go b/internal/provider/destination/resource.go
index 02377db..4825a69 100644
--- a/internal/provider/destination/resource.go
+++ b/internal/provider/destination/resource.go
@@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
)
@@ -31,6 +32,14 @@ func (r *destinationResource) Metadata(_ context.Context, req resource.MetadataR
resp.TypeName = req.ProviderTypeName + "_destination"
}
+// Schema returns the resource schema.
+func (r *destinationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Destination Resource",
+ Attributes: schemaAttributes(),
+ }
+}
+
// Configure adds the provider configured client to the resource.
func (r *destinationResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
diff --git a/internal/provider/destination/schema.go b/internal/provider/destination/schema.go
index 2694520..b5491f2 100644
--- a/internal/provider/destination/schema.go
+++ b/internal/provider/destination/schema.go
@@ -1,12 +1,9 @@
package destination
import (
- "context"
-
"terraform-provider-hookdeck/internal/validators"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
- "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
@@ -17,134 +14,130 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
-func (r *destinationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = schema.Schema{
- MarkdownDescription: "Destination Resource",
-
- Attributes: map[string]schema.Attribute{
- "auth_method": schema.SingleNestedAttribute{
- Computed: true,
- Optional: true,
- Attributes: getAuthenticationMethodSchemaAttributes(),
- Validators: []validator.Object{
- validators.ExactlyOneChild(),
- },
- Default: objectdefault.StaticValue(
- types.ObjectValueMust(
- getAuthenticationMethodSchemaAttrTypes(),
- getAuthenticationMethodSchemaDefaultValue(),
- ),
+func schemaAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "auth_method": schema.SingleNestedAttribute{
+ Computed: true,
+ Optional: true,
+ Attributes: getAuthenticationMethodSchemaAttributes(),
+ Validators: []validator.Object{
+ validators.ExactlyOneChild(),
+ },
+ Default: objectdefault.StaticValue(
+ types.ObjectValueMust(
+ getAuthenticationMethodSchemaAttrTypes(),
+ getAuthenticationMethodSchemaDefaultValue(),
),
- Description: `Config for the destination's auth method`,
+ ),
+ Description: `Config for the destination's auth method`,
+ },
+ "cli_path": schema.StringAttribute{
+ Optional: true,
+ Description: `Path for the CLI destination`,
+ },
+ "created_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "cli_path": schema.StringAttribute{
- Optional: true,
- Description: `Path for the CLI destination`,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "created_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: `Date the destination was created`,
- },
- "description": schema.StringAttribute{
- Optional: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "Description for the destination",
+ Description: `Date the destination was created`,
+ },
+ "description": schema.StringAttribute{
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "disabled_at": schema.StringAttribute{
- Computed: true,
- Optional: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: `Date the destination was disabled`,
+ Description: "Description for the destination",
+ },
+ "disabled_at": schema.StringAttribute{
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "http_method": schema.StringAttribute{
- // Computed: true,
- Optional: true,
- Validators: []validator.String{
- stringvalidator.OneOf(
- "GET",
- "POST",
- "PUT",
- "PATCH",
- "DELETE",
- ),
- },
- MarkdownDescription: `must be one of ["GET", "POST", "PUT", "PATCH", "DELETE"]` + "\n" +
- `HTTP method used on requests sent to the destination, overrides the method used on requests sent to the source.`,
+ Description: `Date the destination was disabled`,
+ },
+ "http_method": schema.StringAttribute{
+ // Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(
+ "GET",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE",
+ ),
},
- "id": schema.StringAttribute{
- Computed: true,
- Description: `ID of the destination`,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
+ MarkdownDescription: `must be one of ["GET", "POST", "PUT", "PATCH", "DELETE"]` + "\n" +
+ `HTTP method used on requests sent to the destination, overrides the method used on requests sent to the source.`,
+ },
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: `ID of the destination`,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "name": schema.StringAttribute{
- Required: true,
- Description: `A unique, human-friendly name for the destination`,
+ },
+ "name": schema.StringAttribute{
+ Required: true,
+ Description: `A unique, human-friendly name for the destination`,
+ },
+ "path_forwarding_disabled": schema.BoolAttribute{
+ Computed: true,
+ Optional: true,
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.UseStateForUnknown(),
},
- "path_forwarding_disabled": schema.BoolAttribute{
- Computed: true,
- Optional: true,
- PlanModifiers: []planmodifier.Bool{
- boolplanmodifier.UseStateForUnknown(),
+ },
+ "rate_limit": schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "limit": schema.Int64Attribute{
+ Required: true,
+ PlanModifiers: []planmodifier.Int64{
+ int64planmodifier.UseStateForUnknown(),
+ },
+ Description: `Limit event attempts to receive per period. Max value is workspace plan's max attempts thoughput.`,
},
- },
- "rate_limit": schema.SingleNestedAttribute{
- Optional: true,
- Attributes: map[string]schema.Attribute{
- "limit": schema.Int64Attribute{
- Required: true,
- PlanModifiers: []planmodifier.Int64{
- int64planmodifier.UseStateForUnknown(),
- },
- Description: `Limit event attempts to receive per period. Max value is workspace plan's max attempts thoughput.`,
+ "period": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(
+ "second",
+ "minute",
+ "hour",
+ ),
},
- "period": schema.StringAttribute{
- Required: true,
- Validators: []validator.String{
- stringvalidator.OneOf(
- "second",
- "minute",
- "hour",
- ),
- },
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- MarkdownDescription: `must be one of ["second", "minute", "hour"]` + "\n" +
- `Period to rate limit attempts`,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
+ MarkdownDescription: `must be one of ["second", "minute", "hour"]` + "\n" +
+ `Period to rate limit attempts`,
},
- Description: "Rate limit",
- },
- "team_id": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: `ID of the workspace`,
},
- "updated_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: `Date the destination was last updated`,
+ Description: "Rate limit",
+ },
+ "team_id": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "url": schema.StringAttribute{
- Optional: true,
- Description: `HTTP endpoint of the destination`,
+ Description: `ID of the workspace`,
+ },
+ "updated_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
+ Description: `Date the destination was last updated`,
+ },
+ "url": schema.StringAttribute{
+ Optional: true,
+ Description: `HTTP endpoint of the destination`,
},
}
}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index b88cfc7..73e1de6 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -157,7 +157,11 @@ func (p *hookdeckProvider) Resources(ctx context.Context) []func() resource.Reso
}
func (p *hookdeckProvider) DataSources(_ context.Context) []func() datasource.DataSource {
- return []func() datasource.DataSource{}
+ return []func() datasource.DataSource{
+ connection.NewConnectionDataSource,
+ destination.NewDestinationDataSource,
+ source.NewSourceDataSource,
+ }
}
func New(version string) func() provider.Provider {
diff --git a/internal/provider/source/datasource.go b/internal/provider/source/datasource.go
new file mode 100644
index 0000000..19b16dd
--- /dev/null
+++ b/internal/provider/source/datasource.go
@@ -0,0 +1,82 @@
+package source
+
+import (
+ "context"
+ "fmt"
+ schemaHelpers "terraform-provider-hookdeck/internal/schemahelpers"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ hookdeck "github.com/hookdeck/hookdeck-go-sdk"
+ hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &sourceDataSource{}
+ _ datasource.DataSourceWithConfigure = &sourceDataSource{}
+)
+
+// NewSourceDataSource is a helper function to simplify the provider implementation.
+func NewSourceDataSource() datasource.DataSource {
+ return &sourceDataSource{}
+}
+
+// sourceDataSource is the datasource implementation.
+type sourceDataSource struct {
+ client hookdeckClient.Client
+}
+
+// Metadata returns the datasource type name.
+func (r *sourceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_source"
+}
+
+// Schema returns the data source schema.
+func (r *sourceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Source Data Source",
+ Attributes: schemaHelpers.DataSourceSchemaFromResourceSchema(schemaAttributes(), "id"),
+ }
+}
+
+// Configure adds the provider configured client to the datasource.
+func (r *sourceDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*hookdeckClient.Client)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *hookdeckClient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = *client
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (r *sourceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ // Get data from Terraform state
+ var data *sourceResourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get refreshed datasource value
+ source, err := r.client.Source.Retrieve(context.Background(), data.ID.ValueString(), &hookdeck.SourceRetrieveRequest{})
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading source", err.Error())
+ return
+ }
+
+ // Save refreshed data into Terraform state
+ data.Refresh(source)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/source/resource.go b/internal/provider/source/resource.go
index 3aad547..85cd5be 100644
--- a/internal/provider/source/resource.go
+++ b/internal/provider/source/resource.go
@@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
hookdeck "github.com/hookdeck/hookdeck-go-sdk"
hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
)
@@ -32,6 +33,14 @@ func (r *sourceResource) Metadata(_ context.Context, req resource.MetadataReques
resp.TypeName = req.ProviderTypeName + "_source"
}
+// Schema returns the resource schema.
+func (r *sourceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Source Resource",
+ Attributes: schemaAttributes(),
+ }
+}
+
// Configure adds the provider configured client to the resource.
func (r *sourceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
diff --git a/internal/provider/source/schema.go b/internal/provider/source/schema.go
index 5395d65..6ae66e3 100644
--- a/internal/provider/source/schema.go
+++ b/internal/provider/source/schema.go
@@ -7,7 +7,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
- "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
@@ -17,110 +16,107 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
-func (r *sourceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+func schemaAttributes() map[string]schema.Attribute {
defaultAllowedHttpMethods, _ := types.ListValueFrom(context.Background(), types.StringType, []string{"POST", "PUT", "PATCH", "DELETE"})
- resp.Schema = schema.Schema{
- Description: "Source Resource",
- Attributes: map[string]schema.Attribute{
- "allowed_http_methods": schema.ListAttribute{
- Computed: true,
- Optional: true,
- ElementType: types.StringType,
- Validators: []validator.List{
- listvalidator.ValueStringsAre(stringvalidator.OneOf(
- "GET",
- "POST",
- "PUT",
- "PATCH",
- "DELETE",
- )),
- },
- Default: listdefault.StaticValue(defaultAllowedHttpMethods),
- Description: "List of allowed HTTP methods. Defaults to PUT, POST, PATCH, DELETE.",
+ return map[string]schema.Attribute{
+ "allowed_http_methods": schema.ListAttribute{
+ Computed: true,
+ Optional: true,
+ ElementType: types.StringType,
+ Validators: []validator.List{
+ listvalidator.ValueStringsAre(stringvalidator.OneOf(
+ "GET",
+ "POST",
+ "PUT",
+ "PATCH",
+ "DELETE",
+ )),
},
- "created_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "Date the source was created",
+ Default: listdefault.StaticValue(defaultAllowedHttpMethods),
+ Description: "List of allowed HTTP methods. Defaults to PUT, POST, PATCH, DELETE.",
+ },
+ "created_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "custom_response": schema.SingleNestedAttribute{
- Optional: true,
- PlanModifiers: []planmodifier.Object{
- objectplanmodifier.UseStateForUnknown(),
+ Description: "Date the source was created",
+ },
+ "custom_response": schema.SingleNestedAttribute{
+ Optional: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.UseStateForUnknown(),
+ },
+ Attributes: map[string]schema.Attribute{
+ "body": schema.StringAttribute{
+ Required: true,
+ Description: "Body of the custom response",
},
- Attributes: map[string]schema.Attribute{
- "body": schema.StringAttribute{
- Required: true,
- Description: "Body of the custom response",
- },
- "content_type": schema.StringAttribute{
- Required: true,
- Validators: []validator.String{
- stringvalidator.OneOf(
- "json",
- "text",
- "xml",
- ),
- },
- MarkdownDescription: "must be one of [json, text, xml]" + "\n" +
- "Content type of the custom response",
+ "content_type": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(
+ "json",
+ "text",
+ "xml",
+ ),
},
+ MarkdownDescription: "must be one of [json, text, xml]" + "\n" +
+ "Content type of the custom response",
},
- Description: "Custom response object",
},
- "description": schema.StringAttribute{
- Optional: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "Description for the source",
- },
- "disabled_at": schema.StringAttribute{
- Computed: true,
- Optional: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: "Date the source was disabled",
+ Description: "Custom response object",
+ },
+ "description": schema.StringAttribute{
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "id": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "ID of the source",
+ Description: "Description for the source",
+ },
+ "disabled_at": schema.StringAttribute{
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "name": schema.StringAttribute{
- Required: true,
- Description: "A unique, human-friendly name for the source",
+ Description: "Date the source was disabled",
+ },
+ "id": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "team_id": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "ID of the workspace",
+ Description: "ID of the source",
+ },
+ "name": schema.StringAttribute{
+ Required: true,
+ Description: "A unique, human-friendly name for the source",
+ },
+ "team_id": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "updated_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: "Date the source was last updated",
+ Description: "ID of the workspace",
+ },
+ "updated_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "url": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "A unique URL that must be supplied to your webhook's provider",
+ Description: "Date the source was last updated",
+ },
+ "url": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
+ Description: "A unique URL that must be supplied to your webhook's provider",
},
}
}
diff --git a/internal/provider/sourceverification/resource.go b/internal/provider/sourceverification/resource.go
index 0bc0fae..91fed43 100644
--- a/internal/provider/sourceverification/resource.go
+++ b/internal/provider/sourceverification/resource.go
@@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
hookdeck "github.com/hookdeck/hookdeck-go-sdk"
hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
)
@@ -32,6 +33,14 @@ func (r *sourceVerificationResource) Metadata(_ context.Context, req resource.Me
resp.TypeName = req.ProviderTypeName + "_source_verification"
}
+// Schema returns the resource schema.
+func (r *sourceVerificationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Transformation Resource",
+ Attributes: schemaAttributes(),
+ }
+}
+
// Configure adds the provider configured client to the resource.
func (r *sourceVerificationResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
diff --git a/internal/provider/sourceverification/schema.go b/internal/provider/sourceverification/schema.go
index 1657ef6..e8a34ca 100644
--- a/internal/provider/sourceverification/schema.go
+++ b/internal/provider/sourceverification/schema.go
@@ -1,95 +1,90 @@
package sourceverification
import (
- "context"
"terraform-provider-hookdeck/internal/validators"
- "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"
)
-func (r *sourceVerificationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Description: "Transformation Resource",
- Attributes: map[string]schema.Attribute{
- "source_id": schema.StringAttribute{
- Required: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.RequiresReplace(),
- },
- Description: "ID of the source",
+func schemaAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "source_id": schema.StringAttribute{
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
},
- "verification": schema.SingleNestedAttribute{
- Required: true,
- Attributes: map[string]schema.Attribute{
- // generic
- "api_key": apiKeyConfigSchema(),
- "basic_auth": basicAuthConfigSchema(),
- "hmac": hmacConfigSchema(),
- "json": jsonConfigSchema(),
- // providers
- "adyen": adyenConfigSchema(),
- "akeneo": akeneoConfigSchema(),
- "aws_sns": awsSNSConfigSchema(),
- "bondsmith": bondsmithConfigSchema(),
- "cloudsignal": cloudsignalConfigSchema(),
- "commercelayer": commercelayerConfigSchema(),
- "courier": courierConfigSchema(),
- "discord": discordConfigSchema(),
- "ebay": ebayConfigSchema(),
- "enode": enodeConfigSchema(),
- "favro": favroConfigSchema(),
- "fiserv": fiservConfigSchema(),
- "frontapp": frontAppConfigSchema(),
- "github": githubConfigSchema(),
- "gitlab": gitlabConfigSchema(),
- "linear": linearConfigSchema(),
- "mailgun": mailgunConfigSchema(),
- "nmi": nmiConfigSchema(),
- "orb": orbConfigSchema(),
- "oura": ouraConfigSchema(),
- "persona": personaConfigSchema(),
- "pipedrive": pipedriveConfigSchema(),
- "postmark": postmarkConfigSchema(),
- "property_finder": propertyFinderConfigSchema(),
- "pylon": pylonConfigSchema(),
- "razorpay": razorpayConfigSchema(),
- "recharge": rechargeConfigSchema(),
- "repay": repayConfigSchema(),
- "sanity": sanityConfigSchema(),
- "sendgrid": sendgridConfigSchema(),
- "shopify": shopifyConfigSchema(),
- "shopline": shoplineConfigSchema(),
- "slack": slackConfigSchema(),
- "solidgate": solidgateConfigSchema(),
- "square": squareConfigSchema(),
- "stripe": stripeConfigSchema(),
- "svix": svixConfigSchema(),
- "synctera": syncteraConfigSchema(),
- "tebex": tebexConfigSchema(),
- "telnyx": telnyxConfigSchema(),
- "threedeye": threeDEyeConfigSchema(),
- "tokenio": tokenIoConfigSchema(),
- "trello": trelloConfigSchema(),
- "twitch": twitchConfigSchema(),
- "twitter": twitterConfigSchema(),
- "typeform": typeformConfigSchema(),
- "vercel": vercelConfigSchema(),
- "vercel_log_drains": vercelLogDrainsConfigSchema(),
- "wix": wixConfigSchema(),
- "woocommerce": woocommerceConfigSchema(),
- "workos": workOSConfigSchema(),
- "xero": xeroConfigSchema(),
- "zoom": zoomConfigSchema(),
- },
- Validators: []validator.Object{
- validators.ExactlyOneChild(),
- },
- Description: "The verification configs for the specific verification type",
+ Description: "ID of the source",
+ },
+ "verification": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ // generic
+ "api_key": apiKeyConfigSchema(),
+ "basic_auth": basicAuthConfigSchema(),
+ "hmac": hmacConfigSchema(),
+ "json": jsonConfigSchema(),
+ // providers
+ "adyen": adyenConfigSchema(),
+ "akeneo": akeneoConfigSchema(),
+ "aws_sns": awsSNSConfigSchema(),
+ "bondsmith": bondsmithConfigSchema(),
+ "cloudsignal": cloudsignalConfigSchema(),
+ "commercelayer": commercelayerConfigSchema(),
+ "courier": courierConfigSchema(),
+ "discord": discordConfigSchema(),
+ "ebay": ebayConfigSchema(),
+ "enode": enodeConfigSchema(),
+ "favro": favroConfigSchema(),
+ "fiserv": fiservConfigSchema(),
+ "frontapp": frontAppConfigSchema(),
+ "github": githubConfigSchema(),
+ "gitlab": gitlabConfigSchema(),
+ "linear": linearConfigSchema(),
+ "mailgun": mailgunConfigSchema(),
+ "nmi": nmiConfigSchema(),
+ "orb": orbConfigSchema(),
+ "oura": ouraConfigSchema(),
+ "persona": personaConfigSchema(),
+ "pipedrive": pipedriveConfigSchema(),
+ "postmark": postmarkConfigSchema(),
+ "property_finder": propertyFinderConfigSchema(),
+ "pylon": pylonConfigSchema(),
+ "razorpay": razorpayConfigSchema(),
+ "recharge": rechargeConfigSchema(),
+ "repay": repayConfigSchema(),
+ "sanity": sanityConfigSchema(),
+ "sendgrid": sendgridConfigSchema(),
+ "shopify": shopifyConfigSchema(),
+ "shopline": shoplineConfigSchema(),
+ "slack": slackConfigSchema(),
+ "solidgate": solidgateConfigSchema(),
+ "square": squareConfigSchema(),
+ "stripe": stripeConfigSchema(),
+ "svix": svixConfigSchema(),
+ "synctera": syncteraConfigSchema(),
+ "tebex": tebexConfigSchema(),
+ "telnyx": telnyxConfigSchema(),
+ "threedeye": threeDEyeConfigSchema(),
+ "tokenio": tokenIoConfigSchema(),
+ "trello": trelloConfigSchema(),
+ "twitch": twitchConfigSchema(),
+ "twitter": twitterConfigSchema(),
+ "typeform": typeformConfigSchema(),
+ "vercel": vercelConfigSchema(),
+ "vercel_log_drains": vercelLogDrainsConfigSchema(),
+ "wix": wixConfigSchema(),
+ "woocommerce": woocommerceConfigSchema(),
+ "workos": workOSConfigSchema(),
+ "xero": xeroConfigSchema(),
+ "zoom": zoomConfigSchema(),
+ },
+ Validators: []validator.Object{
+ validators.ExactlyOneChild(),
},
+ Description: "The verification configs for the specific verification type",
},
}
}
diff --git a/internal/provider/transformation/resource.go b/internal/provider/transformation/resource.go
index 0c80883..156db38 100644
--- a/internal/provider/transformation/resource.go
+++ b/internal/provider/transformation/resource.go
@@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
hookdeck "github.com/hookdeck/hookdeck-go-sdk"
hookdeckClient "github.com/hookdeck/hookdeck-go-sdk/client"
)
@@ -33,6 +34,14 @@ func (r *transformationResource) Metadata(_ context.Context, req resource.Metada
resp.TypeName = req.ProviderTypeName + "_transformation"
}
+// Schema returns the resource schema.
+func (r *transformationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Transformation Resource",
+ Attributes: schemaAttributes(),
+ }
+}
+
// Configure adds the provider configured client to the resource.
func (r *transformationResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
diff --git a/internal/provider/transformation/schema.go b/internal/provider/transformation/schema.go
index e1e7fec..571e63a 100644
--- a/internal/provider/transformation/schema.go
+++ b/internal/provider/transformation/schema.go
@@ -1,64 +1,59 @@
package transformation
import (
- "context"
"terraform-provider-hookdeck/internal/validators"
- "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"
)
-func (r *transformationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Description: "Transformation Resource",
- Attributes: map[string]schema.Attribute{
- "code": schema.StringAttribute{
- Required: true,
- Description: "JavaScript code to be executed",
- },
- "created_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "Date the transformation was created",
- },
- "env": schema.StringAttribute{
- Optional: true,
- Sensitive: true,
- Description: "Key-value environment variables to be passed to the transformation",
+func schemaAttributes() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "code": schema.StringAttribute{
+ Required: true,
+ Description: "JavaScript code to be executed",
+ },
+ "created_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
- "id": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "ID of the transformation",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "name": schema.StringAttribute{
- Required: true,
- Description: "A unique, human-friendly name for the transformation",
+ Description: "Date the transformation was created",
+ },
+ "env": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ Description: "Key-value environment variables to be passed to the transformation",
+ },
+ "id": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "team_id": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- Description: "ID of the workspace",
+ Description: "ID of the transformation",
+ },
+ "name": schema.StringAttribute{
+ Required: true,
+ Description: "A unique, human-friendly name for the transformation",
+ },
+ "team_id": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
},
- "updated_at": schema.StringAttribute{
- Computed: true,
- Validators: []validator.String{
- validators.IsRFC3339(),
- },
- Description: "Date the transformation was last updated",
+ Description: "ID of the workspace",
+ },
+ "updated_at": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{
+ validators.IsRFC3339(),
},
+ Description: "Date the transformation was last updated",
},
}
}
diff --git a/internal/provider/webhookregistration/resource.go b/internal/provider/webhookregistration/resource.go
index c7dd375..6122f65 100644
--- a/internal/provider/webhookregistration/resource.go
+++ b/internal/provider/webhookregistration/resource.go
@@ -5,6 +5,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
// Ensure the implementation satisfies the expected interfaces.
@@ -28,6 +29,14 @@ func (r *webhookRegistrationResource) Metadata(_ context.Context, req resource.M
resp.TypeName = req.ProviderTypeName + "_webhook_registration"
}
+// Schema returns the resource schema.
+func (r *webhookRegistrationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Webhook Registration Resource",
+ Attributes: schemaAttributes(),
+ }
+}
+
// Configure adds the provider configured client to the resource.
func (r *webhookRegistrationResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
}
diff --git a/internal/provider/webhookregistration/schema.go b/internal/provider/webhookregistration/schema.go
index c8a947a..8a6cf31 100644
--- a/internal/provider/webhookregistration/schema.go
+++ b/internal/provider/webhookregistration/schema.go
@@ -1,16 +1,13 @@
package webhookregistration
import (
- "context"
-
- "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
)
-func (r *webhookRegistrationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+func schemaAttributes() map[string]schema.Attribute {
requestSchemaAttributes := map[string]schema.Attribute{
"body": schema.StringAttribute{
Optional: true,
@@ -26,34 +23,31 @@ func (r *webhookRegistrationResource) Schema(_ context.Context, _ resource.Schem
},
}
- resp.Schema = schema.Schema{
- Description: "Webhook Resource",
- Attributes: map[string]schema.Attribute{
- "register": schema.SingleNestedAttribute{
- Required: true,
- Attributes: map[string]schema.Attribute{
- "request": schema.SingleNestedAttribute{
- Required: true,
- Attributes: requestSchemaAttributes,
- },
- "response": schema.StringAttribute{
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- },
+ return map[string]schema.Attribute{
+ "register": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "request": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: requestSchemaAttributes,
},
- PlanModifiers: []planmodifier.Object{
- objectplanmodifier.RequiresReplace(),
+ "response": schema.StringAttribute{
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
},
},
- "unregister": schema.SingleNestedAttribute{
- Optional: true,
- Attributes: map[string]schema.Attribute{
- "request": schema.SingleNestedAttribute{
- Required: true,
- Attributes: requestSchemaAttributes,
- },
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.RequiresReplace(),
+ },
+ },
+ "unregister": schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "request": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: requestSchemaAttributes,
},
},
},
diff --git a/internal/schemahelpers/schemahelpers.go b/internal/schemahelpers/schemahelpers.go
new file mode 100644
index 0000000..19e9064
--- /dev/null
+++ b/internal/schemahelpers/schemahelpers.go
@@ -0,0 +1,167 @@
+package schemahelpers
+
+import (
+ "log"
+
+ datasource_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ resource_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+)
+
+func DataSourceSchemaFromResourceSchema(resourceSchema map[string]resource_schema.Attribute, idField string) map[string]datasource_schema.Attribute {
+ foundIdField := false
+
+ datasourceSchema := make(map[string]datasource_schema.Attribute)
+ for name, srcAttr := range resourceSchema {
+ optional := false
+ required := false
+ computed := true
+
+ if name == idField {
+ required = true
+ computed = false
+ foundIdField = true
+ }
+
+ switch srcAttrTyped := srcAttr.(type) {
+ case resource_schema.StringAttribute:
+ datasourceSchema[name] = datasource_schema.StringAttribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.BoolAttribute:
+ datasourceSchema[name] = datasource_schema.BoolAttribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.NumberAttribute:
+ datasourceSchema[name] = datasource_schema.NumberAttribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.Int64Attribute:
+ datasourceSchema[name] = datasource_schema.Int64Attribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.Int32Attribute:
+ datasourceSchema[name] = datasource_schema.Int32Attribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.ListAttribute:
+ datasourceSchema[name] = datasource_schema.ListAttribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ ElementType: srcAttrTyped.ElementType,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.MapAttribute:
+ datasourceSchema[name] = datasource_schema.MapAttribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ ElementType: srcAttrTyped.ElementType,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.SetAttribute:
+ datasourceSchema[name] = datasource_schema.SetAttribute{
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ ElementType: srcAttrTyped.ElementType,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.SetNestedAttribute:
+ datasourceSchema[name] = datasource_schema.SetNestedAttribute{
+ NestedObject: datasource_schema.NestedAttributeObject{
+ Attributes: DataSourceSchemaFromResourceSchema(srcAttrTyped.NestedObject.Attributes, ""),
+ CustomType: srcAttrTyped.NestedObject.CustomType,
+ Validators: srcAttrTyped.NestedObject.Validators,
+ },
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.ObjectAttribute:
+ datasourceSchema[name] = datasource_schema.ObjectAttribute{
+ AttributeTypes: srcAttrTyped.AttributeTypes,
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ case resource_schema.SingleNestedAttribute:
+ datasourceSchema[name] = datasource_schema.SingleNestedAttribute{
+ Attributes: DataSourceSchemaFromResourceSchema(srcAttrTyped.Attributes, ""),
+ Validators: srcAttrTyped.Validators,
+ Description: srcAttrTyped.Description,
+ MarkdownDescription: srcAttrTyped.MarkdownDescription,
+ CustomType: srcAttrTyped.CustomType,
+ Sensitive: srcAttrTyped.Sensitive,
+ Optional: optional,
+ Required: required,
+ Computed: computed,
+ }
+ default:
+ log.Panicf("unknown attribute type: %v", srcAttr.GetType().String())
+ }
+ }
+
+ if !foundIdField && idField != "" {
+ log.Panicf("id field \"%s\" not found in resource schema", idField)
+ }
+
+ return datasourceSchema
+}