Skip to content

Commit

Permalink
Add approval and rejection support (#82)
Browse files Browse the repository at this point in the history
- Added the ability to approve and reject runs and tasks.
- The approval and rejection mutations accept an optional run ID (if not specified they target the stack blocker). Because of that I've created an extra optional run ID flag and renamed the required one.

Issues: #81
  • Loading branch information
adamconnelly authored Aug 29, 2022
1 parent 6ce41eb commit ed0540b
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 10 deletions.
12 changes: 12 additions & 0 deletions client/enums/run_review_decision.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package enums

// RunReviewDecision represents the API RunReviewDecision enum.
type RunReviewDecision string

const (
// RunReviewDecisionApprove represents an approval decision.
RunReviewDecisionApprove = "APPROVE"

// RunReviewDecisionReject represents a rejection decision.
RunReviewDecisionReject = "REJECT"
)
7 changes: 6 additions & 1 deletion internal/cmd/stack/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ var flagRequiredCommitSHA = &cli.StringFlag{
Required: true,
}

var flagRun = &cli.StringFlag{
var flagRequiredRun = &cli.StringFlag{
Name: "run",
Usage: "[Required] `ID` of the run",
Required: true,
}

var flagRun = &cli.StringFlag{
Name: "run",
Usage: "`ID` of the run",
}

var flagNoInit = &cli.BoolFlag{
Name: "noinit",
Usage: "Indicate whether to skip initialization for a task",
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/stack/run_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func runConfirm() cli.ActionFunc {

variables := map[string]interface{}{
"stack": graphql.ID(stackID),
"run": graphql.ID(cliCtx.String(flagRun.Name)),
"run": graphql.ID(cliCtx.String(flagRequiredRun.Name)),
}

ctx := context.Background()
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/stack/run_discard.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func runDiscard() cli.ActionFunc {

variables := map[string]interface{}{
"stack": graphql.ID(stackID),
"run": graphql.ID(cliCtx.String(flagRun.Name)),
"run": graphql.ID(cliCtx.String(flagRequiredRun.Name)),
}

ctx := context.Background()
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/stack/run_retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func runRetry(cliCtx *cli.Context) error {
stackID := cliCtx.String(flagStackID.Name)
runID := cliCtx.String(flagRun.Name)
runID := cliCtx.String(flagRequiredRun.Name)

var mutation struct {
RunRetry struct {
Expand Down
65 changes: 65 additions & 0 deletions internal/cmd/stack/run_review.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package stack

import (
"context"
"fmt"

"github.com/shurcooL/graphql"
"github.com/urfave/cli/v2"

"github.com/spacelift-io/spacectl/client/enums"
"github.com/spacelift-io/spacectl/internal/cmd/authenticated"
)

type runReviewMutation struct {
Review struct {
ID string `graphql:"id"`
} `graphql:"runReview(stack: $stack, run: $run, decision: $decision, note: $note)"`
}

var flagRunReviewNote = &cli.StringFlag{
Name: "note",
Usage: "Description of why the review decision was made.",
Required: false,
}

func runApprove(cliCtx *cli.Context) error {
stackID := cliCtx.String(flagStackID.Name)
runID := cliCtx.String(flagRequiredRun.Name)
note := cliCtx.String(flagRunReviewNote.Name)

if nArgs := cliCtx.NArg(); nArgs != 0 {
return fmt.Errorf("expected zero arguments but got %d", nArgs)
}

return addRunReview(cliCtx.Context, stackID, runID, note, enums.RunReviewDecisionApprove)
}

func runReject(cliCtx *cli.Context) error {
stackID := cliCtx.String(flagStackID.Name)
runID := cliCtx.String(flagRequiredRun.Name)
note := cliCtx.String(flagRunReviewNote.Name)

if nArgs := cliCtx.NArg(); nArgs != 0 {
return fmt.Errorf("expected zero arguments but got %d", nArgs)
}

return addRunReview(cliCtx.Context, stackID, runID, note, enums.RunReviewDecisionReject)
}

func addRunReview(ctx context.Context, stackID, runID, note string, decision enums.RunReviewDecision) error {
var runIDGQL *graphql.ID
if runID != "" {
runIDGQL = graphql.NewID(runID)
}

var mutation runReviewMutation
variables := map[string]interface{}{
"stack": graphql.ID(stackID),
"run": runIDGQL,
"decision": decision,
"note": graphql.String(note),
}

return authenticated.Client.Mutate(ctx, &mutation, variables)
}
36 changes: 31 additions & 5 deletions internal/cmd/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func Command() *cli.Command {
Usage: "Confirm an unconfirmed tracked run",
Flags: []cli.Flag{
flagStackID,
flagRun,
flagRequiredRun,
flagRunMetadata,
flagTail,
},
Expand All @@ -35,13 +35,39 @@ func Command() *cli.Command {
Usage: "Discard an unconfirmed tracked run",
Flags: []cli.Flag{
flagStackID,
flagRun,
flagRequiredRun,
flagTail,
},
Action: runDiscard(),
Before: authenticated.Ensure,
ArgsUsage: cmd.EmptyArgsUsage,
},
{
Category: "Run management",
Name: "approve",
Usage: "Approves a run or task. If no run is specified, the approval will be added to the current stack blocker.",
Flags: []cli.Flag{
flagStackID,
flagRun,
flagRunReviewNote,
},
Action: runApprove,
Before: authenticated.Ensure,
ArgsUsage: cmd.EmptyArgsUsage,
},
{
Category: "Run management",
Name: "reject",
Usage: "Rejects a run or task. If no run is specified, the rejection will be added to the current stack blocker.",
Flags: []cli.Flag{
flagStackID,
flagRun,
flagRunReviewNote,
},
Action: runReject,
Before: authenticated.Ensure,
ArgsUsage: cmd.EmptyArgsUsage,
},
{
Category: "Run management",
Name: "deploy",
Expand All @@ -62,7 +88,7 @@ func Command() *cli.Command {
Usage: "Retry a failed run",
Flags: []cli.Flag{
flagStackID,
flagRun,
flagRequiredRun,
flagTail,
},
Action: runRetry,
Expand Down Expand Up @@ -100,11 +126,11 @@ func Command() *cli.Command {
Usage: "Show logs for a particular run",
Flags: []cli.Flag{
flagStackID,
flagRun,
flagRequiredRun,
},
Action: func(cliCtx *cli.Context) error {
stackID := cliCtx.String(flagStackID.Name)
_, err := runLogs(context.Background(), stackID, cliCtx.String(flagRun.Name))
_, err := runLogs(context.Background(), stackID, cliCtx.String(flagRequiredRun.Name))
return err
},
Before: authenticated.Ensure,
Expand Down
5 changes: 4 additions & 1 deletion specs/spacectl-to-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,11 @@ We should support the following commands for working with Stacks:
- [ ] `delete` - deletes a stack
- [x] `show` - outputs information about a specified Stack
- [ ] `edit` - edits the name, labels and description for the stack
- [ ] `set-current-commit` - sets the current commit for the stack
- [x] `set-current-commit` - sets the current commit for the stack
- [x] `confirm` - confirms a run awaiting approval
- [x] `discard` - discards a run awaiting approval
- [x] `approve` - approves a run or task
- [x] `reject` - rejects a run or task
- [x] `deploy` - triggers a tracked (i.e. deployment) run
- [x] `preview` - triggers a preview run for a specific commit
- [x] `local-preview` - triggers a local-preview run using the current directory as the workspace
Expand Down

0 comments on commit ed0540b

Please sign in to comment.