Skip to content

Commit

Permalink
SCALRCORE-33497 Provider > Manage remote state consumers (#384)
Browse files Browse the repository at this point in the history
* SCALRCORE-33497 Add remote_state_consumers attribute to scalr_workspace

* SCALRCORE-33497 Ignore linter for deprecated check

* SCALRCORE-33497 Add test
  • Loading branch information
petroprotsakh authored Jan 21, 2025
1 parent cae7e2d commit fc6b575
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `scalr_policy_group` and `data.scalr_policy_group`: new attribute `common_functions_folder` ([#380](https://github.com/Scalr/terraform-provider-scalr/pull/380))
- `scalr_workspace`: new attribute `remote_state_consumers` ([#384](https://github.com/Scalr/terraform-provider-scalr/pull/384))

## [2.3.0] - 2024-12-20

Expand Down
1 change: 1 addition & 0 deletions docs/resources/workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ resource "scalr_workspace" "example-b" {
- `module_version_id` (String) The identifier of a module version in the format `modver-<RANDOM STRING>`. This attribute conflicts with `vcs_provider_id` and `vcs_repo` attributes.
- `operations` (Boolean, Deprecated) Set (true/false) to configure workspace remote execution. When `false` workspace is only used to store state. Defaults to `true`.
- `provider_configuration` (Block Set) Provider configurations used in workspace runs. (see [below for nested schema](#nestedblock--provider_configuration))
- `remote_state_consumers` (Set of String) The list of workspace identifiers that are allowed to access the state of this workspace. Use `["*"]` to share the state with all the workspaces within the environment.
- `run_operation_timeout` (Number) The number of minutes run operation can be executed before termination. Defaults to `0` (not set, backend default is used).
- `ssh_key_id` (String) The identifier of the SSH key to use for the workspace.
- `tag_ids` (Set of String) List of tag IDs associated with the workspace.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0
github.com/hashicorp/terraform-plugin-testing v1.11.0
github.com/hashicorp/terraform-svchost v0.1.1
github.com/scalr/go-scalr v0.0.0-20250106085405-b4b290b8364e
github.com/scalr/go-scalr v0.0.0-20250115101005-ccf67688eb14
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/scalr/go-scalr v0.0.0-20250106085405-b4b290b8364e h1:Ae/dv3iR7viRcrqxsGzs4e1g6i/3gT+1m8nYWuV+U5U=
github.com/scalr/go-scalr v0.0.0-20250106085405-b4b290b8364e/go.mod h1:p34SHb25YRvbgft7SUjSDYESeoQhWzAlxGXId/BbaSE=
github.com/scalr/go-scalr v0.0.0-20250115101005-ccf67688eb14 h1:lzee+F20vQN/iQA0eQZGS1ZXtf7la1ak3cdZdo739BI=
github.com/scalr/go-scalr v0.0.0-20250115101005-ccf67688eb14/go.mod h1:p34SHb25YRvbgft7SUjSDYESeoQhWzAlxGXId/BbaSE=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
Expand Down
12 changes: 12 additions & 0 deletions internal/provider/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ func InterfaceArrToTagRelationArr(arr []interface{}) []*scalr.TagRelation {
return tags
}

func InterfaceArrToWorkspaceRelationArr(arr []interface{}) []*scalr.WorkspaceRelation {
relations := make([]*scalr.WorkspaceRelation, 0)
for _, id := range arr {
strID := id.(string)
if strID == "*" {
continue
}
relations = append(relations, &scalr.WorkspaceRelation{ID: strID})
}
return relations
}

func getDefaultScalrAccountID() (string, bool) {
if v := os.Getenv(defaults.CurrentAccountIDEnvVar); v != "" {
return v, true
Expand Down
84 changes: 83 additions & 1 deletion internal/provider/resource_scalr_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,13 @@ func resourceScalrWorkspace() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"remote_state_consumers": {
Description: "The list of workspace identifiers that are allowed to access the state of this workspace. Use `[\"*\"]` to share the state with all the workspaces within the environment.",
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
Expand Down Expand Up @@ -537,6 +544,19 @@ func resourceScalrWorkspaceCreate(ctx context.Context, d *schema.ResourceData, m
options.Tags = tags
}

remoteStateConsumers := make([]*scalr.WorkspaceRelation, 0)
if consumersI, ok := d.GetOkExists("remote_state_consumers"); ok { //nolint:staticcheck
options.RemoteStateSharing = ptr(false)
consumers := consumersI.(*schema.Set).List()
if (len(consumers) == 1) && (consumers[0].(string) == "*") {
options.RemoteStateSharing = ptr(true)
} else if len(consumers) > 0 {
for _, ws := range consumers {
remoteStateConsumers = append(remoteStateConsumers, &scalr.WorkspaceRelation{ID: ws.(string)})
}
}
}

log.Printf("[DEBUG] Create workspace %s for environment: %s", name, environmentID)
workspace, err := scalrClient.Workspaces.Create(ctx, options)
if err != nil {
Expand Down Expand Up @@ -571,6 +591,13 @@ func resourceScalrWorkspaceCreate(ctx context.Context, d *schema.ResourceData, m
}
}

if len(remoteStateConsumers) > 0 {
err = scalrClient.RemoteStateConsumers.Add(ctx, workspace.ID, remoteStateConsumers)
if err != nil {
return diag.Errorf("Error adding remote state consumers to workspace: %v", err)
}
}

return resourceScalrWorkspaceRead(ctx, d, meta)
}

Expand Down Expand Up @@ -695,6 +722,30 @@ func resourceScalrWorkspaceRead(ctx context.Context, d *schema.ResourceData, met
}
_ = d.Set("tag_ids", tagIDs)

if workspace.RemoteStateSharing {
all := []string{"*"}
_ = d.Set("remote_state_consumers", all)
} else {
consumers := make([]string, 0)
listOpts := scalr.ListOptions{}
for {
cl, err := scalrClient.RemoteStateConsumers.List(ctx, id, listOpts)
if err != nil {
return diag.Errorf("Error reading remote state consumers: %v", err)
}

for _, c := range cl.Items {
consumers = append(consumers, c.ID)
}

if cl.CurrentPage >= cl.TotalPages {
break
}
listOpts.PageNumber = cl.NextPage
}
_ = d.Set("remote_state_consumers", consumers)
}

return nil
}

Expand All @@ -709,7 +760,8 @@ func resourceScalrWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m
d.HasChange("vcs_provider_id") || d.HasChange("agent_pool_id") || d.HasChange("deletion_protection_enabled") ||
d.HasChange("hooks") || d.HasChange("module_version_id") || d.HasChange("var_files") ||
d.HasChange("run_operation_timeout") || d.HasChange("iac_platform") ||
d.HasChange("type") || d.HasChange("terragrunt_version") || d.HasChange("terragrunt_use_run_all") {
d.HasChange("type") || d.HasChange("terragrunt_version") || d.HasChange("terragrunt_use_run_all") ||
d.HasChange("remote_state_consumers") {
// Create a new options struct.
options := scalr.WorkspaceUpdateOptions{
Name: ptr(d.Get("name").(string)),
Expand Down Expand Up @@ -828,6 +880,14 @@ func resourceScalrWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m
}
}

if consumersI, ok := d.GetOkExists("remote_state_consumers"); ok { //nolint:staticcheck
options.RemoteStateSharing = ptr(false)
consumers := consumersI.(*schema.Set).List()
if (len(consumers) == 1) && (consumers[0].(string) == "*") {
options.RemoteStateSharing = ptr(true)
}
}

log.Printf("[DEBUG] Update workspace %s", id)
_, err := scalrClient.Workspaces.Update(ctx, id, options)
if err != nil {
Expand Down Expand Up @@ -923,6 +983,28 @@ func resourceScalrWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m
}
}

if d.HasChange("remote_state_consumers") {
oldConsumers, newConsumers := d.GetChange("remote_state_consumers")
oldSet := oldConsumers.(*schema.Set)
newSet := newConsumers.(*schema.Set)
consumersToAdd := InterfaceArrToWorkspaceRelationArr(newSet.Difference(oldSet).List())
consumersToDelete := InterfaceArrToWorkspaceRelationArr(oldSet.Difference(newSet).List())

if len(consumersToAdd) > 0 {
err := scalrClient.RemoteStateConsumers.Add(ctx, id, consumersToAdd)
if err != nil {
return diag.Errorf("Error adding remote state consumers: %v", err)
}
}

if len(consumersToDelete) > 0 {
err := scalrClient.RemoteStateConsumers.Delete(ctx, id, consumersToDelete)
if err != nil {
return diag.Errorf("Error deleting remote state consumers: %v", err)
}
}
}

return resourceScalrWorkspaceRead(ctx, d, meta)
}

Expand Down
107 changes: 107 additions & 0 deletions internal/provider/resource_scalr_workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,37 @@ func TestAccScalrWorkspaceSSHKey(t *testing.T) {
})
}

func TestAccScalrWorkspaceStateConsumers(t *testing.T) {
workspace := &scalr.Workspace{}
rInt := GetRandomInteger()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: protoV5ProviderFactories(t),
CheckDestroy: testAccCheckScalrWorkspaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccScalrWorkspaceWithStateConsumersConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckScalrWorkspaceExists("scalr_workspace.test", workspace),
resource.TestCheckResourceAttr(
"scalr_workspace.test", "remote_state_consumers.#", "2"),
testAccCheckScalrWorkspaceStateSharing("scalr_workspace.test", false),
),
},
{
Config: testAccScalrWorkspaceWithStateConsumersUpdatedConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckScalrWorkspaceExists("scalr_workspace.test", workspace),
resource.TestCheckResourceAttr(
"scalr_workspace.test", "remote_state_consumers.#", "1"),
testAccCheckScalrWorkspaceStateSharing("scalr_workspace.test", true),
),
},
},
})
}

