This repository has no description
1package xrpc
2
3import (
4 "context"
5 _ "embed"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "log/slog"
10 "net/http"
11
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "github.com/go-chi/chi/v5"
14
15 "tangled.org/core/api/tangled"
16 "tangled.org/core/idresolver"
17 "tangled.org/core/notifier"
18 "tangled.org/core/rbac"
19 "tangled.org/core/spindle/config"
20 "tangled.org/core/spindle/db"
21 "tangled.org/core/spindle/models"
22 "tangled.org/core/spindle/secrets"
23 xrpcerr "tangled.org/core/xrpc/errors"
24 "tangled.org/core/xrpc/serviceauth"
25)
26
27const ActorDid = serviceauth.ActorDid
28
29var ErrNoMatchingWorkflows = errors.New("no workflows to run")
30
31func requireSha(sha string) error {
32 if len(sha) != 40 {
33 return fmt.Errorf("sha must be a 40-character commit hash")
34 }
35 return nil
36}
37
38// this is to break an import cycle. spindle imports this package for Xrpc,
39// so this package can't import *spindle.Spindle back.
40type PipelineTrigger interface {
41 TriggerManual(ctx context.Context, repoDid syntax.DID, sha, ref string, workflows []string, sourceRepo syntax.DID, pull PullContext, inputs []*tangled.Pipeline_Pair) (syntax.ATURI, error)
42 DescribeWorkflowDefinition(ctx context.Context, repoDid syntax.DID, sha string, sourceRepo syntax.DID) (*tangled.CiDescribeWorkflowDefinition_Output, error)
43}
44
45type PullContext struct {
46 IsPullRequest bool
47 Pull syntax.ATURI
48 SourceBranch string
49 TargetBranch string
50}
51
52type Xrpc struct {
53 Logger *slog.Logger
54 Db *db.DB
55 Enforcer *rbac.Enforcer
56 Engines map[string]models.Engine
57 Config *config.Config
58 Resolver *idresolver.Resolver
59 Vault secrets.Manager
60 Notifier *notifier.Notifier
61 ServiceAuth *serviceauth.ServiceAuth
62 Trigger PipelineTrigger
63}
64
65func (x *Xrpc) Router() http.Handler {
66 r := chi.NewRouter()
67
68 r.Group(func(r chi.Router) {
69 r.Use(x.ServiceAuth.VerifyServiceAuth)
70
71 r.Post("/"+tangled.RepoAddSecretNSID, x.AddSecret)
72 r.Post("/"+tangled.RepoRemoveSecretNSID, x.RemoveSecret)
73 r.Get("/"+tangled.RepoListSecretsNSID, x.ListSecrets)
74 r.Post("/"+tangled.CiCancelPipelineNSID, x.CancelPipeline)
75 r.Post("/"+tangled.CiTriggerPipelineNSID, x.TriggerPipeline)
76 })
77
78 // service query endpoints (no auth required)
79 r.Get("/"+tangled.OwnerNSID, x.Owner)
80 r.Get("/"+tangled.CiDescribeWorkflowDefinitionNSID, x.DescribeWorkflowDefinition)
81 r.Get("/"+tangled.CiSubscribePipelineLogsNSID, x.HandleCiSubscribePipelineLogs)
82 r.Get("/"+tangled.CiQueryPipelinesNSID, x.HandleCiQueryPipelines)
83 r.Get("/"+tangled.CiGetPipelineNSID, x.HandleCiGetPipeline)
84
85 return r
86}
87
88// this is slightly different from http_util::write_error to follow the spec:
89//
90// the json object returned must include an "error" and a "message"
91func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {
92 w.Header().Set("Content-Type", "application/json")
93 w.WriteHeader(status)
94 json.NewEncoder(w).Encode(e)
95}
96
97func writeJson(w http.ResponseWriter, status int, response any) error {
98 w.Header().Set("Content-Type", "application/json")
99 w.WriteHeader(status)
100 return json.NewEncoder(w).Encode(response)
101}