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 | |||
| 142 | 142 | } | |
| 143 | 143 | ||
| 144 | 144 | 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: | |
| 147 | 147 | addStep("route", "configured") | |
| 148 | 148 | default: | |
| 149 | 149 | addStep("route", "failed") | |
| 150 | 150 | 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 | 169 | 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 { | |
| 157 | 172 | addStep("attachment:"+attachment, "created") | |
| 158 | - | case errors.Is(err, domain.ErrAttachmentExists): | |
| 173 | + | continue | |
| 174 | + | } | |
| 175 | + | if errors.Is(err, domain.ErrAttachmentExists) { | |
| 159 | 176 | addStep("attachment:"+attachment, "noop") | |
| 160 | - | default: | |
| 161 | - | addStep("attachment:"+attachment, "failed") | |
| 162 | - | return resp, err | |
| 177 | + | continue | |
| 163 | 178 | } | |
| 179 | + | addStep("attachment:"+attachment, "failed") | |
| 180 | + | return err | |
| 164 | 181 | } | |
| 165 | 182 | ||
| 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 | |
| 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 | 201 | for service, env := range req.AttachmentEnv { | |
| 175 | 202 | if err := l.secretSvc.SetAttachment(ctx, req.Domain, service, env); err != nil { | |
| 176 | 203 | addStep("attachment_env:"+service, "failed") | |
| 177 | - | return resp, err | |
| 204 | + | return err | |
| 178 | 205 | } | |
| 179 | 206 | addStep("attachment_env:"+service, "updated") | |
| 180 | 207 | } | |
| 181 | 208 | ||
| 182 | - | return resp, nil | |
| 209 | + | return nil | |
| 183 | 210 | } | |
| 184 | 211 | ||
| 185 | 212 | 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, err | 150 | 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, err | 186 | + | 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, err | 204 | + | return err |
| 178 | } | 205 | } | ||
| 179 | addStep("attachment_env:"+service, "updated") | 206 | addStep("attachment_env:"+service, "updated") | ||
| 180 | } | 207 | } | ||
| 181 | 208 | ||||
| 182 | - | return resp, nil | 209 | + | 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 { | |||
| 62 | 62 | if jsonOut { | |
| 63 | 63 | return writeJSON(out, []map[string]any{}) | |
| 64 | 64 | } | |
| 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>")) | |
| 70 | 75 | } | |
| 71 | 76 | ||
| 72 | 77 | names := make([]string, 0, len(remotes)) | |
| @@ -89,8 +94,12 @@ func runRemotesList(out io.Writer, jsonOut bool) error { | |||
| 89 | 94 | return writeJSON(out, payload) | |
| 90 | 95 | } | |
| 91 | 96 | ||
| 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 | + | } | |
| 94 | 103 | ||
| 95 | 104 | rows := make([][]string, 0, len(names)) | |
| 96 | 105 | 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 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>")) | |||
| 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 | |||
| 152 | 152 | } | |
| 153 | 153 | ||
| 154 | 154 | 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) | |
| 191 | 156 | } | |
| 192 | 157 | ||
| 193 | 158 | const imageColWidth = 35 | |
| @@ -252,6 +217,46 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut boo | |||
| 252 | 217 | 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 | 260 | // runRoutesListLocal lists routes from local configuration. | |
| 256 | 261 | func runRoutesListLocal(ctx context.Context, cfgPath string, jsonOut bool, out io.Writer) error { | |
| 257 | 262 | 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 = 35 | 158 | const imageColWidth = 35 | ||
| @@ -252,6 +217,46 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut boo | |||||
| 252 | return nil | 217 | 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 @@ | |||
| 2 | 2 | package admin | |
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | + | "context" | |
| 5 | 6 | "encoding/json" | |
| 6 | 7 | "errors" | |
| 7 | 8 | "fmt" | |
| @@ -311,12 +312,8 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) { | |||
| 311 | 312 | return | |
| 312 | 313 | } | |
| 313 | 314 | ||
| 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()) | |
| 320 | 317 | return | |
| 321 | 318 | } | |
| 322 | 319 | ||
| @@ -344,42 +341,80 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) { | |||
| 344 | 341 | return | |
| 345 | 342 | } | |
| 346 | 343 | ||
| 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) | |
| 349 | 379 | switch { | |
| 350 | 380 | case err == nil: | |
| 351 | - | addStep("attachment:"+attachment, "created") | |
| 381 | + | *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "created"}) | |
| 352 | 382 | case errors.Is(err, domain.ErrAttachmentExists): | |
| 353 | - | addStep("attachment:"+attachment, "noop") | |
| 383 | + | *steps = append(*steps, dto.BootstrapStep{Name: "attachment:" + attachment, Status: "noop"}) | |
| 354 | 384 | 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 | |
| 359 | 387 | } | |
| 360 | 388 | } | |
| 361 | 389 | ||
| 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 | |
| 370 | 396 | } | |
| 371 | 397 | ||
| 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 | |
| 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 | 420 | // sendJSON sends a JSON response. | |
| @@ -2,6 +2,7 @@ | |||||
| 2 | package admin | 2 | 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 | return | 312 | 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 | return | 317 | return | ||
| 321 | } | 318 | } | ||
| 322 | 319 | ||||
| @@ -344,42 +341,80 @@ func (h *Handler) handleBootstrap(w http.ResponseWriter, r *http.Request) { | |||||
| 344 | return | 341 | 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 | - | return | 395 | + | 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 | - | return | 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 | |||
| 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. | ||