nextmv 0.40.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +1 -2
- nextmv/__init__.py +2 -4
- nextmv/cli/CONTRIBUTING.md +583 -0
- nextmv/cli/cloud/__init__.py +49 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +391 -0
- nextmv/cli/cloud/acceptance/delete.py +64 -0
- nextmv/cli/cloud/acceptance/get.py +103 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +59 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +140 -0
- nextmv/cli/cloud/app/delete.py +57 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +432 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +452 -0
- nextmv/cli/cloud/batch/delete.py +64 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +33 -0
- nextmv/cli/cloud/ensemble/create.py +413 -0
- nextmv/cli/cloud/ensemble/delete.py +63 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/list.py +63 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +32 -0
- nextmv/cli/cloud/input_set/create.py +168 -0
- nextmv/cli/cloud/input_set/delete.py +64 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +289 -0
- nextmv/cli/cloud/instance/delete.py +61 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +144 -0
- nextmv/cli/cloud/managed_input/delete.py +64 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +524 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +166 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +500 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +61 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +63 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +144 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +64 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +53 -0
- nextmv/cli/cloud/shadow/update.py +96 -0
- nextmv/cli/cloud/switchback/__init__.py +33 -0
- nextmv/cli/cloud/switchback/create.py +151 -0
- nextmv/cli/cloud/switchback/delete.py +64 -0
- nextmv/cli/cloud/switchback/get.py +62 -0
- nextmv/cli/cloud/switchback/list.py +63 -0
- nextmv/cli/cloud/switchback/metadata.py +68 -0
- nextmv/cli/cloud/switchback/start.py +43 -0
- nextmv/cli/cloud/switchback/stop.py +53 -0
- nextmv/cli/cloud/switchback/update.py +96 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +96 -0
- nextmv/cli/cloud/version/delete.py +61 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +20 -204
- nextmv/cli/community/list.py +61 -126
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +103 -6
- nextmv/cli/configuration/create.py +17 -18
- nextmv/cli/configuration/delete.py +25 -13
- nextmv/cli/configuration/list.py +4 -4
- nextmv/cli/confirm.py +34 -0
- nextmv/cli/main.py +68 -36
- nextmv/cli/message.py +170 -0
- nextmv/cli/options.py +196 -0
- nextmv/cli/version.py +20 -1
- nextmv/cloud/__init__.py +17 -38
- nextmv/cloud/acceptance_test.py +20 -83
- nextmv/cloud/account.py +269 -30
- nextmv/cloud/application/__init__.py +898 -0
- nextmv/cloud/application/_acceptance.py +424 -0
- nextmv/cloud/application/_batch_scenario.py +845 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +263 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +320 -0
- nextmv/cloud/application/_switchback.py +332 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +304 -0
- nextmv/cloud/batch_experiment.py +6 -2
- nextmv/cloud/community.py +446 -0
- nextmv/cloud/instance.py +11 -1
- nextmv/cloud/integration.py +8 -5
- nextmv/cloud/package.py +50 -9
- nextmv/cloud/shadow.py +254 -0
- nextmv/cloud/switchback.py +228 -0
- nextmv/deprecated.py +5 -3
- nextmv/input.py +20 -88
- nextmv/local/application.py +3 -15
- nextmv/local/runner.py +1 -1
- nextmv/model.py +50 -11
- nextmv/options.py +11 -256
- nextmv/output.py +0 -62
- nextmv/polling.py +54 -16
- nextmv/run.py +84 -37
- nextmv/status.py +1 -51
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
- nextmv-1.0.0.dist-info/RECORD +185 -0
- nextmv-1.0.0.dist-info/entry_points.txt +2 -0
- nextmv/cli/community/community.py +0 -24
- nextmv/cli/configuration/configuration.py +0 -23
- nextmv/cli/error.py +0 -22
- nextmv/cloud/application.py +0 -4204
- nextmv-0.40.0.dist-info/RECORD +0 -66
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud scenario create command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from nextmv.cli.configuration.config import build_app
|
|
11
|
+
from nextmv.cli.message import error, in_progress, print_json, success
|
|
12
|
+
from nextmv.cli.options import AppIDOption, ProfileOption
|
|
13
|
+
from nextmv.cloud.scenario import Scenario
|
|
14
|
+
from nextmv.polling import default_polling_options
|
|
15
|
+
|
|
16
|
+
# Set up subcommand application.
|
|
17
|
+
app = typer.Typer()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def create(
|
|
22
|
+
app_id: AppIDOption,
|
|
23
|
+
# Options for scenario test configuration.
|
|
24
|
+
scenarios: Annotated[
|
|
25
|
+
list[str],
|
|
26
|
+
typer.Option(
|
|
27
|
+
"--scenarios",
|
|
28
|
+
"-s",
|
|
29
|
+
help="Scenarios to use for the test. Data should be valid [magenta]json[/magenta]. "
|
|
30
|
+
"Pass multiple scenarios by repeating the flag, or providing a list of objects. "
|
|
31
|
+
"See command help for details on scenario formatting.",
|
|
32
|
+
metavar="SCENARIOS",
|
|
33
|
+
rich_help_panel="Scenario test configuration",
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
description: Annotated[
|
|
37
|
+
str | None,
|
|
38
|
+
typer.Option(
|
|
39
|
+
"--description",
|
|
40
|
+
"-d",
|
|
41
|
+
help="Description of the scenario test.",
|
|
42
|
+
metavar="DESCRIPTION",
|
|
43
|
+
rich_help_panel="Scenario test configuration",
|
|
44
|
+
),
|
|
45
|
+
] = None,
|
|
46
|
+
name: Annotated[
|
|
47
|
+
str | None,
|
|
48
|
+
typer.Option(
|
|
49
|
+
"--name",
|
|
50
|
+
"-n",
|
|
51
|
+
help="Name of the scenario test. If not provided, the ID will be used as the name.",
|
|
52
|
+
metavar="NAME",
|
|
53
|
+
rich_help_panel="Scenario test configuration",
|
|
54
|
+
),
|
|
55
|
+
] = None,
|
|
56
|
+
repetitions: Annotated[
|
|
57
|
+
int | None,
|
|
58
|
+
typer.Option(
|
|
59
|
+
"--repetitions",
|
|
60
|
+
"-r",
|
|
61
|
+
help="Number of times the scenario test is [italic]repeated[/italic]. "
|
|
62
|
+
"0 repetitions = 1 execution, 1 repetition = 2 executions, etc.",
|
|
63
|
+
metavar="REPETITIONS",
|
|
64
|
+
rich_help_panel="Scenario test configuration",
|
|
65
|
+
),
|
|
66
|
+
] = 0,
|
|
67
|
+
scenario_test_id: Annotated[
|
|
68
|
+
str | None,
|
|
69
|
+
typer.Option(
|
|
70
|
+
"--scenario-test-id",
|
|
71
|
+
"-i",
|
|
72
|
+
help="ID for the scenario test. Will be generated if not provided.",
|
|
73
|
+
envvar="NEXTMV_SCENARIO_TEST_ID",
|
|
74
|
+
metavar="SCENARIO_TEST_ID",
|
|
75
|
+
rich_help_panel="Scenario test configuration",
|
|
76
|
+
),
|
|
77
|
+
] = None,
|
|
78
|
+
# Options for controlling output.
|
|
79
|
+
output: Annotated[
|
|
80
|
+
str | None,
|
|
81
|
+
typer.Option(
|
|
82
|
+
"--output",
|
|
83
|
+
"-o",
|
|
84
|
+
help="Waits for the test to complete and saves the results to this location.",
|
|
85
|
+
metavar="OUTPUT_PATH",
|
|
86
|
+
rich_help_panel="Output control",
|
|
87
|
+
),
|
|
88
|
+
] = None,
|
|
89
|
+
timeout: Annotated[
|
|
90
|
+
int,
|
|
91
|
+
typer.Option(
|
|
92
|
+
help="The maximum time in seconds to wait for results when polling. Poll indefinitely if not set.",
|
|
93
|
+
metavar="TIMEOUT_SECONDS",
|
|
94
|
+
rich_help_panel="Output control",
|
|
95
|
+
),
|
|
96
|
+
] = -1,
|
|
97
|
+
wait: Annotated[
|
|
98
|
+
bool,
|
|
99
|
+
typer.Option(
|
|
100
|
+
"--wait",
|
|
101
|
+
"-w",
|
|
102
|
+
help="Wait for the scenario test to complete. Results are printed to [magenta]stdout[/magenta]. "
|
|
103
|
+
"Specify output location with --output.",
|
|
104
|
+
rich_help_panel="Output control",
|
|
105
|
+
),
|
|
106
|
+
] = False,
|
|
107
|
+
profile: ProfileOption = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Create a new Nextmv Cloud scenario test.
|
|
111
|
+
|
|
112
|
+
A scenario test allows you to run multiple scenarios with different inputs,
|
|
113
|
+
instances/versions, and configurations in a single test.
|
|
114
|
+
|
|
115
|
+
Use the --wait flag to wait for the scenario test to complete,
|
|
116
|
+
polling for results. Using the --output flag will also
|
|
117
|
+
activate waiting, and allows you to specify a destination file for the
|
|
118
|
+
results.
|
|
119
|
+
|
|
120
|
+
[bold][underline]Scenarios[/underline][/bold]
|
|
121
|
+
|
|
122
|
+
Scenarios are provided as [magenta]json[/magenta] objects using the
|
|
123
|
+
--scenarios flag. Each scenario defines the configuration for a scenario
|
|
124
|
+
test execution.
|
|
125
|
+
|
|
126
|
+
You can provide scenarios in three ways:
|
|
127
|
+
- A single scenario as a [magenta]json[/magenta] object.
|
|
128
|
+
- Multiple scenarios by repeating the --scenarios flag.
|
|
129
|
+
- Multiple scenarios as a [magenta]json[/magenta] array in a single --scenarios flag.
|
|
130
|
+
|
|
131
|
+
Each scenario must have the following fields:
|
|
132
|
+
- [magenta]instance_id[/magenta]: ID of the instance to use for this scenario (required).
|
|
133
|
+
- [magenta]scenario_input[/magenta]: Object containing the scenario input (required), with:
|
|
134
|
+
- [magenta]scenario_input_type[/magenta]: Type of the scenario input (required).
|
|
135
|
+
- [magenta]scenario_input_data[/magenta]: Data for the scenario input (required).
|
|
136
|
+
- [magenta]scenario_id[/magenta]: ID of the scenario (optional). The
|
|
137
|
+
default value will be set as [magenta]scenario-<index>[/magenta] if not set.
|
|
138
|
+
- [magenta]configuration[/magenta]: An array of configuration objects
|
|
139
|
+
(optional). Use this attribute to configure variation of options for the scenario. Each scenario
|
|
140
|
+
configuration object requires:
|
|
141
|
+
- [magenta]name[/magenta]: Name of the configuration option.
|
|
142
|
+
- [magenta]values[/magenta]: List of values for the configuration option.
|
|
143
|
+
|
|
144
|
+
Example object format:
|
|
145
|
+
[dim]{
|
|
146
|
+
"instance_id": "bunny-hopper-v2",
|
|
147
|
+
"scenario_input": {
|
|
148
|
+
"scenario_input_type": "input_set",
|
|
149
|
+
"scenario_input_data": {
|
|
150
|
+
"input_id": "meadow-input-a1",
|
|
151
|
+
"input_set_id": "spring-gardens"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"configuration": [
|
|
155
|
+
{
|
|
156
|
+
"name": "speed",
|
|
157
|
+
"values": ["optimized", "balanced", "safe"]
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}[/dim]
|
|
161
|
+
|
|
162
|
+
[bold][underline]Examples[/underline][/bold]
|
|
163
|
+
|
|
164
|
+
- Create a scenario test with a single scenario.
|
|
165
|
+
$ [dim]SCENARIO='{
|
|
166
|
+
"instance_id": "warren-planner-v1",
|
|
167
|
+
"scenario_input": {
|
|
168
|
+
"scenario_input_type": "input_set",
|
|
169
|
+
"scenario_input_data": {
|
|
170
|
+
"input_id": "carrot-patch-a",
|
|
171
|
+
"input_set_id": "spring-gardens"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}'
|
|
175
|
+
nextmv cloud scenario create --app-id hare-app --name "Spring Meadow Routes" --scenarios "$SCENARIO"[/dim]
|
|
176
|
+
|
|
177
|
+
- Create with multiple scenarios by repeating the flag.
|
|
178
|
+
$ [dim]SCENARIO1='{
|
|
179
|
+
"instance_id": "hop-optimizer",
|
|
180
|
+
"scenario_input": {
|
|
181
|
+
"scenario_input_type": "input_set",
|
|
182
|
+
"scenario_input_data": {
|
|
183
|
+
"input_id": "lettuce-field-1",
|
|
184
|
+
"input_set_id": "veggie-gardens"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}'
|
|
188
|
+
SCENARIO2='{
|
|
189
|
+
"instance_id": "hop-optimizer",
|
|
190
|
+
"scenario_input": {
|
|
191
|
+
"scenario_input_type": "input_set",
|
|
192
|
+
"scenario_input_data": {
|
|
193
|
+
"input_id": "lettuce-field-2",
|
|
194
|
+
"input_set_id": "veggie-gardens"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}'
|
|
198
|
+
nextmv cloud scenario create --app-id hare-app --name "Lettuce Delivery Optimization" \\
|
|
199
|
+
--scenarios "$SCENARIO1" --scenarios "$SCENARIO2"[/dim]
|
|
200
|
+
|
|
201
|
+
- Create with multiple scenarios in a single [magenta]json[/magenta] array.
|
|
202
|
+
$ [dim]SCENARIOS='[
|
|
203
|
+
{
|
|
204
|
+
"instance_id": "burrow-builder",
|
|
205
|
+
"scenario_input": {
|
|
206
|
+
"scenario_input_type": "input_set",
|
|
207
|
+
"scenario_input_data": {
|
|
208
|
+
"input_id": "warren-zone-a",
|
|
209
|
+
"input_set_id": "burrow-sites"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"instance_id": "tunnel-planner-v3",
|
|
215
|
+
"scenario_input": {
|
|
216
|
+
"scenario_input_type": "input_set",
|
|
217
|
+
"scenario_input_data": {
|
|
218
|
+
"input_id": "warren-zone-b",
|
|
219
|
+
"input_set_id": "burrow-sites"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
]'
|
|
224
|
+
nextmv cloud scenario create --app-id hare-app --name "Warren Construction Plans" \\
|
|
225
|
+
--scenarios "$SCENARIOS"[/dim]
|
|
226
|
+
|
|
227
|
+
- Create a scenario test and wait for it to complete.
|
|
228
|
+
$ [dim]SCENARIO='{
|
|
229
|
+
"instance_id": "foraging-route",
|
|
230
|
+
"scenario_input": {
|
|
231
|
+
"scenario_input_type": "input_set",
|
|
232
|
+
"scenario_input_data": {
|
|
233
|
+
"input_id": "carrot-harvest",
|
|
234
|
+
"input_set_id": "harvest-season"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}'
|
|
238
|
+
nextmv cloud scenario create --app-id hare-app --name "Autumn Carrot Collection" --scenarios "$SCENARIO" \\
|
|
239
|
+
--wait[/dim]
|
|
240
|
+
|
|
241
|
+
- Create a scenario test and save the results to a file, waiting for completion.
|
|
242
|
+
$ [dim]SCENARIO='{
|
|
243
|
+
"instance_id": "safe-hopper",
|
|
244
|
+
"scenario_input": {
|
|
245
|
+
"scenario_input_type": "input_set",
|
|
246
|
+
"scenario_input_data": {
|
|
247
|
+
"input_id": "predator-zones",
|
|
248
|
+
"input_set_id": "danger-zones"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}'
|
|
252
|
+
nextmv cloud scenario create --app-id hare-app --name "Fox Avoidance Routes" --scenarios "$SCENARIO" \\
|
|
253
|
+
--output bunny-safety-results.json[/dim]
|
|
254
|
+
|
|
255
|
+
- Create a scenario test with configuration options.
|
|
256
|
+
$ [dim]SCENARIO='{
|
|
257
|
+
"instance_id": "hop-optimizer",
|
|
258
|
+
"scenario_input": {
|
|
259
|
+
"scenario_input_type": "input_set",
|
|
260
|
+
"scenario_input_data": {
|
|
261
|
+
"input_id": "garden-route-1",
|
|
262
|
+
"input_set_id": "garden-paths"
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
"configuration": [
|
|
266
|
+
{
|
|
267
|
+
"name": "speed",
|
|
268
|
+
"values": ["fast", "careful"]
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}'
|
|
272
|
+
nextmv cloud scenario create --app-id hare-app --name "Speed Analysis" --scenarios "$SCENARIO"[/dim]
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
cloud_app = build_app(app_id=app_id, profile=profile)
|
|
276
|
+
|
|
277
|
+
# Build the scenario list from the CLI options
|
|
278
|
+
scenario_list = build_scenarios(scenarios)
|
|
279
|
+
|
|
280
|
+
scenario_id = cloud_app.new_scenario_test(
|
|
281
|
+
scenarios=scenario_list,
|
|
282
|
+
id=scenario_test_id,
|
|
283
|
+
name=name,
|
|
284
|
+
description=description,
|
|
285
|
+
repetitions=repetitions,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# If we don't need to poll at all we are done.
|
|
289
|
+
if not wait and (output is None or output == ""):
|
|
290
|
+
print_json({"scenario_test_id": scenario_id})
|
|
291
|
+
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
success(f"Scenario test [magenta]{scenario_id}[/magenta] created.")
|
|
295
|
+
|
|
296
|
+
# Build the polling options.
|
|
297
|
+
polling_options = default_polling_options()
|
|
298
|
+
polling_options.max_duration = timeout
|
|
299
|
+
|
|
300
|
+
in_progress(msg="Getting scenario test results...")
|
|
301
|
+
scenario_test = cloud_app.scenario_test_with_polling(
|
|
302
|
+
scenario_test_id=scenario_id,
|
|
303
|
+
polling_options=polling_options,
|
|
304
|
+
)
|
|
305
|
+
scenario_test_dict = scenario_test.to_dict()
|
|
306
|
+
|
|
307
|
+
# Handle output
|
|
308
|
+
if output is not None and output != "":
|
|
309
|
+
with open(output, "w") as f:
|
|
310
|
+
json.dump(scenario_test_dict, f, indent=2)
|
|
311
|
+
|
|
312
|
+
success(msg=f"Scenario test output saved to [magenta]{output}[/magenta].")
|
|
313
|
+
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
print_json(scenario_test_dict)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def build_scenarios(scenarios: list[str]) -> list[Scenario]:
|
|
320
|
+
"""
|
|
321
|
+
Build a list of Scenario objects from CLI JSON input.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
scenarios : list[str]
|
|
326
|
+
List of scenario JSON strings provided via the CLI. Each string can be
|
|
327
|
+
a single scenario object or a list of scenario objects.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
list[Scenario]
|
|
332
|
+
The built list of Scenario objects.
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
scenario_list = []
|
|
336
|
+
|
|
337
|
+
for scenario_str in scenarios:
|
|
338
|
+
try:
|
|
339
|
+
scenario_data = json.loads(scenario_str)
|
|
340
|
+
|
|
341
|
+
# Handle the case where the value is a list of scenarios.
|
|
342
|
+
if isinstance(scenario_data, list):
|
|
343
|
+
scenario_list.extend(_process_scenario_list(scenario_data, scenario_str))
|
|
344
|
+
|
|
345
|
+
# Handle the case where the value is a single scenario.
|
|
346
|
+
elif isinstance(scenario_data, dict):
|
|
347
|
+
scenario_list.append(_process_single_scenario(scenario_data, scenario_str))
|
|
348
|
+
|
|
349
|
+
else:
|
|
350
|
+
error(
|
|
351
|
+
f"Invalid scenario format: [magenta]{scenario_str}[/magenta]. "
|
|
352
|
+
"Expected [magenta]json[/magenta] object or array."
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
|
356
|
+
error(f"Invalid scenario format: [magenta]{scenario_str}[/magenta]. Error: {e}")
|
|
357
|
+
|
|
358
|
+
return scenario_list
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _process_scenario_list(scenario_data: list, scenario_str: str) -> list[Scenario]:
|
|
362
|
+
"""
|
|
363
|
+
Process a list of scenario dictionaries into Scenario objects.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
scenario_data : list
|
|
368
|
+
List of scenario dictionaries.
|
|
369
|
+
scenario_str : str
|
|
370
|
+
Original string for error messages.
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
list[Scenario]
|
|
375
|
+
List of processed Scenario objects.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
processed_scenarios = []
|
|
379
|
+
for ix, item in enumerate(scenario_data):
|
|
380
|
+
_validate_scenario_fields(item, scenario_str, ix)
|
|
381
|
+
scenario = Scenario.from_dict(item)
|
|
382
|
+
processed_scenarios.append(scenario)
|
|
383
|
+
|
|
384
|
+
return processed_scenarios
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _process_single_scenario(scenario_data: dict, scenario_str: str) -> "Scenario":
|
|
388
|
+
"""
|
|
389
|
+
Process a single scenario dictionary into a Scenario object.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
scenario_data : dict
|
|
394
|
+
Scenario dictionary.
|
|
395
|
+
scenario_str : str
|
|
396
|
+
Original string for error messages.
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
Scenario
|
|
401
|
+
Processed Scenario object.
|
|
402
|
+
"""
|
|
403
|
+
|
|
404
|
+
_validate_scenario_fields(scenario_data, scenario_str)
|
|
405
|
+
|
|
406
|
+
return Scenario.from_dict(scenario_data)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _validate_scenario_fields(scenario_data: dict, scenario_str: str, index: int | None = None) -> None:
|
|
410
|
+
"""
|
|
411
|
+
Validate that required fields are present in a scenario dictionary.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
scenario_data : dict
|
|
416
|
+
Scenario dictionary to validate.
|
|
417
|
+
scenario_str : str
|
|
418
|
+
Original string for error messages.
|
|
419
|
+
index : int | None
|
|
420
|
+
Index in array if validating array element (for error reporting).
|
|
421
|
+
"""
|
|
422
|
+
location = f"at index [magenta]{index}[/magenta] in " if index is not None else "in "
|
|
423
|
+
|
|
424
|
+
if scenario_data.get("instance_id") is None:
|
|
425
|
+
error(
|
|
426
|
+
f"Invalid scenario format {location}"
|
|
427
|
+
f"[magenta]{scenario_str}[/magenta]. Each scenario must have an "
|
|
428
|
+
"[magenta]instance_id[/magenta] field."
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
scenario_input = scenario_data.get("scenario_input")
|
|
432
|
+
if scenario_input is None:
|
|
433
|
+
error(
|
|
434
|
+
f"Invalid scenario format {location}"
|
|
435
|
+
f"[magenta]{scenario_str}[/magenta]. Each scenario must have a "
|
|
436
|
+
"[magenta]scenario_input[/magenta] field."
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
if scenario_input.get("scenario_input_type") is None:
|
|
440
|
+
error(
|
|
441
|
+
f"Invalid scenario format {location}"
|
|
442
|
+
f"[magenta]{scenario_str}[/magenta]. Each [magenta]scenario_input[/magenta] must have a "
|
|
443
|
+
"[magenta]scenario_input_type[/magenta] field."
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if scenario_input.get("scenario_input_data") is None:
|
|
447
|
+
error(
|
|
448
|
+
f"Invalid scenario format {location}"
|
|
449
|
+
f"[magenta]{scenario_str}[/magenta]. Each [magenta]scenario_input[/magenta] must have a "
|
|
450
|
+
"[magenta]scenario_input_data[/magenta] field."
|
|
451
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud scenario delete command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from nextmv.cli.configuration.config import build_app
|
|
10
|
+
from nextmv.cli.confirm import get_confirmation
|
|
11
|
+
from nextmv.cli.message import info, success
|
|
12
|
+
from nextmv.cli.options import AppIDOption, ProfileOption, ScenarioTestIDOption
|
|
13
|
+
|
|
14
|
+
# Set up subcommand application.
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def delete(
|
|
20
|
+
app_id: AppIDOption,
|
|
21
|
+
scenario_test_id: ScenarioTestIDOption,
|
|
22
|
+
yes: Annotated[
|
|
23
|
+
bool,
|
|
24
|
+
typer.Option(
|
|
25
|
+
"--yes",
|
|
26
|
+
"-y",
|
|
27
|
+
help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
|
|
28
|
+
),
|
|
29
|
+
] = False,
|
|
30
|
+
profile: ProfileOption = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Deletes a Nextmv Cloud scenario test.
|
|
34
|
+
|
|
35
|
+
This action is permanent and cannot be undone. The scenario test and all
|
|
36
|
+
associated data will be deleted. Use the --yes flag to skip
|
|
37
|
+
the confirmation prompt.
|
|
38
|
+
|
|
39
|
+
[bold][underline]Examples[/underline][/bold]
|
|
40
|
+
|
|
41
|
+
- Delete the scenario test with the ID [magenta]hop-analysis[/magenta] from application
|
|
42
|
+
[magenta]hare-app[/magenta].
|
|
43
|
+
$ [dim]nextmv cloud scenario delete --app-id hare-app --scenario-test-id hop-analysis[/dim]
|
|
44
|
+
|
|
45
|
+
- Delete the scenario test without confirmation prompt.
|
|
46
|
+
$ [dim]nextmv cloud scenario delete --app-id hare-app --scenario-test-id carrot-routes --yes[/dim]
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
if not yes:
|
|
50
|
+
confirm = get_confirmation(
|
|
51
|
+
f"Are you sure you want to delete scenario test [magenta]{scenario_test_id}[/magenta] "
|
|
52
|
+
f"from application [magenta]{app_id}[/magenta]? This action cannot be undone.",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if not confirm:
|
|
56
|
+
info(f"Scenario test [magenta]{scenario_test_id}[/magenta] will not be deleted.")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
cloud_app = build_app(app_id=app_id, profile=profile)
|
|
60
|
+
cloud_app.delete_scenario_test(scenario_test_id=scenario_test_id)
|
|
61
|
+
success(msg=f"Scenario test [magenta]{scenario_test_id}[/magenta] deleted.")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud scenario get command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from nextmv.cli.configuration.config import build_app
|
|
11
|
+
from nextmv.cli.message import in_progress, print_json, success
|
|
12
|
+
from nextmv.cli.options import AppIDOption, ProfileOption, ScenarioTestIDOption
|
|
13
|
+
from nextmv.polling import default_polling_options
|
|
14
|
+
|
|
15
|
+
# Set up subcommand application.
|
|
16
|
+
app = typer.Typer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
def get(
|
|
21
|
+
app_id: AppIDOption,
|
|
22
|
+
scenario_test_id: ScenarioTestIDOption,
|
|
23
|
+
output: Annotated[
|
|
24
|
+
str | None,
|
|
25
|
+
typer.Option(
|
|
26
|
+
"--output",
|
|
27
|
+
"-o",
|
|
28
|
+
help="Waits for the scenario test to complete and saves the results to this location.",
|
|
29
|
+
metavar="OUTPUT_PATH",
|
|
30
|
+
),
|
|
31
|
+
] = None,
|
|
32
|
+
timeout: Annotated[
|
|
33
|
+
int,
|
|
34
|
+
typer.Option(
|
|
35
|
+
help="The maximum time in seconds to wait for results when polling. Poll indefinitely if not set.",
|
|
36
|
+
metavar="TIMEOUT_SECONDS",
|
|
37
|
+
),
|
|
38
|
+
] = -1,
|
|
39
|
+
wait: Annotated[
|
|
40
|
+
bool,
|
|
41
|
+
typer.Option(
|
|
42
|
+
"--wait",
|
|
43
|
+
"-w",
|
|
44
|
+
help="Wait for the scenario test to complete. Results are printed to [magenta]stdout[/magenta]. "
|
|
45
|
+
"Specify output location with --output.",
|
|
46
|
+
),
|
|
47
|
+
] = False,
|
|
48
|
+
profile: ProfileOption = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Get a Nextmv Cloud scenario test, including its runs.
|
|
52
|
+
|
|
53
|
+
Use the --wait flag to wait for the scenario test to
|
|
54
|
+
complete, polling for results. Using the --output flag will
|
|
55
|
+
also activate waiting, and allows you to specify a destination file for the
|
|
56
|
+
results.
|
|
57
|
+
|
|
58
|
+
[bold][underline]Examples[/underline][/bold]
|
|
59
|
+
|
|
60
|
+
- Get the scenario test with ID [magenta]carrot-optimization[/magenta] from application
|
|
61
|
+
[magenta]hare-app[/magenta].
|
|
62
|
+
$ [dim]nextmv cloud scenario get --app-id hare-app --scenario-test-id carrot-optimization[/dim]
|
|
63
|
+
|
|
64
|
+
- Get the scenario test and wait for it to complete if necessary.
|
|
65
|
+
$ [dim]nextmv cloud scenario get --app-id hare-app --scenario-test-id bunny-hop-test --wait[/dim]
|
|
66
|
+
|
|
67
|
+
- Get the scenario test and save the results to a file.
|
|
68
|
+
$ [dim]nextmv cloud scenario get --app-id hare-app --scenario-test-id warren-planning \\
|
|
69
|
+
--output results.json[/dim]
|
|
70
|
+
|
|
71
|
+
- Get the scenario test using a specific profile.
|
|
72
|
+
$ [dim]nextmv cloud scenario get --app-id hare-app --scenario-test-id lettuce-routes --profile prod[/dim]
|
|
73
|
+
"""
|
|
74
|
+
cloud_app = build_app(app_id=app_id, profile=profile)
|
|
75
|
+
|
|
76
|
+
# Build the polling options.
|
|
77
|
+
polling_options = default_polling_options()
|
|
78
|
+
polling_options.max_duration = timeout
|
|
79
|
+
|
|
80
|
+
# Determine if we should wait
|
|
81
|
+
should_wait = wait or (output is not None and output != "")
|
|
82
|
+
|
|
83
|
+
in_progress(msg="Getting scenario test...")
|
|
84
|
+
if should_wait:
|
|
85
|
+
scenario_test = cloud_app.scenario_test_with_polling(
|
|
86
|
+
scenario_test_id=scenario_test_id,
|
|
87
|
+
polling_options=polling_options,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
scenario_test = cloud_app.scenario_test(scenario_test_id=scenario_test_id)
|
|
91
|
+
|
|
92
|
+
scenario_test_dict = scenario_test.to_dict()
|
|
93
|
+
|
|
94
|
+
if output is not None and output != "":
|
|
95
|
+
with open(output, "w") as f:
|
|
96
|
+
json.dump(scenario_test_dict, f, indent=2)
|
|
97
|
+
|
|
98
|
+
success(msg=f"Scenario test results saved to [magenta]{output}[/magenta].")
|
|
99
|
+
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
print_json(scenario_test_dict)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud scenario list command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from nextmv.cli.configuration.config import build_app
|
|
11
|
+
from nextmv.cli.message import in_progress, print_json, success
|
|
12
|
+
from nextmv.cli.options import AppIDOption, ProfileOption
|
|
13
|
+
|
|
14
|
+
# Set up subcommand application.
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def list(
|
|
20
|
+
app_id: AppIDOption,
|
|
21
|
+
output: Annotated[
|
|
22
|
+
str | None,
|
|
23
|
+
typer.Option(
|
|
24
|
+
"--output",
|
|
25
|
+
"-o",
|
|
26
|
+
help="Saves the list of scenario tests to this location.",
|
|
27
|
+
metavar="OUTPUT_PATH",
|
|
28
|
+
),
|
|
29
|
+
] = None,
|
|
30
|
+
profile: ProfileOption = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
List all Nextmv Cloud scenario tests for an application.
|
|
34
|
+
|
|
35
|
+
This command retrieves all scenario tests associated with the specified
|
|
36
|
+
application.
|
|
37
|
+
|
|
38
|
+
[bold][underline]Examples[/underline][/bold]
|
|
39
|
+
|
|
40
|
+
- List all scenario tests for application [magenta]hare-app[/magenta].
|
|
41
|
+
$ [dim]nextmv cloud scenario list --app-id hare-app[/dim]
|
|
42
|
+
|
|
43
|
+
- List all scenario tests and save to a file.
|
|
44
|
+
$ [dim]nextmv cloud scenario list --app-id hare-app --output scenario_tests.json[/dim]
|
|
45
|
+
|
|
46
|
+
- List all scenario tests using a specific profile.
|
|
47
|
+
$ [dim]nextmv cloud scenario list --app-id hare-app --profile prod[/dim]
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
cloud_app = build_app(app_id=app_id, profile=profile)
|
|
51
|
+
in_progress(msg="Listing scenario tests...")
|
|
52
|
+
scenario_tests = cloud_app.list_scenario_tests()
|
|
53
|
+
scenario_tests_dict = [exp.to_dict() for exp in scenario_tests]
|
|
54
|
+
|
|
55
|
+
if output is not None and output != "":
|
|
56
|
+
with open(output, "w") as f:
|
|
57
|
+
json.dump(scenario_tests_dict, f, indent=2)
|
|
58
|
+
|
|
59
|
+
success(msg=f"Scenario tests list saved to [magenta]{output}[/magenta].")
|
|
60
|
+
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
print_json(scenario_tests_dict)
|