Monorepo for Tangled
tangled.org
1package xrpc
2
3import (
4 "context"
5 _ "embed"
6 "encoding/json"
7 "errors"
8 "log/slog"
9 "net/http"
10
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "github.com/go-chi/chi/v5"
13
14 "tangled.org/core/api/tangled"
15 "tangled.org/core/idresolver"
16 "tangled.org/core/notifier"
17 "tangled.org/core/rbac"
18 "tangled.org/core/spindle/config"
19 "tangled.org/core/spindle/db"
20 "tangled.org/core/spindle/models"
21 "tangled.org/core/spindle/secrets"
22 xrpcerr "tangled.org/core/xrpc/errors"
23 "tangled.org/core/xrpc/serviceauth"
24)
25
26const ActorDid = serviceauth.ActorDid
27
28// ErrNoMatchingWorkflows is returned when a manual dispatch resolves to no
29// workflows to run: the repo defines none at the requested commit, or none of
30// the requested workflow names exist.
31var ErrNoMatchingWorkflows = errors.New("no workflows to run")
32
33// PipelineTrigger builds and enqueues a manually-dispatched pipeline. It is
34// implemented by *spindle.Spindle, which owns the queue and engines; the xrpc
35// handler only does auth and input validation before delegating here.
36type PipelineTrigger interface {
37 TriggerManual(ctx context.Context, repoDid syntax.DID, sha, ref string, workflows []string) (syntax.ATURI, error)
38}
39
40type Xrpc struct {
41 Logger *slog.Logger
42 Db *db.DB
43 Enforcer *rbac.Enforcer
44 Engines map[string]models.Engine
45 Config *config.Config
46 Resolver *idresolver.Resolver
47 Vault secrets.Manager
48 Notifier *notifier.Notifier
49 ServiceAuth *serviceauth.ServiceAuth
50 Trigger PipelineTrigger
51}
52
53func (x *Xrpc) Router() http.Handler {
54 r := chi.NewRouter()
55
56 r.Group(func(r chi.Router) {
57 r.Use(x.ServiceAuth.VerifyServiceAuth)
58
59 r.Post("/"+tangled.RepoAddSecretNSID, x.AddSecret)
60 r.Post("/"+tangled.RepoRemoveSecretNSID, x.RemoveSecret)
61 r.Get("/"+tangled.RepoListSecretsNSID, x.ListSecrets)
62 r.Post("/"+tangled.CiPipelineCancelPipelineNSID, x.CancelPipeline)
63 r.Post("/"+tangled.CiPipelineTriggerPipelineNSID, x.TriggerPipeline)
64 })
65
66 // service query endpoints (no auth required)
67 r.Get("/"+tangled.OwnerNSID, x.Owner)
68 r.Get("/"+tangled.CiPipelineSubscribeLogsNSID, x.HandleCiPipelineSubscribeLogs)
69 r.Get("/"+tangled.CiQueryPipelinesNSID, x.HandleCiQueryPipelines)
70 r.Get("/"+tangled.CiGetPipelineNSID, x.HandleCiGetPipeline)
71
72 return r
73}
74
75// this is slightly different from http_util::write_error to follow the spec:
76//
77// the json object returned must include an "error" and a "message"
78func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {
79 w.Header().Set("Content-Type", "application/json")
80 w.WriteHeader(status)
81 json.NewEncoder(w).Encode(e)
82}
83
84func writeJson(w http.ResponseWriter, status int, response any) error {
85 w.Header().Set("Content-Type", "application/json")
86 w.WriteHeader(status)
87 return json.NewEncoder(w).Encode(response)
88}