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
|
@@ -7,7 +7,10 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
|
-
from nextmv.cli.
|
|
10
|
+
from nextmv.cli.confirm import get_confirmation
|
|
11
|
+
from nextmv.cli.message import error, success, warning
|
|
12
|
+
from nextmv.cloud.account import Account
|
|
13
|
+
from nextmv.cloud.application import Application
|
|
11
14
|
from nextmv.cloud.client import Client
|
|
12
15
|
|
|
13
16
|
# Some useful constants.
|
|
@@ -57,6 +60,18 @@ def save_config(config: dict[str, Any]) -> None:
|
|
|
57
60
|
yaml.safe_dump(config, file)
|
|
58
61
|
|
|
59
62
|
|
|
63
|
+
def non_profile_keys() -> set[str]:
|
|
64
|
+
"""
|
|
65
|
+
Returns the set of keys that are not profile names in the configuration.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
set[str]
|
|
70
|
+
The set of non-profile keys.
|
|
71
|
+
"""
|
|
72
|
+
return {API_KEY_KEY, ENDPOINT_KEY}
|
|
73
|
+
|
|
74
|
+
|
|
60
75
|
def build_client(profile: str | None = None) -> Client:
|
|
61
76
|
"""
|
|
62
77
|
Builds a `cloud.Client` using the API key and endpoint for the given
|
|
@@ -89,27 +104,109 @@ def build_client(profile: str | None = None) -> Client:
|
|
|
89
104
|
|
|
90
105
|
if profile is not None:
|
|
91
106
|
if profile not in config:
|
|
92
|
-
error(
|
|
107
|
+
error(
|
|
108
|
+
f"Profile [magenta]{profile}[/magenta] does not exist. "
|
|
109
|
+
"Create it using [code]nextmv configuration create[/code] with the --profile option."
|
|
110
|
+
)
|
|
93
111
|
|
|
94
112
|
api_key = config[profile].get(API_KEY_KEY)
|
|
95
113
|
if api_key is None or api_key == "":
|
|
96
|
-
error(
|
|
114
|
+
error(
|
|
115
|
+
f"API key for profile [magenta]{profile}[/magenta] is not set or is empty. "
|
|
116
|
+
"Set it using [code]nextmv configuration create[/code] with the --profile and --api-key options."
|
|
117
|
+
)
|
|
97
118
|
|
|
98
119
|
endpoint = config[profile].get(ENDPOINT_KEY)
|
|
99
120
|
if endpoint is None or endpoint == "":
|
|
100
|
-
error(
|
|
121
|
+
error(
|
|
122
|
+
f"Endpoint for profile [magenta]{profile}[/magenta] is not set or is empty. "
|
|
123
|
+
"Please run [code]nextmv configuration create[/code]."
|
|
124
|
+
)
|
|
101
125
|
else:
|
|
102
126
|
api_key = config.get(API_KEY_KEY)
|
|
103
127
|
if api_key is None or api_key == "":
|
|
104
|
-
error(
|
|
128
|
+
error(
|
|
129
|
+
"Default API key is not set or is empty. "
|
|
130
|
+
"Please run [code]nextmv configuration create[/code] with the --api-key option."
|
|
131
|
+
)
|
|
105
132
|
|
|
106
133
|
endpoint = config.get(ENDPOINT_KEY)
|
|
107
134
|
if endpoint is None or endpoint == "":
|
|
108
|
-
error("Default endpoint is not set or is empty.")
|
|
135
|
+
error("Default endpoint is not set or is empty. Please run [code]nextmv configuration create[/code].")
|
|
109
136
|
|
|
110
137
|
return Client(api_key=api_key, url=f"https://{endpoint}")
|
|
111
138
|
|
|
112
139
|
|
|
140
|
+
def build_app(app_id: str, profile: str | None = None) -> Application:
|
|
141
|
+
"""
|
|
142
|
+
Builds a `cloud.Application` using the given application ID and the API
|
|
143
|
+
key and endpoint for the given profile. If no profile is given, the default
|
|
144
|
+
profile is used. If the application does not exist, an exception is raised.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
app_id : str
|
|
149
|
+
The application ID.
|
|
150
|
+
profile : str | None
|
|
151
|
+
The profile name to use. If None, the default profile is used.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
Application
|
|
156
|
+
An application object for the given application ID.
|
|
157
|
+
|
|
158
|
+
Raises
|
|
159
|
+
------
|
|
160
|
+
typer.Exit
|
|
161
|
+
If the application does not exist.
|
|
162
|
+
"""
|
|
163
|
+
client = build_client(profile)
|
|
164
|
+
exists = Application.exists(client=client, id=app_id)
|
|
165
|
+
if exists:
|
|
166
|
+
return Application(client=client, id=app_id)
|
|
167
|
+
|
|
168
|
+
warning(f"Application with ID [magenta]{app_id}[/magenta] does not exist.")
|
|
169
|
+
should_create = get_confirmation(f"Do you want to create a new application with ID [magenta]{app_id}[/magenta]?")
|
|
170
|
+
if not should_create:
|
|
171
|
+
error(
|
|
172
|
+
f"Application with ID [magenta]{app_id}[/magenta] was not created and does not exist. "
|
|
173
|
+
"Use [code]nextmv cloud app create[/code] to create a new app."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
app = Application.new(client=client, id=app_id, name=app_id)
|
|
177
|
+
success(f"Application with ID and name [magenta]{app_id}[/magenta] created successfully.")
|
|
178
|
+
|
|
179
|
+
return app
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def build_account(account_id: str | None = None, profile: str | None = None) -> Account:
|
|
183
|
+
"""
|
|
184
|
+
Builds a `cloud.Account` using the API key and endpoint for the given
|
|
185
|
+
profile. If no profile is given, the default profile is used.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
account_id : str | None
|
|
190
|
+
The account ID. If None, no account ID is set.
|
|
191
|
+
profile : str | None
|
|
192
|
+
The profile name to use. If None, the default profile is used.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
Account
|
|
197
|
+
An account object for the configured profile.
|
|
198
|
+
|
|
199
|
+
Raises
|
|
200
|
+
------
|
|
201
|
+
typer.Exit
|
|
202
|
+
If the configuration is invalid or missing.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
client = build_client(profile)
|
|
206
|
+
|
|
207
|
+
return Account(account_id=account_id, client=client)
|
|
208
|
+
|
|
209
|
+
|
|
113
210
|
def obscure_api_key(api_key: str) -> str:
|
|
114
211
|
"""
|
|
115
212
|
Obscure an API key for display purposes.
|
|
@@ -4,7 +4,6 @@ This module defines the configuration create command for the Nextmv CLI.
|
|
|
4
4
|
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
import rich
|
|
8
7
|
import typer
|
|
9
8
|
|
|
10
9
|
from nextmv.cli.configuration.config import (
|
|
@@ -15,7 +14,7 @@ from nextmv.cli.configuration.config import (
|
|
|
15
14
|
obscure_api_key,
|
|
16
15
|
save_config,
|
|
17
16
|
)
|
|
18
|
-
from nextmv.cli.
|
|
17
|
+
from nextmv.cli.message import error, message, success
|
|
19
18
|
|
|
20
19
|
# Set up subcommand application.
|
|
21
20
|
app = typer.Typer()
|
|
@@ -34,6 +33,14 @@ def create(
|
|
|
34
33
|
metavar="NEXTMV_API_KEY",
|
|
35
34
|
),
|
|
36
35
|
],
|
|
36
|
+
endpoint: Annotated[ # Hidden because it is meant for internal use.
|
|
37
|
+
str | None,
|
|
38
|
+
typer.Option(
|
|
39
|
+
"--endpoint",
|
|
40
|
+
"-e",
|
|
41
|
+
hidden=True,
|
|
42
|
+
),
|
|
43
|
+
] = DEFAULT_ENDPOINT,
|
|
37
44
|
profile: Annotated[ # Similar to nextmv.cli.options.ProfileOption but with different help text.
|
|
38
45
|
str | None,
|
|
39
46
|
typer.Option(
|
|
@@ -44,14 +51,6 @@ def create(
|
|
|
44
51
|
metavar="PROFILE_NAME",
|
|
45
52
|
),
|
|
46
53
|
] = None,
|
|
47
|
-
endpoint: Annotated[ # Hidden because it is meant for internal use.
|
|
48
|
-
str | None,
|
|
49
|
-
typer.Option(
|
|
50
|
-
"--endpoint",
|
|
51
|
-
"-e",
|
|
52
|
-
hidden=True,
|
|
53
|
-
),
|
|
54
|
-
] = DEFAULT_ENDPOINT,
|
|
55
54
|
) -> None:
|
|
56
55
|
"""
|
|
57
56
|
Create a new configuration or update an existing one.
|
|
@@ -59,14 +58,14 @@ def create(
|
|
|
59
58
|
[bold][underline]Examples[/underline][/bold]
|
|
60
59
|
|
|
61
60
|
- Default configuration.
|
|
62
|
-
$ [
|
|
61
|
+
$ [dim]nextmv configuration create --api-key NEXTMV_API_KEY[/dim]
|
|
63
62
|
|
|
64
|
-
- Configure a profile named [
|
|
65
|
-
$ [
|
|
63
|
+
- Configure a profile named [magenta]hare[/magenta].
|
|
64
|
+
$ [dim]nextmv configuration create --api-key NEXTMV_API_KEY --profile hare[/dim]
|
|
66
65
|
"""
|
|
67
66
|
|
|
68
67
|
if profile is not None and profile.strip().lower() == "default":
|
|
69
|
-
error("[
|
|
68
|
+
error("[magenta]default[/magenta] is a reserved profile name.")
|
|
70
69
|
|
|
71
70
|
endpoint = str(endpoint)
|
|
72
71
|
if endpoint.startswith("https://"):
|
|
@@ -88,8 +87,8 @@ def create(
|
|
|
88
87
|
|
|
89
88
|
save_config(config)
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
success("Configuration saved successfully.")
|
|
91
|
+
message(f"\t[bold]Profile[/bold]: [magenta]{profile or 'Default'}[/magenta]")
|
|
92
|
+
message(f"\t[bold]API Key[/bold]: [magenta]{obscure_api_key(api_key)}[/magenta]")
|
|
94
93
|
if endpoint != DEFAULT_ENDPOINT:
|
|
95
|
-
|
|
94
|
+
message(f"\t[bold]Endpoint[/bold]: [magenta]{endpoint}[/magenta]")
|
|
@@ -4,12 +4,11 @@ This module defines the configuration delete command for the Nextmv CLI.
|
|
|
4
4
|
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
import rich
|
|
8
7
|
import typer
|
|
9
|
-
from rich.prompt import Confirm
|
|
10
8
|
|
|
11
9
|
from nextmv.cli.configuration.config import load_config, save_config
|
|
12
|
-
from nextmv.cli.
|
|
10
|
+
from nextmv.cli.confirm import get_confirmation
|
|
11
|
+
from nextmv.cli.message import error, info, success
|
|
13
12
|
|
|
14
13
|
# Set up subcommand application.
|
|
15
14
|
app = typer.Typer()
|
|
@@ -27,29 +26,42 @@ def delete(
|
|
|
27
26
|
metavar="PROFILE_NAME",
|
|
28
27
|
),
|
|
29
28
|
],
|
|
29
|
+
yes: Annotated[
|
|
30
|
+
bool,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--yes",
|
|
33
|
+
"-y",
|
|
34
|
+
help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
|
|
35
|
+
),
|
|
36
|
+
] = False,
|
|
30
37
|
) -> None:
|
|
31
38
|
"""
|
|
32
39
|
Delete a profile from the configuration.
|
|
33
40
|
|
|
41
|
+
Use the --yes flag to skip the confirmation prompt.
|
|
42
|
+
|
|
34
43
|
[bold][underline]Examples[/underline][/bold]
|
|
35
44
|
|
|
36
45
|
- Delete a profile named [magenta]hare[/magenta].
|
|
37
|
-
$ [
|
|
46
|
+
$ [dim]nextmv configuration delete --profile hare[/dim]
|
|
47
|
+
|
|
48
|
+
- Delete a profile named [magenta]hare[/magenta] without confirmation prompt.
|
|
49
|
+
$ [dim]nextmv configuration delete --profile hare --yes[/dim]
|
|
38
50
|
"""
|
|
39
51
|
config = load_config()
|
|
40
52
|
if profile not in config:
|
|
41
|
-
error(f"Profile [
|
|
53
|
+
error(f"Profile [magenta]{profile}[/magenta] does not exist.")
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
if not yes:
|
|
56
|
+
confirm = get_confirmation(
|
|
57
|
+
f"Are you sure you want to delete profile [magenta]{profile}[/magenta]? This action cannot be undone.",
|
|
58
|
+
)
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
if not confirm:
|
|
61
|
+
info(f"Profile [magenta]{profile}[/magenta] will not be deleted.")
|
|
62
|
+
return
|
|
51
63
|
|
|
52
64
|
del config[profile]
|
|
53
65
|
save_config(config)
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
success(f"Profile [magenta]{profile}[/magenta] deleted successfully.")
|
nextmv/cli/configuration/list.py
CHANGED
|
@@ -6,8 +6,8 @@ import typer
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
|
8
8
|
|
|
9
|
-
from nextmv.cli.configuration.config import API_KEY_KEY, ENDPOINT_KEY, load_config, obscure_api_key
|
|
10
|
-
from nextmv.cli.
|
|
9
|
+
from nextmv.cli.configuration.config import API_KEY_KEY, ENDPOINT_KEY, load_config, non_profile_keys, obscure_api_key
|
|
10
|
+
from nextmv.cli.message import error
|
|
11
11
|
|
|
12
12
|
# Set up subcommand application.
|
|
13
13
|
app = typer.Typer()
|
|
@@ -22,7 +22,7 @@ def list() -> None:
|
|
|
22
22
|
[bold][underline]Examples[/underline][/bold]
|
|
23
23
|
|
|
24
24
|
- Show current configuration and all profiles.
|
|
25
|
-
$ [
|
|
25
|
+
$ [dim]nextmv configuration list[/dim]
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
config = load_config()
|
|
@@ -38,7 +38,7 @@ def list() -> None:
|
|
|
38
38
|
|
|
39
39
|
for k, v in config.items():
|
|
40
40
|
# Skip default configuration.
|
|
41
|
-
if k in
|
|
41
|
+
if k in non_profile_keys():
|
|
42
42
|
continue
|
|
43
43
|
|
|
44
44
|
profile = {
|
nextmv/cli/confirm.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from rich.prompt import Confirm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_confirmation(msg: str, default: bool = False) -> bool:
|
|
7
|
+
"""
|
|
8
|
+
Method to get a yes/no confirmation from the user.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
msg : str
|
|
13
|
+
The message to display to the user.
|
|
14
|
+
default : bool, optional
|
|
15
|
+
The default value if the user just presses Enter. Default is False.
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
bool
|
|
20
|
+
True if the user confirmed, False otherwise.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# If this is not an interactive terminal, do not ask for confirmation, to
|
|
24
|
+
# avoid hanging indefinitely waiting for a user response.
|
|
25
|
+
if not sys.stdin.isatty():
|
|
26
|
+
return default
|
|
27
|
+
|
|
28
|
+
return Confirm.ask(
|
|
29
|
+
msg,
|
|
30
|
+
default=default,
|
|
31
|
+
case_sensitive=False,
|
|
32
|
+
show_default=True,
|
|
33
|
+
show_choices=True,
|
|
34
|
+
)
|
nextmv/cli/main.py
CHANGED
|
@@ -13,17 +13,24 @@ about the features used here. An example of Rich markup can be found in the
|
|
|
13
13
|
epilog of the Typer application defined below.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import sys
|
|
17
|
+
from typing import Annotated
|
|
17
18
|
|
|
18
19
|
import rich
|
|
19
20
|
import typer
|
|
20
|
-
from
|
|
21
|
+
from typer import rich_utils
|
|
21
22
|
|
|
22
|
-
from nextmv.cli.
|
|
23
|
+
from nextmv.cli.cloud import app as cloud_app
|
|
24
|
+
from nextmv.cli.community import app as community_app
|
|
25
|
+
from nextmv.cli.configuration import app as configuration_app
|
|
23
26
|
from nextmv.cli.configuration.config import CONFIG_DIR, GO_CLI_PATH, load_config
|
|
24
|
-
from nextmv.cli.
|
|
25
|
-
from nextmv.cli.
|
|
27
|
+
from nextmv.cli.confirm import get_confirmation
|
|
28
|
+
from nextmv.cli.message import error, info, success, warning
|
|
26
29
|
from nextmv.cli.version import app as version_app
|
|
30
|
+
from nextmv.cli.version import version_callback
|
|
31
|
+
|
|
32
|
+
# Disable dim text for the extended help of commands.
|
|
33
|
+
rich_utils.STYLE_HELPTEXT = ""
|
|
27
34
|
|
|
28
35
|
# Main CLI application.
|
|
29
36
|
app = typer.Typer(
|
|
@@ -32,22 +39,45 @@ app = typer.Typer(
|
|
|
32
39
|
rich_markup_mode="rich",
|
|
33
40
|
context_settings={"help_option_names": ["--help", "-h"]},
|
|
34
41
|
no_args_is_help=True,
|
|
42
|
+
invoke_without_command=True,
|
|
43
|
+
pretty_exceptions_show_locals=False,
|
|
35
44
|
)
|
|
36
45
|
|
|
37
46
|
# Register subcommands. The `name` parameter is required when the subcommand
|
|
38
47
|
# module has a callback function defined.
|
|
48
|
+
app.add_typer(cloud_app, name="cloud")
|
|
39
49
|
app.add_typer(community_app, name="community")
|
|
40
50
|
app.add_typer(configuration_app, name="configuration")
|
|
41
51
|
app.add_typer(version_app)
|
|
42
52
|
|
|
43
53
|
|
|
44
54
|
@app.callback()
|
|
45
|
-
def callback(
|
|
55
|
+
def callback(
|
|
56
|
+
ctx: typer.Context,
|
|
57
|
+
version: Annotated[
|
|
58
|
+
bool | None,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--version",
|
|
61
|
+
"-v",
|
|
62
|
+
help="Show the current version of the Nextmv CLI.",
|
|
63
|
+
callback=version_callback,
|
|
64
|
+
),
|
|
65
|
+
] = None,
|
|
66
|
+
) -> None:
|
|
46
67
|
"""
|
|
47
68
|
Callback function that runs before any command. Useful for checks on the
|
|
48
69
|
environment.
|
|
49
70
|
"""
|
|
50
71
|
|
|
72
|
+
# Skip checks for help commands.
|
|
73
|
+
if "--help" in sys.argv or "-h" in sys.argv:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Skip checks for certain commands.
|
|
77
|
+
ignored_commands = {"configuration", "version"}
|
|
78
|
+
if ctx.invoked_subcommand in ignored_commands:
|
|
79
|
+
return
|
|
80
|
+
|
|
51
81
|
handle_go_cli()
|
|
52
82
|
handle_config_existence(ctx)
|
|
53
83
|
|
|
@@ -62,18 +92,20 @@ def handle_go_cli() -> None:
|
|
|
62
92
|
|
|
63
93
|
exists = go_cli_exists()
|
|
64
94
|
if exists:
|
|
65
|
-
delete =
|
|
95
|
+
delete = get_confirmation(
|
|
66
96
|
"Do you want to delete the [italic red]deprecated[/italic red] Nextmv CLI "
|
|
67
|
-
f"at [
|
|
68
|
-
default=True,
|
|
97
|
+
f"at [magenta]{GO_CLI_PATH}[/magenta] now?"
|
|
69
98
|
)
|
|
70
99
|
if delete:
|
|
71
100
|
remove_go_cli()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
info(
|
|
104
|
+
"You can delete the [italic red]deprecated[/italic red] Nextmv CLI later by removing "
|
|
105
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. "
|
|
106
|
+
"Make sure you also clean up your [code]PATH[/code], "
|
|
107
|
+
f"by removing references to [magenta]{CONFIG_DIR}[/magenta] from it."
|
|
108
|
+
)
|
|
77
109
|
|
|
78
110
|
|
|
79
111
|
def handle_config_existence(ctx: typer.Context) -> None:
|
|
@@ -86,10 +118,6 @@ def handle_config_existence(ctx: typer.Context) -> None:
|
|
|
86
118
|
The Typer context object.
|
|
87
119
|
"""
|
|
88
120
|
|
|
89
|
-
ignored_commands = {"configuration", "version"}
|
|
90
|
-
if ctx.invoked_subcommand in ignored_commands:
|
|
91
|
-
return
|
|
92
|
-
|
|
93
121
|
config = load_config()
|
|
94
122
|
if config == {}:
|
|
95
123
|
error("No configuration found. Please run [code]nextmv configuration create[/code].")
|
|
@@ -109,13 +137,11 @@ def go_cli_exists() -> bool:
|
|
|
109
137
|
# Check if the Go CLI executable exists
|
|
110
138
|
exists = GO_CLI_PATH.exists()
|
|
111
139
|
if exists:
|
|
112
|
-
|
|
113
|
-
"
|
|
114
|
-
f"[
|
|
140
|
+
warning(
|
|
141
|
+
"A [italic red]deprecated[/italic red] Nextmv CLI is installed at "
|
|
142
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. You should delete it to avoid conflicts."
|
|
115
143
|
)
|
|
116
144
|
|
|
117
|
-
check_config_in_path()
|
|
118
|
-
|
|
119
145
|
return exists
|
|
120
146
|
|
|
121
147
|
|
|
@@ -126,21 +152,27 @@ def remove_go_cli() -> None:
|
|
|
126
152
|
|
|
127
153
|
if GO_CLI_PATH.exists():
|
|
128
154
|
GO_CLI_PATH.unlink()
|
|
129
|
-
|
|
155
|
+
success(f"Deleted [italic red]deprecated[/italic red] [magenta]{GO_CLI_PATH}[/magenta].")
|
|
130
156
|
|
|
131
|
-
check_config_in_path()
|
|
132
157
|
|
|
133
|
-
|
|
134
|
-
def check_config_in_path() -> None:
|
|
135
|
-
"""
|
|
136
|
-
Check if the configuration directory is in the PATH and notify the user.
|
|
158
|
+
def main() -> None:
|
|
137
159
|
"""
|
|
160
|
+
Entry point for the CLI with global exception handling.
|
|
138
161
|
|
|
139
|
-
|
|
140
|
-
|
|
162
|
+
Catches all exceptions except Typer/Click exceptions (which handle their
|
|
163
|
+
own exit codes) and displays a clean error message instead of a traceback.
|
|
164
|
+
"""
|
|
141
165
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
try:
|
|
167
|
+
app()
|
|
168
|
+
except (typer.Exit, typer.Abort, SystemExit):
|
|
169
|
+
raise
|
|
170
|
+
except Exception as e:
|
|
171
|
+
# We do not use the messages.error function here because doing so would
|
|
172
|
+
# raise a Typer exception, which would print a traceback.
|
|
173
|
+
msg = str(e).rstrip("\n")
|
|
174
|
+
if not msg.endswith("."):
|
|
175
|
+
msg += "."
|
|
176
|
+
|
|
177
|
+
rich.print(f"[red]Error:[/red] {msg}", file=sys.stderr)
|
|
178
|
+
sys.exit(1)
|