Skip to content

Commit

Permalink
Fork workflows (runatlantis#64)
Browse files Browse the repository at this point in the history
* Name LockURL route

* Support pull requests from forks
  • Loading branch information
lkysow authored and anubhavmishra committed Jul 2, 2017
1 parent 5ea4ccd commit ac83457
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 61 deletions.
22 changes: 11 additions & 11 deletions server/apply_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,18 @@ func (n NoPlansFailure) Template() *CompiledTemplate {
}

func (a *ApplyExecutor) execute(ctx *CommandContext, github *GithubClient) {
if a.concurrentRunLocker.TryLock(ctx.Repo.FullName, ctx.Command.environment, ctx.Pull.Num) != true {
if a.concurrentRunLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.environment, ctx.Pull.Num) != true {
ctx.Log.Info("run was locked by a concurrent run")
github.CreateComment(ctx.Repo, ctx.Pull, "This environment is currently locked by another command that is running for this pull request. Wait until command is complete and try again")
github.CreateComment(ctx.BaseRepo, ctx.Pull, "This environment is currently locked by another command that is running for this pull request. Wait until command is complete and try again")
return
}
defer a.concurrentRunLocker.Unlock(ctx.Repo.FullName, ctx.Command.environment, ctx.Pull.Num)
defer a.concurrentRunLocker.Unlock(ctx.BaseRepo.FullName, ctx.Command.environment, ctx.Pull.Num)

a.githubStatus.Update(ctx.Repo, ctx.Pull, Pending, ApplyStep)
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Pending, ApplyStep)
res := a.setupAndApply(ctx)
res.Command = Apply
comment := a.githubCommentRenderer.render(res, ctx.Log.History.String(), ctx.Command.verbose)
github.CreateComment(ctx.Repo, ctx.Pull, comment)
github.CreateComment(ctx.BaseRepo, ctx.Pull, comment)
}

func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) ExecutionResult {
Expand All @@ -86,7 +86,7 @@ func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) ExecutionResult {
repoDir, err := a.workspace.GetWorkspace(ctx)
if err != nil {
ctx.Log.Err(err.Error())
a.githubStatus.Update(ctx.Repo, ctx.Pull, Error, ApplyStep)
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Error, ApplyStep)
return ExecutionResult{SetupError: GeneralError{errors.New("Workspace missing, please plan again")}}
}

Expand All @@ -100,7 +100,7 @@ func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) ExecutionResult {
if !info.IsDir() && info.Name() == ctx.Command.environment+".tfplan" {
rel, _ := filepath.Rel(repoDir, filepath.Dir(path))
plans = append(plans, models.Plan{
Project: models.NewProject(ctx.Repo.FullName, rel),
Project: models.NewProject(ctx.BaseRepo.FullName, rel),
LocalPath: path,
})
}
Expand All @@ -109,7 +109,7 @@ func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) ExecutionResult {
if len(plans) == 0 {
failure := "found 0 plans for that environment"
ctx.Log.Warn(failure)
a.githubStatus.Update(ctx.Repo, ctx.Pull, Failure, ApplyStep)
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Failure, ApplyStep)
return ExecutionResult{SetupFailure: NoPlansFailure{}}
}

Expand Down Expand Up @@ -240,16 +240,16 @@ func (a *ApplyExecutor) apply(ctx *CommandContext, repoDir string, plan models.P
}

func (a *ApplyExecutor) isApproved(ctx *CommandContext) (bool, ExecutionResult) {
ok, err := a.github.PullIsApproved(ctx.Repo, ctx.Pull)
ok, err := a.github.PullIsApproved(ctx.BaseRepo, ctx.Pull)
if err != nil {
msg := fmt.Sprintf("failed to determine if pull request was approved: %v", err)
ctx.Log.Err(msg)
a.githubStatus.Update(ctx.Repo, ctx.Pull, Error, ApplyStep)
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Error, ApplyStep)
return false, ExecutionResult{SetupError: GeneralError{errors.New(msg)}}
}
if !ok {
ctx.Log.Info("pull request was not approved")
a.githubStatus.Update(ctx.Repo, ctx.Pull, Failure, ApplyStep)
a.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Failure, ApplyStep)
return false, ExecutionResult{SetupFailure: PullNotApprovedFailure{}}
}
return true, ExecutionResult{}
Expand Down
13 changes: 7 additions & 6 deletions server/command_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,28 @@ type Command struct {
}

