diff --git a/runatlantis.io/docs/repo-level-atlantis-yaml.md b/runatlantis.io/docs/repo-level-atlantis-yaml.md index c864f27a0b..0dc0623b2a 100644 --- a/runatlantis.io/docs/repo-level-atlantis-yaml.md +++ b/runatlantis.io/docs/repo-level-atlantis-yaml.md @@ -57,6 +57,7 @@ version: 3 # Available since v0.1.0 automerge: true # Available since v0.15.0 autodiscover: # Available since v0.18.0 mode: auto + workspace: default ignore_paths: - some/path delete_source_branch_on_merge: true # Available since v0.15.0 @@ -418,6 +419,16 @@ autodiscover: Autodiscover can also be configured to skip over directories that match a path glob (as defined [here](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4)) +```yaml +autodiscover: + mode: "enabled" + workspace: qa + ignore_paths: + - terraform/aws-test/** +``` + +Use `workspace` to set the Terraform workspace for all autodiscovered projects. This is useful when running separate Atlantis instances per workspace on a shared repository. The configured workspace takes precedence over any workspace detected from `terraform { cloud { workspaces { name = "..." } } }` blocks in Terraform code. + ### Custom Backend Config See [Custom Workflow Use Cases: Custom Backend Config](custom-workflows.md#custom-backend-config) diff --git a/runatlantis.io/docs/server-side-repo-config.md b/runatlantis.io/docs/server-side-repo-config.md index 5372446c20..57c9a981d3 100644 --- a/runatlantis.io/docs/server-side-repo-config.md +++ b/runatlantis.io/docs/server-side-repo-config.md @@ -100,6 +100,8 @@ repos: # If any part of this setting is set here, it overrides the entire setting in the repo config. autodiscover: mode: auto + # Optionally set a workspace for all autodiscovered projects + workspace: "" # Optionally ignore some paths for autodiscovery by a glob path ignore_paths: - foo/* @@ -546,7 +548,7 @@ If you set a workflow with the key `default`, it will override this. | repo_locks | [RepoLocks](#repolocks) | `mode: on_plan` | no | Whether or not repository locks are enabled for this project on plan or apply. See [RepoLocks](#repolocks) for more details. | | policy_check | bool | false | no | Whether or not to run policy checks on this repository. | | custom_policy_check | bool | false | no | Whether or not to enable custom policy check tools outside of Conftest on this repository. | -| autodiscover | AutoDiscover | none | no | Auto discover settings for this repo | +| autodiscover | [AutoDiscover](#autodiscover) | none | no | Auto discover settings for this repo. See [AutoDiscover](#autodiscover) for more details. | | silence_pr_comments | []string | none | no | Silence PR comments from defined stages while preserving PR status checks. Useful in large environments with many Atlantis instances and/or projects, when the comments are too big and too many, therefore it is preferable to rely solely on PR status checks. Supported values are: `plan`, `apply`. | :::tip Notes @@ -584,6 +586,21 @@ If you set a workflow with the key `default`, it will override this. by the `id: github.com/owner/repo` config because it didn't define that key. ::: +### AutoDiscover + +```yaml +mode: auto +workspace: "" +ignore_paths: + - some/path/** +``` + +| Key | Type | Default | Required | Description | +|--------------|-----------------|----------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| mode | string | `"auto"` | no | When `auto`, projects are discovered only if the repo has no `projects` configured. When `enabled`, discovery always runs. When `disabled`, discovery never runs. | +| workspace | string | `""` | no | Workspace to use for all autodiscovered projects. When set, this overrides workspace detection from `terraform { cloud { workspaces { name = "..." } } }` blocks. Useful when running one Atlantis instance per workspace. | +| ignore_paths | array\[string\] | `[]` | no | List of path globs (as defined [here](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4)) to exclude from autodiscovery. | + ### RepoLocks ```yaml diff --git a/server/core/config/raw/autodiscover.go b/server/core/config/raw/autodiscover.go index c395e56a72..5fe1134c4f 100644 --- a/server/core/config/raw/autodiscover.go +++ b/server/core/config/raw/autodiscover.go @@ -18,6 +18,7 @@ var DefaultAutoDiscoverMode = valid.AutoDiscoverAutoMode type AutoDiscover struct { Mode *valid.AutoDiscoverMode `yaml:"mode,omitempty"` IgnorePaths []string `yaml:"ignore_paths,omitempty"` + Workspace string `yaml:"workspace,omitempty"` } func (a AutoDiscover) ToValid() *valid.AutoDiscover { @@ -30,6 +31,7 @@ func (a AutoDiscover) ToValid() *valid.AutoDiscover { } v.IgnorePaths = a.IgnorePaths + v.Workspace = a.Workspace return &v } diff --git a/server/core/config/raw/autodiscover_test.go b/server/core/config/raw/autodiscover_test.go index aa3e8f69b6..a96fb08a56 100644 --- a/server/core/config/raw/autodiscover_test.go +++ b/server/core/config/raw/autodiscover_test.go @@ -38,6 +38,16 @@ ignore_paths: IgnorePaths: []string{"foobar"}, }, }, + { + description: "workspace set", + input: ` +workspace: qa +`, + exp: raw.AutoDiscover{ + Mode: nil, + Workspace: "qa", + }, + }, } for _, c := range cases { @@ -198,6 +208,17 @@ func TestAutoDiscover_ToValid(t *testing.T) { }, }, }, + { + description: "workspace set", + input: raw.AutoDiscover{ + Mode: &autoDiscoverEnabled, + Workspace: "qa", + }, + exp: &valid.AutoDiscover{ + Mode: valid.AutoDiscoverEnabledMode, + Workspace: "qa", + }, + }, } for _, c := range cases { t.Run(c.description, func(t *testing.T) { diff --git a/server/core/config/valid/autodiscover.go b/server/core/config/valid/autodiscover.go index 5aa5267770..78ca5b0ffd 100644 --- a/server/core/config/valid/autodiscover.go +++ b/server/core/config/valid/autodiscover.go @@ -17,6 +17,7 @@ const ( type AutoDiscover struct { Mode AutoDiscoverMode IgnorePaths []string + Workspace string } func (a AutoDiscover) IsPathIgnored(path string) bool { diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index a2a06b13bc..4d8073a0dd 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -384,13 +384,13 @@ func (p *DefaultProjectCommandBuilder) autoDiscoverModeEnabled(ctx *command.Cont // isAutoDiscoverPathIgnored determines whether this particular path is ignored for the purposes of auto discovery func (p *DefaultProjectCommandBuilder) isAutoDiscoverPathIgnored(ctx *command.Context, repoCfg valid.RepoCfg, path string) bool { fromGlobalAutoDiscover := p.GlobalCfg.RepoAutoDiscoverCfg(ctx.Pull.BaseRepo.ID()) - if fromGlobalAutoDiscover != nil { + // Only use global config's IgnorePaths if they are explicitly set; otherwise fall through to repo-level. + if fromGlobalAutoDiscover != nil && fromGlobalAutoDiscover.IgnorePaths != nil { return fromGlobalAutoDiscover.IsPathIgnored(path) } if repoCfg.AutoDiscover != nil { return repoCfg.AutoDiscover.IsPathIgnored(path) } - return false } @@ -447,12 +447,26 @@ func (p *DefaultProjectCommandBuilder) getMergedProjectCfgs(ctx *command.Context } ctx.Log.Info("automatically determined that there were %d additional projects modified in this pull request: %s", len(modifiedProjects), modifiedProjects) + // Determine the workspace to use for autodiscovered projects. Global config takes precedence + // over repo-level config; both are only consulted when explicitly set (non-empty). + configuredWorkspace := "" + fromGlobalAutoDiscover := p.GlobalCfg.RepoAutoDiscoverCfg(ctx.Pull.BaseRepo.ID()) + if fromGlobalAutoDiscover != nil && fromGlobalAutoDiscover.Workspace != "" { + configuredWorkspace = fromGlobalAutoDiscover.Workspace + } else if repoCfg.AutoDiscover != nil && repoCfg.AutoDiscover.Workspace != "" { + configuredWorkspace = repoCfg.AutoDiscover.Workspace + } for _, mp := range modifiedProjects { ctx.Log.Debug("determining config for project at dir: '%s'", mp.Path) absProjectDir := filepath.Join(repoDir, mp.Path) - pWorkspace, err := p.ProjectFinder.DetermineWorkspaceFromHCL(ctx.Log, absProjectDir) - if err != nil { - return nil, fmt.Errorf("looking for Terraform Cloud workspace from configuration in '%s': %w", absProjectDir, err) + var pWorkspace string + if configuredWorkspace != "" { + pWorkspace = configuredWorkspace + } else { + pWorkspace, err = p.ProjectFinder.DetermineWorkspaceFromHCL(ctx.Log, absProjectDir) + if err != nil { + return nil, fmt.Errorf("looking for Terraform Cloud workspace from configuration in '%s': %w", absProjectDir, err) + } } pCfg := p.GlobalCfg.DefaultProjCfg(ctx.Log, ctx.Pull.BaseRepo.ID(), mp.Path, pWorkspace) diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index ad80a99c4e..bea4761c51 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -1117,6 +1117,30 @@ projects: }, }, }, + "autodiscover workspace set in autodiscover config": { + DirStructure: map[string]any{ + "project1": map[string]any{ + "main.tf": nil, + }, + "project2": map[string]any{ + "main.tf": nil, + }, + }, + AtlantisYAML: `version: 3 +autodiscover: + mode: enabled + workspace: qa + ignore_paths: + - project2`, + ModifiedFiles: []string{"project1/main.tf", "project2/main.tf"}, + Exp: []expCtxFields{ + { + ProjectName: "", + RepoRelDir: "project1", + Workspace: "qa", + }, + }, + }, "autodiscover enabled but project excluded by empty when_modified": { DirStructure: map[string]any{ "project1": map[string]any{