Monorepo for Tangled

[WIP] knotserver: DID-based repo resolution in internal, ingester, git, router

+245 -157
+30 -24
knotserver/git.go
··· 5 5 "fmt" 6 6 "io" 7 7 "net/http" 8 - "path/filepath" 9 8 "strings" 10 9 11 - securejoin "github.com/cyphar/filepath-securejoin" 12 10 "github.com/go-chi/chi/v5" 13 11 "tangled.org/core/knotserver/git/service" 14 12 ) 15 13 16 - func (h *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) { 14 + func (h *Knot) resolveRepoPath(r *http.Request) (string, string, error) { 17 15 did := chi.URLParam(r, "did") 18 16 name := chi.URLParam(r, "name") 19 - repoName, err := securejoin.SecureJoin(did, name) 17 + 18 + if name == "" && strings.HasPrefix(did, "did:") { 19 + repoPath, _, repoName, err := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, did) 20 + if err != nil { 21 + return "", "", fmt.Errorf("unknown repo DID: %w", err) 22 + } 23 + return repoPath, repoName, nil 24 + } 25 + 26 + repoDid, err := h.db.GetRepoDid(did, name) 27 + if err != nil { 28 + return "", "", fmt.Errorf("repo not found: %w", err) 29 + } 30 + repoPath, _, _, err := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 20 31 if err != nil { 21 - gitError(w, "repository not found", http.StatusNotFound) 22 - h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 23 - return 32 + return "", "", fmt.Errorf("repo not found: %w", err) 24 33 } 34 + return repoPath, name, nil 35 + } 25 36 26 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, repoName) 37 + func (h *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) { 38 + repoPath, name, err := h.resolveRepoPath(r) 27 39 if err != nil { 28 40 gitError(w, "repository not found", http.StatusNotFound) 29 - h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 41 + h.l.Error("git: failed to resolve repo path", "handler", "InfoRefs", "error", err) 30 42 return 31 43 } 32 44 ··· 57 69 } 58 70 59 71 func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 60 - did := chi.URLParam(r, "did") 61 - name := chi.URLParam(r, "name") 62 - repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 72 + repo, _, err := h.resolveRepoPath(r) 63 73 if err != nil { 64 - gitError(w, err.Error(), http.StatusInternalServerError) 65 - h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 74 + gitError(w, "repository not found", http.StatusNotFound) 75 + h.l.Error("git: failed to resolve repo path", "handler", "UploadArchive", "error", err) 66 76 return 67 77 } 68 78 ··· 104 114 } 105 115 106 116 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 107 - did := chi.URLParam(r, "did") 108 - name := chi.URLParam(r, "name") 109 - repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 117 + repo, _, err := h.resolveRepoPath(r) 110 118 if err != nil { 111 - gitError(w, err.Error(), http.StatusInternalServerError) 112 - h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 119 + gitError(w, "repository not found", http.StatusNotFound) 120 + h.l.Error("git: failed to resolve repo path", "handler", "UploadPack", "error", err) 113 121 return 114 122 } 115 123 ··· 153 161 } 154 162 155 163 func (h *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) { 156 - did := chi.URLParam(r, "did") 157 - name := chi.URLParam(r, "name") 158 - _, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 164 + _, name, err := h.resolveRepoPath(r) 159 165 if err != nil { 160 - gitError(w, err.Error(), http.StatusForbidden) 161 - h.l.Error("git: failed to secure join repo path", "handler", "ReceivePack", "error", err) 166 + gitError(w, "repository not found", http.StatusNotFound) 167 + h.l.Error("git: failed to resolve repo path", "handler", "ReceivePack", "error", err) 162 168 return 163 169 } 164 170
+95 -57
knotserver/ingester.go
··· 14 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 15 "github.com/bluesky-social/indigo/xrpc" 16 16 "github.com/bluesky-social/jetstream/pkg/models" 17 - securejoin "github.com/cyphar/filepath-securejoin" 18 17 "tangled.org/core/api/tangled" 19 18 "tangled.org/core/knotserver/db" 20 19 "tangled.org/core/knotserver/git" ··· 102 101 return fmt.Errorf("ignoring pull record: target repo is nil") 103 102 } 104 103 105 - l = l.With("target_repo", record.Target.Repo) 104 + l = l.With("target_repo", record.Target.Repo, "target_repo_did", record.Target.RepoDid) 106 105 l = l.With("target_branch", record.Target.Branch) 107 106 108 107 if record.Source == nil { 109 108 return fmt.Errorf("ignoring pull record: not a branch-based pull request") 110 109 } 111 110 112 - if record.Source.Repo != nil { 111 + if record.Source.Repo != nil || record.Source.RepoDid != nil { 113 112 return fmt.Errorf("ignoring pull record: fork based pull") 114 113 } 115 114 116 - repoAt, err := syntax.ParseATURI(record.Target.Repo) 117 - if err != nil { 118 - return fmt.Errorf("failed to parse ATURI: %w", err) 119 - } 115 + var repoPath, ownerDid, repoName, repoDid string 116 + switch { 117 + case record.Target.RepoDid != nil && *record.Target.RepoDid != "": 118 + repoDid = *record.Target.RepoDid 119 + var lookupErr error 120 + repoPath, ownerDid, repoName, lookupErr = h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 121 + if lookupErr != nil { 122 + return fmt.Errorf("unknown target repo DID %s: %w", repoDid, lookupErr) 123 + } 120 124 121 - // resolve this aturi to extract the repo record 122 - ident, err := h.resolver.ResolveIdent(ctx, repoAt.Authority().String()) 123 - if err != nil || ident.Handle.IsInvalidHandle() { 124 - return fmt.Errorf("failed to resolve handle: %w", err) 125 - } 125 + case record.Target.Repo != nil: 126 + // TODO: get rid of this PDS fetch once all repos have DIDs 127 + repoAt, parseErr := syntax.ParseATURI(*record.Target.Repo) 128 + if parseErr != nil { 129 + return fmt.Errorf("failed to parse ATURI: %w", parseErr) 130 + } 131 + 132 + ident, resolveErr := h.resolver.ResolveIdent(ctx, repoAt.Authority().String()) 133 + if resolveErr != nil || ident.Handle.IsInvalidHandle() { 134 + return fmt.Errorf("failed to resolve handle: %w", resolveErr) 135 + } 136 + 137 + xrpcc := xrpc.Client{ 138 + Host: ident.PDSEndpoint(), 139 + } 140 + 141 + resp, getErr := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 142 + if getErr != nil { 143 + return fmt.Errorf("failed to resolve repo: %w", getErr) 144 + } 126 145 127 - xrpcc := xrpc.Client{ 128 - Host: ident.PDSEndpoint(), 129 - } 146 + repo := resp.Value.Val.(*tangled.Repo) 130 147 131 - resp, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 132 - if err != nil { 133 - return fmt.Errorf("failed to resolver repo: %w", err) 134 - } 148 + if repo.Knot != h.c.Server.Hostname { 149 + return fmt.Errorf("rejected pull record: not this knot, %s != %s", repo.Knot, h.c.Server.Hostname) 150 + } 135 151 136 - repo := resp.Value.Val.(*tangled.Repo) 152 + ownerDid = ident.DID.String() 153 + repoName = repo.Name 137 154 138 - if repo.Knot != h.c.Server.Hostname { 139 - return fmt.Errorf("rejected pull record: not this knot, %s != %s", repo.Knot, h.c.Server.Hostname) 140 - } 155 + repoDid, didErr := h.db.GetRepoDid(ownerDid, repoName) 156 + if didErr != nil { 157 + return fmt.Errorf("failed to resolve repo DID for %s/%s: %w", ownerDid, repoName, didErr) 158 + } 141 159 142 - didSlashRepo, err := securejoin.SecureJoin(ident.DID.String(), repo.Name) 143 - if err != nil { 144 - return fmt.Errorf("failed to construct relative repo path: %w", err) 145 - } 160 + var lookupErr error 161 + repoPath, _, _, lookupErr = h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 162 + if lookupErr != nil { 163 + return fmt.Errorf("failed to resolve repo on disk: %w", lookupErr) 164 + } 146 165 147 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 148 - if err != nil { 149 - return fmt.Errorf("failed to construct absolute repo path: %w", err) 166 + default: 167 + return fmt.Errorf("ignoring pull record: target has neither repo nor repoDid") 150 168 } 151 169 152 170 gr, err := git.Open(repoPath, record.Source.Sha) ··· 189 207 Kind: string(workflow.TriggerKindPullRequest), 190 208 PullRequest: &trigger, 191 209 Repo: &tangled.Pipeline_TriggerRepo{ 192 - Did: ident.DID.String(), 193 - Knot: repo.Knot, 194 - Repo: repo.Name, 210 + Did: ownerDid, 211 + Knot: h.c.Server.Hostname, 212 + Repo: &repoName, 213 + RepoDid: &repoDid, 195 214 }, 196 215 }, 197 216 } ··· 226 245 return fmt.Errorf("failed to unmarshal record: %w", err) 227 246 } 228 247 229 - repoAt, err := syntax.ParseATURI(record.Repo) 230 - if err != nil { 231 - return err 232 - } 233 - 234 248 subjectId, err := h.resolver.ResolveIdent(ctx, record.Subject) 235 249 if err != nil || subjectId.Handle.IsInvalidHandle() { 236 250 return err 237 251 } 238 252 239 - // TODO: fix this for good, we need to fetch the record here unfortunately 240 - // resolve this aturi to extract the repo record 241 - owner, err := h.resolver.ResolveIdent(ctx, repoAt.Authority().String()) 242 - if err != nil || owner.Handle.IsInvalidHandle() { 243 - return fmt.Errorf("failed to resolve handle: %w", err) 244 - } 253 + var rbacResource string 254 + switch { 255 + case record.RepoDid != nil && *record.RepoDid != "": 256 + ownerDid, _, lookupErr := h.db.GetRepoKeyOwner(*record.RepoDid) 257 + if lookupErr != nil { 258 + return fmt.Errorf("unknown repo DID %s: %w", *record.RepoDid, lookupErr) 259 + } 260 + if ownerDid != did { 261 + return fmt.Errorf("collaborator record author %s does not own repo %s", did, *record.RepoDid) 262 + } 263 + rbacResource = *record.RepoDid 245 264 246 - xrpcc := xrpc.Client{ 247 - Host: owner.PDSEndpoint(), 248 - } 265 + case record.Repo != nil: 266 + // TODO: get rid of this PDS fetch once all repos have DIDs 267 + repoAt, parseErr := syntax.ParseATURI(*record.Repo) 268 + if parseErr != nil { 269 + return parseErr 270 + } 249 271 250 - resp, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 251 - if err != nil { 252 - return err 253 - } 272 + owner, resolveErr := h.resolver.ResolveIdent(ctx, repoAt.Authority().String()) 273 + if resolveErr != nil || owner.Handle.IsInvalidHandle() { 274 + return fmt.Errorf("failed to resolve handle: %w", resolveErr) 275 + } 254 276 255 - repo := resp.Value.Val.(*tangled.Repo) 256 - didSlashRepo, _ := securejoin.SecureJoin(owner.DID.String(), repo.Name) 277 + xrpcc := xrpc.Client{ 278 + Host: owner.PDSEndpoint(), 279 + } 257 280 258 - // check perms for this user 259 - ok, err := h.e.IsCollaboratorInviteAllowed(did, rbac.ThisServer, didSlashRepo) 281 + resp, getErr := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 282 + if getErr != nil { 283 + return getErr 284 + } 285 + 286 + repo := resp.Value.Val.(*tangled.Repo) 287 + repoDid, didErr := h.db.GetRepoDid(owner.DID.String(), repo.Name) 288 + if didErr != nil { 289 + return fmt.Errorf("failed to resolve repo DID for %s/%s: %w", owner.DID.String(), repo.Name, didErr) 290 + } 291 + rbacResource = repoDid 292 + 293 + default: 294 + return fmt.Errorf("collaborator record has neither repo nor repoDid") 295 + } 296 + 297 + ok, err := h.e.IsCollaboratorInviteAllowed(did, rbac.ThisServer, rbacResource) 260 298 if err != nil { 261 299 return fmt.Errorf("failed to check permissions: %w", err) 262 300 } 263 301 if !ok { 264 - return fmt.Errorf("insufficient permissions: %s, %s, %s", did, "IsCollaboratorInviteAllowed", didSlashRepo) 302 + return fmt.Errorf("insufficient permissions: %s, %s, %s", did, "IsCollaboratorInviteAllowed", rbacResource) 265 303 } 266 304 267 305 if err := h.db.AddDid(subjectId.DID.String()); err != nil { ··· 269 307 } 270 308 h.jc.AddDid(subjectId.DID.String()) 271 309 272 - if err := h.e.AddCollaborator(subjectId.DID.String(), rbac.ThisServer, didSlashRepo); err != nil { 310 + if err := h.e.AddCollaborator(subjectId.DID.String(), rbac.ThisServer, rbacResource); err != nil { 273 311 return err 274 312 } 275 313
+110 -74
knotserver/internal.go
··· 3 3 import ( 4 4 "context" 5 5 "encoding/json" 6 - "errors" 7 6 "fmt" 8 7 "log/slog" 9 8 "net/http" 10 9 "path/filepath" 11 10 "strings" 12 11 13 - securejoin "github.com/cyphar/filepath-securejoin" 14 12 "github.com/go-chi/chi/v5" 15 13 "github.com/go-chi/chi/v5/middleware" 16 14 "github.com/go-git/go-git/v5/plumbing" ··· 72 70 // the body will be qualified repository path on success/push-denied 73 71 // or an error message when process failed 74 72 func (h *InternalHandle) Guard(w http.ResponseWriter, r *http.Request) { 75 - l := h.l.With("handler", "PostReceiveHook") 73 + l := h.l.With("handler", "Guard") 76 74 77 75 var ( 78 76 incomingUser = r.URL.Query().Get("user") ··· 87 85 return 88 86 } 89 87 90 - // did:foo/repo-name or 91 - // handle/repo-name or 92 - // any of the above with a leading slash (/) 93 88 components := strings.Split(strings.TrimPrefix(strings.Trim(repo, "'"), "/"), "/") 94 89 l.Info("command components", "components", components) 95 90 96 - if len(components) != 2 { 97 - w.WriteHeader(http.StatusBadRequest) 98 - l.Error("invalid repo format", "components", components) 99 - fmt.Fprintln(w, "invalid repo format, needs <user>/<repo> or /<user>/<repo>") 100 - return 101 - } 102 - repoOwner := components[0] 103 - repoName := components[1] 91 + var rbacResource string 92 + var diskRelative string 104 93 105 - resolver := idresolver.DefaultResolver(h.c.Server.PlcUrl) 94 + switch { 95 + case len(components) == 1 && strings.HasPrefix(components[0], "did:"): 96 + repoDid := components[0] 97 + repoPath, _, _, lookupErr := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 98 + if lookupErr != nil { 99 + w.WriteHeader(http.StatusNotFound) 100 + l.Error("repo DID not found", "repoDid", repoDid, "err", lookupErr) 101 + fmt.Fprintln(w, "repo not found") 102 + return 103 + } 104 + rbacResource = repoDid 105 + rel, relErr := filepath.Rel(h.c.Repo.ScanPath, repoPath) 106 + if relErr != nil { 107 + w.WriteHeader(http.StatusInternalServerError) 108 + l.Error("failed to compute relative path", "repoPath", repoPath, "err", relErr) 109 + fmt.Fprintln(w, "internal error") 110 + return 111 + } 112 + diskRelative = rel 106 113 107 - repoOwnerIdent, err := resolver.ResolveIdent(r.Context(), repoOwner) 108 - if err != nil || repoOwnerIdent.Handle.IsInvalidHandle() { 109 - l.Error("Error resolving handle", "handle", repoOwner, "err", err) 110 - w.WriteHeader(http.StatusInternalServerError) 111 - fmt.Fprintf(w, "error resolving handle: invalid handle\n") 114 + case len(components) == 2: 115 + repoOwner := components[0] 116 + resolver := idresolver.DefaultResolver(h.c.Server.PlcUrl) 117 + repoOwnerIdent, resolveErr := resolver.ResolveIdent(r.Context(), repoOwner) 118 + if resolveErr != nil || repoOwnerIdent.Handle.IsInvalidHandle() { 119 + l.Error("Error resolving handle", "handle", repoOwner, "err", resolveErr) 120 + w.WriteHeader(http.StatusInternalServerError) 121 + fmt.Fprintf(w, "error resolving handle: invalid handle\n") 122 + return 123 + } 124 + ownerDid := repoOwnerIdent.DID.String() 125 + repoName := components[1] 126 + repoDid, didErr := h.db.GetRepoDid(ownerDid, repoName) 127 + if didErr != nil { 128 + w.WriteHeader(http.StatusNotFound) 129 + l.Error("repo DID not found", "owner", ownerDid, "name", repoName, "err", didErr) 130 + fmt.Fprintln(w, "repo not found") 131 + return 132 + } 133 + repoPath, _, _, lookupErr := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 134 + if lookupErr != nil { 135 + w.WriteHeader(http.StatusNotFound) 136 + l.Error("repo not found on disk", "repoDid", repoDid, "err", lookupErr) 137 + fmt.Fprintln(w, "repo not found") 138 + return 139 + } 140 + rbacResource = repoDid 141 + rel, relErr := filepath.Rel(h.c.Repo.ScanPath, repoPath) 142 + if relErr != nil { 143 + w.WriteHeader(http.StatusInternalServerError) 144 + l.Error("failed to compute relative path", "repoPath", repoPath, "err", relErr) 145 + fmt.Fprintln(w, "internal error") 146 + return 147 + } 148 + diskRelative = rel 149 + 150 + default: 151 + w.WriteHeader(http.StatusBadRequest) 152 + l.Error("invalid repo format", "components", components) 153 + fmt.Fprintln(w, "invalid repo format, needs <user>/<repo>, /<user>/<repo>, or <repo-did>") 112 154 return 113 155 } 114 - repoOwnerDid := repoOwnerIdent.DID.String() 115 - 116 - qualifiedRepo, _ := securejoin.SecureJoin(repoOwnerDid, repoName) 117 156 118 157 if gitCommand == "git-receive-pack" { 119 - ok, err := h.e.IsPushAllowed(incomingUser, rbac.ThisServer, qualifiedRepo) 158 + ok, err := h.e.IsPushAllowed(incomingUser, rbac.ThisServer, rbacResource) 120 159 if err != nil || !ok { 121 160 w.WriteHeader(http.StatusForbidden) 122 161 fmt.Fprint(w, repo) ··· 125 164 } 126 165 127 166 w.WriteHeader(http.StatusOK) 128 - fmt.Fprint(w, qualifiedRepo) 167 + fmt.Fprint(w, diskRelative) 129 168 } 130 169 131 170 type PushOptions struct { ··· 140 179 gitRelativeDir, err := filepath.Rel(h.c.Repo.ScanPath, gitAbsoluteDir) 141 180 if err != nil { 142 181 l.Error("failed to calculate relative git dir", "scanPath", h.c.Repo.ScanPath, "gitAbsoluteDir", gitAbsoluteDir) 182 + w.WriteHeader(http.StatusInternalServerError) 143 183 return 144 184 } 145 185 146 - parts := strings.SplitN(gitRelativeDir, "/", 2) 147 - if len(parts) != 2 { 148 - l.Error("invalid git dir", "gitRelativeDir", gitRelativeDir) 186 + repoDid := gitRelativeDir 187 + if !strings.HasPrefix(repoDid, "did:") { 188 + l.Error("invalid git dir, expected repo DID", "gitRelativeDir", gitRelativeDir) 189 + w.WriteHeader(http.StatusBadRequest) 149 190 return 150 191 } 151 - repoDid := parts[0] 152 - repoName := parts[1] 192 + 193 + ownerDid, repoName, err := h.db.GetRepoKeyOwner(repoDid) 194 + if err != nil { 195 + l.Error("failed to resolve repo DID from git dir", "repoDid", repoDid, "err", err) 196 + w.WriteHeader(http.StatusBadRequest) 197 + return 198 + } 153 199 154 200 gitUserDid := r.Header.Get("X-Git-User-Did") 155 201 ··· 176 222 } 177 223 178 224 for _, line := range lines { 179 - err := h.insertRefUpdate(line, gitUserDid, repoDid, repoName) 225 + err := h.insertRefUpdate(line, gitUserDid, ownerDid, repoName, repoDid) 180 226 if err != nil { 181 227 l.Error("failed to insert op", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 182 - // non-fatal 183 228 } 184 229 185 - err = h.emitCompareLink(&resp.Messages, line, repoDid, repoName) 230 + err = h.emitCompareLink(&resp.Messages, line, ownerDid, repoName, repoDid) 186 231 if err != nil { 187 232 l.Error("failed to reply with compare link", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 188 - // non-fatal 189 233 } 190 234 191 - err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) 235 + err = h.triggerPipeline(&resp.Messages, line, gitUserDid, ownerDid, repoName, repoDid, pushOptions) 192 236 if err != nil { 193 237 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 194 - // non-fatal 195 238 } 196 239 } 197 240 198 241 writeJSON(w, resp) 199 242 } 200 243 201 - func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error { 202 - didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName) 203 - if err != nil { 204 - return err 205 - } 206 - 207 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 208 - if err != nil { 209 - return err 244 + func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, ownerDid, repoName, repoDid string) error { 245 + repoPath, _, _, resolveErr := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 246 + if resolveErr != nil { 247 + return fmt.Errorf("failed to resolve repo on disk: %w", resolveErr) 210 248 } 211 249 212 250 gr, err := git.Open(repoPath, line.Ref) ··· 214 252 return fmt.Errorf("failed to open git repo at ref %s: %w", line.Ref, err) 215 253 } 216 254 217 - var errs error 218 255 meta, err := gr.RefUpdateMeta(line) 219 - errors.Join(errs, err) 256 + if err != nil { 257 + return fmt.Errorf("failed to get ref update metadata: %w", err) 258 + } 220 259 221 260 metaRecord := meta.AsRecord() 222 261 ··· 225 264 NewSha: line.NewSha.String(), 226 265 Ref: line.Ref, 227 266 CommitterDid: gitUserDid, 228 - RepoDid: repoDid, 267 + OwnerDid: &ownerDid, 229 268 RepoName: repoName, 269 + RepoDid: &repoDid, 230 270 Meta: &metaRecord, 231 271 } 272 + 232 273 eventJson, err := json.Marshal(refUpdate) 233 274 if err != nil { 234 275 return err ··· 240 281 EventJson: string(eventJson), 241 282 } 242 283 243 - return errors.Join(errs, h.db.InsertEvent(event, h.n)) 284 + return h.db.InsertEvent(event, h.n) 244 285 } 245 286 246 287 func (h *InternalHandle) triggerPipeline( 247 288 clientMsgs *[]string, 248 289 line git.PostReceiveLine, 249 290 gitUserDid string, 291 + ownerDid string, 292 + repoName string, 250 293 repoDid string, 251 - repoName string, 252 294 pushOptions PushOptions, 253 295 ) error { 254 296 if pushOptions.skipCi { 255 297 return nil 256 298 } 257 299 258 - didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName) 259 - if err != nil { 260 - return err 261 - } 262 - 263 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 264 - if err != nil { 265 - return err 300 + repoPath, _, _, resolveErr := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 301 + if resolveErr != nil { 302 + return fmt.Errorf("failed to resolve repo on disk: %w", resolveErr) 266 303 } 267 304 268 305 gr, err := git.Open(repoPath, line.Ref) ··· 299 336 NewSha: line.NewSha.String(), 300 337 } 301 338 339 + triggerRepo := &tangled.Pipeline_TriggerRepo{ 340 + Did: ownerDid, 341 + Knot: h.c.Server.Hostname, 342 + Repo: &repoName, 343 + RepoDid: &repoDid, 344 + } 345 + 302 346 compiler := workflow.Compiler{ 303 347 Trigger: tangled.Pipeline_TriggerMetadata{ 304 348 Kind: string(workflow.TriggerKindPush), 305 349 Push: &trigger, 306 - Repo: &tangled.Pipeline_TriggerRepo{ 307 - Did: repoDid, 308 - Knot: h.c.Server.Hostname, 309 - Repo: repoName, 310 - }, 350 + Repo: triggerRepo, 311 351 }, 312 352 } 313 353 ··· 348 388 func (h *InternalHandle) emitCompareLink( 349 389 clientMsgs *[]string, 350 390 line git.PostReceiveLine, 391 + ownerDid string, 392 + repoName string, 351 393 repoDid string, 352 - repoName string, 353 394 ) error { 354 395 // this is a second push to a branch, don't reply with the link again 355 396 if !line.OldSha.IsZero() { ··· 365 406 366 407 pushedRef := plumbing.ReferenceName(line.Ref) 367 408 368 - userIdent, err := h.res.ResolveIdent(context.Background(), repoDid) 369 - user := repoDid 409 + userIdent, err := h.res.ResolveIdent(context.Background(), ownerDid) 410 + user := ownerDid 370 411 if err == nil { 371 412 user = userIdent.Handle.String() 372 413 } 373 414 374 - didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName) 375 - if err != nil { 376 - return err 377 - } 378 - 379 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 380 - if err != nil { 381 - return err 415 + repoPath, _, _, resolveErr := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 416 + if resolveErr != nil { 417 + return fmt.Errorf("failed to resolve repo on disk: %w", resolveErr) 382 418 } 383 419 384 420 gr, err := git.PlainOpen(repoPath)
+10 -2
knotserver/router.go
··· 81 81 82 82 r.Route("/{did}", func(r chi.Router) { 83 83 r.Use(h.resolveDidRedirect) 84 + 85 + r.Get("/info/refs", h.InfoRefs) 86 + r.Post("/git-upload-archive", h.UploadArchive) 87 + r.Post("/git-upload-pack", h.UploadPack) 88 + r.Post("/git-receive-pack", h.ReceivePack) 89 + 84 90 r.Route("/{name}", func(r chi.Router) { 85 - // routes for git operations 86 91 r.Get("/info/refs", h.InfoRefs) 87 92 r.Post("/git-upload-archive", h.UploadArchive) 88 93 r.Post("/git-upload-pack", h.UploadPack) ··· 136 141 } 137 142 138 143 suffix := strings.TrimPrefix(r.URL.Path, "/"+didOrHandle) 139 - newPath := fmt.Sprintf("/%s/%s?%s", id.DID.String(), suffix, r.URL.RawQuery) 144 + newPath := "/" + id.DID.String() + suffix 145 + if r.URL.RawQuery != "" { 146 + newPath += "?" + r.URL.RawQuery 147 + } 140 148 http.Redirect(w, r, newPath, http.StatusTemporaryRedirect) 141 149 }) 142 150 }