From 342de1f33ad1f68070767c16ff7c60edf1f823d6 Mon Sep 17 00:00:00 2001
From: Petro Protsakh
Date: Wed, 15 Jan 2025 17:21:59 +0200
Subject: [PATCH 1/3] SCALRCORE-33497 Add remote_state_consumers attribute to
scalr_workspace
---
CHANGELOG.md | 3 +
docs/resources/workspace.md | 1 +
go.mod | 2 +-
go.sum | 4 +-
internal/provider/helpers.go | 12 +++
internal/provider/resource_scalr_workspace.go | 84 ++++++++++++++++++-
6 files changed, 102 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7be1f809..9cc33f44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/docs/resources/workspace.md b/docs/resources/workspace.md
index 2f77a9ed..8ffb3ea0 100644
--- a/docs/resources/workspace.md
+++ b/docs/resources/workspace.md
@@ -163,6 +163,7 @@ resource "scalr_workspace" "example-b" {
- `module_version_id` (String) The identifier of a module version in the format `modver-`. 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.
diff --git a/go.mod b/go.mod
index 8fa4a840..e5f9a7ba 100644
--- a/go.mod
+++ b/go.mod
@@ -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 (
diff --git a/go.sum b/go.sum
index e81a1601..fb7c1dc1 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go
index a10d3916..93194d99 100644
--- a/internal/provider/helpers.go
+++ b/internal/provider/helpers.go
@@ -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
diff --git a/internal/provider/resource_scalr_workspace.go b/internal/provider/resource_scalr_workspace.go
index 546ebf0e..714bc013 100644
--- a/internal/provider/resource_scalr_workspace.go
+++ b/internal/provider/resource_scalr_workspace.go
@@ -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},
+ },
},
}
}
@@ -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 {
+ 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 {
@@ -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)
}
@@ -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
}
@@ -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)),
@@ -828,6 +880,14 @@ func resourceScalrWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m
}
}
+ if consumersI, ok := d.GetOkExists("remote_state_consumers"); ok {
+ 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 {
@@ -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)
}
From 89f0431d4acb05e09dd419d5864d4bffc4df9acb Mon Sep 17 00:00:00 2001
From: Petro Protsakh
Date: Wed, 15 Jan 2025 17:24:49 +0200
Subject: [PATCH 2/3] SCALRCORE-33497 Ignore linter for deprecated check
---
internal/provider/resource_scalr_workspace.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/internal/provider/resource_scalr_workspace.go b/internal/provider/resource_scalr_workspace.go
index 714bc013..fece4bbe 100644
--- a/internal/provider/resource_scalr_workspace.go
+++ b/internal/provider/resource_scalr_workspace.go
@@ -545,7 +545,7 @@ func resourceScalrWorkspaceCreate(ctx context.Context, d *schema.ResourceData, m
}
remoteStateConsumers := make([]*scalr.WorkspaceRelation, 0)
- if consumersI, ok := d.GetOkExists("remote_state_consumers"); ok {
+ 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) == "*") {
@@ -880,7 +880,7 @@ func resourceScalrWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m
}
}
- if consumersI, ok := d.GetOkExists("remote_state_consumers"); ok {
+ 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) == "*") {
From 79b066b55616ac3eda25f338aeda89a9f1504c32 Mon Sep 17 00:00:00 2001
From: Petro Protsakh
Date: Thu, 16 Jan 2025 11:31:31 +0200
Subject: [PATCH 3/3] SCALRCORE-33497 Add test
---
.../provider/resource_scalr_workspace_test.go | 107 ++++++++++++++++++
1 file changed, 107 insertions(+)
diff --git a/internal/provider/resource_scalr_workspace_test.go b/internal/provider/resource_scalr_workspace_test.go
index c8df2227..c3a1027a 100644
--- a/internal/provider/resource_scalr_workspace_test.go
+++ b/internal/provider/resource_scalr_workspace_test.go
@@ -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)
@@ -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)
@@ -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)
+}