Skip to content

Commit

Permalink
feat: make api plan apply support workflow hooks (#4482)
Browse files Browse the repository at this point in the history
Co-authored-by: PePe Amengual <jose.amengual@gmail.com>
  • Loading branch information
keidarcy and jamengual authored May 1, 2024
1 parent 93578d8 commit 267b2f1
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 41 deletions.
64 changes: 47 additions & 17 deletions server/controllers/api_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ import (
const atlantisTokenHeader = "X-Atlantis-Token"

type APIController struct {
APISecret []byte
Locker locking.Locker
Logger logging.SimpleLogging
Parser events.EventParsing
ProjectCommandBuilder events.ProjectCommandBuilder
ProjectPlanCommandRunner events.ProjectPlanCommandRunner
ProjectApplyCommandRunner events.ProjectApplyCommandRunner
RepoAllowlistChecker *events.RepoAllowlistChecker
Scope tally.Scope
VCSClient vcs.Client
APISecret []byte
Locker locking.Locker
Logger logging.SimpleLogging
Parser events.EventParsing
ProjectCommandBuilder events.ProjectCommandBuilder
ProjectPlanCommandRunner events.ProjectPlanCommandRunner
ProjectApplyCommandRunner events.ProjectApplyCommandRunner
FailOnPreWorkflowHookError bool
PreWorkflowHooksCommandRunner events.PreWorkflowHooksCommandRunner
PostWorkflowHooksCommandRunner events.PostWorkflowHooksCommandRunner
RepoAllowlistChecker *events.RepoAllowlistChecker
Scope tally.Scope
VCSClient vcs.Client
}

type APIRequest struct {
Expand All @@ -44,7 +47,7 @@ type APIRequest struct {
}
}

func (a *APIRequest) getCommands(ctx *command.Context, cmdBuilder func(*command.Context, *events.CommentCommand) ([]command.ProjectContext, error)) ([]command.ProjectContext, error) {
func (a *APIRequest) getCommands(ctx *command.Context, cmdBuilder func(*command.Context, *events.CommentCommand) ([]command.ProjectContext, error)) ([]command.ProjectContext, []*events.CommentCommand, error) {
cc := make([]*events.CommentCommand, 0)

for _, project := range a.Projects {
Expand All @@ -63,12 +66,12 @@ func (a *APIRequest) getCommands(ctx *command.Context, cmdBuilder func(*command.
for _, commentCommand := range cc {
projectCmds, err := cmdBuilder(ctx, commentCommand)
if err != nil {
return nil, fmt.Errorf("failed to build command: %v", err)
return nil, nil, fmt.Errorf("failed to build command: %v", err)
}
cmds = append(cmds, projectCmds...)
}

return cmds, nil
return cmds, cc, nil
}

func (a *APIController) apiReportError(w http.ResponseWriter, code int, err error) {
Expand Down Expand Up @@ -142,29 +145,55 @@ func (a *APIController) Apply(w http.ResponseWriter, r *http.Request) {
}

func (a *APIController) apiPlan(request *APIRequest, ctx *command.Context) (*command.Result, error) {
cmds, err := request.getCommands(ctx, a.ProjectCommandBuilder.BuildPlanCommands)
cmds, cc, err := request.getCommands(ctx, a.ProjectCommandBuilder.BuildPlanCommands)
if err != nil {
return nil, err
}

var projectResults []command.ProjectResult
for _, cmd := range cmds {
for i, cmd := range cmds {
err = a.PreWorkflowHooksCommandRunner.RunPreHooks(ctx, cc[i])
if err != nil {
ctx.Log.Err("Error running pre-workflow hooks %s.", err)
if a.FailOnPreWorkflowHookError {
return nil, err
}
}

res := a.ProjectPlanCommandRunner.Plan(cmd)
projectResults = append(projectResults, res)

err = a.PostWorkflowHooksCommandRunner.RunPostHooks(ctx, cc[i])
if err != nil {
ctx.Log.Err("Error running post-workflow hooks %s.", err)
}
}
return &command.Result{ProjectResults: projectResults}, nil
}

func (a *APIController) apiApply(request *APIRequest, ctx *command.Context) (*command.Result, error) {
cmds, err := request.getCommands(ctx, a.ProjectCommandBuilder.BuildApplyCommands)
cmds, cc, err := request.getCommands(ctx, a.ProjectCommandBuilder.BuildApplyCommands)
if err != nil {
return nil, err
}

var projectResults []command.ProjectResult
for _, cmd := range cmds {
for i, cmd := range cmds {
err = a.PreWorkflowHooksCommandRunner.RunPreHooks(ctx, cc[i])
if err != nil {
ctx.Log.Err("Error running pre-workflow hooks %s.", err)
if a.FailOnPreWorkflowHookError {
return nil, err
}
}

res := a.ProjectApplyCommandRunner.Apply(cmd)
projectResults = append(projectResults, res)

err = a.PostWorkflowHooksCommandRunner.RunPostHooks(ctx, cc[i])
if err != nil {
ctx.Log.Err("Error running post-workflow hooks %s.", err)
}
}
return &command.Result{ProjectResults: projectResults}, nil
}
Expand Down Expand Up @@ -223,6 +252,7 @@ func (a *APIController) apiParseAndValidate(r *http.Request) (*APIRequest, *comm
},
Scope: a.Scope,
Log: a.Logger,
API: true,
}, http.StatusOK, nil
}

Expand Down
30 changes: 20 additions & 10 deletions server/controllers/api_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,27 @@ func setup(t *testing.T) (controllers.APIController, *MockProjectCommandBuilder,
ApplySuccess: "success",
})

preWorkflowHooksCommandRunner := NewMockPreWorkflowHooksCommandRunner()

When(preWorkflowHooksCommandRunner.RunPreHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(nil)

postWorkflowHooksCommandRunner := NewMockPostWorkflowHooksCommandRunner()

When(postWorkflowHooksCommandRunner.RunPostHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(nil)

ac := controllers.APIController{
APISecret: []byte(atlantisToken),
Locker: locker,
Logger: logger,
Scope: scope,
Parser: parser,
ProjectCommandBuilder: projectCommandBuilder,
ProjectPlanCommandRunner: projectCommandRunner,
ProjectApplyCommandRunner: projectCommandRunner,
VCSClient: vcsClient,
RepoAllowlistChecker: repoAllowlistChecker,
APISecret: []byte(atlantisToken),
Locker: locker,
Logger: logger,
Scope: scope,
Parser: parser,
ProjectCommandBuilder: projectCommandBuilder,
ProjectPlanCommandRunner: projectCommandRunner,
ProjectApplyCommandRunner: projectCommandRunner,
PreWorkflowHooksCommandRunner: preWorkflowHooksCommandRunner,
PostWorkflowHooksCommandRunner: postWorkflowHooksCommandRunner,
VCSClient: vcsClient,
RepoAllowlistChecker: repoAllowlistChecker,
}
return ac, projectCommandBuilder, projectCommandRunner
}
3 changes: 3 additions & 0 deletions server/events/command/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ type Context struct {
ClearPolicyApproval bool

Trigger Trigger

// API is true if plan/apply by API endpoints
API bool
}
2 changes: 2 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,8 @@ type WorkflowHookCommandContext struct {
// Workspace is the Terraform workspace this project is in. It will always
// be set.
Workspace string
// API is true if plan/apply by API endpoints
API bool
}

// PlanSuccessStats holds stats for a plan.
Expand Down
3 changes: 2 additions & 1 deletion server/events/post_workflow_hooks_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (w *DefaultPostWorkflowHooksCommandRunner) RunPostHooks(ctx *command.Contex
Verbose: false,
EscapedCommentArgs: escapedArgs,
CommandName: cmd.Name.String(),
API: ctx.API,
},
postWorkflowHooks, repoDir)

Expand Down Expand Up @@ -124,7 +125,7 @@ func (w *DefaultPostWorkflowHooksCommandRunner) runHooks(
shellArgs = "-c"
}
url, err := w.Router.GenerateProjectWorkflowHookURL(ctx.HookID)
if err != nil {
if err != nil && !ctx.API {
return err
}

Expand Down
13 changes: 10 additions & 3 deletions server/events/pre_workflow_hooks_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (w *DefaultPreWorkflowHooksCommandRunner) RunPreHooks(ctx *command.Context,
Verbose: false,
EscapedCommentArgs: escapedArgs,
CommandName: cmd.Name.String(),
API: ctx.API,
},
preWorkflowHooks, repoDir)

Expand Down Expand Up @@ -135,13 +136,17 @@ func (w *DefaultPreWorkflowHooksCommandRunner) runHooks(
shellArgs = "-c"
}
url, err := w.Router.GenerateProjectWorkflowHookURL(ctx.HookID)
if err != nil {
if err != nil && !ctx.API {
return err
}

if err := w.CommitStatusUpdater.UpdatePreWorkflowHook(ctx.Log, ctx.Pull, models.PendingCommitStatus, ctx.HookDescription, "", url); err != nil {
ctx.Log.Warn("unable to update pre workflow hook status: %s", err)
return err
ctx.Log.Info("is api? %v", ctx.API)
if !ctx.API {
ctx.Log.Info("is api? %v", ctx.API)
return err
}
}

_, runtimeDesc, err := w.PreWorkflowHookRunner.Run(ctx, hook.RunCommand, shell, shellArgs, repoDir)
Expand All @@ -155,7 +160,9 @@ func (w *DefaultPreWorkflowHooksCommandRunner) runHooks(

if err := w.CommitStatusUpdater.UpdatePreWorkflowHook(ctx.Log, ctx.Pull, models.SuccessCommitStatus, ctx.HookDescription, runtimeDesc, url); err != nil {
ctx.Log.Warn("unable to update pre workflow hook status: %s", err)
return err
if !ctx.API {
return err
}
}
}

Expand Down
23 changes: 13 additions & 10 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,16 +876,19 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
StatsScope: statsScope.SubScope("api"),
}
apiController := &controllers.APIController{
APISecret: []byte(userConfig.APISecret),
Locker: lockingClient,
Logger: logger,
Parser: eventParser,
ProjectCommandBuilder: projectCommandBuilder,
ProjectPlanCommandRunner: instrumentedProjectCmdRunner,
ProjectApplyCommandRunner: instrumentedProjectCmdRunner,
RepoAllowlistChecker: repoAllowlist,
Scope: statsScope.SubScope("api"),
VCSClient: vcsClient,
APISecret: []byte(userConfig.APISecret),
Locker: lockingClient,
Logger: logger,
Parser: eventParser,
ProjectCommandBuilder: projectCommandBuilder,
ProjectPlanCommandRunner: instrumentedProjectCmdRunner,
ProjectApplyCommandRunner: instrumentedProjectCmdRunner,
FailOnPreWorkflowHookError: userConfig.FailOnPreWorkflowHookError,
PreWorkflowHooksCommandRunner: preWorkflowHooksCommandRunner,
PostWorkflowHooksCommandRunner: postWorkflowHooksCommandRunner,
RepoAllowlistChecker: repoAllowlist,
Scope: statsScope.SubScope("api"),
VCSClient: vcsClient,
}

eventsController := &events_controllers.VCSEventsController{
Expand Down

0 comments on commit 267b2f1

Please sign in to comment.