func testAccCheckScalrSSHKeyExists(n string, sshKey *scalr.SSHKey) resource.TestCheckFunc {
return func(s *terraform.State) error {
scalrClient := testAccProviderSDK.Meta().(*scalr.Client)
Expand Down Expand Up @@ -557,6 +588,34 @@ func testAccCheckScalrWorkspaceProviderConfigurationsUpdated(
}
}

func testAccCheckScalrWorkspaceStateSharing(
n string, isShared bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
scalrClient := testAccProviderSDK.Meta().(*scalr.Client)

rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No instance ID is set")
}

// Get the workspace
w, err := scalrClient.Workspaces.ReadByID(ctx, rs.Primary.ID)
if err != nil {
return err
}

if w.RemoteStateSharing != isShared {
return fmt.Errorf("Expected RemoteStateSharing %t, got %t", isShared, w.RemoteStateSharing)
}

return nil
}
}

func testAccCheckScalrWorkspaceDestroy(s *terraform.State) error {
scalrClient := testAccProviderSDK.Meta().(*scalr.Client)

Expand Down Expand Up @@ -853,3 +912,51 @@ resource "scalr_workspace" "test" {
working_directory = ""
}`, rInt, defaultAccount, sshKeyName)
}

func testAccScalrWorkspaceWithStateConsumersConfig(rInt int) string {
return fmt.Sprintf(`
resource "scalr_environment" "test" {
name = "test-env-%[1]d"
account_id = "%[2]s"
}
resource "scalr_workspace" "consumer1" {
name = "consumer1-%[1]d"
environment_id = scalr_environment.test.id
}
resource "scalr_workspace" "consumer2" {
name = "consumer2-%[1]d"
environment_id = scalr_environment.test.id
}
resource "scalr_workspace" "test" {
name = "state-sharing-%[1]d"
environment_id = scalr_environment.test.id
remote_state_consumers = [ scalr_workspace.consumer1.id, scalr_workspace.consumer2.id ]
}`, rInt, defaultAccount)
}

func testAccScalrWorkspaceWithStateConsumersUpdatedConfig(rInt int) string {
return fmt.Sprintf(`
resource "scalr_environment" "test" {
name = "test-env-%[1]d"
account_id = "%[2]s"
}
resource "scalr_workspace" "consumer1" {
name = "consumer1-%[1]d"
environment_id = scalr_environment.test.id
}
resource "scalr_workspace" "consumer2" {
name = "consumer2-%[1]d"
environment_id = scalr_environment.test.id
}
resource "scalr_workspace" "test" {
name = "state-sharing-%[1]d"
environment_id = scalr_environment.test.id
remote_state_consumers = [ "*" ]
}`, rInt, defaultAccount)
}

0 comments on commit fc6b575

Please sign in to comment.