Browse Source

cancel job run on interrupt from cli (#4466)

ianedwards 2 years ago
parent
commit
53ac5d40db
2 changed files with 98 additions and 20 deletions
  1. 33 0
      api/client/porter_app.go
  2. 65 20
      cli/cmd/v2/run_app_job.go

+ 33 - 0
api/client/porter_app.go

@@ -738,6 +738,39 @@ func (c *Client) RunAppJob(
 	return resp, err
 }
 
+// CancelAppJobInput contains all the information necessary to cancel a job
+type CancelAppJobInput struct {
+	ProjectID            uint
+	ClusterID            uint
+	AppName              string
+	JobName              string
+	DeploymentTargetName string
+}
+
+// CancelAppJobRun cancels a in progress job run
+func (c *Client) CancelAppJobRun(
+	ctx context.Context,
+	inp CancelAppJobInput,
+) (*porter_app.CancelJobRunResponse, error) {
+	resp := &porter_app.CancelJobRunResponse{}
+
+	req := &porter_app.CancelJobRunRequest{
+		DeploymentTargetName: inp.DeploymentTargetName,
+	}
+
+	err := c.postRequest(
+		fmt.Sprintf(
+			"/projects/%d/clusters/%d/apps/%s/jobs/%s/cancel",
+			inp.ProjectID, inp.ClusterID,
+			inp.AppName, inp.JobName,
+		),
+		req,
+		resp,
+	)
+
+	return resp, err
+}
+
 // RunAppJobStatusInput contains all the information necessary to check the status of a job
 type RunAppJobStatusInput struct {
 	// AppName is the name of the app associated with the job

+ 65 - 20
cli/cmd/v2/run_app_job.go

@@ -4,6 +4,9 @@ import (
 	"context"
 	"encoding/base64"
 	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
 	"time"
 
 	"github.com/fatih/color"
@@ -36,6 +39,14 @@ type RunAppJobInput struct {
 
 // RunAppJob triggers a job run for an app and returns without waiting for the job to complete
 func RunAppJob(ctx context.Context, inp RunAppJobInput) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	termChan := make(chan os.Signal, 1)
+	signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
+
+	var runFinished bool
+
 	currentAppRevisionResp, err := inp.Client.CurrentAppRevision(ctx, api.CurrentAppRevisionInput{
 		ProjectID:            inp.CLIConfig.Project,
 		ClusterID:            inp.CLIConfig.Cluster,
@@ -51,7 +62,6 @@ func RunAppJob(ctx context.Context, inp RunAppJobInput) error {
 	if err != nil {
 		return fmt.Errorf("unable to run job: %w", err)
 	}
-
 	triggeredBackgroundColor := color.FgGreen
 	if inp.WaitForExit {
 		triggeredBackgroundColor = color.FgBlue
@@ -99,27 +109,62 @@ func RunAppJob(ctx context.Context, inp RunAppJobInput) error {
 		ProjectID:            inp.CLIConfig.Project,
 	}
 
-	for time.Now().Before(deadline) {
-		statusResp, err := inp.Client.RunAppJobStatus(ctx, input)
-		if err != nil {
-			return fmt.Errorf("unable to get job status: %w", err)
+	go func() {
+		select {
+		case <-termChan:
+			color.New(color.FgYellow).Println("Shutdown signal received, canceling processes") // nolint:errcheck,gosec
+
+			if !runFinished {
+				color.New(color.FgBlue).Println("\nCanceling job...") // nolint:errcheck,gosec
+				_, err := inp.Client.CancelAppJobRun(ctx, api.CancelAppJobInput{
+					ProjectID:            inp.CLIConfig.Project,
+					ClusterID:            inp.CLIConfig.Cluster,
+					AppName:              inp.AppName,
+					DeploymentTargetName: inp.DeploymentTargetName,
+					JobName:              resp.JobRunName,
+				})
+				if err != nil {
+					fmt.Println("Error canceling job:", err)
+					return
+				}
+
+				color.New(color.FgYellow).Println("\nJob run canceled") // nolint:errcheck,gosec
+			}
+			cancel()
+			return
+		case <-ctx.Done():
 		}
+	}()
 
-		switch statusResp.Status {
-		case porter_app_internal.InstanceStatusDescriptor_Pending:
-			print(".")
-			time.Sleep(WaitIntervalInSeconds)
-		case porter_app_internal.InstanceStatusDescriptor_Running:
-			print(".")
-			time.Sleep(WaitIntervalInSeconds)
-		case porter_app_internal.InstanceStatusDescriptor_Succeeded:
-			print("\n")
-			color.New(color.FgGreen).Println("Job completed successfully") // nolint:errcheck,gosec
-			return nil
-		case porter_app_internal.InstanceStatusDescriptor_Failed:
-			return fmt.Errorf("job exited with non-zero exit code: %w", err)
-		case porter_app_internal.InstanceStatusDescriptor_Unknown:
-			return fmt.Errorf("unknown job status: %w", err)
+	for time.Now().Before(deadline) {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		default:
+			statusResp, err := inp.Client.RunAppJobStatus(ctx, input)
+			if err != nil {
+				return fmt.Errorf("unable to get job status: %w", err)
+			}
+
+			switch statusResp.Status {
+			case porter_app_internal.InstanceStatusDescriptor_Pending:
+				print(".")
+				time.Sleep(WaitIntervalInSeconds)
+			case porter_app_internal.InstanceStatusDescriptor_Running:
+				print(".")
+				time.Sleep(WaitIntervalInSeconds)
+			case porter_app_internal.InstanceStatusDescriptor_Succeeded:
+				runFinished = true
+				print("\n")
+				color.New(color.FgGreen).Println("Job completed successfully") // nolint:errcheck,gosec
+				return nil
+			case porter_app_internal.InstanceStatusDescriptor_Failed:
+				runFinished = true
+				return fmt.Errorf("job exited with non-zero exit code: %w", err)
+			case porter_app_internal.InstanceStatusDescriptor_Unknown:
+				runFinished = true
+				return fmt.Errorf("unknown job status: %w", err)
+			}
 		}
 	}