v0.18.0 0cea275

ci_settings.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package rpc
4
5import (
6	"context"
7
8	"connectrpc.com/connect"
9
10	pb "bnema.dev/ermite-api/gen/ermite"
11	"bnema.dev/ermite/internal/domain"
12	"bnema.dev/ermite/internal/ports"
13)
14
15// FeatureCISecrets is listed by GetVersion when the server serves the secret,
16// variable and environment services and sends secrets, variables and the trust
17// flag to runners.
18const FeatureCISecrets = "ci.secrets"
19
20// settingsScopeFromPB reads a SettingsScope. A scope that is not set, a false instance
21// and a name that breaks the rules are invalid_argument.
22func settingsScopeFromPB(s *pb.SettingsScope) (domain.SettingsScope, error) {
23	bad := func(reason string) error {
24		return &domain.InvalidFieldsError{Fields: []domain.FieldViolation{{Field: "scope", Reason: reason}}}
25	}
26	switch w := s.GetWhere().(type) {
27	case *pb.SettingsScope_Instance:
28		if !w.Instance {
29			return domain.SettingsScope{}, bad("instance must be true when set")
30		}
31		return domain.InstanceScope(), nil
32	case *pb.SettingsScope_Repo:
33		name, err := domain.ParseRepoName(w.Repo)
34		if err != nil {
35			return domain.SettingsScope{}, err
36		}
37		return domain.RepoScope(name), nil
38	case *pb.SettingsScope_Environment:
39		name, err := domain.ParseRepoName(w.Environment.GetRepo())
40		if err != nil {
41			return domain.SettingsScope{}, err
42		}
43		env, err := domain.ParseEnvironmentName(w.Environment.GetName())
44		if err != nil {
45			return domain.SettingsScope{}, bad("environment name " + err.Error())
46		}
47		return domain.EnvironmentScope(name, env), nil
48	}
49	return domain.SettingsScope{}, bad("is required")
50}
51
52func secretToPB(s domain.Secret) *pb.Secret {
53	return &pb.Secret{Name: s.Name, UpdatedAt: timestamp(s.UpdatedAt)} // never a value
54}
55
56func variableToPB(v domain.Variable) *pb.Variable {
57	return &pb.Variable{Name: v.Name, Value: v.Value, UpdatedAt: timestamp(v.UpdatedAt)}
58}
59
60func environmentToPB(e domain.Environment) *pb.Environment {
61	return &pb.Environment{Repo: e.Repo.String(), Name: e.Name, CreatedAt: timestamp(e.CreatedAt)}
62}
63
64// secretService implements ermiteconnect.SecretServiceHandler. A request holds
65// a secret value: nothing of it is logged here, and the interceptor logs
66// procedures only.
67type secretService struct{ secrets ports.SecretService }
68
69func (s *secretService) SetSecret(ctx context.Context, req *connect.Request[pb.SetSecretRequest]) (*connect.Response[pb.SetSecretResponse], error) {
70	scope, err := settingsScopeFromPB(req.Msg.GetScope())
71	if err != nil {
72		return nil, connectError(ctx, err)
73	}
74	sec, err := s.secrets.SetSecret(ctx, caller(ctx), scope, req.Msg.GetName(), req.Msg.GetValue())
75	if err != nil {
76		return nil, connectError(ctx, err)
77	}
78	return connect.NewResponse(&pb.SetSecretResponse{Secret: secretToPB(sec)}), nil
79}
80
81func (s *secretService) ListSecrets(ctx context.Context, req *connect.Request[pb.ListSecretsRequest]) (*connect.Response[pb.ListSecretsResponse], error) {
82	scope, err := settingsScopeFromPB(req.Msg.GetScope())
83	if err != nil {
84		return nil, connectError(ctx, err)
85	}
86	page, err := s.secrets.ListSecrets(ctx, caller(ctx), scope, int(req.Msg.GetPageSize()), req.Msg.GetPageToken())
87	if err != nil {
88		return nil, connectError(ctx, err)
89	}
90	out := &pb.ListSecretsResponse{NextPageToken: page.NextPageToken, Secrets: make([]*pb.Secret, 0, len(page.Secrets))}
91	for _, x := range page.Secrets {
92		out.Secrets = append(out.Secrets, secretToPB(x))
93	}
94	return connect.NewResponse(out), nil
95}
96
97func (s *secretService) DeleteSecret(ctx context.Context, req *connect.Request[pb.DeleteSecretRequest]) (*connect.Response[pb.DeleteSecretResponse], error) {
98	scope, err := settingsScopeFromPB(req.Msg.GetScope())
99	if err != nil {
100		return nil, connectError(ctx, err)
101	}
102	if err := s.secrets.DeleteSecret(ctx, caller(ctx), scope, req.Msg.GetName()); err != nil {
103		return nil, connectError(ctx, err)
104	}
105	return connect.NewResponse(&pb.DeleteSecretResponse{}), nil
106}
107
108// variableService implements ermiteconnect.VariableServiceHandler.
109type variableService struct{ vars ports.VariableService }
110
111func (s *variableService) SetVariable(ctx context.Context, req *connect.Request[pb.SetVariableRequest]) (*connect.Response[pb.SetVariableResponse], error) {
112	scope, err := settingsScopeFromPB(req.Msg.GetScope())
113	if err != nil {
114		return nil, connectError(ctx, err)
115	}
116	v, err := s.vars.SetVariable(ctx, caller(ctx), scope, req.Msg.GetName(), req.Msg.GetValue())
117	if err != nil {
118		return nil, connectError(ctx, err)
119	}
120	return connect.NewResponse(&pb.SetVariableResponse{Variable: variableToPB(v)}), nil
121}
122
123func (s *variableService) GetVariable(ctx context.Context, req *connect.Request[pb.GetVariableRequest]) (*connect.Response[pb.GetVariableResponse], error) {
124	scope, err := settingsScopeFromPB(req.Msg.GetScope())
125	if err != nil {
126		return nil, connectError(ctx, err)
127	}
128	v, err := s.vars.GetVariable(ctx, caller(ctx), scope, req.Msg.GetName())
129	if err != nil {
130		return nil, connectError(ctx, err)
131	}
132	return connect.NewResponse(&pb.GetVariableResponse{Variable: variableToPB(v)}), nil
133}
134
135func (s *variableService) ListVariables(ctx context.Context, req *connect.Request[pb.ListVariablesRequest]) (*connect.Response[pb.ListVariablesResponse], error) {
136	scope, err := settingsScopeFromPB(req.Msg.GetScope())
137	if err != nil {
138		return nil, connectError(ctx, err)
139	}
140	page, err := s.vars.ListVariables(ctx, caller(ctx), scope, int(req.Msg.GetPageSize()), req.Msg.GetPageToken())
141	if err != nil {
142		return nil, connectError(ctx, err)
143	}
144	out := &pb.ListVariablesResponse{NextPageToken: page.NextPageToken, Variables: make([]*pb.Variable, 0, len(page.Variables))}
145	for _, x := range page.Variables {
146		out.Variables = append(out.Variables, variableToPB(x))
147	}
148	return connect.NewResponse(out), nil
149}
150
151func (s *variableService) DeleteVariable(ctx context.Context, req *connect.Request[pb.DeleteVariableRequest]) (*connect.Response[pb.DeleteVariableResponse], error) {
152	scope, err := settingsScopeFromPB(req.Msg.GetScope())
153	if err != nil {
154		return nil, connectError(ctx, err)
155	}
156	if err := s.vars.DeleteVariable(ctx, caller(ctx), scope, req.Msg.GetName()); err != nil {
157		return nil, connectError(ctx, err)
158	}
159	return connect.NewResponse(&pb.DeleteVariableResponse{}), nil
160}
161
162// environmentService implements ermiteconnect.EnvironmentServiceHandler.
163type environmentService struct{ envs ports.EnvironmentService }
164
165func (s *environmentService) CreateEnvironment(ctx context.Context, req *connect.Request[pb.CreateEnvironmentRequest]) (*connect.Response[pb.CreateEnvironmentResponse], error) {
166	repo, err := repoOf(req.Msg.GetRepo())
167	if err != nil {
168		return nil, connectError(ctx, err)
169	}
170	e, err := s.envs.CreateEnvironment(ctx, caller(ctx), repo, req.Msg.GetName())
171	if err != nil {
172		return nil, connectError(ctx, err)
173	}
174	return connect.NewResponse(&pb.CreateEnvironmentResponse{Environment: environmentToPB(e)}), nil
175}
176
177func (s *environmentService) ListEnvironments(ctx context.Context, req *connect.Request[pb.ListEnvironmentsRequest]) (*connect.Response[pb.ListEnvironmentsResponse], error) {
178	repo, err := repoOf(req.Msg.GetRepo())
179	if err != nil {
180		return nil, connectError(ctx, err)
181	}
182	page, err := s.envs.ListEnvironments(ctx, caller(ctx), repo, int(req.Msg.GetPageSize()), req.Msg.GetPageToken())
183	if err != nil {
184		return nil, connectError(ctx, err)
185	}
186	out := &pb.ListEnvironmentsResponse{NextPageToken: page.NextPageToken, Environments: make([]*pb.Environment, 0, len(page.Environments))}
187	for _, x := range page.Environments {
188		out.Environments = append(out.Environments, environmentToPB(x))
189	}
190	return connect.NewResponse(out), nil
191}
192
193func (s *environmentService) DeleteEnvironment(ctx context.Context, req *connect.Request[pb.DeleteEnvironmentRequest]) (*connect.Response[pb.DeleteEnvironmentResponse], error) {
194	repo, err := repoOf(req.Msg.GetRepo())
195	if err != nil {
196		return nil, connectError(ctx, err)
197	}
198	if err := s.envs.DeleteEnvironment(ctx, caller(ctx), repo, req.Msg.GetName()); err != nil {
199		return nil, connectError(ctx, err)
200	}
201	return connect.NewResponse(&pb.DeleteEnvironmentResponse{}), nil
202}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close