Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit 924bdfa

Browse files
authored
Merge pull request #467 from thestormforge/applications-run
Applications run
2 parents 62b6d96 + 96d2e14 commit 924bdfa

File tree

8 files changed

+126
-107
lines changed

8 files changed

+126
-107
lines changed

api/apps/v1alpha1/application_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ type Parameter struct {
6666
type ContainerResources struct {
6767
filters.ResourceMetaFilter
6868
// Label selector of Kubernetes objects to consider when generating container resources patches.
69-
// Deprecated: use GenericSelector.LabelSelector instead.
69+
// Deprecated: use ResourceMetaFilter.LabelSelector instead.
7070
Selector string `json:"selector,omitempty"`
7171
// Regular expression matching the container name.
7272
ContainerName string `json:"containerName,omitempty"`
@@ -84,7 +84,7 @@ type ContainerResources struct {
8484
type Replicas struct {
8585
filters.ResourceMetaFilter
8686
// Label selector of Kubernetes objects to consider when generating replica patches.
87-
// Deprecated: use GenericSelector.LabelSelector instead.
87+
// Deprecated: use ResourceMetaFilter.LabelSelector instead.
8888
Selector string `json:"selector,omitempty"`
8989
// Path to the replica field.
9090
Path string `json:"path,omitempty"`

cli/internal/commands/run/choiceinput/choiceinput.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ type Model struct {
3737

3838
// Spinner to use while loading.
3939
LoadingSpinner spinner.Model
40-
// Message to display while loading.
40+
// Message to display while loading (choices is nil).
4141
LoadingMessage string
4242

43+
// Message to display if there are no results (choices is empty).
44+
NoResultsMessage string
45+
4346
selected int
4447
}
4548

@@ -63,7 +66,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
6366
)
6467

6568
// Only update the text input if there are choices present
66-
if _, isKey := msg.(tea.KeyMsg); (m.Editable && len(m.Choices) > 0) || !isKey {
69+
if _, isKey := msg.(tea.KeyMsg); (m.Editable && m.Choices != nil) || !isKey {
6770
m.Model, cmd = m.Model.Update(msg)
6871
cmds = append(cmds, cmd)
6972
}
@@ -85,6 +88,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
8588
case "down":
8689
m.Select(m.selected + 1)
8790

91+
default:
92+
if m.Editable {
93+
m.selected = -1
94+
}
95+
8896
}
8997
}
9098

@@ -106,7 +114,11 @@ func (m *Model) Select(i int) {
106114
}
107115

108116
if len(m.Choices) > 0 {
109-
m.Model.SetValue(m.Choices[m.selected])
117+
value := m.Choices[m.selected]
118+
if pos := strings.IndexRune(value, '\t'); pos >= 0 {
119+
value = value[0:pos]
120+
}
121+
m.Model.SetValue(value)
110122
} else {
111123
m.Model.SetValue("")
112124
}
@@ -119,19 +131,30 @@ func (m *Model) SelectOnly() {
119131
}
120132
}
121133

134+
func (m *Model) SelectLast() {
135+
if len(m.Choices) > 0 {
136+
m.Select(len(m.Choices) - 1)
137+
}
138+
}
139+
122140
func (m Model) View() string {
123141
var lines []string
124142

125143
// Only render the whole text input if we allow edits
126-
if m.Editable && len(m.Choices) > 0 {
144+
if m.Editable && m.Choices != nil {
127145
lines = append(lines, m.Model.View())
128146
} else {
129147
lines = append(lines, m.Model.Prompt)
130148
}
131149

132150
// If there are no choices yet, show the loading spinner/message
133-
if len(m.Choices) == 0 {
151+
if m.Choices == nil {
134152
lines = append(lines, "\n", m.LoadingSpinner.View(), m.LoadingMessage)
153+
} else if len(m.Choices) == 0 {
154+
lines = append(lines, m.NoResultsMessage)
155+
} else if m.Editable && !strings.HasSuffix(m.Prompt, "\n") {
156+
// TODO Normally we put the newline on the prompt, maybe it's not such a good idea
157+
lines = append(lines, "\n")
135158
}
136159

137160
// Render the list of choices
@@ -155,6 +178,9 @@ func viewChoice(value string, selected, highlighted, focused bool) string {
155178
checkboxStyle = checkboxStyle.Bold()
156179
choiceStyle = choiceStyle.Bold()
157180
}
181+
if pos := strings.IndexRune(value, '\t'); pos >= 0 {
182+
value = value[pos+1:]
183+
}
158184

159185
choice.WriteString("\n")
160186
choice.WriteString(checkboxStyle.Styled("["))

cli/internal/commands/run/command.go

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -255,45 +255,28 @@ func (o *Options) listApplicationNames() tea.Msg {
255255
l := applications.Lister{API: o.ApplicationsAPI}
256256
q := applications.ApplicationListQuery{}
257257
err := l.ForEachApplication(ctx, q, func(item *applications.ApplicationItem) error {
258-
msg = append(msg, fmt.Sprintf("%-32s (%s)", item.Title(), item.Name))
258+
switch {
259+
case item.Title() != "":
260+
msg = append(msg, fmt.Sprintf("%s\t%s", item.Name, item.Title()))
261+
default:
262+
msg = append(msg, item.Name.String())
263+
}
259264
return nil
260265
})
261266
if err != nil {
262-
// TODO Temporarily return an empty list
263267
return msg
264268
}
265269

266-
sort.Strings(msg)
267-
268-
return msg
269-
}
270-
271-
// listScenarioNames returns a list of scenarios.
272-
func (o *Options) listScenarioNames() tea.Msg {
273-
ctx := context.TODO()
274-
msg := internal.ScenarioMsg{}
275-
276-
// Isolate ULID from selection
277-
parts := strings.Fields(o.generatorModel.ApplicationInput.Value())
278-
appName := strings.Trim(parts[len(parts)-1], "()")
279-
280-
// Look up application to find url
281-
app, err := o.ApplicationsAPI.GetApplicationByName(ctx, applications.ApplicationName(appName))
282-
if err != nil {
283-
return err
284-
}
285-
286-
l := applications.Lister{API: o.ApplicationsAPI}
287-
q := applications.ScenarioListQuery{}
288-
err = l.ForEachScenario(ctx, &app, q, func(item *applications.ScenarioItem) error {
289-
msg = append(msg, fmt.Sprintf("%-32s (%s)", item.Title(), item.Name))
290-
return nil
270+
sort.Slice(msg, func(i, j int) bool {
271+
vi, vj := msg[i], msg[j]
272+
if pos := strings.IndexRune(vi, '\t'); pos >= 0 {
273+
vi = vi[pos+1:]
274+
}
275+
if pos := strings.IndexRune(vj, '\t'); pos >= 0 {
276+
vj = vj[pos+1:]
277+
}
278+
return vi < vj
291279
})
292-
if err != nil {
293-
return err
294-
}
295-
296-
sort.Strings(msg)
297280

298281
return msg
299282
}

cli/internal/commands/run/form/validation.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,26 @@ func (v *ContainerImage) ValidateTextField(value string) tea.Msg {
152152

153153
return ValidationMsg("")
154154
}
155+
156+
var nameRegexp = regexp.MustCompile(`^[a-z\d](?:[-a-z\d]{0,61}[a-z\d])?$`)
157+
158+
type Name struct {
159+
Required string
160+
Valid string
161+
}
162+
163+
func (v *Name) ValidateTextField(value string) tea.Msg {
164+
if value == "" {
165+
return ValidationMsg(v.Required)
166+
}
167+
168+
if !nameRegexp.MatchString(value) {
169+
return ValidationMsg(v.Valid)
170+
}
171+
172+
return ValidationMsg("")
173+
}
174+
175+
func (v *Name) ValidateChoiceField(value string) tea.Msg {
176+
return v.ValidateTextField(value)
177+
}

cli/internal/commands/run/internal/messages.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,9 @@ type KubernetesNamespacesMsg []string
5959
// test case names.
6060
type StormForgeTestCasesMsg []string
6161

62-
// ApplicationMsg contains the application ULID if an existing application is used.
62+
// ApplicationMsg contains the names (and optional titles) of existing applications.
6363
type ApplicationMsg []string
6464

65-
// DoScenarioLookup triggers the retrieval of scenario names for an application.
66-
type DoScenarioLookup struct{}
67-
68-
// ScenarioMsg contains the scenario ULID if an existing scenario is used.
69-
type ScenarioMsg []string
70-
7165
// ExperimentMsg represents the generated experiment.
7266
type ExperimentMsg []*yaml.RNode
7367

cli/internal/commands/run/model.go

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ func (m initializationModel) Update(msg tea.Msg) (initializationModel, tea.Cmd)
124124

125125
// generatorModel holds the inputs for values on the generator.
126126
type generatorModel struct {
127+
ApplicationName form.ChoiceField
128+
129+
ScenarioName form.TextField
127130
ScenarioType form.ChoiceField
128131
StormForgeTestCaseInput form.ChoiceField
129132
StormForgeGettingStarted form.ExitField
@@ -141,15 +144,15 @@ type generatorModel struct {
141144
ReplicasSelectorInput form.TextField
142145

143146
ObjectiveInput form.MultiChoiceField
144-
145-
ApplicationInput form.ChoiceField
146-
ScenarioInput form.ChoiceField
147147
}
148148

149149
func (m generatorModel) Update(msg tea.Msg) (generatorModel, tea.Cmd) {
150150
var cmds []tea.Cmd
151151
switch msg := msg.(type) {
152152

153+
case internal.ApplicationMsg:
154+
m.ApplicationName.Choices = msg
155+
153156
case internal.StormForgeTestCasesMsg:
154157
m.StormForgeTestCaseInput.Choices = msg
155158
m.StormForgeTestCaseInput.SelectOnly()
@@ -158,24 +161,13 @@ func (m generatorModel) Update(msg tea.Msg) (generatorModel, tea.Cmd) {
158161
m.NamespaceInput.Choices = msg
159162
m.NamespaceInput.SelectOnly()
160163

161-
case internal.ApplicationMsg:
162-
m.ApplicationInput.Choices = msg
163-
m.ApplicationInput.SelectOnly()
164-
165-
case internal.ScenarioMsg:
166-
m.ScenarioInput.Choices = msg
167-
m.ScenarioInput.SelectOnly()
168-
169164
case tea.KeyMsg:
170165
switch msg.Type {
171166
case tea.KeyEnter:
172167
// If we just completed namespace selection, create the per-namespace label selector inputs
173168
if m.NamespaceInput.Focused() {
174169
m.updateLabelSelectorInputs()
175170
}
176-
if m.ApplicationInput.Focused() {
177-
cmds = append(cmds, func() tea.Msg { return internal.DoScenarioLookup{} })
178-
}
179171
}
180172
}
181173

@@ -184,6 +176,12 @@ func (m generatorModel) Update(msg tea.Msg) (generatorModel, tea.Cmd) {
184176
cmd = m.form().Update(msg)
185177
cmds = append(cmds, cmd)
186178

179+
m.ApplicationName, cmd = m.ApplicationName.Update(msg)
180+
cmds = append(cmds, cmd)
181+
182+
m.ScenarioName, cmd = m.ScenarioName.Update(msg)
183+
cmds = append(cmds, cmd)
184+
187185
m.ScenarioType, cmd = m.ScenarioType.Update(msg)
188186
cmds = append(cmds, cmd)
189187

@@ -222,18 +220,14 @@ func (m generatorModel) Update(msg tea.Msg) (generatorModel, tea.Cmd) {
222220
m.ObjectiveInput, cmd = m.ObjectiveInput.Update(msg)
223221
cmds = append(cmds, cmd)
224222

225-
m.ApplicationInput, cmd = m.ApplicationInput.Update(msg)
226-
cmds = append(cmds, cmd)
227-
228-
m.ScenarioInput, cmd = m.ScenarioInput.Update(msg)
229-
cmds = append(cmds, cmd)
230-
231223
return m, tea.Batch(cmds...)
232224
}
233225

234226
// form returns a slice of everything on the model that implements `form.Field`.
235227
func (m *generatorModel) form() form.Fields {
236228
var fields form.Fields
229+
fields = append(fields, &m.ApplicationName)
230+
fields = append(fields, &m.ScenarioName)
237231
fields = append(fields, &m.ScenarioType)
238232
fields = append(fields, &m.StormForgeTestCaseInput)
239233
fields = append(fields, &m.StormForgeGettingStarted)
@@ -248,8 +242,6 @@ func (m *generatorModel) form() form.Fields {
248242
fields = append(fields, &m.ContainerResourcesSelectorInput)
249243
fields = append(fields, &m.ReplicasSelectorInput)
250244
fields = append(fields, &m.ObjectiveInput)
251-
fields = append(fields, &m.ApplicationInput)
252-
fields = append(fields, &m.ScenarioInput)
253245
return fields
254246
}
255247

cli/internal/commands/run/run.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
tea "github.com/charmbracelet/bubbletea"
2929
"github.com/spf13/cobra"
3030
konjurev1beta2 "github.com/thestormforge/konjure/pkg/api/core/v1beta2"
31+
"github.com/thestormforge/konjure/pkg/filters"
3132
"github.com/thestormforge/konjure/pkg/konjure"
3233
optimizeappsv1alpha1 "github.com/thestormforge/optimize-controller/v2/api/apps/v1alpha1"
3334
"github.com/thestormforge/optimize-controller/v2/cli/internal/commander"
@@ -219,9 +220,6 @@ func (o *Options) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
219220
// Refresh the trials list
220221
cmds = append(cmds, o.refreshTrials)
221222

222-
case internal.DoScenarioLookup:
223-
cmds = append(cmds, o.listScenarioNames)
224-
225223
case error:
226224
// Handle errors so any command returning tea.Msg can just return an error
227225
o.lastErr = msg
@@ -284,19 +282,16 @@ func (o *Options) ReadApplication(args []string) error {
284282

285283
// applyToApp takes all of the what is on the model and applies it to an application.
286284
func (m generatorModel) applyToApp(app *optimizeappsv1alpha1.Application) {
287-
if m.ApplicationInput.Enabled() {
288-
parts := strings.Fields(m.ApplicationInput.Value())
289-
app.Name = strings.Trim(parts[len(parts)-1], "()")
285+
if m.ApplicationName.Enabled() {
286+
app.Name = m.ApplicationName.Value()
290287
}
291288

292289
var scenarioName string
293-
if m.ScenarioInput.Enabled() {
294-
parts := strings.Fields(m.ScenarioInput.Value())
295-
scenarioName = strings.Trim(parts[len(parts)-1], "()")
290+
if m.ScenarioName.Enabled() {
291+
scenarioName = m.ScenarioName.Value()
296292
}
297293

298294
if m.NamespaceInput.Enabled() {
299-
300295
// TODO We need a better way to set the name/namespace of the application
301296
if namespaces := m.NamespaceInput.Values(); len(namespaces) == 1 {
302297
app.Namespace = namespaces[0]
@@ -365,7 +360,9 @@ func (m generatorModel) applyToApp(app *optimizeappsv1alpha1.Application) {
365360
// the presence of any parameter bypasses the default inclusion of container resources.
366361
app.Configuration = append(app.Configuration, optimizeappsv1alpha1.Parameter{
367362
ContainerResources: &optimizeappsv1alpha1.ContainerResources{
368-
Selector: m.ContainerResourcesSelectorInput.Value(),
363+
ResourceMetaFilter: filters.ResourceMetaFilter{
364+
LabelSelector: m.ContainerResourcesSelectorInput.Value(),
365+
},
369366
},
370367
})
371368
}
@@ -374,7 +371,9 @@ func (m generatorModel) applyToApp(app *optimizeappsv1alpha1.Application) {
374371
if sel := m.ReplicasSelectorInput.Value(); sel != "" {
375372
app.Configuration = append(app.Configuration, optimizeappsv1alpha1.Parameter{
376373
Replicas: &optimizeappsv1alpha1.Replicas{
377-
Selector: sel,
374+
ResourceMetaFilter: filters.ResourceMetaFilter{
375+
LabelSelector: sel,
376+
},
378377
},
379378
})
380379
}

0 commit comments

Comments
 (0)