This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / xrpc / pipeline_cancel_pipeline.go
2.9 kB 115 lines
1package xrpc 2 3import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/spindle/engine" 11 "tangled.org/core/spindle/models" 12 xrpcerr "tangled.org/core/xrpc/errors" 13) 14 15func (x *Xrpc) CancelPipeline(w http.ResponseWriter, r *http.Request) { 16 l := x.Logger 17 fail := func(e xrpcerr.XrpcError) { 18 l.Error("failed", "kind", e.Tag, "error", e.Message) 19 writeError(w, e, http.StatusBadRequest) 20 } 21 l.Debug("cancel pipeline") 22 23 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 24 if !ok { 25 fail(xrpcerr.MissingActorDidError) 26 return 27 } 28 29 var input tangled.CiCancelPipeline_Input 30 if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 31 fail(xrpcerr.GenericError(err)) 32 return 33 } 34 35 pipelineTid, err := syntax.ParseTID(input.Pipeline) 36 if err != nil { 37 fail(xrpcerr.GenericError(fmt.Errorf("invalid pipeline TID %q: %w", input.Pipeline, err))) 38 return 39 } 40 41 repoDid, xerr, ok := x.resolveOwnedRepo(r.Context(), actorDid, input.Repo) 42 if !ok { 43 fail(xerr) 44 return 45 } 46 repo, err := x.Db.GetRepoByDid(repoDid) 47 if err != nil { 48 fail(xrpcerr.GenericError(fmt.Errorf("failed to get repo: %w", err))) 49 return 50 } 51 52 // the actor is only authorized against input.Repo, so make sure the 53 // pipeline actually belongs to it before cancelling anything 54 p, err := x.Db.GetPipeline(r.Context(), pipelineTid.String()) 55 if err != nil { 56 fail(xrpcerr.GenericError(fmt.Errorf("failed to get pipeline: %w", err))) 57 return 58 } 59 if p.Repo == nil || *p.Repo != repoDid.String() { 60 fail(xrpcerr.AccessControlError(actorDid.String())) 61 return 62 } 63 64 pipelineId := models.PipelineId{ 65 Knot: repo.Knot, 66 Rkey: pipelineTid.String(), 67 } 68 l = l.With("input.pipeline", pipelineTid, "input.workflows", input.Workflows) 69 70 workflows := input.Workflows 71 if len(workflows) == 0 { 72 // cancel every workflow when none are specified 73 for _, w := range p.Workflows { 74 workflows = append(workflows, w.Name) 75 } 76 } 77 78 canceled := false 79 defer func() { 80 l.Debug("canceled pipeline", "canceled", canceled) 81 }() 82 83 for _, wName := range workflows { 84 wid := models.WorkflowId{ 85 PipelineId: pipelineId, 86 Name: wName, 87 } 88 l.Debug("cancel pipeline", "wid", wid) 89 90 // dont cancel a workflow that already finished 91 st, err := x.Db.GetStatus(wid) 92 if err == nil && models.StatusKind(st.Status).IsFinish() { 93 continue 94 } 95 96 if err := x.Db.StatusCancelled(wid, "User canceled the workflow", -1, x.Notifier); err != nil { 97 fail(xrpcerr.GenericError(fmt.Errorf("failed to emit status cancelled: %w", err))) 98 return 99 } 100 101 engine.CancelWorkflow(wid) 102 103 for _, eng := range x.Engines { 104 l.Debug("destroying workflow", "wid", wid) 105 if err := eng.DestroyWorkflow(r.Context(), wid); err != nil { 106 fail(xrpcerr.GenericError(fmt.Errorf("failed to destroy workflow: %w", err))) 107 return 108 } 109 } 110 } 111 112 canceled = true 113 114 w.WriteHeader(http.StatusOK) 115}