detached 2889a53

fix: resolve golangci-lint errors

Fix errcheck in remotes.go (use cliWriteLine pattern). Reduce cyclomatic
complexity in handleBootstrap by extracting permission/attachment/secret
helpers. Reduce complexity in runRoutesListRemote by extracting JSON
helper. Fix staticcheck QF1002 tagged switch in controlplane_local.go.

Part of #98.
commit
2889a53b6818529d42f951e261b76b42a2cf508c
author
brice · · 2026-03-13 21:10 UTC
parent
7944c3c
signature
unsigned
4 files · +166 -90
M internal/adapters/in/cli/controlplane_local.go +44 -17
@@ -142,44 +142,71 @@ func (l *localControlPlane) Bootstrap(ctx context.Context, req dto.BootstrapRequ
142142 }
143143
144144 err := l.configSvc.AddRoute(ctx, domain.Route{Domain: req.Domain, Image: req.Image})
145- switch {
146- case err == nil:
145+ switch err {
146+ case nil:
147147 addStep("route", "configured")
148148 default:
149149 addStep("route", "failed")
150150 return resp, err
151151 }
152152
153+ if err := l.bootstrapAttachments(ctx, req, addStep); err != nil {
154+ return resp, err
155+ }
156+
157+ if err := l.bootstrapSecrets(ctx, req, addStep); err != nil {
158+ return resp, err
159+ }
160+
161+ if err := l.bootstrapAttachmentSecrets(ctx, req, addStep); err != nil {
162+ return resp, err
163+ }
164+
165+ return resp, nil
166+}
167+
168+func (l *localControlPlane) bootstrapAttachments(ctx context.Context, req dto.BootstrapRequest, addStep func(name, status string)) error {
153169 for _, attachment := range req.Attachments {
154- err = l.configSvc.AddAttachment(ctx, req.Domain, attachment)
155- switch {
156- case err == nil:
170+ err := l.configSvc.AddAttachment(ctx, req.Domain, attachment)
171+ if err == nil {
157172 addStep("attachment:"+attachment, "created")
158- case errors.Is(err, domain.ErrAttachmentExists):
173+ continue
174+ }
175+ if errors.Is(err, domain.ErrAttachmentExists) {
159176 addStep("attachment:"+attachment, "noop")
160- default:
161- addStep("attachment:"+attachment, "failed")
162- return resp, err
177+ continue
163178 }
179+ addStep("attachment:"+attachment, "failed")
180+ return err
164181 }
165182
166- if len(req.Env) > 0 {
167- if err := l.secretSvc.Set(ctx, req.Domain, req.Env); err != nil {
168- addStep("env", "failed")
169- return resp, err
170- }
171- addStep("env", "updated")
183+ return nil
184+}
185+
186+func (l *localControlPlane) bootstrapSecrets(ctx context.Context, req dto.BootstrapRequest, addStep func(name, status string)) error {
187+ if len(req.Env) == 0 {
188+ return nil
172189 }
173190
191+ if err := l.secretSvc.Set(ctx, req.Domain, req.Env); err != nil {
192+ addStep("env", "failed")
193+ return err
194+ }
195+
196+ addStep("env", "updated")
197+ return nil
198+}
199+
200+func (l *localControlPlane) bootstrapAttachmentSecrets(ctx context.Context, req dto.BootstrapRequest, addStep func(name, status string)) error {
174201 for service, env := range req.AttachmentEnv {
175202 if err := l.secretSvc.SetAttachment(ctx, req.Domain, service, env); err != nil {
176203 addStep("attachment_env:"+service, "failed")
177- return resp, err
204+ return err
178205 }
179206 addStep("attachment_env:"+service, "updated")
180207 }
181208
182- return resp, nil
209+ return nil
183210 }
184211
185212 func (l *localControlPlane) ListSecretsWithAttachments(ctx context.Context, secretDomain string) (*remote.SecretsListResult, error) {
@@ -142,44 +142,71 @@ func (l *localControlPlane) Bootstrap(ctx context.Context, req dto.BootstrapRequ
142 }142 }
143 143
144 err := l.configSvc.AddRoute(ctx, domain.Route{Domain: req.Domain, Image: req.Image})144 err := l.configSvc.AddRoute(ctx, domain.Route{Domain: req.Domain, Image: req.Image})
145- switch {145+ switch err {
146- case err == nil:146+ case nil:
147 addStep("route", "configured")147 addStep("route", "configured")
148 default:148 default:
149 addStep("route", "failed")149 addStep("route", "failed")
150 return resp, err150 return resp, err
151 }151 }
152 152
153+ if err := l.bootstrapAttachments(ctx, req, addStep); err != nil {
154+ return resp, err
155+ }
156+
157+ if err := l.bootstrapSecrets(ctx, req, addStep); err != nil {
158+ return resp, err
159+ }
160+
161+ if err := l.bootstrapAttachmentSecrets(ctx, req, addStep); err != nil {
162+ return resp, err
163+ }
164+
165+ return resp, nil
166+}
167+
168+func (l *localControlPlane) bootstrapAttachments(ctx context.Context, req dto.BootstrapRequest, addStep func(name, status string)) error {
153 for _, attachment := range req.Attachments {169 for _, attachment := range req.Attachments {
154- err = l.configSvc.AddAttachment(ctx, req.Domain, attachment)170+ err := l.configSvc.AddAttachment(ctx, req.Domain, attachment)
155- switch {171+ if err == nil {
156- case err == nil:
157 addStep("attachment:"+attachment, "created")172 addStep("attachment:"+attachment, "created")
158- case errors.Is(err, domain.ErrAttachmentExists):173+ continue
174+ }
175+ if errors.Is(err, domain.ErrAttachmentExists) {
159 addStep("attachment:"+attachment, "noop")176 addStep("attachment:"+attachment, "noop")
160- default:177+ continue
161- addStep("attachment:"+attachment, "failed")
162- return resp, err
163 }178 }
179+ addStep("attachment:"+attachment, "failed")
180+ return err
164 }181 }
165 182
166- if len(req.Env) > 0 {183+ return nil
167- if err := l.secretSvc.Set(ctx, req.Domain, req.Env); err != nil {184+}
168- addStep("env", "failed")185+
169- return resp, err186+func (l *localControlPlane) bootstrapSecrets(ctx context.Context, req dto.BootstrapRequest, addStep func(name, status string)) error {
170- }187+ if len(req.Env) == 0 {
171- addStep("env", "updated")188+ return nil
172 }189 }
173 190
191+ if err := l.secretSvc.Set(ctx, req.Domain, req.Env); err != nil {
192+ addStep("env", "failed")
193+ return err
194+ }
195+
196+ addStep("env", "updated")
197+ return nil
198+}
199+
200+func (l *localControlPlane) bootstrapAttachmentSecrets(ctx context.Context, req dto.BootstrapRequest, addStep func(name, status string)) error {
174 for service, env := range req.AttachmentEnv {201 for service, env := range req.AttachmentEnv {
175 if err := l.secretSvc.SetAttachment(ctx, req.Domain, service, env); err != nil {202 if err := l.secretSvc.SetAttachment(ctx, req.Domain, service, env); err != nil {
176 addStep("attachment_env:"+service, "failed")203 addStep("attachment_env:"+service, "failed")
177- return resp, err204+ return err
178 }205 }
179 addStep("attachment_env:"+service, "updated")206 addStep("attachment_env:"+service, "updated")
180 }207 }
181 208
182- return resp, nil209+ return nil
183 }210 }
184 211
185 func (l *localControlPlane) ListSecretsWithAttachments(ctx context.Context, secretDomain string) (*remote.SecretsListResult, error) {212 func (l *localControlPlane) ListSecretsWithAttachments(ctx context.Context, secretDomain string) (*remote.SecretsListResult, error) {
M internal/adapters/in/cli/remotes.go +16 -7
@@ -62,11 +62,16 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
6262 if jsonOut {
6363 return writeJSON(out, []map[string]any{})
6464 }
65- fmt.Fprintln(out, styles.Theme.Muted.Render("No remotes configured"))
66- fmt.Fprintln(out)
67- fmt.Fprintln(out, "Add a remote with:")
68- fmt.Fprintln(out, styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))
69- return nil
65+ if err := cliWriteLine(out, cliRenderMuted("No remotes configured")); err != nil {
66+ return err
67+ }
68+ if err := cliWriteLine(out, ""); err != nil {
69+ return err
70+ }
71+ if err := cliWriteLine(out, "Add a remote with:"); err != nil {
72+ return err
73+ }
74+ return cliWriteLine(out, styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))
7075 }
7176
7277 names := make([]string, 0, len(remotes))
@@ -89,8 +94,12 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
8994 return writeJSON(out, payload)
9095 }
9196
92- fmt.Fprintln(out, styles.Theme.Title.Render("Saved Remotes"))
93- fmt.Fprintln(out)
97+ if err := cliWriteLine(out, cliRenderTitle("Saved Remotes")); err != nil {
98+ return err
99+ }
100+ if err := cliWriteLine(out, ""); err != nil {
101+ return err
102+ }
94103
95104 rows := make([][]string, 0, len(names))
96105 for _, name := range names {
@@ -62,11 +62,16 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
62 if jsonOut {62 if jsonOut {
63 return writeJSON(out, []map[string]any{})63 return writeJSON(out, []map[string]any{})
64 }64 }
65- fmt.Fprintln(out, styles.Theme.Muted.Render("No remotes configured"))65+ if err := cliWriteLine(out, cliRenderMuted("No remotes configured")); err != nil {
66- fmt.Fprintln(out)66+ return err
67- fmt.Fprintln(out, "Add a remote with:")67+ }
68- fmt.Fprintln(out, styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))68+ if err := cliWriteLine(out, ""); err != nil {
69- return nil69+ return err
70+ }
71+ if err := cliWriteLine(out, "Add a remote with:"); err != nil {
72+ return err
73+ }
74+ return cliWriteLine(out, styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))
70 }75 }
71 76
72 names := make([]string, 0, len(remotes))77 names := make([]string, 0, len(remotes))
@@ -89,8 +94,12 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
89 return writeJSON(out, payload)94 return writeJSON(out, payload)
90 }95 }
91 96
92- fmt.Fprintln(out, styles.Theme.Title.Render("Saved Remotes"))97+ if err := cliWriteLine(out, cliRenderTitle("Saved Remotes")); err != nil {
93- fmt.Fprintln(out)98+ return err
99+ }
100+ if err := cliWriteLine(out, ""); err != nil {
101+ return err
102+ }
94 103
95 rows := make([][]string, 0, len(names))104 rows := make([][]string, 0, len(names))
96 for _, name := range names {105 for _, name := range names {
M internal/adapters/in/cli/routes.go +41 -36
@@ -152,42 +152,7 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut boo
152152 }
153153
154154 if jsonOut {
155- payload := make([]map[string]any, 0, len(routes))
156- for _, route := range routes {
157- routeHealth := health[route.Domain]
158- containerStatus := route.ContainerStatus
159- if containerStatus == "" {
160- if routeHealth != nil {
161- containerStatus = routeHealth.ContainerStatus
162- } else {
163- containerStatus = "unknown"
164- }
165- }
166-
167- httpStatus := 0
168- if routeHealth != nil {
169- httpStatus = routeHealth.HTTPStatus
170- }
171-
172- attachments := make([]map[string]string, 0, len(route.Attachments))
173- for _, attachment := range route.Attachments {
174- attachments = append(attachments, map[string]string{
175- "name": attachment.Name,
176- "image": attachment.Image,
177- "status": attachment.Status,
178- })
179- }
180-
181- payload = append(payload, map[string]any{
182- "domain": route.Domain,
183- "image": route.Image,
184- "container_status": containerStatus,
185- "network": route.Network,
186- "http_status": httpStatus,
187- "attachments": attachments,
188- })
189- }
190- return writeJSON(out, payload)
155+ return routesListJSON(out, routes, health)
191156 }
192157
193158 const imageColWidth = 35
@@ -252,6 +217,46 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut boo
252217 return nil
253218 }
254219
220+func routesListJSON(out io.Writer, routes []remote.RouteInfo, health map[string]*remote.RouteHealth) error {
221+ payload := make([]map[string]any, 0, len(routes))
222+ for _, route := range routes {
223+ routeHealth := health[route.Domain]
224+ containerStatus := route.ContainerStatus
225+ if containerStatus == "" {
226+ if routeHealth != nil {
227+ containerStatus = routeHealth.ContainerStatus
228+ } else {
229+ containerStatus = "unknown"
230+ }
231+ }
232+
233+ httpStatus := 0
234+ if routeHealth != nil {
235+ httpStatus = routeHealth.HTTPStatus
236+ }
237+
238+ attachments := make([]map[string]string, 0, len(route.Attachments))
239+ for _, attachment := range route.Attachments {
240+ attachments = append(attachments, map[string]string{
241+ "name": attachment.Name,
242+ "image": attachment.Image,
243+ "status": attachment.Status,
244+ })
245+ }
246+
247+ payload = append(payload, map[string]any{
248+ "domain": route.Domain,
249+ "image": route.Image,
250+ "container_status": containerStatus,
251+ "network": route.Network,
252+ "http_status": httpStatus,
253+ "attachments": attachments,
254+ })
255+ }
256+
257+ return writeJSON(out, payload)
258+}
259+
255260 // runRoutesListLocal lists routes from local configuration.
256261 func runRoutesListLocal(ctx context.Context, cfgPath string, jsonOut bool, out io.Writer) error {
257262 local, err := GetLocalServices(cfgPath)
@@ -152,42 +152,7 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut boo
152 }152 }
153 153
154 if jsonOut {154 if jsonOut {
155- payload := make([]map[string]any, 0, len(routes))155+ return routesListJSON(out, routes, health)
156- for _, route := range routes {
157- routeHealth := health[route.Domain]
158- containerStatus := route.ContainerStatus
159- if containerStatus == "" {
160- if routeHealth != nil {
161- containerStatus = routeHealth.ContainerStatus
162- } else {
163- containerStatus = "unknown"
164- }
165- }
166-
167- httpStatus := 0
168- if routeHealth != nil {
169- httpStatus = routeHealth.HTTPStatus
170- }
171-
172- attachments := make([]map[string]string, 0, len(route.Attachments))
173- for _, attachment := range route.Attachments {
174- attachments = append(attachments, map[string]string{
175- "name": attachment.Name,
176- "image": attachment.Image,
177- "status": attachment.Status,
178- })
179- }
180-
181- payload = append(payload, map[string]any{
182- "domain": route.Domain,
183- "image": route.Image,
184- "container_status": containerStatus,
185- "network": route.Network,
186- "http_status": httpStatus,
187- "attachments": attachments,
188- })
189- }
190- return writeJSON(out, payload)
191 }156 }
192 157
193 const imageColWidth = 35158 const imageColWidth = 35
@@ -252,6 +217,46 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut boo
252 return nil217 return nil
253 }218 }
254 219
220+func routesListJSON(out io.Writer, routes []remote.RouteInfo, health map[string]*remote.RouteHealth) error {
221+ payload := make([]map[string]any, 0, len(routes))
222+ for _, route := range routes {
223+ routeHealth := health[route.Domain]
224+ containerStatus := route.ContainerStatus
225+ if containerStatus == "" {
226+ if routeHealth != nil {
227+ containerStatus = routeHealth.ContainerStatus
228+ } else {
229+ containerStatus = "unknown"
230+ }
231+ }
232+
233+ httpStatus := 0
234+ if routeHealth != nil {
235+ httpStatus = routeHealth.HTTPStatus
236+ }
237+
238+ attachments := make([]map[string]string, 0, len(route.Attachments))
239+ for _, attachment := range route.Attachments {
240+ attachments = append(attachments, map[string]string{
241+ "name": attachment.Name,
242+ "image": attachment.Image,
243+ "status": attachment.Status,
244+ })
245+ }
246+
247+ payload = append(payload, map[string]any{
248+ "domain": route.Domain,
249+ "image": route.Image,
250+ "container_status": containerStatus,
251+ "network": route.Network,
252+ "http_status": httpStatus,
253+ "attachments": attachments,
254+ })
255+ }
256+
257+ return writeJSON(out, payload)
258+}
259+
255 // runRoutesListLocal lists routes from local configuration.260 // runRoutesListLocal lists routes from local configuration.
256 func runRoutesListLocal(ctx context.Context, cfgPath string, jsonOut bool, out io.Writer) error {261 func runRoutesListLocal(ctx context.Context, cfgPath string, jsonOut bool, out io.Writer) error {
257 local, err := GetLocalServices(cfgPath)262 local, err := GetLocalServices(cfgPath)
M internal/adapters/in/http/admin/handler.go +65 -30
@@ -2,6 +2,7 @@
22 package admin
33
44 import (
5+ "context"
56 "encoding/json"
67 "errors"
78 "fmt"
@@ -311,12 +312,8 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) {
311312 return
312313 }
313314
314- if (len(req.Env) > 0 || len(req.AttachmentEnv) > 0) && !HasAccess(ctx, domain.AdminResourceSecrets, domain.AdminActionWrite) {
315- h.sendError(w, http.StatusForbidden, "insufficient permissions for secrets:write")
316- return
317- }
318- if len(req.Attachments) > 0 && !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) {
319- h.sendError(w, http.StatusForbidden, "insufficient permissions for config:write")
315+ if err := h.validateBootstrapPermissions(ctx, req); err != nil {
316+ h.sendError(w, http.StatusForbidden, err.Error())
320317 return
321318 }
322319
@@ -344,42 +341,80 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) {
344341 return
345342 }
346343
347- for _, attachment := range req.Attachments {
348- err = h.configSvc.AddAttachment(ctx, req.Domain, attachment)
344+ if err := h.bootstrapAttachments(ctx, h.configSvc, req.Domain, req.Attachments, &resp.Steps); err != nil {
345+ log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap attachments")
346+ h.sendJSON(w, http.StatusInternalServerError, resp)
347+ return
348+ }
349+
350+ if err := h.bootstrapSecrets(ctx, h.secretSvc, req.Domain, req.Env, &resp.Steps); err != nil {
351+ log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap env")
352+ h.sendJSON(w, http.StatusInternalServerError, resp)
353+ return
354+ }
355+
356+ if err := h.bootstrapAttachmentSecrets(ctx, h.secretSvc, req.Domain, req.AttachmentEnv, &resp.Steps); err != nil {
357+ log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap attachment env")
358+ h.sendJSON(w, http.StatusInternalServerError, resp)
359+ return
360+ }
361+
362+ h.sendJSON(w, http.StatusOK, resp)
363+}
364+
365+func (h *Handler) validateBootstrapPermissions(ctx context.Context, req dto.BootstrapRequest) error {
366+ if (len(req.Env) > 0 || len(req.AttachmentEnv) > 0) && !HasAccess(ctx, domain.AdminResourceSecrets, domain.AdminActionWrite) {
367+ return errors.New("insufficient permissions for secrets:write")
368+ }
369+ if len(req.Attachments) > 0 && !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) {
370+ return errors.New("insufficient permissions for config:write")
371+ }
372+
373+ return nil
374+}
375+
376+func (h *Handler) bootstrapAttachments(ctx context.Context, configSvc in.ConfigService, domainName string, attachments []string, steps *[]dto.BootstrapStep) error {
377+ for _, attachment := range attachments {
378+ err := configSvc.AddAttachment(ctx, domainName, attachment)
349379 switch {
350380 case err == nil:
351- addStep("attachment:"+attachment, "created")
381+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "created"})
352382 case errors.Is(err, domain.ErrAttachmentExists):
353- addStep("attachment:"+attachment, "noop")
383+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "noop"})
354384 default:
355- addStep("attachment:"+attachment, "failed")
356- log.Error().Err(err).Str("domain", req.Domain).Str("image", attachment).Msg("failed to bootstrap attachment")
357- h.sendJSON(w, http.StatusInternalServerError, resp)
358- return
385+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "failed"})
386+ return err
359387 }
360388 }
361389
362- if len(req.Env) > 0 {
363- if err := h.secretSvc.Set(ctx, req.Domain, req.Env); err != nil {
364- addStep("env", "failed")
365- log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap env")
366- h.sendJSON(w, http.StatusInternalServerError, resp)
367- return
368- }
369- addStep("env", "updated")
390+ return nil
391+}
392+
393+func (h *Handler) bootstrapSecrets(ctx context.Context, secretSvc in.SecretService, domainName string, env map[string]string, steps *[]dto.BootstrapStep) error {
394+ if len(env) == 0 {
395+ return nil
370396 }
371397
372- for service, env := range req.AttachmentEnv {
373- if err := h.secretSvc.SetAttachment(ctx, req.Domain, service, env); err != nil {
374- addStep("attachment_env:"+service, "failed")
375- log.Error().Err(err).Str("domain", req.Domain).Str("service", service).Msg("failed to bootstrap attachment env")
376- h.sendJSON(w, http.StatusInternalServerError, resp)
377- return
398+ if err := secretSvc.Set(ctx, domainName, env); err != nil {
399+ *steps = append(*steps, dto.BootstrapStep{Name: "env", Status: "failed"})
400+ return err
401+ }
402+
403+ *steps = append(*steps, dto.BootstrapStep{Name: "env", Status: "updated"})
404+ return nil
405+}
406+
407+func (h *Handler) bootstrapAttachmentSecrets(ctx context.Context, secretSvc in.SecretService, domainName string, attachmentEnv map[string]map[string]string, steps *[]dto.BootstrapStep) error {
408+ for service, env := range attachmentEnv {
409+ if err := secretSvc.SetAttachment(ctx, domainName, service, env); err != nil {
410+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment_env:" + service, Status: "failed"})
411+ return err
378412 }
379- addStep("attachment_env:"+service, "updated")
413+
414+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment_env:" + service, Status: "updated"})
380415 }
381416
382- h.sendJSON(w, http.StatusOK, resp)
417+ return nil
383418 }
384419
385420 // sendJSON sends a JSON response.
@@ -2,6 +2,7 @@
2 package admin2 package admin
3 3
4 import (4 import (
5+ "context"
5 "encoding/json"6 "encoding/json"
6 "errors"7 "errors"
7 "fmt"8 "fmt"
@@ -311,12 +312,8 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) {
311 return312 return
312 }313 }
313 314
314- if (len(req.Env) > 0 || len(req.AttachmentEnv) > 0) && !HasAccess(ctx, domain.AdminResourceSecrets, domain.AdminActionWrite) {315+ if err := h.validateBootstrapPermissions(ctx, req); err != nil {
315- h.sendError(w, http.StatusForbidden, "insufficient permissions for secrets:write")316+ h.sendError(w, http.StatusForbidden, err.Error())
316- return
317- }
318- if len(req.Attachments) > 0 && !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) {
319- h.sendError(w, http.StatusForbidden, "insufficient permissions for config:write")
320 return317 return
321 }318 }
322 319
@@ -344,42 +341,80 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) {
344 return341 return
345 }342 }
346 343
347- for _, attachment := range req.Attachments {344+ if err := h.bootstrapAttachments(ctx, h.configSvc, req.Domain, req.Attachments, &resp.Steps); err != nil {
348- err = h.configSvc.AddAttachment(ctx, req.Domain, attachment)345+ log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap attachments")
346+ h.sendJSON(w, http.StatusInternalServerError, resp)
347+ return
348+ }
349+
350+ if err := h.bootstrapSecrets(ctx, h.secretSvc, req.Domain, req.Env, &resp.Steps); err != nil {
351+ log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap env")
352+ h.sendJSON(w, http.StatusInternalServerError, resp)
353+ return
354+ }
355+
356+ if err := h.bootstrapAttachmentSecrets(ctx, h.secretSvc, req.Domain, req.AttachmentEnv, &resp.Steps); err != nil {
357+ log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap attachment env")
358+ h.sendJSON(w, http.StatusInternalServerError, resp)
359+ return
360+ }
361+
362+ h.sendJSON(w, http.StatusOK, resp)
363+}
364+
365+func (h *Handler) validateBootstrapPermissions(ctx context.Context, req dto.BootstrapRequest) error {
366+ if (len(req.Env) > 0 || len(req.AttachmentEnv) > 0) && !HasAccess(ctx, domain.AdminResourceSecrets, domain.AdminActionWrite) {
367+ return errors.New("insufficient permissions for secrets:write")
368+ }
369+ if len(req.Attachments) > 0 && !HasAccess(ctx, domain.AdminResourceConfig, domain.AdminActionWrite) {
370+ return errors.New("insufficient permissions for config:write")
371+ }
372+
373+ return nil
374+}
375+
376+func (h *Handler) bootstrapAttachments(ctx context.Context, configSvc in.ConfigService, domainName string, attachments []string, steps *[]dto.BootstrapStep) error {
377+ for _, attachment := range attachments {
378+ err := configSvc.AddAttachment(ctx, domainName, attachment)
349 switch {379 switch {
350 case err == nil:380 case err == nil:
351- addStep("attachment:"+attachment, "created")381+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "created"})
352 case errors.Is(err, domain.ErrAttachmentExists):382 case errors.Is(err, domain.ErrAttachmentExists):
353- addStep("attachment:"+attachment, "noop")383+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "noop"})
354 default:384 default:
355- addStep("attachment:"+attachment, "failed")385+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "failed"})
356- log.Error().Err(err).Str("domain", req.Domain).Str("image", attachment).Msg("failed to bootstrap attachment")386+ return err
357- h.sendJSON(w, http.StatusInternalServerError, resp)
358- return
359 }387 }
360 }388 }
361 389
362- if len(req.Env) > 0 {390+ return nil
363- if err := h.secretSvc.Set(ctx, req.Domain, req.Env); err != nil {391+}
364- addStep("env", "failed")392+
365- log.Error().Err(err).Str("domain", req.Domain).Msg("failed to bootstrap env")393+func (h *Handler) bootstrapSecrets(ctx context.Context, secretSvc in.SecretService, domainName string, env map[string]string, steps *[]dto.BootstrapStep) error {
366- h.sendJSON(w, http.StatusInternalServerError, resp)394+ if len(env) == 0 {
367- return395+ return nil
368- }
369- addStep("env", "updated")
370 }396 }
371 397
372- for service, env := range req.AttachmentEnv {398+ if err := secretSvc.Set(ctx, domainName, env); err != nil {
373- if err := h.secretSvc.SetAttachment(ctx, req.Domain, service, env); err != nil {399+ *steps = append(*steps, dto.BootstrapStep{Name: "env", Status: "failed"})
374- addStep("attachment_env:"+service, "failed")400+ return err
375- log.Error().Err(err).Str("domain", req.Domain).Str("service", service).Msg("failed to bootstrap attachment env")401+ }
376- h.sendJSON(w, http.StatusInternalServerError, resp)402+
377- return403+ *steps = append(*steps, dto.BootstrapStep{Name: "env", Status: "updated"})
404+ return nil
405+}
406+
407+func (h *Handler) bootstrapAttachmentSecrets(ctx context.Context, secretSvc in.SecretService, domainName string, attachmentEnv map[string]map[string]string, steps *[]dto.BootstrapStep) error {
408+ for service, env := range attachmentEnv {
409+ if err := secretSvc.SetAttachment(ctx, domainName, service, env); err != nil {
410+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment_env:" + service, Status: "failed"})
411+ return err
378 }412 }
379- addStep("attachment_env:"+service, "updated")413+
414+ *steps = append(*steps, dto.BootstrapStep{Name: "attachment_env:" + service, Status: "updated"})
380 }415 }
381 416
382- h.sendJSON(w, http.StatusOK, resp)417+ return nil
383 }418 }
384 419
385 // sendJSON sends a JSON response.420 // sendJSON sends a JSON response.

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 7

Tags 106

↑↓ moveenter openesc close