v2.31.0-dev23 58a92f3

bootstrap.go RawHistoryFolder

1package cli
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"sort"
8	"strings"
9
10	"github.com/spf13/cobra"
11
12	"github.com/bnema/gordon/internal/adapters/dto"
13)
14
15func newBootstrapCmd() *cobra.Command {
16	var attachments []string
17	var envPairs []string
18	var attachmentEnvPairs []string
19	var configPath string
20
21	cmd := &cobra.Command{
22		Use:   "bootstrap <domain> <image>",
23		Short: "Create a route, attachments, and secrets together",
24		Args:  cobra.ExactArgs(2),
25		RunE: func(cmd *cobra.Command, args []string) error {
26			req, err := parseBootstrapRequest(args, attachments, envPairs, attachmentEnvPairs)
27			if err != nil {
28				return err
29			}
30			return runBootstrap(cmd.Context(), req, configPath, cmd.OutOrStdout())
31		},
32	}
33
34	cmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file")
35	cmd.Flags().StringArrayVar(&attachments, "attachment", nil, "Attachment image (repeatable)")
36	cmd.Flags().StringArrayVar(&envPairs, "env", nil, "Environment variable KEY=VALUE (repeatable)")
37	cmd.Flags().StringArrayVar(&attachmentEnvPairs, "attachment-env", nil, "Attachment env service:KEY=VALUE (repeatable)")
38
39	return cmd
40}
41
42func parseBootstrapRequest(args []string, attachments, envPairs, attachmentEnvPairs []string) (dto.BootstrapRequest, error) {
43	req := dto.BootstrapRequest{
44		Domain:        args[0],
45		Image:         args[1],
46		Attachments:   append([]string(nil), attachments...),
47		Env:           map[string]string{},
48		AttachmentEnv: map[string]map[string]string{},
49	}
50
51	for _, pair := range envPairs {
52		parts := strings.SplitN(pair, "=", 2)
53		if len(parts) != 2 {
54			return dto.BootstrapRequest{}, fmt.Errorf("invalid --env value %q (expected KEY=VALUE)", pair)
55		}
56		req.Env[parts[0]] = parts[1]
57	}
58
59	for _, pair := range attachmentEnvPairs {
60		serviceAndKV := strings.SplitN(pair, ":", 2)
61		if len(serviceAndKV) != 2 {
62			return dto.BootstrapRequest{}, fmt.Errorf("invalid --attachment-env value %q (expected service:KEY=VALUE)", pair)
63		}
64		keyValue := strings.SplitN(serviceAndKV[1], "=", 2)
65		if len(keyValue) != 2 {
66			return dto.BootstrapRequest{}, fmt.Errorf("invalid --attachment-env value %q (expected service:KEY=VALUE)", pair)
67		}
68		service := serviceAndKV[0]
69		if req.AttachmentEnv[service] == nil {
70			req.AttachmentEnv[service] = map[string]string{}
71		}
72		req.AttachmentEnv[service][keyValue[0]] = keyValue[1]
73	}
74
75	if len(req.Env) == 0 {
76		req.Env = nil
77	}
78	if len(req.AttachmentEnv) == 0 {
79		req.AttachmentEnv = nil
80	}
81
82	return req, nil
83}
84
85func runBootstrap(ctx context.Context, req dto.BootstrapRequest, configPath string, out io.Writer) error {
86	handle, err := resolveControlPlane(configPath)
87	if err != nil {
88		return err
89	}
90	defer handle.close()
91
92	resp, err := handle.plane.Bootstrap(ctx, req)
93	if resp != nil {
94		if writeErr := printBootstrapSummary(out, resp); writeErr != nil {
95			return writeErr
96		}
97	}
98	if err != nil {
99		return fmt.Errorf("failed to bootstrap: %w", err)
100	}
101	if err := cliWriteLine(out, cliRenderSuccess("Bootstrap complete")); err != nil {
102		return err
103	}
104	nextImage := req.Image
105	if resp != nil && resp.Image != "" {
106		nextImage = resp.Image
107	}
108	return cliWriteLine(out, fmt.Sprintf("Next: gordon push %s --build", nextImage))
109}
110
111func printBootstrapSummary(out io.Writer, resp *dto.BootstrapResponse) error {
112	if err := cliWriteLine(out, cliRenderTitle("Bootstrap")); err != nil {
113		return err
114	}
115	if err := cliWriteLine(out, cliRenderMeta("Domain:", resp.Domain)); err != nil {
116		return err
117	}
118	if err := cliWriteLine(out, cliRenderMeta("Image:", resp.Image)); err != nil {
119		return err
120	}
121	for _, step := range resp.Steps {
122		if err := cliWriteLine(out, fmt.Sprintf("- %s: %s", step.Name, step.Status)); err != nil {
123			return err
124		}
125	}
126	if len(resp.Warnings) > 0 {
127		sorted := append([]string(nil), resp.Warnings...)
128		sort.Strings(sorted)
129		for _, warning := range sorted {
130			if err := cliWriteLine(out, cliRenderWarning(warning)); err != nil {
131				return err
132			}
133		}
134	}
135	return nil
136}

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