func (c *CommandHandler) ExecuteCommand(ctx *CommandContext) {
src := fmt.Sprintf("%s/pull/%d", ctx.Repo.FullName, ctx.Pull.Num)
// it'e safe to reuse the underlying logger e.logger.Log
src := fmt.Sprintf("%s/pull/%d", ctx.BaseRepo.FullName, ctx.Pull.Num)
// it's safe to reuse the underlying logger e.logger.Log
ctx.Log = logging.NewSimpleLogger(src, c.logger.Log, true, c.logger.Level)
defer c.recover(ctx)

// need to get additional data from the PR
ghPull, _, err := c.githubClient.GetPullRequest(ctx.Repo, ctx.Pull.Num)
ghPull, _, err := c.githubClient.GetPullRequest(ctx.BaseRepo, ctx.Pull.Num)
if err != nil {
ctx.Log.Err("pull request data api call failed: %v", err)
return
}
pull, err := c.eventParser.ExtractPullData(ghPull)
pull, headRepo, err := c.eventParser.ExtractPullData(ghPull)
if err != nil {
ctx.Log.Err("failed to extract required fields from comment data: %v", err)
return
}
ctx.Pull = pull
ctx.HeadRepo = headRepo

if ghPull.GetState() != "open" {
ctx.Log.Info("command run on closed pull request")
c.githubClient.CreateComment(ctx.Repo, ctx.Pull, "Atlantis commands can't be run on closed pull requests")
c.githubClient.CreateComment(ctx.BaseRepo, ctx.Pull, "Atlantis commands can't be run on closed pull requests")
return
}

Expand All @@ -88,7 +89,7 @@ func (c *CommandHandler) SetLockURL(f func(id string) (url string)) {
func (c *CommandHandler) recover(ctx *CommandContext) {
if err := recover(); err != nil {
stack := recovery.Stack(3)
c.githubClient.CreateComment(ctx.Repo, ctx.Pull, fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack))
c.githubClient.CreateComment(ctx.BaseRepo, ctx.Pull, fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack))
ctx.Log.Err("PANIC: %s\n%s", err, stack)
}
}
26 changes: 17 additions & 9 deletions server/event_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (e *EventParser) ExtractCommentData(comment *github.IssueCommentEvent, ctx
if htmlURL == "" {
return errors.New("comment.issue.html_url is null")
}
ctx.Repo = repo
ctx.BaseRepo = repo
ctx.User = models.User{
Username: commentorUsername,
}
Expand All @@ -85,40 +85,48 @@ func (e *EventParser) ExtractCommentData(comment *github.IssueCommentEvent, ctx
return nil
}

func (e *EventParser) ExtractPullData(pull *github.PullRequest) (models.PullRequest, error) {
func (e *EventParser) ExtractPullData(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
var pullModel models.PullRequest
var headRepoModel models.Repo

commit := pull.Head.GetSHA()
if commit == "" {
return pullModel, errors.New("head.sha is null")
return pullModel, headRepoModel, errors.New("head.sha is null")
}
base := pull.Base.GetSHA()
if base == "" {
return pullModel, errors.New("base.sha is null")
return pullModel, headRepoModel, errors.New("base.sha is null")
}
url := pull.GetHTMLURL()
if url == "" {
return pullModel, errors.New("html_url is null")
return pullModel, headRepoModel, errors.New("html_url is null")
}
branch := pull.Head.GetRef()
if branch == "" {
return pullModel, errors.New("head.ref is null")
return pullModel, headRepoModel, errors.New("head.ref is null")
}
authorUsername := pull.User.GetLogin()
if authorUsername == "" {
return pullModel, errors.New("user.login is null")
return pullModel, headRepoModel, errors.New("user.login is null")
}
num := pull.GetNumber()
if num == 0 {
return pullModel, errors.New("number is null")
return pullModel, headRepoModel, errors.New("number is null")
}

headRepoModel, err := e.ExtractRepoData(pull.Head.Repo)
if err != nil {
return pullModel, headRepoModel, err
}

return models.PullRequest{
BaseCommit: base,
Author: authorUsername,
Branch: branch,
HeadCommit: commit,
URL: url,
Num: num,
}, nil
}, headRepoModel, nil
}

func (e *EventParser) ExtractRepoData(ghRepo *github.Repository) (models.Repo, error) {
Expand Down
25 changes: 19 additions & 6 deletions server/github_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,25 @@ type GithubClient struct {
// The names include the path to the file from the repo root, ex. parent/child/file.txt
func (g *GithubClient) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
var files []string
comparison, _, err := g.client.Repositories.CompareCommits(g.ctx, repo.Owner, repo.Name, pull.BaseCommit, pull.HeadCommit)
if err != nil {
return files, err
}
for _, file := range comparison.Files {
files = append(files, *file.Filename)
nextPage := 0
for {
opts := github.ListOptions{
PerPage: 300,
}
if nextPage != 0 {
opts.Page = nextPage
}
pageFiles, resp, err := g.client.PullRequests.ListFiles(g.ctx, repo.Owner, repo.Name, pull.Num, &opts)
if err != nil {
return files, err
}
for _, f := range pageFiles {
files = append(files, f.GetFilename())
}
if resp.NextPage == 0 {
break
}
nextPage = resp.NextPage
}
return files, nil
}
Expand Down
2 changes: 1 addition & 1 deletion server/github_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (g *GithubStatus) UpdatePathResult(ctx *CommandContext, pathResults []PathR
statuses = append(statuses, p.Status)
}
worst := g.worstStatus(statuses)
return g.Update(ctx.Repo, ctx.Pull, worst, ctx.Command.commandType.String())
return g.Update(ctx.BaseRepo, ctx.Pull, worst, ctx.Command.commandType.String())
}

func (g *GithubStatus) worstStatus(ss []Status) Status {
Expand Down
2 changes: 1 addition & 1 deletion server/help_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ atlantis apply

func (h *HelpExecutor) execute(ctx *CommandContext, github *GithubClient) {
ctx.Log.Info("generating help comment....")
github.CreateComment(ctx.Repo, ctx.Pull, helpComment)
github.CreateComment(ctx.BaseRepo, ctx.Pull, helpComment)
return
}
20 changes: 10 additions & 10 deletions server/plan_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,39 +73,39 @@ func (e EnvironmentFailure) Template() *CompiledTemplate {
}

func (p *PlanExecutor) execute(ctx *CommandContext, github *GithubClient) {
if p.concurrentRunLocker.TryLock(ctx.Repo.FullName, ctx.Command.environment, ctx.Pull.Num) != true {
if p.concurrentRunLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.environment, ctx.Pull.Num) != true {
ctx.Log.Info("run was locked by a concurrent run")
github.CreateComment(ctx.Repo, ctx.Pull, "This environment is currently locked by another command that is running for this pull request. Wait until command is complete and try again")
github.CreateComment(ctx.BaseRepo, ctx.Pull, "This environment is currently locked by another command that is running for this pull request. Wait until command is complete and try again")
return
}
defer p.concurrentRunLocker.Unlock(ctx.Repo.FullName, ctx.Command.environment, ctx.Pull.Num)
defer p.concurrentRunLocker.Unlock(ctx.BaseRepo.FullName, ctx.Command.environment, ctx.Pull.Num)
res := p.setupAndPlan(ctx)
res.Command = Plan
comment := p.githubCommentRenderer.render(res, ctx.Log.History.String(), ctx.Command.verbose)
github.CreateComment(ctx.Repo, ctx.Pull, comment)
github.CreateComment(ctx.BaseRepo, ctx.Pull, comment)
}

func (p *PlanExecutor) setupAndPlan(ctx *CommandContext) ExecutionResult {
p.githubStatus.Update(ctx.Repo, ctx.Pull, Pending, PlanStep)
p.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Pending, PlanStep)

// figure out what projects have been modified so we know where to run plan
ctx.Log.Info("listing modified files from pull request")
modifiedFiles, err := p.github.GetModifiedFiles(ctx.Repo, ctx.Pull)
modifiedFiles, err := p.github.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
if err != nil {
return p.setupError(ctx, errors.Wrap(err, "getting modified files"))
}
modifiedTerraformFiles := p.filterToTerraform(modifiedFiles)
if len(modifiedTerraformFiles) == 0 {
ctx.Log.Info("no modified terraform files found, exiting")
p.githubStatus.Update(ctx.Repo, ctx.Pull, Failure, PlanStep)
p.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Failure, PlanStep)
return ExecutionResult{SetupError: GeneralError{errors.New("Plan Failed: no modified terraform files found")}}
}
ctx.Log.Debug("Found %d modified terraform files: %v", len(modifiedTerraformFiles), modifiedTerraformFiles)

projects := p.ModifiedProjects(ctx.Repo.FullName, modifiedTerraformFiles)
projects := p.ModifiedProjects(ctx.BaseRepo.FullName, modifiedTerraformFiles)
if len(projects) == 0 {
ctx.Log.Info("no Terraform projects were modified")
p.githubStatus.Update(ctx.Repo, ctx.Pull, Failure, PlanStep)
p.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Failure, PlanStep)
return ExecutionResult{SetupError: GeneralError{errors.New("Plan Failed: we determined that no terraform projects were modified")}}
}

Expand Down Expand Up @@ -316,6 +316,6 @@ func (p *PlanExecutor) getProjectPath(modifiedFilePath string) string {

func (p *PlanExecutor) setupError(ctx *CommandContext, err error) ExecutionResult {
ctx.Log.Err(err.Error())
p.githubStatus.Update(ctx.Repo, ctx.Pull, Error, PlanStep)
p.githubStatus.Update(ctx.BaseRepo, ctx.Pull, Error, PlanStep)
return ExecutionResult{SetupError: GeneralError{err}}
}
22 changes: 12 additions & 10 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"time"

"github.com/aws/aws-sdk-go/aws/session"
homedir "github.com/mitchellh/go-homedir"
"github.com/elazarl/go-bindata-assetfs"
"github.com/google/go-github/github"
"github.com/gorilla/mux"
Expand All @@ -23,12 +22,14 @@ import (
"github.com/hootsuite/atlantis/middleware"
"github.com/hootsuite/atlantis/models"
"github.com/hootsuite/atlantis/prerun"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/urfave/cli"
"github.com/urfave/negroni"
)

const (
lockRoute = "lock-detail"
LockingFileBackend = "file"
LockingDynamoDBBackend = "dynamodb"
)
Expand Down Expand Up @@ -62,11 +63,12 @@ type ServerConfig struct {
}

type CommandContext struct {
Repo models.Repo
Pull models.PullRequest
User models.User
Command *Command
Log *logging.SimpleLogger
BaseRepo models.Repo
HeadRepo models.Repo
Pull models.PullRequest
User models.User
Command *Command
Log *logging.SimpleLogger
}

// todo: These structs have nothing to do with the server. Move to a different file/package #refactor
Expand Down Expand Up @@ -213,7 +215,7 @@ func (s *Server) Start() error {
s.router.PathPrefix("/static/").Handler(http.FileServer(&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo}))
s.router.HandleFunc("/hooks", s.postHooks).Methods("POST")
s.router.HandleFunc("/locks", s.deleteLock).Methods("DELETE").Queries("id", "{id:.*}")
lockRoute := s.router.HandleFunc("/lock", s.lock).Methods("GET").Queries("id", "{id}")
lockRoute := s.router.HandleFunc("/lock", s.lock).Methods("GET").Queries("id", "{id}").Name(lockRoute)
// function that planExecutor can use to construct detail view url
// injecting this here because this is the earliest routes are created
s.commandHandler.SetLockURL(func(lockID string) string {
Expand Down Expand Up @@ -248,9 +250,9 @@ func (s *Server) index(w http.ResponseWriter, r *http.Request) {
}
var results []lock
for id, v := range locks {
url, _ := s.router.Get(lockRoute).URL("id", url.QueryEscape(id))
results = append(results, lock{
// todo: make LockURL use the router to get /lock endpoint
LockURL: fmt.Sprintf("/lock?id=%s", url.QueryEscape(id)),
LockURL: url.String(),
RepoFullName: v.Project.RepoFullName,
PullNum: v.Pull.Num,
Time: v.Time,
Expand Down Expand Up @@ -380,7 +382,7 @@ func (s *Server) handlePullRequestEvent(w http.ResponseWriter, pullEvent *github
fmt.Fprintln(w, "Ignoring")
return
}
pull, err := s.eventParser.ExtractPullData(pullEvent.PullRequest)
pull, _, err := s.eventParser.ExtractPullData(pullEvent.PullRequest)
if err != nil {
s.logger.Err("parsing pull data: %s", err)
w.WriteHeader(http.StatusBadRequest)
Expand Down
11 changes: 4 additions & 7 deletions server/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,10 @@ func (w *Workspace) Clone(ctx *CommandContext) (string, error) {
return "", errors.Wrap(err, "creating new workspace")
}

// Check if ssh key is set and create git ssh wrapper
cloneCmd := exec.Command("git", "clone", ctx.Repo.SSHURL, cloneDir)

// clone the repo
ctx.Log.Info("git cloning %q into %q", ctx.Repo.SSHURL, cloneDir)
ctx.Log.Info("git cloning %q into %q", ctx.HeadRepo.SSHURL, cloneDir)
cloneCmd := exec.Command("git", "clone", ctx.HeadRepo.SSHURL, cloneDir)
if output, err := cloneCmd.CombinedOutput(); err != nil {
return "", errors.Wrapf(err, "cloning %s: %s", ctx.Repo.SSHURL, string(output))
return "", errors.Wrapf(err, "cloning %s: %s", ctx.HeadRepo.SSHURL, string(output))
}

// check out the branch for this PR
Expand Down Expand Up @@ -69,5 +66,5 @@ func (w *Workspace) repoPullDir(repo models.Repo, pull models.PullRequest) strin
}

func (w *Workspace) cloneDir(ctx *CommandContext) string {
return filepath.Join(w.repoPullDir(ctx.Repo, ctx.Pull), ctx.Command.environment)
return filepath.Join(w.repoPullDir(ctx.BaseRepo, ctx.Pull), ctx.Command.environment)
}

0 comments on commit ac83457

Please sign in to comment.