Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option flag to filter the modified files #151

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
GitlabWebHookSecret = "gitlab-webhook-secret"
LogLevelFlag = "log-level"
PortFlag = "port"
RepoSubdirFlag = "repo-subdir"
RepoWhitelistFlag = "repo-whitelist"
RequireApprovalFlag = "require-approval"
SSLCertFileFlag = "ssl-cert-file"
Expand Down Expand Up @@ -127,6 +128,11 @@ var stringFlags = []stringFlag{
"The format is {hostname}/{owner}/{repo}, ex. github.com/runatlantis/atlantis. '*' matches any characters until the next comma and can be used for example to whitelist " +
"all repos: '*' (not recommended), an entire hostname: 'internalgithub.com/*' or an organization: 'github.com/runatlantis/*'.",
},
{
name: RepoSubdirFlag,
description: "Optional subdirectory inside the repository. to filter the modifications only from this particular subdirectory. It supports regular expression syntax.",
defaultValue: "",
},
{
name: SSLCertFileFlag,
description: "File containing x509 Certificate used for serving HTTPS. If the cert is signed by a CA, the file should be the concatenation of the server's certificate, any intermediates, and the CA's certificate.",
Expand Down
8 changes: 8 additions & 0 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ func TestExecute_Flags(t *testing.T) {
cmd.GitlabWebHookSecret: "gitlab-secret",
cmd.LogLevelFlag: "debug",
cmd.PortFlag: 8181,
cmd.RepoSubdirFlag: "/dir",
cmd.RepoWhitelistFlag: "github.com/runatlantis/atlantis",
cmd.RequireApprovalFlag: true,
cmd.SSLCertFileFlag: "cert-file",
Expand All @@ -393,6 +394,7 @@ func TestExecute_Flags(t *testing.T) {
Equals(t, "gitlab-secret", passedConfig.GitlabWebHookSecret)
Equals(t, "debug", passedConfig.LogLevel)
Equals(t, 8181, passedConfig.Port)
Equals(t, "/dir", passedConfig.RepoSubdir)
Equals(t, "github.com/runatlantis/atlantis", passedConfig.RepoWhitelist)
Equals(t, true, passedConfig.RequireApproval)
Equals(t, "cert-file", passedConfig.SSLCertFile)
Expand Down Expand Up @@ -531,6 +533,7 @@ gitlab-user: "gitlab-user"
gitlab-webhook-secret: "gitlab-secret"
log-level: "debug"
port: 8181
repo-subdir: 8181
repo-whitelist: "github.com/runatlantis/atlantis"
require-approval: true
ssl-cert-file: cert-file
Expand All @@ -552,6 +555,7 @@ ssl-key-file: key-file
cmd.GitlabWebHookSecret: "override-gitlab-webhook-secret",
cmd.LogLevelFlag: "info",
cmd.PortFlag: 8282,
cmd.RepoSubdirFlag: "/override-dir",
cmd.RepoWhitelistFlag: "override,override",
cmd.RequireApprovalFlag: false,
cmd.SSLCertFileFlag: "override-cert-file",
Expand All @@ -572,6 +576,7 @@ ssl-key-file: key-file
Equals(t, "override-gitlab-webhook-secret", passedConfig.GitlabWebHookSecret)
Equals(t, "info", passedConfig.LogLevel)
Equals(t, 8282, passedConfig.Port)
Equals(t, "/override-dir", passedConfig.RepoSubdir)
Equals(t, "override,override", passedConfig.RepoWhitelist)
Equals(t, false, passedConfig.RequireApproval)
Equals(t, "override-cert-file", passedConfig.SSLCertFile)
Expand All @@ -595,6 +600,7 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
"GITLAB_WEBHOOK_SECRET": "gitlab-webhook-secret",
"LOG_LEVEL": "debug",
"PORT": "8181",
"REPO_SUBDIR": "/dir",
"REPO_WHITELIST": "*",
"REQUIRE_APPROVAL": "true",
"SSL_CERT_FILE": "cert-file",
Expand All @@ -617,6 +623,7 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
cmd.GitlabWebHookSecret: "override-gitlab-webhook-secret",
cmd.LogLevelFlag: "info",
cmd.PortFlag: 8282,
cmd.RepoSubdirFlag: "/override-dir",
cmd.RepoWhitelistFlag: "override,override",
cmd.RequireApprovalFlag: false,
cmd.SSLCertFileFlag: "override-cert-file",
Expand All @@ -638,6 +645,7 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
Equals(t, "override-gitlab-webhook-secret", passedConfig.GitlabWebHookSecret)
Equals(t, "info", passedConfig.LogLevel)
Equals(t, 8282, passedConfig.Port)
Equals(t, "/override-dir", passedConfig.RepoSubdir)
Equals(t, "override,override", passedConfig.RepoWhitelist)
Equals(t, false, passedConfig.RequireApproval)
Equals(t, "override-cert-file", passedConfig.SSLCertFile)
Expand Down
3 changes: 2 additions & 1 deletion server/events/plan_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type PlanExecutor struct {
Terraform terraform.Client
Locker locking.Locker
LockURL func(id string) (url string)
RepoSubdir string
Run run.Runner
Workspace AtlantisWorkspace
ProjectPreExecute ProjectPreExecutor
Expand Down Expand Up @@ -79,7 +80,7 @@ func (p *PlanExecutor) Execute(ctx *CommandContext) CommandResponse {
return CommandResponse{Error: errors.Wrap(err, "getting modified files")}
}
ctx.Log.Info("found %d files modified in this pull request", len(modifiedFiles))
projects = p.ProjectFinder.DetermineProjects(ctx.Log, modifiedFiles, ctx.BaseRepo.FullName, cloneDir)
projects = p.ProjectFinder.DetermineProjects(ctx.Log, modifiedFiles, p.RepoSubdir, ctx.BaseRepo.FullName, cloneDir)
if len(projects) == 0 {
return CommandResponse{Failure: "No Terraform files were modified."}
}
Expand Down
15 changes: 9 additions & 6 deletions server/events/project_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/runatlantis/atlantis/server/events/models"
Expand All @@ -29,7 +30,7 @@ import (
type ProjectFinder interface {
// DetermineProjects returns the list of projects that were modified based on
// the modifiedFiles. The list will be de-duplicated.
DetermineProjects(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string, repoDir string) []models.Project
DetermineProjects(log *logging.SimpleLogger, modifiedFiles []string, restrictSubDir string, repoFullName string, repoDir string) []models.Project
}

// DefaultProjectFinder implements ProjectFinder.
Expand All @@ -39,10 +40,10 @@ var excludeList = []string{"terraform.tfstate", "terraform.tfstate.backup"}

// DetermineProjects returns the list of projects that were modified based on
// the modifiedFiles. The list will be de-duplicated.
func (p *DefaultProjectFinder) DetermineProjects(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string, repoDir string) []models.Project {
func (p *DefaultProjectFinder) DetermineProjects(log *logging.SimpleLogger, modifiedFiles []string, restrictSubDir string, repoFullName string, repoDir string) []models.Project {
var projects []models.Project

modifiedTerraformFiles := p.filterToTerraform(modifiedFiles)
modifiedTerraformFiles := p.filterToTerraform(modifiedFiles, restrictSubDir)
if len(modifiedTerraformFiles) == 0 {
return projects
}
Expand All @@ -65,11 +66,13 @@ func (p *DefaultProjectFinder) DetermineProjects(log *logging.SimpleLogger, modi
return projects
}

func (p *DefaultProjectFinder) filterToTerraform(files []string) []string {
func (p *DefaultProjectFinder) filterToTerraform(files []string, restrictSubDir string) []string {
var filtered []string
for _, fileName := range files {
if !p.isInExcludeList(fileName) && strings.Contains(fileName, ".tf") {
filtered = append(filtered, fileName)
if matched, err := regexp.MatchString(restrictSubDir, fileName); matched && err == nil {
if !p.isInExcludeList(fileName) && strings.Contains(fileName, ".tf") {
filtered = append(filtered, fileName)
}
}
}
return filtered
Expand Down
23 changes: 22 additions & 1 deletion server/events/project_finder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,89 +81,110 @@ func TestDetermineProjects(t *testing.T) {
files []string
expProjectPaths []string
repoDir string
restricPath string
}{
{
"If no files were modified then should return an empty list",
nil,
nil,
"",
"",
},
{
"Should ignore non .tf files and return an empty list",
[]string{"non-tf"},
nil,
"",
"",
},
{
"Should plan in the parent directory from modules if that dir has a main.tf",
[]string{"project1/modules/main.tf"},
[]string{"project1"},
nestedModules1,
"",
},
{
"Should plan in the parent directory from modules if that dir has a main.tf",
[]string{"modules/main.tf"},
[]string{"."},
nestedModules2,
"",
},
{
"Should plan in the parent directory from modules when module is in a subdir if that dir has a main.tf",
[]string{"modules/subdir/main.tf"},
[]string{"."},
nestedModules2,
"",
},
{
"Should not plan in the parent directory from modules if that dir does not have a main.tf",
[]string{"modules/main.tf"},
[]string{},
topLevelModules,
"",
},
{
"Should not plan in the parent directory from modules if that dir does not have a main.tf",
[]string{"modules/main.tf", "project1/main.tf"},
[]string{"project1"},
topLevelModules,
"",
},
{
"Should ignore tfstate files and return an empty list",
[]string{"terraform.tfstate", "terraform.tfstate.backup", "parent/terraform.tfstate", "parent/terraform.tfstate.backup"},
nil,
"",
"",
},
{
"Should ignore tfstate files and return an empty list",
[]string{"terraform.tfstate", "terraform.tfstate.backup", "parent/terraform.tfstate", "parent/terraform.tfstate.backup"},
nil,
"",
"",
},
{
"Should return '.' when changed file is at root",
[]string{"a.tf"},
[]string{"."},
"",
"",
},
{
"Should return directory when changed file is in a dir",
[]string{"parent/a.tf"},
[]string{"parent"},
"",
"",
},
{
"Should return parent dir when changed file is in an env/ dir",
[]string{"env/a.tfvars"},
[]string{"."},
"",
"",
},
{
"Should de-duplicate when multiple files changed in the same dir",
[]string{"root.tf", "env/env.tfvars", "parent/parent.tf", "parent/parent2.tf", "parent/child/child.tf", "parent/child/env/env.tfvars"},
[]string{".", "parent", "parent/child"},
"",
"",
},
{
"Should de-duplicate when multiple files changed in the same dir",
[]string{"outscope/main.tf", "filter/child/main.tf", "filter/child1/main.tf", "filter/child2/main.tf", "filter/childout/main.tf"},
[]string{"filter/child1", "filter/child2"},
"",
"filter/(child1)|(child2)",
},
}
for _, c := range cases {
t.Log(c.description)
projects := m.DetermineProjects(noopLogger, c.files, modifiedRepo, c.repoDir)
projects := m.DetermineProjects(noopLogger, c.files, c.restricPath, modifiedRepo, c.repoDir)

// Extract the paths from the projects. We use a slice here instead of a
// map so we can test whether there are duplicates returned.
Expand Down
2 changes: 2 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type UserConfig struct {
GitlabWebHookSecret string `mapstructure:"gitlab-webhook-secret"`
LogLevel string `mapstructure:"log-level"`
Port int `mapstructure:"port"`
RepoSubdir string `mapstructure:"repo-subdir"`
RepoWhitelist string `mapstructure:"repo-whitelist"`
// RequireApproval is whether to require pull request approval before
// allowing terraform apply's to be run.
Expand Down Expand Up @@ -204,6 +205,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
Terraform: terraformClient,
Run: run,
Workspace: workspace,
RepoSubdir: userConfig.RepoSubdir,
ProjectPreExecute: projectPreExecute,
Locker: lockingClient,
ProjectFinder: &events.DefaultProjectFinder{},
Expand Down