Skip to content

Commit

Permalink
Discard command available but not working
Browse files Browse the repository at this point in the history
At this point you get the discard command option after
a successful plan, and you can execute the discard command.
However it's mapped to perform a plan atm.

In follow up commit I will change it to actually discard
a previous plan and lock
  • Loading branch information
Paris Morali committed Apr 22, 2020
1 parent fea1891 commit 228818a
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 11 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ docker run --rm -v $(pwd):/go/src/github.com/runatlantis/atlantis -w /go/src/git

## Calling Your Local Atlantis From GitHub
- Create a test terraform repository in your GitHub.
- Create a personal access token for Atlantis. See [Create a GitHub token](https://github.com/runatlantis/atlantis#create-a-github-token).
- Create a personal access token for Atlantis. See [Create a GitHub token](https://github.com/runatlantis/atlantis/tree/master/runatlantis.io/docs/access-credentials.md#generating-an-access-token).
- Start Atlantis in server mode using that token:
```
atlantis server --gh-user <your username> --gh-token <your token> --repo-whitelist <your repo> --gh-webhook-secret <your webhook secret> --log-level debug
Expand All @@ -65,7 +65,7 @@ atlantis server --gh-user <your username> --gh-token <your token> --repo-whiteli
```
ngrok http 4141
```
- Create a Webhook in your repo and use the `https` url that `ngrok` printed out after running `ngrok http 4141`. Be sure to append `/events` so your webhook url looks something like `https://efce3bcd.ngrok.io/events`. See [Add GitHub Webhook](https://github.com/runatlantis/atlantis#add-github-webhook).
- Create a Webhook in your repo and use the `https` url that `ngrok` printed out after running `ngrok http 4141`. Be sure to append `/events` so your webhook url looks something like `https://efce3bcd.ngrok.io/events`. See [Add GitHub Webhook](https://github.com/runatlantis/atlantis/blob/master/runatlantis.io/docs/configuring-webhooks.md#configuring-webhooks).
- Create a pull request and type `atlantis help`. You should see the request in the `ngrok` and Atlantis logs and you should also see Atlantis comment back.

## Code Style
Expand Down
4 changes: 4 additions & 0 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
projectCmds, err = c.ProjectCommandBuilder.BuildPlanCommands(ctx, cmd)
case models.ApplyCommand:
projectCmds, err = c.ProjectCommandBuilder.BuildApplyCommands(ctx, cmd)
case models.DiscardCommand:
projectCmds, err = c.ProjectCommandBuilder.BuildDiscardCommands(ctx, cmd)
default:
ctx.Log.Err("failed to determine desired command, neither plan nor apply")
return
Expand Down Expand Up @@ -348,6 +350,8 @@ func (c *DefaultCommandRunner) runProjectCmds(cmds []models.ProjectCommandContex
res = c.ProjectCommandRunner.Plan(pCmd)
case models.ApplyCommand:
res = c.ProjectCommandRunner.Apply(pCmd)
case models.DiscardCommand:
res = c.ProjectCommandRunner.Discard(pCmd)
}
results = append(results, res)
}
Expand Down
40 changes: 34 additions & 6 deletions server/events/comment_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type CommentBuilder interface {
BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string
// BuildApplyComment builds an apply comment for the specified args.
BuildApplyComment(repoRelDir string, workspace string, project string) string
// BuildDiscardComment builds a discard comment for the specified args.
BuildDiscardComment(repoRelDir string, workspace string, project string, commentArgs []string) string
}

// CommentParser implements CommentParsing
Expand Down Expand Up @@ -157,7 +159,7 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
}

// Need to have a plan or apply at this point.
if !e.stringInSlice(command, []string{models.PlanCommand.String(), models.ApplyCommand.String()}) {
if !e.stringInSlice(command, []string{models.PlanCommand.String(), models.ApplyCommand.String(), models.DiscardCommand.String()}) {
return CommentParseResult{CommentResponse: fmt.Sprintf("```\nError: unknown command %q.\nRun 'atlantis --help' for usage.\n```", command)}
}

Expand Down Expand Up @@ -186,6 +188,14 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Apply the plan for this directory, relative to root of repo, ex. 'child/dir'.")
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Apply the plan for this project. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", yaml.AtlantisYAMLFilename))
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
case models.DiscardCommand.String():
name = models.DiscardCommand
flagSet = pflag.NewFlagSet(models.DiscardCommand.String(), pflag.ContinueOnError)
flagSet.SetOutput(ioutil.Discard)
flagSet.StringVarP(&workspace, workspaceFlagLong, workspaceFlagShort, "", "Switch to this Terraform workspace before planning.")
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run plan in relative to root of repo, ex. 'child/dir'.")
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Which project to discard the plan for. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", yaml.AtlantisYAMLFilename))

default:
return CommentParseResult{CommentResponse: fmt.Sprintf("Error: unknown command %q – this is a bug", command)}
}
Expand Down Expand Up @@ -264,6 +274,22 @@ func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, p
return fmt.Sprintf("%s %s%s", atlantisExecutable, models.ApplyCommand.String(), flags)
}

