-
Notifications
You must be signed in to change notification settings - Fork 106
/
repos.go
130 lines (109 loc) · 4.2 KB
/
repos.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package server
import (
"context"
"errors"
"time"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/rilldata/rill/admin/database"
"github.com/rilldata/rill/admin/server/auth"
adminv1 "github.com/rilldata/rill/proto/gen/rill/admin/v1"
"github.com/rilldata/rill/runtime/pkg/observability"
"go.opentelemetry.io/otel/attribute"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
const gitURLTTL = 30 * time.Minute
func (s *Server) GetRepoMeta(ctx context.Context, req *adminv1.GetRepoMetaRequest) (*adminv1.GetRepoMetaResponse, error) {
observability.AddRequestAttributes(ctx,
attribute.String("args.project_id", req.ProjectId),
attribute.String("args.branch", req.Branch),
)
proj, err := s.admin.DB.FindProject(ctx, req.ProjectId)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
return nil, status.Error(codes.NotFound, "project not found")
}
return nil, status.Error(codes.InvalidArgument, err.Error())
}
permissions := auth.GetClaims(ctx).ProjectPermissions(ctx, proj.OrganizationID, proj.ID)
if !permissions.ReadProdStatus {
return nil, status.Error(codes.PermissionDenied, "does not have permission to read project repo")
}
if proj.ProdBranch != req.Branch {
return nil, status.Error(codes.InvalidArgument, "branch not found")
}
if proj.GithubURL == nil || proj.GithubInstallationID == nil {
return nil, status.Error(codes.FailedPrecondition, "project does not have a github integration")
}
token, err := s.admin.Github.InstallationToken(ctx, *proj.GithubInstallationID)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
ep, err := transport.NewEndpoint(*proj.GithubURL + ".git") // TODO: Can the clone URL be different from the HTTP URL of a Github repo?
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to create endpoint from %q: %s", *proj.GithubURL, err.Error())
}
ep.User = "x-access-token"
ep.Password = token
gitURL := ep.String()
return &adminv1.GetRepoMetaResponse{
GitUrl: gitURL,
GitUrlExpiresOn: timestamppb.New(time.Now().Add(gitURLTTL)),
GitSubpath: proj.Subpath,
}, nil
}
func (s *Server) PullVirtualRepo(ctx context.Context, req *adminv1.PullVirtualRepoRequest) (*adminv1.PullVirtualRepoResponse, error) {
observability.AddRequestAttributes(ctx,
attribute.String("args.project_id", req.ProjectId),
attribute.String("args.branch", req.Branch),
attribute.Int("args.page_size", int(req.PageSize)),
attribute.String("args.page_token", req.PageToken),
)
proj, err := s.admin.DB.FindProject(ctx, req.ProjectId)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
return nil, status.Error(codes.NotFound, "project not found")
}
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if proj.ProdBranch != req.Branch {
return nil, status.Error(codes.InvalidArgument, "branch not found")
}
permissions := auth.GetClaims(ctx).ProjectPermissions(ctx, proj.OrganizationID, proj.ID)
if !permissions.ReadProdStatus {
return nil, status.Error(codes.PermissionDenied, "does not have permission to read project repo")
}
pageToken, err := unmarshalStringTimestampPageToken(req.PageToken)
if err != nil {
return nil, err
}
pageSize := validPageSize(req.PageSize)
vfs, err := s.admin.DB.FindVirtualFiles(ctx, proj.ID, req.Branch, pageToken.Ts.AsTime(), pageToken.Str, pageSize)
if err != nil {
return nil, err
}
// If no files were found, we return the same page token as the next page token.
// This enables the client to poll for new changes continuously. (The client is responsible for pausing when an empty page is returned.)
nextToken := req.PageToken
if len(vfs) > 0 {
f := vfs[len(vfs)-1]
nextToken = marshalStringTimestampPageToken(f.Path, f.UpdatedOn)
}
dtos := make([]*adminv1.VirtualFile, len(vfs))
for i, vf := range vfs {
dtos[i] = virtualFileToDTO(vf)
}
return &adminv1.PullVirtualRepoResponse{
Files: dtos,
NextPageToken: nextToken,
}, nil
}
func virtualFileToDTO(vf *database.VirtualFile) *adminv1.VirtualFile {
return &adminv1.VirtualFile{
Path: vf.Path,
Data: vf.Data,
Deleted: vf.Deleted,
UpdatedOn: timestamppb.New(vf.UpdatedOn),
}
}