Skip to content

Commit

Permalink
[feat] Add an ability to import permissions, source control and sso r…
Browse files Browse the repository at this point in the history
…esources (#30)

* [feat] Add an ability to import permissions, source control and sso resources

* Linter fixes

* Update examples/resources/retool_sso/import.sh

Co-authored-by: Albert Chang <albertchang@users.noreply.github.com>

* Update docs/resources/sso.md

Co-authored-by: Albert Chang <albertchang@users.noreply.github.com>

---------

Co-authored-by: Albert Chang <albertchang@users.noreply.github.com>
  • Loading branch information
Dzmitry Kishylau and albertchang authored Aug 21, 2024
1 parent 54dbed7 commit 1447e33
Show file tree
Hide file tree
Showing 22 changed files with 559 additions and 188 deletions.
7 changes: 7 additions & 0 deletions docs/resources/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,11 @@ Required:
- `id` (String) The ID of the subject.
- `type` (String) The type of the subject - user or group.

## Import

Import is supported using the following syntax:

```shell
# Permissions can be imported by specifying id in the format of subject_type|subject_id
terraform import retool_permissions.example "group|123"
```
7 changes: 7 additions & 0 deletions docs/resources/source_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,11 @@ Required:
- `project_id` (String) The numerical project ID for your GitLab project. Find this ID listed below the project's name on the project's homepage.
- `url` (String) Your base GitLab URL. On GitLab Cloud, this is always https://gitlab.com. On GitLab self-managed, this is the URL where your instance is hosted.

## Import

Import is supported using the following syntax:

```shell
# Source Control configuration can be imported by specifying arbitrary string id
terraform import retool_source_control.example dummy_id
```
7 changes: 7 additions & 0 deletions docs/resources/source_control_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,11 @@ resource "retool_source_control_settings" "scm_settings" {
- `custom_pull_request_template_enabled` (Boolean) When enabled, Retool will use the template specified to create pull requests. Defaults to false.
- `version_control_locked` (Boolean) When set to true, creates a read-only instance of Retool, where app editing is disabled. Defaults to false.

## Import

Import is supported using the following syntax:

```shell
# Source Control Settings can be imported by specifying arbitrary string id
terraform import retool_source_control_settings.example dummy_id
```
7 changes: 7 additions & 0 deletions docs/resources/sso.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,11 @@ Read-Only:
- `encrypted_server_certificate` (String, Sensitive) Encrypted Server Certificate
- `encrypted_server_key` (String, Sensitive) Encrypted Server Key

## Import

Import is supported using the following syntax:

```shell
# SSO configuration can be imported by specifying arbitrary string id
terraform import retool_sso.example dummy_id
```
2 changes: 1 addition & 1 deletion examples/provider-initialization/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
terraform {
required_providers {
retool = {
source = "registry.terraform.io/retool/retool"
source = "tryretool/retool"
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions examples/resources/retool_permissions/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Permissions can be imported by specifying id in the format of subject_type|subject_id
terraform import retool_permissions.example "group|123"
2 changes: 2 additions & 0 deletions examples/resources/retool_source_control/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Source Control configuration can be imported by specifying arbitrary string id
terraform import retool_source_control.example dummy_id
2 changes: 2 additions & 0 deletions examples/resources/retool_source_control_settings/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Source Control Settings can be imported by specifying arbitrary string id
terraform import retool_source_control_settings.example dummy_id
2 changes: 2 additions & 0 deletions examples/resources/retool_sso/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SSO configuration can be imported by specifying arbitrary string id
terraform import retool_sso.example dummy_id
24 changes: 21 additions & 3 deletions internal/provider/permissions/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"context"
"fmt"
"strconv"
"strings"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
Expand All @@ -24,9 +26,9 @@ import (

// Ensure GroupResource implements the tfsdk.Resource interface.
var (
_ resource.Resource = &permissionResource{}
_ resource.ResourceWithConfigure = &permissionResource{}
// Note that unlike other resources, we don't implement ResourceWithImportState here, because there's not much to import for permission.
_ resource.Resource = &permissionResource{}
_ resource.ResourceWithConfigure = &permissionResource{}
_ resource.ResourceWithImportState = &permissionResource{}
)

type permissionResource struct {
Expand Down Expand Up @@ -480,3 +482,19 @@ func (r *permissionResource) Delete(ctx context.Context, req resource.DeleteRequ
}
}
}

func (r *permissionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// We expect id to be in the format 'subjectType|subjectId'.
parts := strings.Split(req.ID, "|")
if len(parts) != 2 {
resp.Diagnostics.AddError("Invalid import ID", "Import ID must be in the format 'subjectType|id'.")
return
}
subjType := parts[0]
id := parts[1]
subject := permissionSubjectModel{
ID: types.StringValue(id),
Type: types.StringValue(subjType),
}
resp.State.SetAttribute(ctx, path.Root("subject"), subject)
}
18 changes: 18 additions & 0 deletions internal/provider/permissions/resource_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package permissions_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"

"github.com/tryretool/terraform-provider-retool/internal/acctest"
)
Expand Down Expand Up @@ -79,6 +81,14 @@ func TestMain(m *testing.M) {
resource.TestMain(m)
}

func importStateIDFunc(state *terraform.State) (string, error) {
permissions, ok := state.RootModule().Resources["retool_permissions.test_permissions"]
if !ok {
return "", fmt.Errorf("Resource not found")
}
return "group|" + permissions.Primary.Attributes["subject.id"], nil
}

func TestAccPermissions(t *testing.T) {
acctest.Test(t, resource.TestCase{
Steps: []resource.TestStep{
Expand All @@ -94,6 +104,14 @@ func TestAccPermissions(t *testing.T) {
resource.TestCheckResourceAttr("retool_permissions.test_permissions", "permissions.0.access_level", "use"),
),
},
// Import state.
{
ResourceName: "retool_permissions.test_permissions",
ImportStateIdFunc: importStateIDFunc,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "subject.id",
},
// Update and Read.
{
Config: testUpdatedPermissionsConfig,
Expand Down
18 changes: 16 additions & 2 deletions internal/provider/sourcecontrol/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (

// Ensure sourceControlResource implements the tfsdk.Resource interface.
var (
_ resource.Resource = &sourceControlResource{}
_ resource.ResourceWithConfigure = &sourceControlResource{}
_ resource.Resource = &sourceControlResource{}
_ resource.ResourceWithConfigure = &sourceControlResource{}
_ resource.ResourceWithImportState = &sourceControlResource{}
)

type sourceControlResource struct {
Expand Down Expand Up @@ -748,3 +749,16 @@ func (r *sourceControlResource) Delete(ctx context.Context, _ resource.DeleteReq
return
}
}

// Import Source Control config.
func (r *sourceControlResource) ImportState(ctx context.Context, _ resource.ImportStateRequest, resp *resource.ImportStateResponse) {
emptyModel := sourceControlModel{
GitHub: types.ObjectNull(githubConfigModel{}.attributeTypes()),
GitLab: types.ObjectNull(gitlabConfigModel{}.attributeTypes()),
AWSCodeCommit: types.ObjectNull(awsCodeCommitConfigModel{}.attributeTypes()),
Bitbucket: types.ObjectNull(bitbucketConfigModel{}.attributeTypes()),
AzureRepos: types.ObjectNull(azureReposConfigModel{}.attributeTypes()),
}
diags := resp.State.Set(ctx, emptyModel)
resp.Diagnostics.Append(diags...)
}
10 changes: 9 additions & 1 deletion internal/provider/sourcecontrol/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestMain(m *testing.M) {
resource.TestMain(m)
}

func TestAccSourceControl(t *testing.T) {
func TestAccSourceControlTest(t *testing.T) {
acctest.Test(t, resource.TestCase{
Steps: []resource.TestStep{
// Read and Create.
Expand Down Expand Up @@ -231,6 +231,14 @@ func TestAccSourceControl(t *testing.T) {
),
ExpectNonEmptyPlan: true, // Because it'd want to refresh secret strings.
},
// Import state.
{
ResourceName: "retool_source_control.scm",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "org",
ImportStateVerifyIgnore: []string{"azure_repos.personal_access_token"}, // This attribute is secret and is set to null on read.
},
},
})
}
Expand Down
12 changes: 10 additions & 2 deletions internal/provider/sourcecontrolsettings/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import (

// Ensure SCM settings implements the tfsdk.Resource interface.
var (
_ resource.Resource = &scmSettingsResource{}
_ resource.ResourceWithConfigure = &scmSettingsResource{}
_ resource.Resource = &scmSettingsResource{}
_ resource.ResourceWithConfigure = &scmSettingsResource{}
_ resource.ResourceWithImportState = &scmSettingsResource{}
)

type scmSettingsResource struct {
Expand Down Expand Up @@ -210,3 +211,10 @@ func (r *scmSettingsResource) Delete(ctx context.Context, _ resource.DeleteReque
return
}
}

// Import Source Control settings.
func (r *scmSettingsResource) ImportState(ctx context.Context, _ resource.ImportStateRequest, resp *resource.ImportStateResponse) {
emptyState := scmSettingsModel{} // We just need to set the state to an empty object. The actual import will then happen in the Read method.
diags := resp.State.Set(ctx, emptyState)
resp.Diagnostics.Append(diags...)
}
7 changes: 7 additions & 0 deletions internal/provider/sourcecontrolsettings/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ func TestAccSourceControlSettings(t *testing.T) {
resource.TestCheckResourceAttr("retool_source_control_settings.scm_settings", "version_control_locked", "true"),
),
},
// Import state.
{
ResourceName: "retool_source_control_settings.scm_settings",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "version_control_locked",
},
// Update and Read.
{
Config: testSCMSettingsUpdated,
Expand Down
16 changes: 14 additions & 2 deletions internal/provider/sso/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (

// Ensure SSOResource implements the tfsdk.Resource interface.
var (
_ resource.Resource = &ssoResource{}
_ resource.ResourceWithConfigure = &ssoResource{}
_ resource.Resource = &ssoResource{}
_ resource.ResourceWithConfigure = &ssoResource{}
_ resource.ResourceWithImportState = &ssoResource{}
)

// ssoResource schema structure.
Expand Down Expand Up @@ -1035,3 +1036,14 @@ func (r *ssoResource) Delete(ctx context.Context, _ resource.DeleteRequest, resp
return
}
}

// Import SSO config into the state.
func (r *ssoResource) ImportState(ctx context.Context, _ resource.ImportStateRequest, resp *resource.ImportStateResponse) {
emptyState := ssoResourceModel{
Google: types.ObjectNull(googleConfigModel{}.attributeTypes()),
OIDC: types.ObjectNull(oidcConfigModel{}.attributeTypes()),
SAML: types.ObjectNull(samlConfigModel{}.attributeTypes()),
} // We just need to set the state to an empty object. The actual import will then happen in the Read method.
diags := resp.State.Set(ctx, emptyState)
resp.Diagnostics.Append(diags...)
}
8 changes: 8 additions & 0 deletions internal/provider/sso/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ func TestAccSSO(t *testing.T) {
resource.TestCheckResourceAttrSet("retool_sso.sso", "saml.ldap_config.encrypted_server_certificate"),
),
},
// Import state.
{
ResourceName: "retool_sso.sso",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "google.client_id",
ImportStateVerifyIgnore: []string{"google.client_secret", "saml.ldap_config.server_certificate", "saml.ldap_config.server_key"}, // These attributes are secret and are set to null on read.
},
// Update and Read.
{
Config: testUpdatedGoogleSamlConfig,
Expand Down
3 changes: 1 addition & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ func main() {
flag.Parse()

opts := providerserver.ServeOpts{
// TODO(dzmitry.kishylau): Update this string with the published name of Retool provider.
Address: "registry.terraform.io/retool/retool",
Address: "registry.terraform.io/tryretool/retool",
Debug: debug,
}

Expand Down
Loading

0 comments on commit 1447e33

Please sign in to comment.