// BuildDiscardComment builds discard comment for the specified args.
func (e *CommentParser) BuildDiscardComment(repoRelDir string, workspace string, project string, commentArgs []string) string {
flags := e.buildFlags(repoRelDir, workspace, project)
commentFlags := ""
if len(commentArgs) > 0 {
var flagsWithoutQuotes []string
for _, f := range commentArgs {
f = strings.TrimPrefix(f, "\"")
f = strings.TrimSuffix(f, "\"")
flagsWithoutQuotes = append(flagsWithoutQuotes, f)
}
commentFlags = fmt.Sprintf(" -- %s", strings.Join(flagsWithoutQuotes, " "))
}
return fmt.Sprintf("%s %s%s%s", atlantisExecutable, models.DiscardCommand.String(), flags, commentFlags)
}

func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string) string {
// Add quotes if dir has spaces.
if strings.Contains(repoRelDir, " ") {
Expand Down Expand Up @@ -342,11 +368,13 @@ Examples:
atlantis apply -d . -w staging
Commands:
plan Runs 'terraform plan' for the changes in this pull request.
To plan a specific project, use the -d, -w and -p flags.
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.
help View help.
plan Runs 'terraform plan' for the changes in this pull request.
To plan a specific project, use the -d, -w and -p flags.
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.
discard Discards a previous plan as well as the atlantis lock.
To discard a specific plan and atlantis lock use the -d flag.
help View help.
Flags:
-h, --help help for atlantis
Expand Down
1 change: 1 addition & 0 deletions server/events/db/boltdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ func TestPullStatus_UpdateMerge(t *testing.T) {
LockURL: "lock-url",
RePlanCmd: "plan command",
ApplyCmd: "apply command",
DiscardCmd: "discard command",
},
},
})
Expand Down
5 changes: 4 additions & 1 deletion server/events/markdown_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ var singleProjectPlanSuccessTmpl = template.Must(template.New("").Parse(
var singleProjectPlanUnsuccessfulTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n" +
"{{$result.Rendered}}\n" + logTmpl))
var singleProjectDiscardTmpl = template.Must(template.New("").Parse(
"{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" + logTmpl))
var multiProjectPlanTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
"Ran {{.Command}} for {{ len .Results }} projects:\n\n" +
"{{ range $result := .Results }}" +
Expand Down Expand Up @@ -249,7 +251,8 @@ var planSuccessWrappedTmpl = template.Must(template.New("").Parse(
// to do next.
var planNextSteps = "{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}* :arrow_forward: To **apply** this plan, comment:\n" +
" * `{{.ApplyCmd}}`\n" +
"* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})\n" +
"* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}}), or comment:\n" +
" * `{{.DiscardCmd}}`\n" +
"* :repeat: To **plan** this project again, comment:\n" +
" * `{{.RePlanCmd}}`{{end}}"
var applyUnwrappedSuccessTmpl = template.Must(template.New("").Parse(
Expand Down
9 changes: 9 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ type ProjectCommandContext struct {
AutoplanEnabled bool
// BaseRepo is the repository that the pull request will be merged into.
BaseRepo Repo
// DiscardCmd is the command that users should run to discard a plan.
// If this is an apply then this will be empty.
DiscardCmd string
// EscapedCommentArgs are the extra arguments that were added to the atlantis
// command, ex. atlantis plan -- -target=resource. We then escape them
// by adding a \ before each character so that they can be used within
Expand Down Expand Up @@ -431,6 +434,8 @@ type PlanSuccess struct {
RePlanCmd string
// ApplyCmd is the command that users should run to apply this plan.
ApplyCmd string
// DiscardCmd is the command that users should run to discard this plan.
DiscardCmd string
// HasDiverged is true if we're using the checkout merge strategy and the
// branch we're merging into has been updated since we cloned and merged
// it.
Expand Down Expand Up @@ -508,6 +513,8 @@ const (
ApplyCommand CommandName = iota
// PlanCommand is a command to run terraform plan.
PlanCommand
// DiscardCommand is a command to discard a previous plan as well as the atlantis lock.
DiscardCommand
// Adding more? Don't forget to update String() below
)

Expand All @@ -518,6 +525,8 @@ func (c CommandName) String() string {
return "apply"
case PlanCommand:
return "plan"
case DiscardCommand:
return "discard"
}
return ""
}
43 changes: 43 additions & 0 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type ProjectCommandBuilder interface {
// comment doesn't specify one project then there may be multiple commands
// to be run.
BuildApplyCommands(ctx *CommandContext, comment *CommentCommand) ([]models.ProjectCommandContext, error)
// BuildDiscardCommands builds project discard commands for ctx and comment. If
// comment doesn't specify one project then there may be multiple commands
// to be run.
BuildDiscardCommands(ctx *CommandContext, comment *CommentCommand) ([]models.ProjectCommandContext, error)
}

// DefaultProjectCommandBuilder implements ProjectCommandBuilder.
Expand Down Expand Up @@ -93,6 +97,15 @@ func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *CommandContext, c
return []models.ProjectCommandContext{pac}, err
}

// See ProjectCommandBuilder.BuildDiscardCommands.
func (p *DefaultProjectCommandBuilder) BuildDiscardCommands(ctx *CommandContext, cmd *CommentCommand) ([]models.ProjectCommandContext, error) {
//if !cmd.IsForSpecificProject() {
// return p.buildDiscardAllCommands(ctx, cmd.Flags, cmd.Verbose)
//}
pcc, err := p.buildProjectDiscardCommand(ctx, cmd)
return []models.ProjectCommandContext{pcc}, err
}

// buildPlanAllCommands builds plan contexts for all projects we determine were
// modified in this ctx.
func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext, commentFlags []string, verbose bool) ([]models.ProjectCommandContext, error) {
Expand Down Expand Up @@ -249,6 +262,35 @@ func (p *DefaultProjectCommandBuilder) buildProjectApplyCommand(ctx *CommandCont
return p.buildProjectCommandCtx(ctx, models.ApplyCommand, cmd.ProjectName, cmd.Flags, repoDir, repoRelDir, workspace, cmd.Verbose)
}

// cmd must be for only one project.
func (p *DefaultProjectCommandBuilder) buildProjectDiscardCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
workspace := DefaultWorkspace
if cmd.Workspace != "" {
workspace = cmd.Workspace
}

var pcc models.ProjectCommandContext
ctx.Log.Debug("building plan command")
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, ctx.Pull.Num, workspace)
if err != nil {
return pcc, err
}
defer unlockFn()

ctx.Log.Debug("cloning repository")
repoDir, _, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
if err != nil {
return pcc, err
}

repoRelDir := DefaultRepoRelDir
if cmd.RepoRelDir != "" {
repoRelDir = cmd.RepoRelDir
}

return p.buildProjectCommandCtx(ctx, models.PlanCommand, cmd.ProjectName, cmd.Flags, repoDir, repoRelDir, workspace, cmd.Verbose)
}

// buildProjectCommandCtx builds a context for a single project identified
// by the parameters.
func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(
Expand Down Expand Up @@ -406,6 +448,7 @@ func (p *DefaultProjectCommandBuilder) buildCtx(ctx *CommandContext,
ProjectName: projCfg.Name,
ApplyRequirements: projCfg.ApplyRequirements,
RePlanCmd: p.CommentBuilder.BuildPlanComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name, commentArgs),
DiscardCmd: p.CommentBuilder.BuildDiscardComment(projCfg.RepoRelDir, projCfg.Workspace, projCfg.Name, commentArgs),
RepoRelDir: projCfg.RepoRelDir,
RepoConfigVersion: projCfg.RepoCfgVersion,
TerraformVersion: projCfg.TerraformVersion,
Expand Down
18 changes: 18 additions & 0 deletions server/events/project_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type ProjectCommandRunner interface {
Plan(ctx models.ProjectCommandContext) models.ProjectResult
// Apply runs terraform apply for the project described by ctx.
Apply(ctx models.ProjectCommandContext) models.ProjectResult
// Discard runs terraform discard for the project described by ctx.
Discard(ctx models.ProjectCommandContext) models.ProjectResult
}

// DefaultProjectCommandRunner implements ProjectCommandRunner.
Expand All @@ -96,6 +98,7 @@ type DefaultProjectCommandRunner struct {
InitStepRunner StepRunner
PlanStepRunner StepRunner
ApplyStepRunner StepRunner
DiscardStepRunner StepRunner
RunStepRunner CustomStepRunner
EnvStepRunner EnvStepRunner
PullApprovedChecker runtime.PullApprovedChecker
Expand Down Expand Up @@ -132,6 +135,20 @@ func (p *DefaultProjectCommandRunner) Apply(ctx models.ProjectCommandContext) mo
}
}

// Discard deletes the atlantis plan and discards the lock for the project described by ctx.
func (p *DefaultProjectCommandRunner) Discard(ctx models.ProjectCommandContext) models.ProjectResult {
planSuccess, failure, err := p.doPlan(ctx)
return models.ProjectResult{
Command: models.PlanCommand,
PlanSuccess: planSuccess,
Error: err,
Failure: failure,
RepoRelDir: ctx.RepoRelDir,
Workspace: ctx.Workspace,
ProjectName: ctx.ProjectName,
}
}

func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (*models.PlanSuccess, string, error) {
// Acquire Atlantis lock for this repo/dir/workspace.
lockAttempt, err := p.Locker.TryLock(ctx.Log, ctx.Pull, ctx.User, ctx.Workspace, models.NewProject(ctx.BaseRepo.FullName, ctx.RepoRelDir))
Expand Down Expand Up @@ -176,6 +193,7 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (
TerraformOutput: strings.Join(outputs, "\n"),
RePlanCmd: ctx.RePlanCmd,
ApplyCmd: ctx.ApplyCmd,
DiscardCmd: ctx.DiscardCmd,
HasDiverged: hasDiverged,
}, "", nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"type": null,
"updated_at": "2018-08-22 06:14:24 UTC",
"updated_by_id": null,
"description": "Ran Plan in dir: `.` workspace: `default`\n\n```diff\nRefreshing Terraform state in-memory prior to plan...\nThe refreshed state will be used to calculate this plan, but will not be\npersisted to local or remote state storage.\n\n\n------------------------------------------------------------------------\n\nAn execution plan has been generated and is shown below.\nResource actions are indicated with the following symbols:\n + create\n\nTerraform will perform the following actions:\n\n+ null_resource.test\n id: <computed>\nPlan: 1 to add, 0 to change, 0 to destroy.\n\n```\n\n* :arrow_forward: To **apply** this plan, comment:\n * `atlantis apply -d .`\n* :put_litter_in_its_place: To **delete** this plan click [here](http://Lukes-Macbook-Pro.local:4141/lock?id=lkysow-test%252Fsubgroup%252Fsub-subgroup%252Fatlantis-example%252F.%252Fdefault)\n* :repeat: To **plan** this project again, comment:\n * `atlantis plan -d .`\n\n---\n* :fast_forward: To **apply** all unapplied plans from this pull request, comment:\n * `atlantis apply`",
"description": "Ran Plan in dir: `.` workspace: `default`\n\n```diff\nRefreshing Terraform state in-memory prior to plan...\nThe refreshed state will be used to calculate this plan, but will not be\npersisted to local or remote state storage.\n\n\n------------------------------------------------------------------------\n\nAn execution plan has been generated and is shown below.\nResource actions are indicated with the following symbols:\n + create\n\nTerraform will perform the following actions:\n\n+ null_resource.test\n id: <computed>\nPlan: 1 to add, 0 to change, 0 to destroy.\n\n```\n\n* :arrow_forward: To **apply** this plan, comment:\n * `atlantis apply -d .`\n* :put_litter_in_its_place: To **delete** this plan click [here](http://Lukes-Macbook-Pro.local:4141/lock?id=lkysow-test%252Fsubgroup%252Fsub-subgroup%252Fatlantis-example%252F.%252Fdefault) or comment:\n * `atlantis discard -d .`\n* :repeat: To **plan** this project again, comment:\n * `atlantis plan -d .`\n\n---\n* :fast_forward: To **apply** all unapplied plans from this pull request, comment:\n * `atlantis apply`",
"url": "https://gitlab.com/lkysow-test/subgroup/sub-subgroup/atlantis-example/merge_requests/2#note_96056916"
},
"repository": {
Expand Down Expand Up @@ -137,4 +137,4 @@
"human_total_time_spent": null,
"human_time_estimate": null
}
}
}
3 changes: 3 additions & 0 deletions temp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

aws-vault exec dev-paris -- atlantis server --gh-user parmouraly --gh-token fa99cb86018c180e4d3d28775da6f01c3627655b --repo-whitelist github.com/parmouraly/gh-atlantis-test --gh-webhook-secret asdfasdf --log-level debug

0 comments on commit 228818a

Please sign in to comment.