From 3aa77c41eb66888710a6327dec6e38fc1e87effc Mon Sep 17 00:00:00 2001 From: Adam Connelly Date: Fri, 26 Aug 2022 15:52:10 +0100 Subject: [PATCH 1/2] Add approval and rejection support - 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 --- client/enums/run_review_decision.go | 12 ++++++ internal/cmd/stack/flags.go | 7 +++- internal/cmd/stack/run_confirm.go | 2 +- internal/cmd/stack/run_discard.go | 2 +- internal/cmd/stack/run_retry.go | 2 +- internal/cmd/stack/run_review.go | 64 +++++++++++++++++++++++++++++ internal/cmd/stack/stack.go | 36 +++++++++++++--- specs/spacectl-to-v1.md | 5 ++- 8 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 client/enums/run_review_decision.go create mode 100644 internal/cmd/stack/run_review.go diff --git a/client/enums/run_review_decision.go b/client/enums/run_review_decision.go new file mode 100644 index 0000000..8d8a35c --- /dev/null +++ b/client/enums/run_review_decision.go @@ -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" +) diff --git a/internal/cmd/stack/flags.go b/internal/cmd/stack/flags.go index 6e73521..8c0765a 100644 --- a/internal/cmd/stack/flags.go +++ b/internal/cmd/stack/flags.go @@ -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", diff --git a/internal/cmd/stack/run_confirm.go b/internal/cmd/stack/run_confirm.go index 6778474..18f91f9 100644 --- a/internal/cmd/stack/run_confirm.go +++ b/internal/cmd/stack/run_confirm.go @@ -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() diff --git a/internal/cmd/stack/run_discard.go b/internal/cmd/stack/run_discard.go index 808210a..afb555a 100644 --- a/internal/cmd/stack/run_discard.go +++ b/internal/cmd/stack/run_discard.go @@ -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() diff --git a/internal/cmd/stack/run_retry.go b/internal/cmd/stack/run_retry.go index aa28e7b..ac9621d 100644 --- a/internal/cmd/stack/run_retry.go +++ b/internal/cmd/stack/run_retry.go @@ -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 { diff --git a/internal/cmd/stack/run_review.go b/internal/cmd/stack/run_review.go new file mode 100644 index 0000000..29000b0 --- /dev/null +++ b/internal/cmd/stack/run_review.go @@ -0,0 +1,64 @@ +package stack + +import ( + "context" + "fmt" + + "github.com/shurcooL/graphql" + "github.com/spacelift-io/spacectl/client/enums" + "github.com/spacelift-io/spacectl/internal/cmd/authenticated" + "github.com/urfave/cli/v2" +) + +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) +} diff --git a/internal/cmd/stack/stack.go b/internal/cmd/stack/stack.go index df5614a..ce12128 100644 --- a/internal/cmd/stack/stack.go +++ b/internal/cmd/stack/stack.go @@ -21,7 +21,7 @@ func Command() *cli.Command { Usage: "Confirm an unconfirmed tracked run", Flags: []cli.Flag{ flagStackID, - flagRun, + flagRequiredRun, flagRunMetadata, flagTail, }, @@ -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", @@ -62,7 +88,7 @@ func Command() *cli.Command { Usage: "Retry a failed run", Flags: []cli.Flag{ flagStackID, - flagRun, + flagRequiredRun, flagTail, }, Action: runRetry, @@ -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, diff --git a/specs/spacectl-to-v1.md b/specs/spacectl-to-v1.md index 4caea33..e242c2d 100644 --- a/specs/spacectl-to-v1.md +++ b/specs/spacectl-to-v1.md @@ -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 From 189f6c693d15c31bb16140cb3e638fbdee35497f Mon Sep 17 00:00:00 2001 From: Adam Connelly Date: Mon, 29 Aug 2022 15:12:04 +0100 Subject: [PATCH 2/2] fixup! Add approval and rejection support --- internal/cmd/stack/run_review.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/cmd/stack/run_review.go b/internal/cmd/stack/run_review.go index 29000b0..7085aed 100644 --- a/internal/cmd/stack/run_review.go +++ b/internal/cmd/stack/run_review.go @@ -5,9 +5,10 @@ import ( "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" - "github.com/urfave/cli/v2" ) type runReviewMutation struct {