Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185package xrpc
import ( "context" "encoding/json" "errors" "fmt" "net/http"
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/api/tangled" "tangled.org/core/rbac" xrpcerr "tangled.org/core/xrpc/errors")
func (x *Xrpc) TriggerPipeline(w http.ResponseWriter, r *http.Request) { l := x.Logger fail := func(e xrpcerr.XrpcError) { l.Error("failed", "kind", e.Tag, "error", e.Message) writeError(w, e, http.StatusBadRequest) } l.Debug("trigger pipeline")
actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) if !ok { fail(xrpcerr.MissingActorDidError) return }
var input tangled.CiTriggerPipeline_Input if err := json.NewDecoder(r.Body).Decode(&input); err != nil { fail(xrpcerr.GenericError(err)) return }
repoDid, xerr, ok := x.resolveOwnedRepo(r.Context(), actorDid, input.Repo) if !ok { fail(xerr) return }
var sha string ref := "" var sourceRepo syntax.DID var pull PullContext var inputs []*tangled.Pipeline_Pair
switch { case input.Trigger == nil: fail(xrpcerr.GenericError(fmt.Errorf("trigger is required"))) return
case input.Trigger.CiTrigger_Manual != nil: manual := input.Trigger.CiTrigger_Manual sha = manual.Sha if manual.Ref != nil { ref = *manual.Ref } parsed, err := parseOptionalDID("sourceRepo", manual.SourceRepo) if err != nil { fail(xrpcerr.GenericError(err)) return } sourceRepo = parsed inputs = ciTriggerPairsToPipelinePairs(manual.Inputs)
case input.Trigger.CiTrigger_PullRequest != nil: pr := input.Trigger.CiTrigger_PullRequest sha = pr.SourceSha parsed, err := parseOptionalDID("sourceRepo", pr.SourceRepo) if err != nil { fail(xrpcerr.GenericError(err)) return } sourceRepo = parsed
if pr.TargetBranch == "" { fail(xrpcerr.GenericError(fmt.Errorf("pull request trigger targetBranch is required"))) return }
var pullAt syntax.ATURI if pr.Pull != nil { var err error pullAt, err = syntax.ParseATURI(*pr.Pull) if err != nil { fail(xrpcerr.InvalidRepoError(*pr.Pull)) return } } sourceBranch := "" if pr.SourceBranch != nil { sourceBranch = *pr.SourceBranch } pull = PullContext{ IsPullRequest: true, Pull: pullAt, SourceBranch: sourceBranch, TargetBranch: pr.TargetBranch, }
default: fail(xrpcerr.GenericError(fmt.Errorf("unsupported trigger variant"))) return }
if len(sha) != 40 { fail(xrpcerr.GenericError(fmt.Errorf("sha must be a 40-character commit hash"))) return }
pipelineAt, err := x.Trigger.TriggerManual(r.Context(), repoDid, sha, ref, input.Workflows, sourceRepo, pull, inputs) if errors.Is(err, ErrNoMatchingWorkflows) { fail(xrpcerr.GenericError(err)) return } if err != nil { fail(xrpcerr.GenericError(fmt.Errorf("failed to trigger pipeline: %w", err))) return }
if err := writeJson(w, http.StatusOK, tangled.CiTriggerPipeline_Output{ Pipeline: pipelineAt.String(), }); err != nil { l.Error("failed to write response", "err", err) }}
func parseOptionalDID(field string, value *string) (syntax.DID, error) { if value == nil || *value == "" { return "", nil } did, err := syntax.ParseDID(*value) if err != nil { return "", fmt.Errorf("invalid %s DID %q: %w", field, *value, err) } return did, nil}
func ciTriggerPairsToPipelinePairs(inputs []*tangled.CiTrigger_Pair) []*tangled.Pipeline_Pair { if len(inputs) == 0 { return nil } pairs := make([]*tangled.Pipeline_Pair, 0, len(inputs)) for _, input := range inputs { if input == nil { continue } pairs = append(pairs, &tangled.Pipeline_Pair{ Key: input.Key, Value: input.Value, }) } return pairs}
// resolveOwnedRepo resolves a repository DID and checks push auth.func (x *Xrpc) resolveOwnedRepo(ctx context.Context, actorDid syntax.DID, repoDidStr string) (syntax.DID, xrpcerr.XrpcError, bool) { repoDid, xerr, ok := x.resolveKnownRepoDid(repoDidStr) if !ok { return "", xerr, false }
isPushAllowed, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid.String()) if err != nil || !isPushAllowed { return "", xrpcerr.AccessControlError(actorDid.String()), false }
return repoDid, xrpcerr.XrpcError{}, true}
func (x *Xrpc) resolveKnownRepoDid(repoDidStr string) (syntax.DID, xrpcerr.XrpcError, bool) { repoDid, err := syntax.ParseDID(repoDidStr) if err != nil { return "", xrpcerr.GenericError(fmt.Errorf("invalid repo DID %q: %w", repoDidStr, err)), false }
if _, err := x.Db.GetRepoByDid(repoDid); err != nil { return "", xrpcerr.RepoNotFoundError, false }
return repoDid, xrpcerr.XrpcError{}, true}