mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-04 00:28:48 +00:00
feat: --allow-commands restricts available atlantis commands (#2877)
* feat: --allow-command configuration restricts available atlantis commands * add version and approve_policies into allow commands defaults * allow-commands accept all keyword which allows all commands * remove redundant nest * more detail abount allow-commands all keyword * Update server/events/comment_parser.go
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/runatlantis/atlantis/server"
|
||||
"github.com/runatlantis/atlantis/server/core/config/valid"
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/events/vcs/bitbucketcloud"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
)
|
||||
@@ -43,6 +44,7 @@ const (
|
||||
ADTokenFlag = "azuredevops-token" // nolint: gosec
|
||||
ADUserFlag = "azuredevops-user"
|
||||
ADHostnameFlag = "azuredevops-hostname"
|
||||
AllowCommandsFlag = "allow-commands"
|
||||
AllowForkPRsFlag = "allow-fork-prs"
|
||||
AllowRepoConfigFlag = "allow-repo-config"
|
||||
AtlantisURLFlag = "atlantis-url"
|
||||
@@ -135,6 +137,7 @@ const (
|
||||
DefaultADBasicPassword = ""
|
||||
DefaultADHostname = "dev.azure.com"
|
||||
DefaultAutoplanFileList = "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl"
|
||||
DefaultAllowCommands = "version,plan,apply,unlock,approve_policies"
|
||||
DefaultCheckoutStrategy = "branch"
|
||||
DefaultBitbucketBaseURL = bitbucketcloud.BaseURL
|
||||
DefaultDataDir = "~/.atlantis"
|
||||
@@ -183,6 +186,10 @@ var stringFlags = map[string]stringFlag{
|
||||
description: "Azure DevOps hostname to support cloud and self hosted instances.",
|
||||
defaultValue: "dev.azure.com",
|
||||
},
|
||||
AllowCommandsFlag: {
|
||||
description: "Comma separated list of acceptable atlantis commands.",
|
||||
defaultValue: DefaultAllowCommands,
|
||||
},
|
||||
AtlantisURLFlag: {
|
||||
description: "URL that Atlantis can be reached at. Defaults to http://$(hostname):$port where $port is from --" + PortFlag + ". Supports a base path ex. https://example.com/basepath.",
|
||||
},
|
||||
@@ -751,6 +758,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) {
|
||||
if c.AutoplanFileList == "" {
|
||||
c.AutoplanFileList = DefaultAutoplanFileList
|
||||
}
|
||||
if c.AllowCommands == "" {
|
||||
c.AllowCommands = DefaultAllowCommands
|
||||
}
|
||||
if c.CheckoutStrategy == "" {
|
||||
c.CheckoutStrategy = DefaultCheckoutStrategy
|
||||
}
|
||||
@@ -904,6 +914,10 @@ func (s *ServerCmd) validate(userConfig server.UserConfig) error {
|
||||
return errors.Wrapf(patternErr, "invalid pattern in --%s, %s", AutoplanFileListFlag, userConfig.AutoplanFileList)
|
||||
}
|
||||
|
||||
if _, err := userConfig.ToAllowCommandNames(); err != nil {
|
||||
return errors.Wrapf(err, "invalid --%s", AllowCommandsFlag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1015,6 +1029,16 @@ func (s *ServerCmd) deprecationWarnings(userConfig *server.UserConfig) error {
|
||||
deprecatedFlags = append(deprecatedFlags, RequireMergeableFlag)
|
||||
commandReqs = append(commandReqs, valid.MergeableCommandReq)
|
||||
}
|
||||
if userConfig.DisableApply {
|
||||
deprecatedFlags = append(deprecatedFlags, DisableApplyFlag)
|
||||
var filtered []string
|
||||
for _, allowCommand := range strings.Split(userConfig.AllowCommands, ",") {
|
||||
if allowCommand != command.Apply.String() {
|
||||
filtered = append(filtered, allowCommand)
|
||||
}
|
||||
}
|
||||
userConfig.AllowCommands = strings.Join(filtered, ",")
|
||||
}
|
||||
|
||||
// Build up strings with what the recommended yaml and json config should
|
||||
// be instead of using the deprecated flags.
|
||||
|
||||
@@ -57,6 +57,7 @@ var testFlags = map[string]interface{}{
|
||||
ADWebhookPasswordFlag: "ad-wh-pass",
|
||||
ADWebhookUserFlag: "ad-wh-user",
|
||||
AtlantisURLFlag: "url",
|
||||
AllowCommandsFlag: "version,plan,unlock,import,approve_policies", // apply is disabled by DisableApply
|
||||
AllowForkPRsFlag: true,
|
||||
AllowRepoConfigFlag: true,
|
||||
AutomergeFlag: true,
|
||||
@@ -553,6 +554,36 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecute_ValidateAllowCommands(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
allowCommandsFlag string
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid allow commands",
|
||||
allowCommandsFlag: "noallow",
|
||||
expErr: "invalid --allow-commands: unknown command name: noallow",
|
||||
},
|
||||
{
|
||||
name: "success with empty allow commands",
|
||||
allowCommandsFlag: "",
|
||||
expErr: "",
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
c := setupWithDefaults(map[string]interface{}{
|
||||
AllowCommandsFlag: testCase.allowCommandsFlag,
|
||||
}, t)
|
||||
err := c.Execute()
|
||||
if testCase.expErr != "" {
|
||||
ErrEquals(t, testCase.expErr, err)
|
||||
} else {
|
||||
Ok(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecute_ExpandHomeInDataDir(t *testing.T) {
|
||||
t.Log("If ~ is used as a data-dir path, should expand to absolute home path")
|
||||
c := setup(map[string]interface{}{
|
||||
@@ -752,6 +783,16 @@ func TestExecute_BothSilenceAllowAndWhitelistErrors(t *testing.T) {
|
||||
ErrEquals(t, "both --silence-allowlist-errors and --silence-whitelist-errors cannot be set–use --silence-allowlist-errors", err)
|
||||
}
|
||||
|
||||
func TestExecute_DisableApplyDeprecation(t *testing.T) {
|
||||
c := setupWithDefaults(map[string]interface{}{
|
||||
DisableApplyFlag: true,
|
||||
AllowCommandsFlag: "plan,apply,unlock",
|
||||
}, t)
|
||||
err := c.Execute()
|
||||
Ok(t, err)
|
||||
Equals(t, "plan,unlock", passedConfig.AllowCommands)
|
||||
}
|
||||
|
||||
// Test that we set the corresponding allow list values on the userConfig
|
||||
// struct if the deprecated whitelist flags are used.
|
||||
func TestExecute_RepoWhitelistDeprecation(t *testing.T) {
|
||||
|
||||
@@ -47,6 +47,19 @@ Values are chosen in this order:
|
||||
|
||||
|
||||
## Flags
|
||||
### `--allow-commands`
|
||||
```bash
|
||||
atlantis server --allow-commands=version,plan,apply,unlock,approve_policies
|
||||
# or
|
||||
ATLANTIS_ALLOW_COMMANDS='version,plan,apply,unlock,approve_policies'
|
||||
```
|
||||
List of allowed commands to be run on the Atlantis server, Defaults to `version,plan,apply,unlock,approve_policies`
|
||||
|
||||
Notes:
|
||||
* Accepts a comma separated list, ex. `command1,command2`.
|
||||
* `version`, `plan`, `apply`, `unlock`, `approve_policies`, `import` and `all` are available.
|
||||
* `all` is a special keyword that allows all commands. If pass `all` then all other commands will be ignored.
|
||||
|
||||
### `--allow-draft-prs`
|
||||
```bash
|
||||
atlantis server --allow-draft-prs
|
||||
@@ -318,11 +331,14 @@ and set `--autoplan-modules` to `false`.
|
||||
if not in `PATH`. See [Terraform Versions](terraform-versions.html) for more details.
|
||||
|
||||
### `--disable-apply`
|
||||
<Badge text="Deprecated" type="warn"/>
|
||||
```bash
|
||||
atlantis server --disable-apply
|
||||
# or
|
||||
ATLANTIS_DISABLE_APPLY=true
|
||||
```
|
||||
Deprecated for `--allow-commands`.
|
||||
|
||||
Disable all `atlantis apply` commands, regardless of which flags are passed with it.
|
||||
|
||||
### `--disable-apply-all`
|
||||
|
||||
@@ -133,6 +133,8 @@ atlantis import [options] ADDRESS ID -- [terraform import flags]
|
||||
Runs `terraform import` that matches the directory/project/workspace.
|
||||
This command discards the terraform plan result. After an import and before an apply, another `atlantis plan` must be run again.
|
||||
|
||||
To allow the `import` command requires [--allow-commands](/docs/server-configuration.html#allow-commands) configuration.
|
||||
|
||||
### Examples
|
||||
```bash
|
||||
# Runs import
|
||||
|
||||
@@ -94,6 +94,8 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
DisableApply bool
|
||||
// ApplyLock creates an apply lock that temporarily disables apply command
|
||||
ApplyLock bool
|
||||
// AllowCommands flag what kind of atlantis commands are available.
|
||||
AllowCommands []command.Name
|
||||
// ExpAutomerge is true if we expect Atlantis to automerge.
|
||||
ExpAutomerge bool
|
||||
// ExpAutoplan is true if we expect Atlantis to autoplan.
|
||||
@@ -108,6 +110,10 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
// Atlantis writes to the pull request in order. A reply from a parallel operation
|
||||
// will be matched using a substring check.
|
||||
ExpReplies [][]string
|
||||
// ExpAllowResponseCommentBack allow http response content with "Commenting back on pull request"
|
||||
ExpAllowResponseCommentBack bool
|
||||
// ExpParseFailedCount represents how many times test sends invalid commands
|
||||
ExpParseFailedCount int
|
||||
}{
|
||||
{
|
||||
Description: "simple",
|
||||
@@ -193,6 +199,19 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
{"exp-output-merge-workspaces.txt"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "simple with allow commands",
|
||||
RepoDir: "simple",
|
||||
AllowCommands: []command.Name{command.Plan, command.Apply},
|
||||
Comments: []string{
|
||||
"atlantis import ADDRESS ID",
|
||||
},
|
||||
ExpReplies: [][]string{
|
||||
{"exp-output-allow-command-unknown-import.txt"},
|
||||
},
|
||||
ExpAllowResponseCommentBack: true,
|
||||
ExpParseFailedCount: 1,
|
||||
},
|
||||
{
|
||||
Description: "simple with atlantis.yaml",
|
||||
RepoDir: "simple-yaml",
|
||||
@@ -472,7 +491,8 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
userConfig = server.UserConfig{}
|
||||
userConfig.DisableApply = c.DisableApply
|
||||
|
||||
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, c.RepoConfigFile)
|
||||
opt := setupOption{repoConfigFile: c.RepoConfigFile, allowCommands: c.AllowCommands}
|
||||
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, opt)
|
||||
// Set the repo to be cloned through the testing backdoor.
|
||||
repoDir, headSHA := initializeRepo(t, c.RepoDir)
|
||||
atlantisWorkspace.TestingOverrideHeadCloneURL = fmt.Sprintf("file://%s", repoDir)
|
||||
@@ -497,7 +517,11 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
commentReq := GitHubCommentEvent(t, comment)
|
||||
w = httptest.NewRecorder()
|
||||
ctrl.Post(w, commentReq)
|
||||
ResponseContains(t, w, 200, "Processing...")
|
||||
if c.ExpAllowResponseCommentBack {
|
||||
ResponseContains(t, w, 200, "Commenting back on pull request")
|
||||
} else {
|
||||
ResponseContains(t, w, 200, "Processing...")
|
||||
}
|
||||
}
|
||||
|
||||
// Send the "pull closed" event which would be triggered by the
|
||||
@@ -507,17 +531,17 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
ctrl.Post(w, pullClosedReq)
|
||||
ResponseContains(t, w, 200, "Pull request cleaned successfully")
|
||||
|
||||
expNumHooks := len(c.Comments) + 1 - c.ExpParseFailedCount
|
||||
// Let's verify the pre-workflow hook was called for each comment including the pull request opened event
|
||||
mockPreWorkflowHookRunner.VerifyWasCalled(Times(len(c.Comments)+1)).Run(runtimematchers.AnyModelsWorkflowHookCommandContext(), EqString("some dummy command"), AnyString())
|
||||
|
||||
mockPreWorkflowHookRunner.VerifyWasCalled(Times(expNumHooks)).Run(runtimematchers.AnyModelsWorkflowHookCommandContext(), EqString("some dummy command"), AnyString())
|
||||
// Let's verify the post-workflow hook was called for each comment including the pull request opened event
|
||||
mockPostWorkflowHookRunner.VerifyWasCalled(Times(len(c.Comments)+1)).Run(runtimematchers.AnyModelsWorkflowHookCommandContext(), EqString("some post dummy command"), AnyString())
|
||||
mockPostWorkflowHookRunner.VerifyWasCalled(Times(expNumHooks)).Run(runtimematchers.AnyModelsWorkflowHookCommandContext(), EqString("some post dummy command"), AnyString())
|
||||
|
||||
// Now we're ready to verify Atlantis made all the comments back (or
|
||||
// replies) that we expect. We expect each plan to have 1 comment,
|
||||
// and apply have 1 for each comment plus one for the locks deleted at the
|
||||
// end.
|
||||
expNumReplies := len(c.Comments) + 1
|
||||
expNumReplies := len(c.Comments) + 1 - c.ExpParseFailedCount
|
||||
|
||||
if c.ExpAutoplan {
|
||||
expNumReplies++
|
||||
@@ -543,7 +567,7 @@ func TestGitHubWorkflow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimlpleWorkflow_terraformLockFile(t *testing.T) {
|
||||
func TestSimpleWorkflow_terraformLockFile(t *testing.T) {
|
||||
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
@@ -621,7 +645,7 @@ func TestSimlpleWorkflow_terraformLockFile(t *testing.T) {
|
||||
userConfig = server.UserConfig{}
|
||||
userConfig.DisableApply = true
|
||||
|
||||
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, "")
|
||||
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, setupOption{})
|
||||
// Set the repo to be cloned through the testing backdoor.
|
||||
repoDir, headSHA := initializeRepo(t, c.RepoDir)
|
||||
|
||||
@@ -864,7 +888,7 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) {
|
||||
userConfig.EnablePolicyChecksFlag = true
|
||||
userConfig.QuietPolicyChecks = c.ExpQuietPolicyChecks
|
||||
|
||||
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, "")
|
||||
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, setupOption{})
|
||||
|
||||
// Set the repo to be cloned through the testing backdoor.
|
||||
repoDir, headSHA := initializeRepo(t, c.RepoDir)
|
||||
@@ -941,7 +965,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupE2E(t *testing.T, repoDir, repoConfigFile string) (events_controllers.VCSEventsController, *vcsmocks.MockClient, *mocks.MockGithubPullGetter, *events.FileWorkspace) {
|
||||
type setupOption struct {
|
||||
repoConfigFile string
|
||||
allowCommands []command.Name
|
||||
}
|
||||
|
||||
func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers.VCSEventsController, *vcsmocks.MockClient, *mocks.MockGithubPullGetter, *events.FileWorkspace) {
|
||||
allowForkPRs := false
|
||||
dataDir, binDir, cacheDir := mkSubDirs(t)
|
||||
|
||||
@@ -961,10 +990,15 @@ func setupE2E(t *testing.T, repoDir, repoConfigFile string) (events_controllers.
|
||||
GitlabUser: "gitlab-user",
|
||||
GitlabToken: "gitlab-token",
|
||||
}
|
||||
allowCommands := command.AllCommentCommands
|
||||
if opt.allowCommands != nil {
|
||||
allowCommands = opt.allowCommands
|
||||
}
|
||||
commentParser := &events.CommentParser{
|
||||
GithubUser: "github-user",
|
||||
GitlabUser: "gitlab-user",
|
||||
ExecutableName: "atlantis",
|
||||
AllowCommands: allowCommands,
|
||||
}
|
||||
terraformClient, err := terraform.NewClient(logger, binDir, cacheDir, "", "", "", "default-tf-version", "https://releases.hashicorp.com", &NoopTFDownloader{}, true, false, projectCmdOutputHandler)
|
||||
Ok(t, err)
|
||||
@@ -989,7 +1023,7 @@ func setupE2E(t *testing.T, repoDir, repoConfigFile string) (events_controllers.
|
||||
parser := &config.ParserValidator{}
|
||||
|
||||
globalCfgArgs := valid.GlobalCfgArgs{
|
||||
RepoConfigFile: repoConfigFile,
|
||||
RepoConfigFile: opt.repoConfigFile,
|
||||
AllowRepoCfg: true,
|
||||
MergeableReq: false,
|
||||
ApprovedReq: false,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
```
|
||||
Error: unknown command "import".
|
||||
Run 'atlantis --help' for usage.
|
||||
Available commands(--allow-commands): plan, apply
|
||||
```
|
||||
@@ -1,6 +1,7 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
@@ -30,6 +31,16 @@ const (
|
||||
// Adding more? Don't forget to update String() below
|
||||
)
|
||||
|
||||
// AllCommentCommands are list of commands that can be run from a comment.
|
||||
var AllCommentCommands = []Name{
|
||||
Version,
|
||||
Plan,
|
||||
Apply,
|
||||
Unlock,
|
||||
ApprovePolicies,
|
||||
Import,
|
||||
}
|
||||
|
||||
// TitleString returns the string representation in title form.
|
||||
// ie. policy_check becomes Policy Check
|
||||
func (c Name) TitleString() string {
|
||||
@@ -66,3 +77,24 @@ func (c Name) DefaultUsage() string {
|
||||
return c.String()
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCommandName parses raw name into a command name.
|
||||
func ParseCommandName(name string) (Name, error) {
|
||||
switch name {
|
||||
case "apply":
|
||||
return Apply, nil
|
||||
case "plan":
|
||||
return Plan, nil
|
||||
case "unlock":
|
||||
return Unlock, nil
|
||||
case "policy_check":
|
||||
return PolicyCheck, nil
|
||||
case "approve_policies":
|
||||
return ApprovePolicies, nil
|
||||
case "version":
|
||||
return Version, nil
|
||||
case "import":
|
||||
return Import, nil
|
||||
}
|
||||
return -1, fmt.Errorf("unknown command name: %s", name)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestName_TitleString(t *testing.T) {
|
||||
@@ -66,3 +67,30 @@ func TestName_DefaultUsage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCommandName(t *testing.T) {
|
||||
tests := []struct {
|
||||
exp command.Name
|
||||
name string
|
||||
}{
|
||||
{command.Apply, "apply"},
|
||||
{command.Plan, "plan"},
|
||||
{command.Unlock, "unlock"},
|
||||
{command.PolicyCheck, "policy_check"},
|
||||
{command.ApprovePolicies, "approve_policies"},
|
||||
{command.Version, "version"},
|
||||
{command.Import, "import"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := command.ParseCommandName(tt.name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.exp, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("unknown command", func(t *testing.T) {
|
||||
_, err := command.ParseCommandName("unknown")
|
||||
assert.ErrorContains(t, err, "unknown command name: unknown")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,8 +74,30 @@ type CommentParser struct {
|
||||
GitlabUser string
|
||||
BitbucketUser string
|
||||
AzureDevopsUser string
|
||||
ApplyDisabled bool
|
||||
ExecutableName string
|
||||
AllowCommands []command.Name
|
||||
}
|
||||
|
||||
// NewCommentParser returns a CommentParser
|
||||
func NewCommentParser(githubUser, gitlabUser, bitbucketUser, azureDevopsUser, executableName string, allowCommands []command.Name) *CommentParser {
|
||||
var commentAllowCommands []command.Name
|
||||
for _, acceptableCommand := range command.AllCommentCommands {
|
||||
for _, allowCommand := range allowCommands {
|
||||
if acceptableCommand == allowCommand {
|
||||
commentAllowCommands = append(commentAllowCommands, allowCommand)
|
||||
break // for distinct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &CommentParser{
|
||||
GithubUser: githubUser,
|
||||
GitlabUser: gitlabUser,
|
||||
BitbucketUser: bitbucketUser,
|
||||
AzureDevopsUser: azureDevopsUser,
|
||||
ExecutableName: executableName,
|
||||
AllowCommands: commentAllowCommands,
|
||||
}
|
||||
}
|
||||
|
||||
// CommentParseResult describes the result of parsing a comment as a command.
|
||||
@@ -160,18 +182,22 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
|
||||
// If they've just typed the name of the executable then give them the help
|
||||
// output.
|
||||
if len(args) == 1 {
|
||||
return CommentParseResult{CommentResponse: e.HelpComment(e.ApplyDisabled)}
|
||||
return CommentParseResult{CommentResponse: e.HelpComment()}
|
||||
}
|
||||
cmd := args[1]
|
||||
|
||||
// Help output.
|
||||
if e.stringInSlice(cmd, []string{"help", "-h", "--help"}) {
|
||||
return CommentParseResult{CommentResponse: e.HelpComment(e.ApplyDisabled)}
|
||||
return CommentParseResult{CommentResponse: e.HelpComment()}
|
||||
}
|
||||
|
||||
// Need to have a plan, apply, approve_policy or unlock at this point.
|
||||
if !e.stringInSlice(cmd, []string{command.Plan.String(), command.Apply.String(), command.Unlock.String(), command.ApprovePolicies.String(), command.Version.String(), command.Import.String()}) {
|
||||
return CommentParseResult{CommentResponse: fmt.Sprintf("```\nError: unknown command %q.\nRun '%s --help' for usage.\n```", cmd, e.ExecutableName)}
|
||||
// Need to have allow commands at this point.
|
||||
if !e.isAllowedCommand(cmd) {
|
||||
var allowCommandList []string
|
||||
for _, allowCommand := range e.AllowCommands {
|
||||
allowCommandList = append(allowCommandList, allowCommand.String())
|
||||
}
|
||||
return CommentParseResult{CommentResponse: fmt.Sprintf("```\nError: unknown command %q.\nRun '%s --help' for usage.\nAvailable commands(--allow-commands): %s\n```", cmd, e.ExecutableName, strings.Join(allowCommandList, ", "))}
|
||||
}
|
||||
|
||||
var workspace string
|
||||
@@ -374,24 +400,42 @@ func (e *CommentParser) stringInSlice(a string, list []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *CommentParser) isAllowedCommand(cmd string) bool {
|
||||
for _, allowed := range e.AllowCommands {
|
||||
if allowed.String() == cmd {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *CommentParser) errMarkdown(errMsg string, cmd string, flagSet *pflag.FlagSet) string {
|
||||
return fmt.Sprintf("```\nError: %s.\nUsage of %s:\n%s```", errMsg, cmd, flagSet.FlagUsagesWrapped(usagesCols))
|
||||
}
|
||||
|
||||
func (e *CommentParser) HelpComment(applyDisabled bool) string {
|
||||
func (e *CommentParser) HelpComment() string {
|
||||
buf := &bytes.Buffer{}
|
||||
var tmpl = template.Must(template.New("").Parse(helpCommentTemplate))
|
||||
if err := tmpl.Execute(buf, struct {
|
||||
ApplyDisabled bool
|
||||
ExecutableName string
|
||||
ExecutableName string
|
||||
AllowVersion bool
|
||||
AllowPlan bool
|
||||
AllowApply bool
|
||||
AllowUnlock bool
|
||||
AllowApprovePolicies bool
|
||||
AllowImport bool
|
||||
}{
|
||||
ApplyDisabled: applyDisabled,
|
||||
ExecutableName: e.ExecutableName,
|
||||
ExecutableName: e.ExecutableName,
|
||||
AllowVersion: e.isAllowedCommand(command.Version.String()),
|
||||
AllowPlan: e.isAllowedCommand(command.Plan.String()),
|
||||
AllowApply: e.isAllowedCommand(command.Apply.String()),
|
||||
AllowUnlock: e.isAllowedCommand(command.Unlock.String()),
|
||||
AllowApprovePolicies: e.isAllowedCommand(command.ApprovePolicies.String()),
|
||||
AllowImport: e.isAllowedCommand(command.Import.String()),
|
||||
}); err != nil {
|
||||
return fmt.Sprintf("Failed to render template, this is a bug: %v", err)
|
||||
}
|
||||
return buf.String()
|
||||
|
||||
}
|
||||
|
||||
var helpCommentTemplate = "```cmake\n" +
|
||||
@@ -402,9 +446,14 @@ Usage:
|
||||
{{ .ExecutableName }} <command> [options] -- [terraform options]
|
||||
|
||||
Examples:
|
||||
# show atlantis help
|
||||
{{ .ExecutableName }} help
|
||||
{{- if .AllowPlan }}
|
||||
|
||||
# run plan in the root directory passing the -target flag to terraform
|
||||
{{ .ExecutableName }} plan -d . -- -target=resource
|
||||
{{- if not .ApplyDisabled }}
|
||||
{{- end }}
|
||||
{{- if .AllowApply }}
|
||||
|
||||
# apply all unapplied plans from this pull request
|
||||
{{ .ExecutableName }} apply
|
||||
@@ -414,19 +463,29 @@ Examples:
|
||||
{{- end }}
|
||||
|
||||
Commands:
|
||||
{{- if .AllowPlan }}
|
||||
plan Runs 'terraform plan' for the changes in this pull request.
|
||||
To plan a specific project, use the -d, -w and -p flags.
|
||||
{{- if not .ApplyDisabled }}
|
||||
{{- end }}
|
||||
{{- if .AllowApply }}
|
||||
apply Runs 'terraform apply' on all unapplied plans from this pull request.
|
||||
To only apply a specific plan, use the -d, -w and -p flags.
|
||||
{{- end }}
|
||||
{{- if .AllowUnlock }}
|
||||
unlock Removes all atlantis locks and discards all plans for this PR.
|
||||
To unlock a specific plan you can use the Atlantis UI.
|
||||
{{- end }}
|
||||
{{- if .AllowApprovePolicies }}
|
||||
approve_policies
|
||||
Approves all current policy checking failures for the PR.
|
||||
{{- end }}
|
||||
{{- if .AllowVersion }}
|
||||
version Print the output of 'terraform version'
|
||||
{{- end }}
|
||||
{{- if .AllowImport }}
|
||||
import Runs 'terraform import' for the changes in this pull request.
|
||||
To plan a specific project, use the -d, -w and -p flags.
|
||||
{{- end }}
|
||||
help View help.
|
||||
|
||||
Flags:
|
||||
|
||||
@@ -22,12 +22,61 @@ import (
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var commentParser = events.CommentParser{
|
||||
GithubUser: "github-user",
|
||||
GitlabUser: "gitlab-user",
|
||||
ExecutableName: "atlantis",
|
||||
AllowCommands: []command.Name{
|
||||
command.Plan,
|
||||
command.Apply,
|
||||
command.Unlock,
|
||||
command.ApprovePolicies,
|
||||
command.Import,
|
||||
},
|
||||
}
|
||||
|
||||
func TestNewCommentParser(t *testing.T) {
|
||||
type args struct {
|
||||
githubUser string
|
||||
gitlabUser string
|
||||
bitbucketUser string
|
||||
azureDevopsUser string
|
||||
executableName string
|
||||
allowCommands []command.Name
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *events.CommentParser
|
||||
}{
|
||||
{
|
||||
name: "duplicate allow commands filtered",
|
||||
args: args{
|
||||
allowCommands: []command.Name{command.Plan, command.Plan, command.Plan},
|
||||
},
|
||||
want: &events.CommentParser{
|
||||
AllowCommands: []command.Name{command.Plan},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "comment un-available commands filtered",
|
||||
args: args{
|
||||
// PolicyCheck and Autoplan cannot be used on comment command, so filtered
|
||||
allowCommands: []command.Name{command.Plan, command.Apply, command.Unlock, command.PolicyCheck, command.ApprovePolicies, command.Autoplan, command.Version, command.Import},
|
||||
},
|
||||
want: &events.CommentParser{
|
||||
AllowCommands: []command.Name{command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, events.NewCommentParser(tt.args.githubUser, tt.args.gitlabUser, tt.args.bitbucketUser, tt.args.azureDevopsUser, tt.args.executableName, tt.args.allowCommands), "NewCommentParser(%v, %v, %v, %v, %v, %v)", tt.args.githubUser, tt.args.gitlabUser, tt.args.bitbucketUser, tt.args.azureDevopsUser, tt.args.executableName, tt.args.allowCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_Ignored(t *testing.T) {
|
||||
@@ -70,6 +119,10 @@ func TestParse_ExecutableName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParse_HelpResponse(t *testing.T) {
|
||||
allowCommandsCases := [][]command.Name{
|
||||
command.AllCommentCommands,
|
||||
{}, // empty case
|
||||
}
|
||||
helpComments := []string{
|
||||
"run",
|
||||
"atlantis",
|
||||
@@ -80,27 +133,18 @@ func TestParse_HelpResponse(t *testing.T) {
|
||||
"atlantis help something else",
|
||||
"atlantis help plan",
|
||||
}
|
||||
for _, c := range helpComments {
|
||||
r := commentParser.Parse(c, models.Github)
|
||||
Equals(t, commentParser.HelpComment(false), r.CommentResponse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_HelpResponseWithApplyDisabled(t *testing.T) {
|
||||
helpComments := []string{
|
||||
"run",
|
||||
"atlantis",
|
||||
"@github-user",
|
||||
"atlantis help",
|
||||
"atlantis --help",
|
||||
"atlantis -h",
|
||||
"atlantis help something else",
|
||||
"atlantis help plan",
|
||||
}
|
||||
for _, c := range helpComments {
|
||||
commentParser.ApplyDisabled = true
|
||||
r := commentParser.Parse(c, models.Github)
|
||||
Equals(t, commentParser.HelpComment(true), r.CommentResponse)
|
||||
for _, allowCommandCase := range allowCommandsCases {
|
||||
for _, c := range helpComments {
|
||||
t.Run(fmt.Sprintf("%s with allow commands %v", c, allowCommandCase), func(t *testing.T) {
|
||||
commentParser := events.CommentParser{
|
||||
GithubUser: "github-user",
|
||||
ExecutableName: "atlantis",
|
||||
AllowCommands: allowCommandCase,
|
||||
}
|
||||
r := commentParser.Parse(c, models.Github)
|
||||
Equals(t, commentParser.HelpComment(), r.CommentResponse)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,11 +283,24 @@ func TestParse_InvalidCommand(t *testing.T) {
|
||||
"atlantis Plan",
|
||||
"atlantis appely apply",
|
||||
}
|
||||
cp := events.NewCommentParser(
|
||||
"github-user",
|
||||
"gitlab-user",
|
||||
"bitbucket-user",
|
||||
"azure-devops-user",
|
||||
"atlantis",
|
||||
[]command.Name{
|
||||
command.Version,
|
||||
command.Unlock,
|
||||
command.Apply,
|
||||
command.Plan,
|
||||
command.Apply, // duplicate command is filtered
|
||||
},
|
||||
)
|
||||
for _, c := range comments {
|
||||
r := commentParser.Parse(c, models.Github)
|
||||
exp := fmt.Sprintf("```\nError: unknown command %q.\nRun 'atlantis --help' for usage.\n```", strings.Fields(c)[1])
|
||||
Assert(t, r.CommentResponse == exp,
|
||||
"For comment %q expected CommentResponse==%q but got %q", c, exp, r.CommentResponse)
|
||||
r := cp.Parse(c, models.Github)
|
||||
exp := fmt.Sprintf("```\nError: unknown command %q.\nRun 'atlantis --help' for usage.\nAvailable commands(--allow-commands): version, plan, apply, unlock\n```", strings.Fields(c)[1])
|
||||
Equals(t, exp, r.CommentResponse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,11 +836,13 @@ func TestBuildPlanApplyVersionComment(t *testing.T) {
|
||||
|
||||
func TestCommentParser_HelpComment(t *testing.T) {
|
||||
cases := []struct {
|
||||
applyDisabled bool
|
||||
name string
|
||||
allowCommands []command.Name
|
||||
expectResult string
|
||||
}{
|
||||
{
|
||||
applyDisabled: false,
|
||||
name: "all commands allowed",
|
||||
allowCommands: command.AllCommentCommands,
|
||||
expectResult: "```cmake\n" +
|
||||
`atlantis
|
||||
Terraform Pull Request Automation
|
||||
@@ -792,6 +851,9 @@ Usage:
|
||||
atlantis <command> [options] -- [terraform options]
|
||||
|
||||
Examples:
|
||||
# show atlantis help
|
||||
atlantis help
|
||||
|
||||
# run plan in the root directory passing the -target flag to terraform
|
||||
atlantis plan -d . -- -target=resource
|
||||
|
||||
@@ -822,7 +884,8 @@ Use "atlantis [command] --help" for more information about a command.` +
|
||||
"\n```",
|
||||
},
|
||||
{
|
||||
applyDisabled: true,
|
||||
name: "all commands disallowed",
|
||||
allowCommands: []command.Name{},
|
||||
expectResult: "```cmake\n" +
|
||||
`atlantis
|
||||
Terraform Pull Request Automation
|
||||
@@ -831,19 +894,46 @@ Usage:
|
||||
atlantis <command> [options] -- [terraform options]
|
||||
|
||||
Examples:
|
||||
# run plan in the root directory passing the -target flag to terraform
|
||||
atlantis plan -d . -- -target=resource
|
||||
# show atlantis help
|
||||
atlantis help
|
||||
|
||||
Commands:
|
||||
plan Runs 'terraform plan' for the changes in this pull request.
|
||||
To plan a specific project, use the -d, -w and -p flags.
|
||||
help View help.
|
||||
|
||||
Flags:
|
||||
-h, --help help for atlantis
|
||||
|
||||
Use "atlantis [command] --help" for more information about a command.` +
|
||||
"\n```",
|
||||
},
|
||||
{
|
||||
name: "partial commands allowed",
|
||||
allowCommands: []command.Name{
|
||||
command.Apply,
|
||||
command.Unlock,
|
||||
},
|
||||
expectResult: "```cmake\n" +
|
||||
`atlantis
|
||||
Terraform Pull Request Automation
|
||||
|
||||
Usage:
|
||||
atlantis <command> [options] -- [terraform options]
|
||||
|
||||
Examples:
|
||||
# show atlantis help
|
||||
atlantis help
|
||||
|
||||
# apply all unapplied plans from this pull request
|
||||
atlantis apply
|
||||
|
||||
# apply the plan for the root directory and staging workspace
|
||||
atlantis apply -d . -w staging
|
||||
|
||||
Commands:
|
||||
apply Runs 'terraform apply' on all unapplied plans from this pull request.
|
||||
To only apply a specific plan, use the -d, -w and -p flags.
|
||||
unlock Removes all atlantis locks and discards all plans for this PR.
|
||||
To unlock a specific plan you can use the Atlantis UI.
|
||||
approve_policies
|
||||
Approves all current policy checking failures for the PR.
|
||||
version Print the output of 'terraform version'
|
||||
import Runs 'terraform import' for the changes in this pull request.
|
||||
To plan a specific project, use the -d, -w and -p flags.
|
||||
help View help.
|
||||
|
||||
Flags:
|
||||
@@ -855,8 +945,12 @@ Use "atlantis [command] --help" for more information about a command.` +
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("ApplyDisabled: %v", c.applyDisabled), func(t *testing.T) {
|
||||
Equals(t, commentParser.HelpComment(c.applyDisabled), c.expectResult)
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
commentParser := events.CommentParser{
|
||||
ExecutableName: "atlantis",
|
||||
AllowCommands: c.allowCommands,
|
||||
}
|
||||
Equals(t, commentParser.HelpComment(), c.expectResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -898,7 +992,7 @@ func TestParse_VCSUsername(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.vcs.String(), func(t *testing.T) {
|
||||
r := cp.Parse(fmt.Sprintf("@%s %s", c.user, "help"), c.vcs)
|
||||
Equals(t, commentParser.HelpComment(false), r.CommentResponse)
|
||||
Equals(t, cp.HelpComment(), r.CommentResponse)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/uber-go/tally"
|
||||
"github.com/uber-go/tally/prometheus"
|
||||
"github.com/urfave/negroni/v3"
|
||||
|
||||
cfg "github.com/runatlantis/atlantis/server/core/config"
|
||||
"github.com/runatlantis/atlantis/server/core/config/valid"
|
||||
@@ -47,8 +48,6 @@ import (
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/negroni/v3"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/controllers"
|
||||
events_controllers "github.com/runatlantis/atlantis/server/controllers/events"
|
||||
"github.com/runatlantis/atlantis/server/controllers/templates"
|
||||
@@ -176,6 +175,18 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
policyChecksEnabled = true
|
||||
}
|
||||
|
||||
allowCommands, err := userConfig.ToAllowCommandNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disableApply := true
|
||||
for _, allowCommand := range allowCommands {
|
||||
if allowCommand == command.Apply {
|
||||
disableApply = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
validator := &cfg.ParserValidator{}
|
||||
|
||||
globalCfg := valid.NewGlobalCfgFromArgs(
|
||||
@@ -405,7 +416,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
gitlabClient.SupportsCommonMark(),
|
||||
userConfig.DisableApplyAll,
|
||||
userConfig.DisableMarkdownFolding,
|
||||
userConfig.DisableApply,
|
||||
disableApply,
|
||||
userConfig.DisableRepoLocking,
|
||||
userConfig.EnableDiffMarkdownFormat,
|
||||
userConfig.MarkdownTemplateOverridesDir,
|
||||
@@ -438,7 +449,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
lockingClient = locking.NewClient(backend)
|
||||
}
|
||||
|
||||
applyLockingClient = locking.NewApplyClient(backend, userConfig.DisableApply)
|
||||
applyLockingClient = locking.NewApplyClient(backend, disableApply)
|
||||
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
||||
|
||||
var workingDir events.WorkingDir = &events.FileWorkspace{
|
||||
@@ -496,14 +507,14 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
AzureDevopsUser: userConfig.AzureDevopsUser,
|
||||
AzureDevopsToken: userConfig.AzureDevopsToken,
|
||||
}
|
||||
commentParser := &events.CommentParser{
|
||||
GithubUser: userConfig.GithubUser,
|
||||
GitlabUser: userConfig.GitlabUser,
|
||||
BitbucketUser: userConfig.BitbucketUser,
|
||||
AzureDevopsUser: userConfig.AzureDevopsUser,
|
||||
ApplyDisabled: userConfig.DisableApply,
|
||||
ExecutableName: userConfig.ExecutableName,
|
||||
}
|
||||
commentParser := events.NewCommentParser(
|
||||
userConfig.GithubUser,
|
||||
userConfig.GitlabUser,
|
||||
userConfig.BitbucketUser,
|
||||
userConfig.AzureDevopsUser,
|
||||
userConfig.ExecutableName,
|
||||
allowCommands,
|
||||
)
|
||||
defaultTfVersion := terraformClient.DefaultVersion()
|
||||
pendingPlanFinder := &events.DefaultPendingPlanFinder{}
|
||||
runStepRunner := &runtime.RunStepRunner{
|
||||
@@ -820,7 +831,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
CommentParser: commentParser,
|
||||
Logger: logger,
|
||||
Scope: statsScope,
|
||||
ApplyDisabled: userConfig.DisableApply,
|
||||
ApplyDisabled: disableApply,
|
||||
GithubWebhookSecret: []byte(userConfig.GithubWebhookSecret),
|
||||
GithubRequestValidator: &events_controllers.DefaultGithubRequestValidator{},
|
||||
GitlabRequestParserValidator: &events_controllers.DefaultGitlabRequestParserValidator{},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
)
|
||||
|
||||
@@ -10,6 +13,7 @@ import (
|
||||
type UserConfig struct {
|
||||
AllowForkPRs bool `mapstructure:"allow-fork-prs"`
|
||||
AllowRepoConfig bool `mapstructure:"allow-repo-config"`
|
||||
AllowCommands string `mapstructure:"allow-commands"`
|
||||
AtlantisURL string `mapstructure:"atlantis-url"`
|
||||
Automerge bool `mapstructure:"automerge"`
|
||||
AutoplanFileList string `mapstructure:"autoplan-file-list"`
|
||||
@@ -115,6 +119,30 @@ type UserConfig struct {
|
||||
WebsocketCheckOrigin bool `mapstructure:"websocket-check-origin"`
|
||||
}
|
||||
|
||||
// ToAllowCommandNames parse AllowCommands into a slice of CommandName
|
||||
func (u UserConfig) ToAllowCommandNames() ([]command.Name, error) {
|
||||
var allowCommands []command.Name
|
||||
var hasAll bool
|
||||
for _, input := range strings.Split(u.AllowCommands, ",") {
|
||||
if input == "" {
|
||||
continue
|
||||
}
|
||||
if input == "all" {
|
||||
hasAll = true
|
||||
continue
|
||||
}
|
||||
cmd, err := command.ParseCommandName(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowCommands = append(allowCommands, cmd)
|
||||
}
|
||||
if hasAll {
|
||||
return command.AllCommentCommands, nil
|
||||
}
|
||||
return allowCommands, nil
|
||||
}
|
||||
|
||||
// ToLogLevel returns the LogLevel object corresponding to the user-passed
|
||||
// log level.
|
||||
func (u UserConfig) ToLogLevel() logging.LogLevel {
|
||||
|
||||
@@ -4,10 +4,70 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/runatlantis/atlantis/server"
|
||||
"github.com/runatlantis/atlantis/server/events/command"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserConfig_ToAllowCommandNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
allowCommands string
|
||||
want []command.Name
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "full commands can be parsed by comma",
|
||||
allowCommands: "apply,plan,unlock,policy_check,approve_policies,version,import",
|
||||
want: []command.Name{
|
||||
command.Apply, command.Plan, command.Unlock, command.PolicyCheck, command.ApprovePolicies, command.Version, command.Import,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all",
|
||||
allowCommands: "all",
|
||||
want: []command.Name{
|
||||
command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all with others returns same with all result",
|
||||
allowCommands: "all,plan",
|
||||
want: []command.Name{
|
||||
command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
allowCommands: "",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid command",
|
||||
allowCommands: "plan,all,invalid",
|
||||
wantErr: "unknown command name: invalid",
|
||||
},
|
||||
{
|
||||
name: "invalid command",
|
||||
allowCommands: "invalid,plan,all",
|
||||
wantErr: "unknown command name: invalid",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
u := server.UserConfig{
|
||||
AllowCommands: tt.allowCommands,
|
||||
}
|
||||
got, err := u.ToAllowCommandNames()
|
||||
if err != nil {
|
||||
assert.ErrorContains(t, err, tt.wantErr, "ToAllowCommandNames()")
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "ToAllowCommandNames()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserConfig_ToLogLevel(t *testing.T) {
|
||||
cases := []struct {
|
||||
userLvl string
|
||||
|
||||
Reference in New Issue
Block a user