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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,13 @@ New version should show up on Terraform Repository in 10-15 minutes.
# Update Retool CLI
Retool CLI has a `terraform` command that generates Terraform configuration from existing Retool org: https://github.com/tryretool/retool-cli/blob/master/src/commands/terraform.ts.
If you added a new resource or updated an existing one, you should update Retool CLI as well, so that your changes are reflected in auto-generated configuration.

# Reviewing

Reviewing PRs to this repo can be a challenge, particularly when they impact the OpenAPI generated code for the SDK.

As a general rule, code within `internal/provider/sdk` will be generated, and can largely be ignored during reviews.

Code _outside_ of this should be given close attention. `internal/provider` code is what drives the terraform behavior, and should be reviewed for correctness and backward compatibility. Tests should accompany new features, and reviewers should make a habit of checking out PR branches and running them against realisitic example retool deployments.

Updates should always include documentation updates for new/changed behaviors.
2 changes: 1 addition & 1 deletion docs/data-sources/folders.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ output "all_folders" {

Read-Only:

- `folder_type` (String) The type of the folder: (app|file|resource|workflow).
- `folder_type` (String) The type of the folder: (app|file|resource|workflow|agent).
- `id` (String) The id of the folder.
- `is_system_folder` (Boolean) Whether the folder is a system folder.
- `legacy_id` (String) The legacy id of the folder.
Expand Down
2 changes: 2 additions & 0 deletions docs/data-sources/groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ output "all_groups" {

Read-Only:

- `account_details_access` (Boolean) Whether the group has access to account details.
- `audit_log_access` (Boolean) Whether the group has audit log access.
- `created_at` (String) The date the group was created.
- `id` (String) The id of the group.
- `landing_page_app_id` (String) The id of the landing page app for the group.
- `legacy_id` (String) The legacy id of the group.
- `members` (Attributes List) The members of the group. (see [below for nested schema](#nestedatt--groups--members))
- `name` (String) The name of the group.
- `theme_access` (Boolean) Whether the group has access to edit themes.
- `universal_app_access` (String) The universal app access level of the group.
- `universal_query_library_access` (String) The universal query library access level of the group.
- `universal_resource_access` (String) The universal resource access level of the group.
Expand Down
36 changes: 18 additions & 18 deletions docs/resources/configuration_variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,46 @@ Manages a Retool configuration variable. Configuration variables are used to sto

```terraform
resource "retool_configuration_variable" "test_config_var" {
name = "Test Space"
value = [
name = "Test Config Var"
values = [
{
environment_id = "production"
environment_id = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" # prod
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quality of life improvement here. IDs for envs are always UUIDs, not names.

value = "value1"
},
{
environment_id = "staging"
environment_id = "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" # staging
value = "value2"
}
]
}

resource "retool_configuration_variable" "test_config_var_with_descritpion" {
name = "Test Space with creation options"
resource "retool_configuration_variable" "test_config_var_with_description" {
name = "Test Config Var with description"
description = "This is a test configuration variable with a description"
value = [
values = [
{
environment_id = "production"
environment_id = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" # prod
value = "value1"
},
{
environment_id = "staging"
environment_id = "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" # staging
value = "value2"
}
]
}

resource "retool_configuration_variable" "test_config_var_as_secret" {
name = "Test Space with creation options"
description = "This is a test configuration variable with a description"
name = "Test Secret Config Var"
description = "This is a secret configuration variable"
secret = true
value = [
values = [
{
environment_id = "production"
value = "value1"
environment_id = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" # prod
value = "secret_value1"
},
{
environment_id = "staging"
value = "value2"
environment_id = "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" # staging
value = "secret_value2"
}
]
}
Expand All @@ -68,18 +68,18 @@ resource "retool_configuration_variable" "test_config_var_as_secret" {
### Optional

- `description` (String) A brief description of the configuration variable's purpose.
- `secret` (Boolean) Whether the configuration variable is a secret. Secrets are encrypted and not exposed in the Retool UI.

### Read-Only

- `id` (String) The unique identifier for the configuration variable.
- `secret` (Boolean) Whether the configuration variable is a secret. Secrets are encrypted and not exposed in the Retool UI. Secert is currently not supported as the values are encrypted and cannot be retrieved via the API.

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

Required:

- `environment_id` (String) The ID of the environment this value is associated with.
- `value` (String) The value of the configuration variable for the specified environment.
- `value` (String, Sensitive) The value of the configuration variable for the specified environment.


12 changes: 11 additions & 1 deletion docs/resources/folder.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,24 @@ resource "retool_folder" "resource_example" {
name = "Terraform Example Folder"
folder_type = "resource"
}
resource "retool_folder" "workflow_example" {
name = "Terraform Workflow Folder"
folder_type = "workflow"
}
resource "retool_folder" "agent_example" {
name = "Terraform Agent Folder"
folder_type = "agent"
}
```

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

### Required

- `folder_type` (String) The type of the folder: (app|resource|workflow).
- `folder_type` (String) The type of the folder: (app|resource|workflow|agent).
- `name` (String) The name of the folder.

### Optional
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ resource "retool_group" "example" {
audit_log_access = true
unpublished_release_access = false
usage_analytics_access = true
theme_access = false
account_details_access = true
landing_page_app_id = "c37676ba-116f-11ea-b17d-d7734e1526f2"
}
Expand All @@ -39,6 +40,7 @@ resource "retool_group" "example" {
- `account_details_access` (Boolean) Whether the group has access to account details.
- `audit_log_access` (Boolean) Whether the group has access to the audit log.
- `landing_page_app_id` (String) The app ID of the landing page.
- `theme_access` (Boolean) Whether the group has access to edit themes.
- `universal_app_access` (String) The universal app access level for the group. This denotes the access level that this group has for all apps. Accepted values: none|use|edit|own
- `universal_query_library_access` (String) Level of access that the group has to the Query Library. Accepted values: none|use|edit
- `universal_resource_access` (String) The universal resource access level for the group. This denotes the access level that this group has for all resources. Accepted values: none|use|edit|own
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/source_control_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ resource "retool_source_control_settings" "scm_settings" {
custom_pull_request_template_enabled = true
custom_pull_request_template = "custom-pull-request-template"
version_control_locked = true
auto_cleanup_branches_enabled = false
force_uuid_mapping = false
}
```
Expand All @@ -26,6 +27,7 @@ resource "retool_source_control_settings" "scm_settings" {
### Optional

- `auto_branch_naming_enabled` (Boolean) When enabled, Retool automatically suggests a branch name on branch creation. Defaults to true.
- `auto_cleanup_branches_enabled` (Boolean) When enabled, Retool automatically cleans up branches after they are merged. Defaults to false.
- `custom_pull_request_template` (String) Pull requests created from Retool will use the template specified.
- `custom_pull_request_template_enabled` (Boolean) When enabled, Retool will use the template specified to create pull requests. Defaults to false.
- `force_uuid_mapping` (Boolean) When set to true, creates a uuid mapping for protected elements to be used in the source control repo. Defaults to false.
Expand Down
32 changes: 16 additions & 16 deletions examples/resources/retool_configuration_variable/resource.tf
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
resource "retool_configuration_variable" "test_config_var" {
name = "Test Space"
value = [
name = "Test Config Var"
values = [
{
environment_id = "production"
environment_id = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" # prod
value = "value1"
},
{
environment_id = "staging"
environment_id = "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" # staging
value = "value2"
}
]
}

resource "retool_configuration_variable" "test_config_var_with_descritpion" {
name = "Test Space with creation options"
resource "retool_configuration_variable" "test_config_var_with_description" {
name = "Test Config Var with description"
description = "This is a test configuration variable with a description"
value = [
values = [
{
environment_id = "production"
environment_id = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" # prod
value = "value1"
},
{
environment_id = "staging"
environment_id = "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" # staging
value = "value2"
}
]
}

resource "retool_configuration_variable" "test_config_var_as_secret" {
name = "Test Space with creation options"
description = "This is a test configuration variable with a description"
name = "Test Secret Config Var"
description = "This is a secret configuration variable"
secret = true
value = [
values = [
{
environment_id = "production"
value = "value1"
environment_id = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" # prod
value = "secret_value1"
},
{
environment_id = "staging"
value = "value2"
environment_id = "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" # staging
value = "secret_value2"
}
]
}
10 changes: 10 additions & 0 deletions examples/resources/retool_folder/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@ resource "retool_folder" "resource_example" {
name = "Terraform Example Folder"
folder_type = "resource"
}

resource "retool_folder" "workflow_example" {
name = "Terraform Workflow Folder"
folder_type = "workflow"
}

resource "retool_folder" "agent_example" {
name = "Terraform Agent Folder"
folder_type = "agent"
}
1 change: 1 addition & 0 deletions examples/resources/retool_group/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ resource "retool_group" "example" {
audit_log_access = true
unpublished_release_access = false
usage_analytics_access = true
theme_access = false
account_details_access = true
landing_page_app_id = "c37676ba-116f-11ea-b17d-d7734e1526f2"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ resource "retool_source_control_settings" "scm_settings" {
custom_pull_request_template_enabled = true
custom_pull_request_template = "custom-pull-request-template"
version_control_locked = true
auto_cleanup_branches_enabled = false
force_uuid_mapping = false
}
11 changes: 11 additions & 0 deletions internal/acctest/http_recorder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package acctest

import (
"crypto/tls"
"net/http"
"os"
"path"
Expand Down Expand Up @@ -28,11 +29,21 @@ const (
func newHTTPRecorder(t *testing.T) *recorder.Recorder {
t.Helper()

// Create an HTTP client with TLS verification disabled for recording.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to make this change to record the tests, at least with an agent.

httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // #nosec G402 -- This is for test recording only
},
},
}

recorderTransport, err := recorder.NewWithOptions(
&recorder.Options{
CassetteName: cassetteName(t.Name()),
Mode: recorder.ModeRecordOnce,
SkipRequestLatency: true,
RealTransport: httpClient.Transport,
},
)
require.NoError(t, err)
Expand Down
60 changes: 44 additions & 16 deletions internal/provider/configurationvariable/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ func (r *configurationVariableResource) Schema(_ context.Context, _ resource.Sch
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"secret": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "Whether the configuration variable is a secret. Secrets are encrypted and not exposed in the Retool UI. Secert is currently not supported as the values are encrypted and cannot be retrieved via the API.",
Description: "Whether the configuration variable is a secret. Secrets are encrypted and not exposed in the Retool UI.",
Default: booldefault.StaticBool(false),
},
"values": schema.ListNestedAttribute{
Expand All @@ -114,6 +115,7 @@ func (r *configurationVariableResource) Schema(_ context.Context, _ resource.Sch
"value": schema.StringAttribute{
Description: "The value of the configuration variable for the specified environment.",
Required: true,
Sensitive: true,
Validators: []validator.String{stringvalidator.LengthBetween(1, 4096)},
},
},
Expand All @@ -139,7 +141,7 @@ func (r *configurationVariableResource) Create(ctx context.Context, req resource
if !plan.Description.IsNull() {
configuratonVariable.Description = plan.Description.ValueStringPointer()
}
configuratonVariable.Secret = false
configuratonVariable.Secret = plan.Secret.ValueBool()

var values []api.ConfigurationVariablesGet200ResponseDataInnerValuesInner
for _, v := range plan.Values {
Expand Down Expand Up @@ -216,22 +218,48 @@ func (r *configurationVariableResource) Read(ctx context.Context, req resource.R
} else {
state.Description = types.StringNull()
}
if response.Data.Secret {
resp.Diagnostics.AddError(
"Could not read configuration variable that is a secret",
fmt.Sprintf("Could not read configuration variable with ID %s", configurationVariableID),
)
}

state.Secret = types.BoolValue(response.Data.Secret)

// Clear current values and repopulate from API response to handle deletions correctly.
state.Values = nil
for _, v := range response.Data.Values {
state.Values = append(state.Values, configurationVariableValueModel{
EnvironmentID: types.StringValue(v.EnvironmentId),
Value: types.StringValue(v.Value),
})
// For secrets, the API returns encrypted values that we cannot use.
// We preserve values from the existing state (which come from the config).
// During import, state values will be empty/null, which signals to the user they must provide them.
if response.Data.Secret {
// Preserve existing state values for secrets since API returns encrypted placeholders.
// We need to update the environment IDs from the API response, but keep the values from state.
existingValues := make(map[string]types.String)
for _, v := range state.Values {
existingValues[v.EnvironmentID.ValueString()] = v.Value
}

state.Values = nil
for _, v := range response.Data.Values {
envID := v.EnvironmentId
existingValue, hasExisting := existingValues[envID]

// Use existing value if present (normal refresh), otherwise null (import scenario).
if hasExisting && !existingValue.IsNull() {
state.Values = append(state.Values, configurationVariableValueModel{
EnvironmentID: types.StringValue(envID),
Value: existingValue,
})
} else {
state.Values = append(state.Values, configurationVariableValueModel{
EnvironmentID: types.StringValue(envID),
Value: types.StringNull(), // API sends placeholder, set to null for import.
})
tflog.Warn(ctx, "Configuration variable is a secret with no value in state. Value must be provided in your Terraform configuration.", map[string]interface{}{"id": configurationVariableID, "environment_id": envID})
}
}
} else {
// Clear current values and repopulate from API response to handle deletions correctly.
state.Values = nil
for _, v := range response.Data.Values {
state.Values = append(state.Values, configurationVariableValueModel{
EnvironmentID: types.StringValue(v.EnvironmentId),
Value: types.StringValue(v.Value),
})
}
}

diags = resp.State.Set(ctx, &state)
Expand Down Expand Up @@ -269,7 +297,7 @@ func (r *configurationVariableResource) Update(ctx context.Context, req resource
updatePayload := api.ConfigurationVariablesPostRequest{
Name: plan.Name.ValueString(),
Description: plan.Description.ValueStringPointer(),
Secret: false,
Secret: plan.Secret.ValueBool(),
Values: values,
}

Expand Down
Loading
Loading