fix: final cleanup from coderabbit review
Remove 'It' sentence starts in bootstrap docs. Add insecure_tls to remotes JSON output. Normalize nil attachments in secrets JSON. Remove misplaced cliRenderTitle in backup. Use cmd.Context() in bootstrap command. Part of #98.
- commit
- 7944c3c8902e10f475a2db31e702b600323be1a3
- author
- brice · · 2026-03-13 21:04 UTC
- parent
- 952bc8b
- signature
- unsigned
6 files · +48 -18
M docs/cli/bootstrap.md +4 -4
| @@ -30,10 +30,10 @@ gordon bootstrap <domain> <image> [options] | |||
| 30 | 30 | ||
| 31 | 31 | `gordon bootstrap` is the recommended first-step setup workflow. | |
| 32 | 32 | ||
| 33 | - | - It creates the route when it does not exist. | |
| 34 | - | - It updates the route when it already exists. | |
| 35 | - | - It can attach services and set environment variables as part of the same command. | |
| 36 | - | - It does not push or deploy the image. | |
| 33 | + | - Creates the route when it does not exist. | |
| 34 | + | - Updates the route when it already exists. | |
| 35 | + | - Can attach services and set environment variables as part of the same command. | |
| 36 | + | - Does not push or deploy the image. | |
| 37 | 37 | ||
| 38 | 38 | Unlike `gordon push`, `gordon bootstrap` does not require the route to exist first. Run `gordon push` separately after bootstrap to upload and deploy an image. | |
| 39 | 39 | ||
| @@ -30,10 +30,10 @@ gordon bootstrap <domain> <image> [options] | |||||
| 30 | 30 | ||||
| 31 | `gordon bootstrap` is the recommended first-step setup workflow. | 31 | `gordon bootstrap` is the recommended first-step setup workflow. | ||
| 32 | 32 | ||||
| 33 | - | - It creates the route when it does not exist. | 33 | + | - Creates the route when it does not exist. |
| 34 | - | - It updates the route when it already exists. | 34 | + | - Updates the route when it already exists. |
| 35 | - | - It can attach services and set environment variables as part of the same command. | 35 | + | - Can attach services and set environment variables as part of the same command. |
| 36 | - | - It does not push or deploy the image. | 36 | + | - Does not push or deploy the image. |
| 37 | 37 | ||||
| 38 | Unlike `gordon push`, `gordon bootstrap` does not require the route to exist first. Run `gordon push` separately after bootstrap to upload and deploy an image. | 38 | Unlike `gordon push`, `gordon bootstrap` does not require the route to exist first. Run `gordon push` separately after bootstrap to upload and deploy an image. | ||
| 39 | 39 | ||||
M internal/adapters/in/cli/backup.go +1 -2
| @@ -34,11 +34,10 @@ instance when --remote targeting is configured.`, | |||
| 34 | 34 | func newBackupListCmd() *cobra.Command { | |
| 35 | 35 | var jsonOut bool | |
| 36 | 36 | ||
| 37 | - | cliRenderTitle("Backups") | |
| 38 | - | ||
| 39 | 37 | cmd := &cobra.Command{ | |
| 40 | 38 | Use: "list [domain]", | |
| 41 | 39 | Short: "List backups", | |
| 40 | + | Long: cliRenderMuted("List backups for all domains or a specific domain."), | |
| 42 | 41 | Args: cobra.MaximumNArgs(1), | |
| 43 | 42 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 44 | 43 | handle, err := resolveControlPlane(configPath) | |
| @@ -34,11 +34,10 @@ instance when --remote targeting is configured.`, | |||||
| 34 | func newBackupListCmd() *cobra.Command { | 34 | func newBackupListCmd() *cobra.Command { | ||
| 35 | var jsonOut bool | 35 | var jsonOut bool | ||
| 36 | 36 | ||||
| 37 | - | cliRenderTitle("Backups") | |||
| 38 | - | ||||
| 39 | cmd := &cobra.Command{ | 37 | cmd := &cobra.Command{ | ||
| 40 | Use: "list [domain]", | 38 | Use: "list [domain]", | ||
| 41 | Short: "List backups", | 39 | Short: "List backups", | ||
| 40 | + | Long: cliRenderMuted("List backups for all domains or a specific domain."), | |||
| 42 | Args: cobra.MaximumNArgs(1), | 41 | Args: cobra.MaximumNArgs(1), | ||
| 43 | RunE: func(cmd *cobra.Command, args []string) error { | 42 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 44 | handle, err := resolveControlPlane(configPath) | 43 | handle, err := resolveControlPlane(configPath) | ||
M internal/adapters/in/cli/bootstrap.go +1 -2
| @@ -1,7 +1,6 @@ | |||
| 1 | 1 | package cli | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "context" | |
| 5 | 4 | "fmt" | |
| 6 | 5 | "os" | |
| 7 | 6 | "sort" | |
| @@ -23,7 +22,7 @@ func newBootstrapCmd() *cobra.Command { | |||
| 23 | 22 | Short: "Create a route, attachments, and secrets together", | |
| 24 | 23 | Args: cobra.ExactArgs(2), | |
| 25 | 24 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 26 | - | ctx := context.Background() | |
| 25 | + | ctx := cmd.Context() | |
| 27 | 26 | req := dto.BootstrapRequest{ | |
| 28 | 27 | Domain: args[0], | |
| 29 | 28 | Image: args[1], | |
| @@ -1,7 +1,6 @@ | |||||
| 1 | package cli | 1 | package cli | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "context" | |||
| 5 | "fmt" | 4 | "fmt" | ||
| 6 | "os" | 5 | "os" | ||
| 7 | "sort" | 6 | "sort" | ||
| @@ -23,7 +22,7 @@ func newBootstrapCmd() *cobra.Command { | |||||
| 23 | Short: "Create a route, attachments, and secrets together", | 22 | Short: "Create a route, attachments, and secrets together", | ||
| 24 | Args: cobra.ExactArgs(2), | 23 | Args: cobra.ExactArgs(2), | ||
| 25 | RunE: func(cmd *cobra.Command, args []string) error { | 24 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 26 | - | ctx := context.Background() | 25 | + | ctx := cmd.Context() |
| 27 | req := dto.BootstrapRequest{ | 26 | req := dto.BootstrapRequest{ | ||
| 28 | Domain: args[0], | 27 | Domain: args[0], | ||
| 29 | Image: args[1], | 28 | Image: args[1], | ||
M internal/adapters/in/cli/json_test.go +35 -7
| @@ -147,26 +147,29 @@ func TestTokenList_JSONShape_RoundTripsTokens(t *testing.T) { | |||
| 147 | 147 | ||
| 148 | 148 | func TestRemotesList_JSONShape_RoundTripsRemoteObjects(t *testing.T) { | |
| 149 | 149 | items := []struct { | |
| 150 | - | Name string `json:"name"` | |
| 151 | - | URL string `json:"url"` | |
| 152 | - | Active bool `json:"active"` | |
| 150 | + | Name string `json:"name"` | |
| 151 | + | URL string `json:"url"` | |
| 152 | + | Active bool `json:"active"` | |
| 153 | + | InsecureTLS bool `json:"insecure_tls"` | |
| 153 | 154 | }{ | |
| 154 | - | {Name: "prod", URL: "https://prod.example.com", Active: true}, | |
| 155 | + | {Name: "prod", URL: "https://prod.example.com", Active: true, InsecureTLS: true}, | |
| 155 | 156 | } | |
| 156 | 157 | ||
| 157 | 158 | payload, err := json.Marshal(items) | |
| 158 | 159 | require.NoError(t, err) | |
| 159 | 160 | ||
| 160 | 161 | var got []struct { | |
| 161 | - | Name string `json:"name"` | |
| 162 | - | URL string `json:"url"` | |
| 163 | - | Active bool `json:"active"` | |
| 162 | + | Name string `json:"name"` | |
| 163 | + | URL string `json:"url"` | |
| 164 | + | Active bool `json:"active"` | |
| 165 | + | InsecureTLS bool `json:"insecure_tls"` | |
| 164 | 166 | } | |
| 165 | 167 | require.NoError(t, json.Unmarshal(payload, &got)) | |
| 166 | 168 | require.Len(t, got, 1) | |
| 167 | 169 | assert.Equal(t, "prod", got[0].Name) | |
| 168 | 170 | assert.Equal(t, "https://prod.example.com", got[0].URL) | |
| 169 | 171 | assert.True(t, got[0].Active) | |
| 172 | + | assert.True(t, got[0].InsecureTLS) | |
| 170 | 173 | } | |
| 171 | 174 | ||
| 172 | 175 | func TestAttachmentsList_JSONShape_RoundTripsTargetPayload(t *testing.T) { | |
| @@ -218,6 +221,31 @@ func TestSecretsList_JSONShape_RoundTripsPayload(t *testing.T) { | |||
| 218 | 221 | assert.Equal(t, payload.Attachments, got.Attachments) | |
| 219 | 222 | } | |
| 220 | 223 | ||
| 224 | + | func TestSecretsList_JSONShape_NormalizesNilAttachmentsToEmptySlice(t *testing.T) { | |
| 225 | + | payload := struct { | |
| 226 | + | Domain string `json:"domain"` | |
| 227 | + | Keys []string `json:"keys"` | |
| 228 | + | Attachments []remote.AttachmentSecrets `json:"attachments"` | |
| 229 | + | }{ | |
| 230 | + | Domain: "app.example.com", | |
| 231 | + | Keys: []string{"API_KEY"}, | |
| 232 | + | Attachments: []remote.AttachmentSecrets{}, | |
| 233 | + | } | |
| 234 | + | ||
| 235 | + | encoded, err := json.Marshal(payload) | |
| 236 | + | require.NoError(t, err) | |
| 237 | + | assert.Contains(t, string(encoded), `"attachments":[]`) | |
| 238 | + | ||
| 239 | + | var got struct { | |
| 240 | + | Domain string `json:"domain"` | |
| 241 | + | Keys []string `json:"keys"` | |
| 242 | + | Attachments []remote.AttachmentSecrets `json:"attachments"` | |
| 243 | + | } | |
| 244 | + | require.NoError(t, json.Unmarshal(encoded, &got)) | |
| 245 | + | assert.NotNil(t, got.Attachments) | |
| 246 | + | assert.Empty(t, got.Attachments) | |
| 247 | + | } | |
| 248 | + | ||
| 221 | 249 | func TestRoutesList_JSONShape_RoundTripsLocalPayload(t *testing.T) { | |
| 222 | 250 | payload := []struct { | |
| 223 | 251 | Domain string `json:"domain"` | |
| @@ -147,26 +147,29 @@ func TestTokenList_JSONShape_RoundTripsTokens(t *testing.T) { | |||||
| 147 | 147 | ||||
| 148 | func TestRemotesList_JSONShape_RoundTripsRemoteObjects(t *testing.T) { | 148 | func TestRemotesList_JSONShape_RoundTripsRemoteObjects(t *testing.T) { | ||
| 149 | items := []struct { | 149 | items := []struct { | ||
| 150 | - | Name string `json:"name"` | 150 | + | Name string `json:"name"` |
| 151 | - | URL string `json:"url"` | 151 | + | URL string `json:"url"` |
| 152 | - | Active bool `json:"active"` | 152 | + | Active bool `json:"active"` |
| 153 | + | InsecureTLS bool `json:"insecure_tls"` | |||
| 153 | }{ | 154 | }{ | ||
| 154 | - | {Name: "prod", URL: "https://prod.example.com", Active: true}, | 155 | + | {Name: "prod", URL: "https://prod.example.com", Active: true, InsecureTLS: true}, |
| 155 | } | 156 | } | ||
| 156 | 157 | ||||
| 157 | payload, err := json.Marshal(items) | 158 | payload, err := json.Marshal(items) | ||
| 158 | require.NoError(t, err) | 159 | require.NoError(t, err) | ||
| 159 | 160 | ||||
| 160 | var got []struct { | 161 | var got []struct { | ||
| 161 | - | Name string `json:"name"` | 162 | + | Name string `json:"name"` |
| 162 | - | URL string `json:"url"` | 163 | + | URL string `json:"url"` |
| 163 | - | Active bool `json:"active"` | 164 | + | Active bool `json:"active"` |
| 165 | + | InsecureTLS bool `json:"insecure_tls"` | |||
| 164 | } | 166 | } | ||
| 165 | require.NoError(t, json.Unmarshal(payload, &got)) | 167 | require.NoError(t, json.Unmarshal(payload, &got)) | ||
| 166 | require.Len(t, got, 1) | 168 | require.Len(t, got, 1) | ||
| 167 | assert.Equal(t, "prod", got[0].Name) | 169 | assert.Equal(t, "prod", got[0].Name) | ||
| 168 | assert.Equal(t, "https://prod.example.com", got[0].URL) | 170 | assert.Equal(t, "https://prod.example.com", got[0].URL) | ||
| 169 | assert.True(t, got[0].Active) | 171 | assert.True(t, got[0].Active) | ||
| 172 | + | assert.True(t, got[0].InsecureTLS) | |||
| 170 | } | 173 | } | ||
| 171 | 174 | ||||
| 172 | func TestAttachmentsList_JSONShape_RoundTripsTargetPayload(t *testing.T) { | 175 | func TestAttachmentsList_JSONShape_RoundTripsTargetPayload(t *testing.T) { | ||
| @@ -218,6 +221,31 @@ func TestSecretsList_JSONShape_RoundTripsPayload(t *testing.T) { | |||||
| 218 | assert.Equal(t, payload.Attachments, got.Attachments) | 221 | assert.Equal(t, payload.Attachments, got.Attachments) | ||
| 219 | } | 222 | } | ||
| 220 | 223 | ||||
| 224 | + | func TestSecretsList_JSONShape_NormalizesNilAttachmentsToEmptySlice(t *testing.T) { | |||
| 225 | + | payload := struct { | |||
| 226 | + | Domain string `json:"domain"` | |||
| 227 | + | Keys []string `json:"keys"` | |||
| 228 | + | Attachments []remote.AttachmentSecrets `json:"attachments"` | |||
| 229 | + | }{ | |||
| 230 | + | Domain: "app.example.com", | |||
| 231 | + | Keys: []string{"API_KEY"}, | |||
| 232 | + | Attachments: []remote.AttachmentSecrets{}, | |||
| 233 | + | } | |||
| 234 | + | ||||
| 235 | + | encoded, err := json.Marshal(payload) | |||
| 236 | + | require.NoError(t, err) | |||
| 237 | + | assert.Contains(t, string(encoded), `"attachments":[]`) | |||
| 238 | + | ||||
| 239 | + | var got struct { | |||
| 240 | + | Domain string `json:"domain"` | |||
| 241 | + | Keys []string `json:"keys"` | |||
| 242 | + | Attachments []remote.AttachmentSecrets `json:"attachments"` | |||
| 243 | + | } | |||
| 244 | + | require.NoError(t, json.Unmarshal(encoded, &got)) | |||
| 245 | + | assert.NotNil(t, got.Attachments) | |||
| 246 | + | assert.Empty(t, got.Attachments) | |||
| 247 | + | } | |||
| 248 | + | ||||
| 221 | func TestRoutesList_JSONShape_RoundTripsLocalPayload(t *testing.T) { | 249 | func TestRoutesList_JSONShape_RoundTripsLocalPayload(t *testing.T) { | ||
| 222 | payload := []struct { | 250 | payload := []struct { | ||
| 223 | Domain string `json:"domain"` | 251 | Domain string `json:"domain"` | ||
M internal/adapters/in/cli/remotes.go +4 -3
| @@ -80,9 +80,10 @@ func runRemotesList(out io.Writer, jsonOut bool) error { | |||
| 80 | 80 | for _, name := range names { | |
| 81 | 81 | r := remotes[name] | |
| 82 | 82 | payload = append(payload, map[string]any{ | |
| 83 | - | "name": name, | |
| 84 | - | "url": r.URL, | |
| 85 | - | "active": name == active, | |
| 83 | + | "name": name, | |
| 84 | + | "url": r.URL, | |
| 85 | + | "active": name == active, | |
| 86 | + | "insecure_tls": r.InsecureTLS, | |
| 86 | 87 | }) | |
| 87 | 88 | } | |
| 88 | 89 | return writeJSON(out, payload) | |
| @@ -80,9 +80,10 @@ func runRemotesList(out io.Writer, jsonOut bool) error { | |||||
| 80 | for _, name := range names { | 80 | for _, name := range names { | ||
| 81 | r := remotes[name] | 81 | r := remotes[name] | ||
| 82 | payload = append(payload, map[string]any{ | 82 | payload = append(payload, map[string]any{ | ||
| 83 | - | "name": name, | 83 | + | "name": name, |
| 84 | - | "url": r.URL, | 84 | + | "url": r.URL, |
| 85 | - | "active": name == active, | 85 | + | "active": name == active, |
| 86 | + | "insecure_tls": r.InsecureTLS, | |||
| 86 | }) | 87 | }) | ||
| 87 | } | 88 | } | ||
| 88 | return writeJSON(out, payload) | 89 | return writeJSON(out, payload) | ||
M internal/adapters/in/cli/secrets.go +3 -0
| @@ -87,6 +87,9 @@ func runSecretsListCmd(cmd *cobra.Command, args []string, jsonOut bool) error { | |||
| 87 | 87 | } | |
| 88 | 88 | ||
| 89 | 89 | if jsonOut { | |
| 90 | + | if attachments == nil { | |
| 91 | + | attachments = []remote.AttachmentSecrets{} | |
| 92 | + | } | |
| 90 | 93 | return writeJSON(cmd.OutOrStdout(), map[string]any{ | |
| 91 | 94 | "domain": secretDomain, | |
| 92 | 95 | "keys": keys, | |
| @@ -87,6 +87,9 @@ func runSecretsListCmd(cmd *cobra.Command, args []string, jsonOut bool) error { | |||||
| 87 | } | 87 | } | ||
| 88 | 88 | ||||
| 89 | if jsonOut { | 89 | if jsonOut { | ||
| 90 | + | if attachments == nil { | |||
| 91 | + | attachments = []remote.AttachmentSecrets{} | |||
| 92 | + | } | |||
| 90 | return writeJSON(cmd.OutOrStdout(), map[string]any{ | 93 | return writeJSON(cmd.OutOrStdout(), map[string]any{ | ||
| 91 | "domain": secretDomain, | 94 | "domain": secretDomain, | ||
| 92 | "keys": keys, | 95 | "keys": keys, | ||