-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsecrets_ingest_tui.go
More file actions
148 lines (136 loc) · 4.18 KB
/
Copy pathsecrets_ingest_tui.go
File metadata and controls
148 lines (136 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package cli
import (
"errors"
"fmt"
"sort"
"strings"
huh "charm.land/huh/v2"
"github.com/open-source-cloud/devstack/internal/envingest"
"github.com/open-source-cloud/devstack/internal/prompt"
)
// runIngestWizard drives the interactive classification flow (huh on Bubble Tea
// v2, degradable: it only runs behind prompt.IsInteractive). It pre-populates the
// computed classes, lets the operator toggle which keys are secret and which
// config keys are host-sourced, previews the plan, and returns the per-key
// overrides as exact-key glob lists (secret/public/from-host) plus a confirm bool.
// A ctrl+c/esc abort returns ok=false (caller exits 0, writes nothing).
func runIngestWizard(plan envingest.Plan) (secretKeys, publicKeys, hostKeys []string, ok bool, err error) {
allKeys := make([]string, 0, len(plan.Decisions))
computedSecret := map[string]bool{}
computedHost := map[string]bool{}
for _, d := range plan.Decisions {
allKeys = append(allKeys, d.Key)
if d.IsSecret() {
computedSecret[d.Key] = true
}
if d.HostFrom {
computedHost[d.Key] = true
}
}
sort.Strings(allKeys)
// Pre-select the computed secrets; the operator toggles off to make a key config.
selectedSecrets := make([]string, 0)
for _, k := range allKeys {
if computedSecret[k] {
selectedSecrets = append(selectedSecrets, k)
}
}
secretOpts := make([]huh.Option[string], 0, len(allKeys))
for _, k := range allKeys {
secretOpts = append(secretOpts, huh.NewOption(k, k).Selected(computedSecret[k]))
}
form := huh.NewForm(
huh.NewGroup(
huh.NewMultiSelect[string]().
Title("Secrets").
Description("selected = secret (encrypted); unselect to inline as config · space toggles").
Options(secretOpts...).
Value(&selectedSecrets),
),
).WithTheme(prompt.Theme())
if err := form.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
return nil, nil, nil, false, nil
}
return nil, nil, nil, false, err
}
secretSet := map[string]bool{}
for _, k := range selectedSecrets {
secretSet[k] = true
}
// Config keys are everything not chosen as secret; offer host-sourcing for them.
var configKeys []string
for _, k := range allKeys {
if !secretSet[k] {
configKeys = append(configKeys, k)
}
}
selectedHost := make([]string, 0)
if len(configKeys) > 0 {
hostOpts := make([]huh.Option[string], 0, len(configKeys))
for _, k := range configKeys {
hostOpts = append(hostOpts, huh.NewOption(k, k).Selected(computedHost[k]))
}
hostForm := huh.NewForm(
huh.NewGroup(
huh.NewMultiSelect[string]().
Title("Host-sourced config keys").
Description("selected keys are emitted as ${env.KEY} (supplied by the host/CI), not inlined").
Options(hostOpts...).
Value(&selectedHost),
),
).WithTheme(prompt.Theme())
if err := hostForm.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
return nil, nil, nil, false, nil
}
return nil, nil, nil, false, err
}
}
// Confirm with a preview of the resulting classification.
confirm := true
cf := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Apply this classification?").
Description(prompt.PreviewBox(previewClassification(allKeys, secretSet, selectedHost, plan))).
Value(&confirm),
),
).WithTheme(prompt.Theme())
if err := cf.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
return nil, nil, nil, false, nil
}
return nil, nil, nil, false, err
}
if !confirm {
return nil, nil, nil, false, nil
}
for _, k := range allKeys {
if secretSet[k] {
secretKeys = append(secretKeys, k)
} else {
publicKeys = append(publicKeys, k)
}
}
hostKeys = append(hostKeys, selectedHost...)
return secretKeys, publicKeys, hostKeys, true, nil
}
func previewClassification(allKeys []string, secretSet map[string]bool, hostKeys []string, plan envingest.Plan) string {
host := map[string]bool{}
for _, k := range hostKeys {
host[k] = true
}
var b strings.Builder
fmt.Fprintf(&b, "destination: %s (%s)\n", plan.Dest, plan.DestPath)
for _, k := range allKeys {
class := "config"
if secretSet[k] {
class = "secret"
} else if host[k] {
class = "config (${env})"
}
fmt.Fprintf(&b, "%-26s %s\n", k, class)
}
return strings.TrimRight(b.String(), "\n")
}