diff --git a/docs/data-sources/stack.md b/docs/data-sources/stack.md index 82a96bb6..ac356bc3 100644 --- a/docs/data-sources/stack.md +++ b/docs/data-sources/stack.md @@ -27,6 +27,7 @@ data "spacelift_stack" "k8s-core" { ### Optional +- `additional_project_globs` (Set of String) Project globs is an optional list of paths to track changes of in addition to the project root. - `after_apply` (List of String) List of after-apply scripts - `after_destroy` (List of String) List of after-destroy scripts - `after_init` (List of String) List of after-init scripts diff --git a/docs/data-sources/stacks.md b/docs/data-sources/stacks.md index 79d11336..96b35c23 100644 --- a/docs/data-sources/stacks.md +++ b/docs/data-sources/stacks.md @@ -127,6 +127,7 @@ Required: Read-Only: +- `additional_project_globs` (Set of String) - `administrative` (Boolean) - `after_apply` (List of String) - `after_destroy` (List of String) diff --git a/docs/resources/stack.md b/docs/resources/stack.md index 2399c50d..5417185a 100644 --- a/docs/resources/stack.md +++ b/docs/resources/stack.md @@ -192,6 +192,7 @@ resource "spacelift_stack" "ansible-stack" { ### Optional +- `additional_project_globs` (Set of String) Project globs is an optional list of paths to track changes of in addition to the project root. - `administrative` (Boolean) Indicates whether this stack can manage others. Defaults to `false`. - `after_apply` (List of String) List of after-apply scripts - `after_destroy` (List of String) List of after-destroy scripts diff --git a/spacelift/data_stack.go b/spacelift/data_stack.go index 301cbb78..f1fbf35d 100644 --- a/spacelift/data_stack.go +++ b/spacelift/data_stack.go @@ -285,6 +285,12 @@ func dataStack() *schema.Resource { Description: "Project root is the optional directory relative to the workspace root containing the entrypoint to the Stack.", Computed: true, }, + "additional_project_globs": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Project globs is an optional list of paths to track changes of in addition to the project root.", + }, "protect_from_deletion": { Type: schema.TypeBool, Description: "Protect this stack from accidental deletion. If set, attempts to delete this stack will fail.", @@ -451,6 +457,12 @@ func dataStackRead(ctx context.Context, d *schema.ResourceData, meta interface{} } d.Set("labels", labels) + globs := schema.NewSet(schema.HashString, []interface{}{}) + for _, gb := range stack.AdditionalProjectGlobs { + globs.Add(gb) + } + d.Set("additional_project_globs", globs) + if iacKey, iacSettings := stack.IaCSettings(); iacKey != "" { if err := d.Set(iacKey, []interface{}{iacSettings}); err != nil { return diag.Errorf("could not set IaC settings: %v", err) diff --git a/spacelift/data_stack_test.go b/spacelift/data_stack_test.go index b4f86d6b..d722cbda 100644 --- a/spacelift/data_stack_test.go +++ b/spacelift/data_stack_test.go @@ -36,6 +36,7 @@ func TestStackData(t *testing.T) { labels = ["one", "two"] name = "Test stack %s" project_root = "root" + additional_project_globs = ["/bacon", "/bacon/eggs/*"] repository = "demo" runner_image = "custom_image:runner" terraform_workspace = "bacon" @@ -83,6 +84,7 @@ func TestStackData(t *testing.T) { SetEquals("labels", "one", "two"), Attribute("name", StartsWith("Test stack")), Attribute("project_root", Equals("root")), + SetEquals("additional_project_globs", "/bacon", "/bacon/eggs/*"), Attribute("repository", Equals("demo")), Attribute("runner_image", Equals("custom_image:runner")), Attribute("terraform_workspace", Equals("bacon")), diff --git a/spacelift/internal/structs/stack.go b/spacelift/internal/structs/stack.go index a0f8ec57..b7cb7f9a 100644 --- a/spacelift/internal/structs/stack.go +++ b/spacelift/internal/structs/stack.go @@ -22,41 +22,42 @@ const StackConfigVendorKubernetes = "StackConfigVendorKubernetes" // Stack represents the Stack data relevant to the provider. type Stack struct { - ID string `graphql:"id"` - Administrative bool `graphql:"administrative"` - AfterApply []string `graphql:"afterApply"` - AfterDestroy []string `graphql:"afterDestroy"` - AfterInit []string `graphql:"afterInit"` - AfterPerform []string `graphql:"afterPerform"` - AfterPlan []string `graphql:"afterPlan"` - AfterRun []string `graphql:"afterRun"` - Autodeploy bool `graphql:"autodeploy"` - Autoretry bool `graphql:"autoretry"` - BeforeApply []string `graphql:"beforeApply"` - BeforeDestroy []string `graphql:"beforeDestroy"` - BeforeInit []string `graphql:"beforeInit"` - BeforePerform []string `graphql:"beforePerform"` - BeforePlan []string `graphql:"beforePlan"` - Branch string `graphql:"branch"` - Deleting bool `graphql:"deleting"` - Description *string `graphql:"description"` - IsDisabled bool `graphql:"isDisabled"` - GitHubActionDeploy bool `graphql:"githubActionDeploy"` - Integrations *Integrations `graphql:"integrations"` - Labels []string `graphql:"labels"` - LocalPreviewEnabled bool `graphql:"localPreviewEnabled"` - ManagesStateFile bool `graphql:"managesStateFile"` - Name string `graphql:"name"` - Namespace string `graphql:"namespace"` - ProjectRoot *string `graphql:"projectRoot"` - ProtectFromDeletion bool `graphql:"protectFromDeletion"` - Provider string `graphql:"provider"` - Repository string `graphql:"repository"` - RepositoryURL *string `graphql:"repositoryURL"` - RunnerImage *string `graphql:"runnerImage"` - Space string `graphql:"space"` - TerraformVersion *string `graphql:"terraformVersion"` - VendorConfig struct { + ID string `graphql:"id"` + Administrative bool `graphql:"administrative"` + AfterApply []string `graphql:"afterApply"` + AfterDestroy []string `graphql:"afterDestroy"` + AfterInit []string `graphql:"afterInit"` + AfterPerform []string `graphql:"afterPerform"` + AfterPlan []string `graphql:"afterPlan"` + AfterRun []string `graphql:"afterRun"` + Autodeploy bool `graphql:"autodeploy"` + Autoretry bool `graphql:"autoretry"` + BeforeApply []string `graphql:"beforeApply"` + BeforeDestroy []string `graphql:"beforeDestroy"` + BeforeInit []string `graphql:"beforeInit"` + BeforePerform []string `graphql:"beforePerform"` + BeforePlan []string `graphql:"beforePlan"` + Branch string `graphql:"branch"` + Deleting bool `graphql:"deleting"` + Description *string `graphql:"description"` + IsDisabled bool `graphql:"isDisabled"` + GitHubActionDeploy bool `graphql:"githubActionDeploy"` + Integrations *Integrations `graphql:"integrations"` + Labels []string `graphql:"labels"` + LocalPreviewEnabled bool `graphql:"localPreviewEnabled"` + ManagesStateFile bool `graphql:"managesStateFile"` + Name string `graphql:"name"` + Namespace string `graphql:"namespace"` + ProjectRoot *string `graphql:"projectRoot"` + AdditionalProjectGlobs []string `graphql:"additionalProjectGlobs"` + ProtectFromDeletion bool `graphql:"protectFromDeletion"` + Provider string `graphql:"provider"` + Repository string `graphql:"repository"` + RepositoryURL *string `graphql:"repositoryURL"` + RunnerImage *string `graphql:"runnerImage"` + Space string `graphql:"space"` + TerraformVersion *string `graphql:"terraformVersion"` + VendorConfig struct { Typename string `graphql:"__typename"` Ansible struct { Playbook string `graphql:"playbook"` @@ -190,6 +191,12 @@ func PopulateStack(d *schema.ResourceData, stack *Stack) error { } d.Set("labels", labels) + globs := schema.NewSet(schema.HashString, []interface{}{}) + for _, gb := range stack.AdditionalProjectGlobs { + globs.Add(gb) + } + d.Set("additional_project_globs", globs) + switch stack.VendorConfig.Typename { case StackConfigVendorAnsible: m := map[string]interface{}{ diff --git a/spacelift/internal/structs/stack_input.go b/spacelift/internal/structs/stack_input.go index 5b1305c7..5cd85a54 100644 --- a/spacelift/internal/structs/stack_input.go +++ b/spacelift/internal/structs/stack_input.go @@ -4,36 +4,37 @@ import "github.com/shurcooL/graphql" // StackInput represents the input required to create or update a Stack. type StackInput struct { - Administrative graphql.Boolean `json:"administrative"` - AfterApply *[]graphql.String `json:"afterApply"` - AfterDestroy *[]graphql.String `json:"afterDestroy"` - AfterInit *[]graphql.String `json:"afterInit"` - AfterPerform *[]graphql.String `json:"afterPerform"` - AfterPlan *[]graphql.String `json:"afterPlan"` - AfterRun *[]graphql.String `json:"afterRun"` - Autodeploy graphql.Boolean `json:"autodeploy"` - Autoretry graphql.Boolean `json:"autoretry"` - BeforeApply *[]graphql.String `json:"beforeApply"` - BeforeDestroy *[]graphql.String `json:"beforeDestroy"` - BeforeInit *[]graphql.String `json:"beforeInit"` - BeforePerform *[]graphql.String `json:"beforePerform"` - BeforePlan *[]graphql.String `json:"beforePlan"` - Branch graphql.String `json:"branch"` - Description *graphql.String `json:"description"` - GitHubActionDeploy graphql.Boolean `json:"githubActionDeploy"` - Labels *[]graphql.String `json:"labels"` - LocalPreviewEnabled graphql.Boolean `json:"localPreviewEnabled"` - Name graphql.String `json:"name"` - Namespace *graphql.String `json:"namespace"` - ProjectRoot *graphql.String `json:"projectRoot"` - ProtectFromDeletion graphql.Boolean `json:"protectFromDeletion"` - Provider *graphql.String `json:"provider"` - Repository graphql.String `json:"repository"` - RepositoryURL *graphql.String `json:"repositoryURL"` - RunnerImage *graphql.String `json:"runnerImage"` - Space *graphql.String `json:"space"` - VendorConfig *VendorConfigInput `json:"vendorConfig"` - WorkerPool *graphql.ID `json:"workerPool"` + Administrative graphql.Boolean `json:"administrative"` + AfterApply *[]graphql.String `json:"afterApply"` + AfterDestroy *[]graphql.String `json:"afterDestroy"` + AfterInit *[]graphql.String `json:"afterInit"` + AfterPerform *[]graphql.String `json:"afterPerform"` + AfterPlan *[]graphql.String `json:"afterPlan"` + AfterRun *[]graphql.String `json:"afterRun"` + Autodeploy graphql.Boolean `json:"autodeploy"` + Autoretry graphql.Boolean `json:"autoretry"` + BeforeApply *[]graphql.String `json:"beforeApply"` + BeforeDestroy *[]graphql.String `json:"beforeDestroy"` + BeforeInit *[]graphql.String `json:"beforeInit"` + BeforePerform *[]graphql.String `json:"beforePerform"` + BeforePlan *[]graphql.String `json:"beforePlan"` + Branch graphql.String `json:"branch"` + Description *graphql.String `json:"description"` + GitHubActionDeploy graphql.Boolean `json:"githubActionDeploy"` + Labels *[]graphql.String `json:"labels"` + LocalPreviewEnabled graphql.Boolean `json:"localPreviewEnabled"` + Name graphql.String `json:"name"` + Namespace *graphql.String `json:"namespace"` + ProjectRoot *graphql.String `json:"projectRoot"` + AddditionalProjectGlobs *[]graphql.String `json:"additionalProjectGlobs"` + ProtectFromDeletion graphql.Boolean `json:"protectFromDeletion"` + Provider *graphql.String `json:"provider"` + Repository graphql.String `json:"repository"` + RepositoryURL *graphql.String `json:"repositoryURL"` + RunnerImage *graphql.String `json:"runnerImage"` + Space *graphql.String `json:"space"` + VendorConfig *VendorConfigInput `json:"vendorConfig"` + WorkerPool *graphql.ID `json:"workerPool"` } // VendorConfigInput represents vendor-specific configuration. diff --git a/spacelift/resource_stack.go b/spacelift/resource_stack.go index 07127116..964826f9 100644 --- a/spacelift/resource_stack.go +++ b/spacelift/resource_stack.go @@ -379,6 +379,12 @@ func resourceStack() *schema.Resource { Description: "Project root is the optional directory relative to the workspace root containing the entrypoint to the Stack.", Optional: true, }, + "additional_project_globs": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Project globs is an optional list of paths to track changes of in addition to the project root.", + }, "protect_from_deletion": { Type: schema.TypeBool, Description: "Protect this stack from accidental deletion. If set, attempts to delete this stack will fail. Defaults to `false`.", @@ -777,6 +783,14 @@ func stackInput(d *schema.ResourceData) structs.StackInput { ret.ProjectRoot = toOptionalString(projectRoot) } + if globsSet, ok := d.Get("additional_project_globs").(*schema.Set); ok { + var gbs []graphql.String + for _, gb := range globsSet.List() { + gbs = append(gbs, graphql.String(gb.(string))) + } + ret.AddditionalProjectGlobs = &gbs + } + if runnerImage, ok := d.GetOk("runner_image"); ok { ret.RunnerImage = toOptionalString(runnerImage) } diff --git a/spacelift/resource_stack_test.go b/spacelift/resource_stack_test.go index dd795d6b..af30da40 100644 --- a/spacelift/resource_stack_test.go +++ b/spacelift/resource_stack_test.go @@ -20,29 +20,30 @@ func TestStackResource(t *testing.T) { config := func(description string, protectFromDeletion bool) string { return fmt.Sprintf(` resource "spacelift_stack" "test" { - administrative = true - after_apply = ["ls -la", "rm -rf /"] - after_destroy = ["echo 'after_destroy'"] - after_init = ["terraform fmt -check", "tflint"] - after_perform = ["echo 'after_perform'"] - after_plan = ["echo 'after_plan'"] - after_run = ["echo 'after_run'"] - autodeploy = true - autoretry = false - before_apply = ["ls -la", "rm -rf /"] - before_destroy = ["echo 'before_destroy'"] - before_init = ["terraform fmt -check", "tflint"] - before_perform = ["echo 'before_perform'"] - before_plan = ["echo 'before_plan'"] - branch = "master" - description = "%s" - import_state = "{}" - labels = ["one", "two"] - name = "Provider test stack %s" - project_root = "root" - protect_from_deletion = %t - repository = "demo" - runner_image = "custom_image:runner" + administrative = true + after_apply = ["ls -la", "rm -rf /"] + after_destroy = ["echo 'after_destroy'"] + after_init = ["terraform fmt -check", "tflint"] + after_perform = ["echo 'after_perform'"] + after_plan = ["echo 'after_plan'"] + after_run = ["echo 'after_run'"] + autodeploy = true + autoretry = false + before_apply = ["ls -la", "rm -rf /"] + before_destroy = ["echo 'before_destroy'"] + before_init = ["terraform fmt -check", "tflint"] + before_perform = ["echo 'before_perform'"] + before_plan = ["echo 'before_plan'"] + branch = "master" + description = "%s" + import_state = "{}" + labels = ["one", "two"] + name = "Provider test stack %s" + project_root = "root" + additional_project_globs = ["/bacon", "/bacon/eggs/*"] + protect_from_deletion = %t + repository = "demo" + runner_image = "custom_image:runner" } `, description, randomID, protectFromDeletion) } @@ -90,6 +91,7 @@ func TestStackResource(t *testing.T) { SetEquals("labels", "one", "two"), Attribute("name", StartsWith("Provider test stack")), Attribute("project_root", Equals("root")), + SetEquals("additional_project_globs", "/bacon", "/bacon/eggs/*"), Attribute("protect_from_deletion", Equals("true")), Attribute("repository", Equals("demo")), Attribute("runner_image", Equals("custom_image:runner")),