v2.31.0-dev19 35467d7

tls.go RawHistoryFolder

1package cli
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"strings"
8	"time"
9
10	"github.com/spf13/cobra"
11
12	"github.com/bnema/gordon/internal/adapters/dto"
13)
14
15// tlsResolveControlPlane allows tests to override control-plane resolution.
16var tlsResolveControlPlane = resolveControlPlane
17
18func newTLSCmd() *cobra.Command {
19	cmd := &cobra.Command{
20		Use:   "tls",
21		Short: "Inspect TLS certificate status",
22	}
23
24	cmd.AddCommand(newTLSStatusCmd())
25
26	return cmd
27}
28
29func newTLSStatusCmd() *cobra.Command {
30	var jsonOut bool
31
32	cmd := &cobra.Command{
33		Use:   "status",
34		Short: "Show public TLS certificate status",
35		RunE: func(cmd *cobra.Command, _ []string) error {
36			handle, err := tlsResolveControlPlane(configPath)
37			if err != nil {
38				return err
39			}
40			defer handle.close()
41			return runTLSStatusCmd(cmd.Context(), handle.plane, cmd.OutOrStdout(), jsonOut)
42		},
43	}
44
45	cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON")
46
47	return cmd
48}
49
50func runTLSStatusCmd(ctx context.Context, cp ControlPlane, out io.Writer, jsonOut bool) error {
51	status, err := cp.GetTLSStatus(ctx)
52	if err != nil {
53		return fmt.Errorf("failed to get TLS status: %w", err)
54	}
55	if status == nil {
56		return fmt.Errorf("failed to get TLS status: empty response")
57	}
58
59	if jsonOut {
60		return writeJSON(out, status)
61	}
62
63	return renderTLSStatusText(out, status)
64}
65
66func renderTLSStatusText(out io.Writer, s *dto.TLSStatusResponse) error {
67	if err := cliWriteLine(out, cliRenderTitle("Public TLS / ACME Status")); err != nil {
68		return err
69	}
70
71	if err := renderTLSModeSection(out, s); err != nil {
72		return err
73	}
74
75	if err := renderTLSCertificatesSection(out, s); err != nil {
76		return err
77	}
78
79	if err := renderTLSRouteCoverageSection(out, s); err != nil {
80		return err
81	}
82
83	return renderTLSErrorsSection(out, s)
84}
85
86func renderTLSModeSection(out io.Writer, s *dto.TLSStatusResponse) error {
87	acmeStatus := "disabled"
88	if s.ACMEEnabled {
89		acmeStatus = "enabled"
90	}
91	if err := cliWriteLine(out, cliRenderMeta("ACME:", acmeStatus)); err != nil {
92		return err
93	}
94	if err := cliWriteLine(out, cliRenderMeta("Configured Mode:", s.ConfiguredMode)); err != nil {
95		return err
96	}
97	if err := cliWriteLine(out, cliRenderMeta("Effective Mode:", s.EffectiveMode)); err != nil {
98		return err
99	}
100	if s.SelectionReason != "" {
101		if err := cliWriteLine(out, cliRenderMeta("Reason:", s.SelectionReason)); err != nil {
102			return err
103		}
104	}
105	return cliWriteLine(out, cliRenderMeta("Token Source:", s.TokenSource))
106}
107
108func renderTLSCertificatesSection(out io.Writer, s *dto.TLSStatusResponse) error {
109	if len(s.Certificates) == 0 {
110		return nil
111	}
112
113	if err := cliWriteLine(out, ""); err != nil {
114		return err
115	}
116	if err := cliWriteLine(out, cliRenderTitle("Certificates")); err != nil {
117		return err
118	}
119
120	for _, cert := range s.Certificates {
121		if err := cliWritef(out, "  %s\n", cliRenderMeta("ID:", cert.ID)); err != nil {
122			return err
123		}
124		if err := cliWriteLine(out, cliRenderMeta("  Names:", strings.Join(cert.Names, ", "))); err != nil {
125			return err
126		}
127		if err := cliWriteLine(out, cliRenderMeta("  Status:", cert.Status)); err != nil {
128			return err
129		}
130		if !cert.NotAfter.IsZero() {
131			if err := cliWriteLine(out, cliRenderMeta("  Not After:", cert.NotAfter.Format(time.DateTime))); err != nil {
132				return err
133			}
134		}
135		if cert.LastError != "" {
136			if err := cliWriteLine(out, cliRenderMeta("  Last Error:", cert.LastError)); err != nil {
137				return err
138			}
139		}
140	}
141
142	return nil
143}
144
145func renderTLSRouteCoverageSection(out io.Writer, s *dto.TLSStatusResponse) error {
146	if len(s.Routes) == 0 {
147		return nil
148	}
149
150	if err := cliWriteLine(out, ""); err != nil {
151		return err
152	}
153	if err := cliWriteLine(out, cliRenderTitle("Route Coverage")); err != nil {
154		return err
155	}
156
157	for _, route := range s.Routes {
158		covered := "no"
159		if route.Covered {
160			covered = "yes"
161		}
162		if err := cliWritef(out, "  %s  covered=%s", route.Domain, covered); err != nil {
163			return err
164		}
165		if route.CoveredBy != "" {
166			if err := cliWritef(out, "  covered_by=%s", route.CoveredBy); err != nil {
167				return err
168			}
169		}
170		if route.Error != "" {
171			if err := cliWritef(out, "  error=%s", route.Error); err != nil {
172				return err
173			}
174		}
175		if err := cliWriteLine(out, ""); err != nil {
176			return err
177		}
178	}
179
180	return nil
181}
182
183func renderTLSErrorsSection(out io.Writer, s *dto.TLSStatusResponse) error {
184	if len(s.Errors) == 0 {
185		return nil
186	}
187
188	if err := cliWriteLine(out, ""); err != nil {
189		return err
190	}
191	if err := cliWriteLine(out, cliRenderTitle("Errors")); err != nil {
192		return err
193	}
194
195	for _, e := range s.Errors {
196		if err := cliWriteLine(out, "  "+e); err != nil {
197			return err
198		}
199	}
200
201	return 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 7

Tags 106

↑↓ moveenter openesc close