nextmv 0.39.0.dev1__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 +86 -0
- nextmv/cli/community/list.py +200 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +228 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/confirm.py +34 -0
- nextmv/cli/main.py +161 -3
- nextmv/cli/message.py +170 -0
- nextmv/cli/options.py +220 -0
- nextmv/cli/version.py +22 -2
- 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.39.0.dev1.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/cloud/application.py +0 -4204
- nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
- nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the community clone 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_client
|
|
10
|
+
from nextmv.cli.message import error
|
|
11
|
+
from nextmv.cli.options import ProfileOption
|
|
12
|
+
from nextmv.cloud.community import clone_community_app
|
|
13
|
+
|
|
14
|
+
# Set up subcommand application.
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
|
|
17
|
+
# Helpful constants.
|
|
18
|
+
LATEST_VERSION = "latest"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.command()
|
|
22
|
+
def clone(
|
|
23
|
+
app: Annotated[
|
|
24
|
+
str,
|
|
25
|
+
typer.Option("--app", "-a", help="The name of the community app to clone.", metavar="COMMUNITY_APP"),
|
|
26
|
+
],
|
|
27
|
+
directory: Annotated[
|
|
28
|
+
str | None,
|
|
29
|
+
typer.Option(
|
|
30
|
+
"--directory",
|
|
31
|
+
"-d",
|
|
32
|
+
help="The directory in which to clone the app. Default is the name of the app at current directory.",
|
|
33
|
+
metavar="DIRECTORY",
|
|
34
|
+
),
|
|
35
|
+
] = None,
|
|
36
|
+
version: Annotated[
|
|
37
|
+
str | None,
|
|
38
|
+
typer.Option(
|
|
39
|
+
"--version",
|
|
40
|
+
"-v",
|
|
41
|
+
help="The version of the community app to clone.",
|
|
42
|
+
metavar="VERSION",
|
|
43
|
+
),
|
|
44
|
+
] = LATEST_VERSION,
|
|
45
|
+
profile: ProfileOption = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Clone a community app locally.
|
|
49
|
+
|
|
50
|
+
By default, the [magenta]latest[/magenta] version will be used. You can
|
|
51
|
+
specify a version with the --version flag, and customize the output
|
|
52
|
+
directory with the --directory flag. If you want to list the available
|
|
53
|
+
apps, use the [code]nextmv community list[/code] command.
|
|
54
|
+
|
|
55
|
+
[bold][underline]Examples[/underline][/bold]
|
|
56
|
+
|
|
57
|
+
- Clone the [magenta]go-nextroute[/magenta] community app (under the
|
|
58
|
+
[magenta]"go-nextroute"[/magenta] directory), using the [magenta]latest[/magenta] version.
|
|
59
|
+
$ [dim]nextmv community clone --app go-nextroute[/dim]
|
|
60
|
+
|
|
61
|
+
- Clone the [magenta]go-nextroute[/magenta] community app under the
|
|
62
|
+
[magenta]"~/sample/my_app"[/magenta] directory, using the [magenta]latest[/magenta] version.
|
|
63
|
+
$ [dim]nextmv community clone --app go-nextroute --directory ~/sample/my_app[/dim]
|
|
64
|
+
|
|
65
|
+
- Clone the [magenta]go-nextroute[/magenta] community app (under the
|
|
66
|
+
[magenta]"go-nextroute"[/magenta] directory), using version [magenta]v1.2.0[/magenta].
|
|
67
|
+
$ [dim]nextmv community clone --app go-nextroute --version v1.2.0[/dim]
|
|
68
|
+
|
|
69
|
+
- Clone the [magenta]go-nextroute[/magenta] community app (under the
|
|
70
|
+
[magenta]"go-nextroute"[/magenta] directory), using the [magenta]latest[/magenta] version
|
|
71
|
+
and a profile named [magenta]hare[/magenta].
|
|
72
|
+
$ [dim]nextmv community clone --app go-nextroute --profile hare[/dim]
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
if version is not None and version == "":
|
|
76
|
+
error("The --version flag cannot be an empty string.")
|
|
77
|
+
|
|
78
|
+
client = build_client(profile)
|
|
79
|
+
clone_community_app(
|
|
80
|
+
client=client,
|
|
81
|
+
app=app,
|
|
82
|
+
directory=directory,
|
|
83
|
+
version=version,
|
|
84
|
+
verbose=True,
|
|
85
|
+
rich_print=True,
|
|
86
|
+
)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the community list command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import rich
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from nextmv.cli.configuration.config import build_client
|
|
13
|
+
from nextmv.cli.message import error
|
|
14
|
+
from nextmv.cli.options import ProfileOption
|
|
15
|
+
from nextmv.cloud.client import Client
|
|
16
|
+
from nextmv.cloud.community import CommunityApp, list_community_apps
|
|
17
|
+
|
|
18
|
+
# Set up subcommand application.
|
|
19
|
+
app = typer.Typer()
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command()
|
|
24
|
+
def list(
|
|
25
|
+
app: Annotated[
|
|
26
|
+
str | None,
|
|
27
|
+
typer.Option(
|
|
28
|
+
"--app",
|
|
29
|
+
"-a",
|
|
30
|
+
help="The community app to list versions for.",
|
|
31
|
+
metavar="COMMUNITY_APP",
|
|
32
|
+
),
|
|
33
|
+
] = None,
|
|
34
|
+
flat: Annotated[bool, typer.Option("--flat", "-f", help="Flatten the list output.")] = False,
|
|
35
|
+
profile: ProfileOption = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
List the available community apps
|
|
39
|
+
|
|
40
|
+
Use the --app flag to list that app's versions. Use the --flat flag to
|
|
41
|
+
flatten the list of names/versions. If you want to clone a community app
|
|
42
|
+
locally, use the [code]nextmv community clone[/code] command.
|
|
43
|
+
|
|
44
|
+
[bold][underline]Examples[/underline][/bold]
|
|
45
|
+
|
|
46
|
+
- List the available community apps.
|
|
47
|
+
$ [dim]nextmv community list[/dim]
|
|
48
|
+
|
|
49
|
+
- List the available versions of the [magenta]go-nextroute[/magenta] community app.
|
|
50
|
+
$ [dim]nextmv community list --app go-nextroute[/dim]
|
|
51
|
+
|
|
52
|
+
- List the names of the available community apps as a flat list.
|
|
53
|
+
$ [dim]nextmv community list --flat[/dim]
|
|
54
|
+
|
|
55
|
+
- List the available versions of the [magenta]go-nextroute[/magenta] community app as a flat list.
|
|
56
|
+
$ [dim]nextmv community list --app go-nextroute --flat[/dim]
|
|
57
|
+
|
|
58
|
+
- List the available community apps using a profile named [magenta]hare[/magenta].
|
|
59
|
+
$ [dim]nextmv community list --profile hare[/dim]
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
if app is not None and app == "":
|
|
63
|
+
error("The --app flag cannot be an empty string.")
|
|
64
|
+
|
|
65
|
+
client = build_client(profile)
|
|
66
|
+
if flat and app is None:
|
|
67
|
+
_apps_list(client)
|
|
68
|
+
raise typer.Exit()
|
|
69
|
+
elif not flat and app is None:
|
|
70
|
+
_apps_table(client)
|
|
71
|
+
raise typer.Exit()
|
|
72
|
+
elif flat and app is not None and app != "":
|
|
73
|
+
_versions_list(client, app)
|
|
74
|
+
raise typer.Exit()
|
|
75
|
+
elif not flat and app is not None and app != "":
|
|
76
|
+
_versions_table(client, app)
|
|
77
|
+
raise typer.Exit()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _apps_table(client: Client) -> None:
|
|
81
|
+
"""
|
|
82
|
+
This function prints a table of community apps.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
client : Client
|
|
87
|
+
The Nextmv Cloud client to use for the request.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
apps = list_community_apps(client)
|
|
91
|
+
table = Table("Name", "Type", "Latest", "Description", border_style="cyan", header_style="cyan")
|
|
92
|
+
for app in apps:
|
|
93
|
+
table.add_row(
|
|
94
|
+
app.name,
|
|
95
|
+
app.app_type,
|
|
96
|
+
app.latest_app_version if app.latest_app_version is not None else "",
|
|
97
|
+
app.description,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
console.print(table)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _apps_list(client: Client) -> None:
|
|
104
|
+
"""
|
|
105
|
+
This function prints a flat list of community app names.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
client : Client
|
|
110
|
+
The Nextmv Cloud client to use for the request.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
apps = list_community_apps(client)
|
|
114
|
+
names = [app.name for app in apps]
|
|
115
|
+
print("\n".join(names))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _versions_table(client: Client, app: str) -> None:
|
|
119
|
+
"""
|
|
120
|
+
This function prints a table of versions for a specific community app.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
client : Client
|
|
125
|
+
The Nextmv Cloud client to use for the request.
|
|
126
|
+
app : str
|
|
127
|
+
The name of the community app.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
comm_app = _find_app(client, app)
|
|
131
|
+
latest_version = comm_app.latest_app_version if comm_app.latest_app_version is not None else ""
|
|
132
|
+
|
|
133
|
+
# Add the latest version with indicator
|
|
134
|
+
table = Table("Version", "Latest?", border_style="cyan", header_style="cyan")
|
|
135
|
+
table.add_row(f"[cyan underline]{latest_version}[/cyan underline]", "[cyan]<--[/cyan]")
|
|
136
|
+
table.add_row("", "") # Empty row to separate latest from others.
|
|
137
|
+
|
|
138
|
+
# Add all other versions (excluding the latest)
|
|
139
|
+
versions = comm_app.app_versions if comm_app.app_versions is not None else []
|
|
140
|
+
for version in versions:
|
|
141
|
+
if version != latest_version:
|
|
142
|
+
table.add_row(version, "")
|
|
143
|
+
|
|
144
|
+
console.print(table)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _versions_list(client: Client, app: str) -> None:
|
|
148
|
+
"""
|
|
149
|
+
This function prints a flat list of versions for a specific community app.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
client : Client
|
|
154
|
+
The Nextmv Cloud client to use for the request.
|
|
155
|
+
app : str
|
|
156
|
+
The name of the community app.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
comm_app = _find_app(client, app)
|
|
160
|
+
versions = comm_app.app_versions if comm_app.app_versions is not None else []
|
|
161
|
+
|
|
162
|
+
versions_output = ""
|
|
163
|
+
for version in versions:
|
|
164
|
+
versions_output += f"{version}\n"
|
|
165
|
+
|
|
166
|
+
print(versions_output.rstrip("\n"))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _find_app(client: Client, app: str) -> CommunityApp:
|
|
170
|
+
"""
|
|
171
|
+
Finds and returns a community app from the manifest by its name.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
client : Client
|
|
176
|
+
The Nextmv Cloud client to use for the request.
|
|
177
|
+
app : str
|
|
178
|
+
The name of the community app to find.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
CommunityApp
|
|
183
|
+
The community app if found.
|
|
184
|
+
|
|
185
|
+
Raises
|
|
186
|
+
------
|
|
187
|
+
ValueError
|
|
188
|
+
If the community app is not found.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
comm_apps = list_community_apps(client)
|
|
192
|
+
for comm_app in comm_apps:
|
|
193
|
+
if comm_app.name == app:
|
|
194
|
+
return comm_app
|
|
195
|
+
|
|
196
|
+
# We don't use error() here to allow printing something before exiting.
|
|
197
|
+
rich.print(f"[red]Error:[/red] Community app [magenta]{app}[/magenta] was not found. Here are the available apps:")
|
|
198
|
+
_apps_table(client)
|
|
199
|
+
|
|
200
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the configuration command tree for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from nextmv.cli.configuration.create import app as create_app
|
|
8
|
+
from nextmv.cli.configuration.delete import app as delete_app
|
|
9
|
+
from nextmv.cli.configuration.list import app as list_app
|
|
10
|
+
|
|
11
|
+
# Set up subcommand application.
|
|
12
|
+
app = typer.Typer()
|
|
13
|
+
app.add_typer(create_app)
|
|
14
|
+
app.add_typer(delete_app)
|
|
15
|
+
app.add_typer(list_app)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.callback()
|
|
19
|
+
def callback() -> None:
|
|
20
|
+
"""
|
|
21
|
+
Configure the CLI and manage profiles.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains configuration utilities for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
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
|
|
14
|
+
from nextmv.cloud.client import Client
|
|
15
|
+
|
|
16
|
+
# Some useful constants.
|
|
17
|
+
CONFIG_DIR = Path.home() / ".nextmv"
|
|
18
|
+
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
19
|
+
API_KEY_KEY = "apikey"
|
|
20
|
+
ENDPOINT_KEY = "endpoint"
|
|
21
|
+
DEFAULT_ENDPOINT = "api.cloud.nextmv.io"
|
|
22
|
+
GO_CLI_PATH = CONFIG_DIR / "nextmv"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_config() -> dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Load the current configuration from the config file. Returns an empty
|
|
28
|
+
dictionary if no configuration file exists.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
dict[str, Any]
|
|
33
|
+
The current configuration as a dictionary.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if not CONFIG_FILE.exists():
|
|
37
|
+
return {}
|
|
38
|
+
|
|
39
|
+
with CONFIG_FILE.open() as file:
|
|
40
|
+
config = yaml.safe_load(file)
|
|
41
|
+
|
|
42
|
+
if config is None:
|
|
43
|
+
return {}
|
|
44
|
+
return config
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def save_config(config: dict[str, Any]) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Save the given configuration to the config file.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
config : dict[str, Any]
|
|
54
|
+
The configuration to save.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
|
|
59
|
+
with CONFIG_FILE.open("w") as file:
|
|
60
|
+
yaml.safe_dump(config, file)
|
|
61
|
+
|
|
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
|
+
|
|
75
|
+
def build_client(profile: str | None = None) -> Client:
|
|
76
|
+
"""
|
|
77
|
+
Builds a `cloud.Client` using the API key and endpoint for the given
|
|
78
|
+
profile. If no profile is given, the default profile is used. If either the
|
|
79
|
+
API key or endpoint is missing, an exception is raised. If the config is
|
|
80
|
+
not available, an exception is raised.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
profile : str | None
|
|
85
|
+
The profile name to use. If None, the default profile is used.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
Client
|
|
90
|
+
A client configured with the API key and endpoint for the selected
|
|
91
|
+
profile or the default configuration.
|
|
92
|
+
|
|
93
|
+
Raises
|
|
94
|
+
------
|
|
95
|
+
typer.Exit
|
|
96
|
+
If no configuration is found, if the requested profile does not exist,
|
|
97
|
+
or if the API key or endpoint (for either the selected profile or the
|
|
98
|
+
default configuration) is not set or is empty.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
config = load_config()
|
|
102
|
+
if config == {}:
|
|
103
|
+
error("No configuration found. Please run [code]nextmv configuration create[/code].")
|
|
104
|
+
|
|
105
|
+
if profile is not None:
|
|
106
|
+
if profile not in config:
|
|
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
|
+
)
|
|
111
|
+
|
|
112
|
+
api_key = config[profile].get(API_KEY_KEY)
|
|
113
|
+
if api_key is None or api_key == "":
|
|
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
|
+
)
|
|
118
|
+
|
|
119
|
+
endpoint = config[profile].get(ENDPOINT_KEY)
|
|
120
|
+
if endpoint is None or endpoint == "":
|
|
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
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
api_key = config.get(API_KEY_KEY)
|
|
127
|
+
if api_key is None or api_key == "":
|
|
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
|
+
)
|
|
132
|
+
|
|
133
|
+
endpoint = config.get(ENDPOINT_KEY)
|
|
134
|
+
if endpoint is None or endpoint == "":
|
|
135
|
+
error("Default endpoint is not set or is empty. Please run [code]nextmv configuration create[/code].")
|
|
136
|
+
|
|
137
|
+
return Client(api_key=api_key, url=f"https://{endpoint}")
|
|
138
|
+
|
|
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
|
+
|
|
210
|
+
def obscure_api_key(api_key: str) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Obscure an API key for display purposes.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
api_key : str
|
|
217
|
+
The API key to obscure.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
str
|
|
222
|
+
The obscured API key.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
if len(api_key) <= 4:
|
|
226
|
+
return "*" * len(api_key)
|
|
227
|
+
|
|
228
|
+
return api_key[:2] + "*" * 4 + api_key[-2:]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the configuration create command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from nextmv.cli.configuration.config import (
|
|
10
|
+
API_KEY_KEY,
|
|
11
|
+
DEFAULT_ENDPOINT,
|
|
12
|
+
ENDPOINT_KEY,
|
|
13
|
+
load_config,
|
|
14
|
+
obscure_api_key,
|
|
15
|
+
save_config,
|
|
16
|
+
)
|
|
17
|
+
from nextmv.cli.message import error, message, success
|
|
18
|
+
|
|
19
|
+
# Set up subcommand application.
|
|
20
|
+
app = typer.Typer()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command()
|
|
24
|
+
def create(
|
|
25
|
+
api_key: Annotated[
|
|
26
|
+
str,
|
|
27
|
+
typer.Option(
|
|
28
|
+
"--api-key",
|
|
29
|
+
"-a",
|
|
30
|
+
help="A valid Nextmv Cloud API key. "
|
|
31
|
+
+ "Get one from [link=https://cloud.nextmv.io][bold]https://cloud.nextmv.io[/bold][/link].",
|
|
32
|
+
envvar="NEXTMV_API_KEY",
|
|
33
|
+
metavar="NEXTMV_API_KEY",
|
|
34
|
+
),
|
|
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,
|
|
44
|
+
profile: Annotated[ # Similar to nextmv.cli.options.ProfileOption but with different help text.
|
|
45
|
+
str | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"--profile",
|
|
48
|
+
"-p",
|
|
49
|
+
help="Profile name to save the configuration under.",
|
|
50
|
+
envvar="NEXTMV_PROFILE",
|
|
51
|
+
metavar="PROFILE_NAME",
|
|
52
|
+
),
|
|
53
|
+
] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Create a new configuration or update an existing one.
|
|
57
|
+
|
|
58
|
+
[bold][underline]Examples[/underline][/bold]
|
|
59
|
+
|
|
60
|
+
- Default configuration.
|
|
61
|
+
$ [dim]nextmv configuration create --api-key NEXTMV_API_KEY[/dim]
|
|
62
|
+
|
|
63
|
+
- Configure a profile named [magenta]hare[/magenta].
|
|
64
|
+
$ [dim]nextmv configuration create --api-key NEXTMV_API_KEY --profile hare[/dim]
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
if profile is not None and profile.strip().lower() == "default":
|
|
68
|
+
error("[magenta]default[/magenta] is a reserved profile name.")
|
|
69
|
+
|
|
70
|
+
endpoint = str(endpoint)
|
|
71
|
+
if endpoint.startswith("https://"):
|
|
72
|
+
endpoint = endpoint[len("https://") :]
|
|
73
|
+
elif endpoint.startswith("http://"):
|
|
74
|
+
endpoint = endpoint[len("http://") :]
|
|
75
|
+
|
|
76
|
+
config = load_config()
|
|
77
|
+
|
|
78
|
+
if profile is None:
|
|
79
|
+
config[API_KEY_KEY] = api_key
|
|
80
|
+
config[ENDPOINT_KEY] = endpoint
|
|
81
|
+
else:
|
|
82
|
+
if profile not in config:
|
|
83
|
+
config[profile] = {}
|
|
84
|
+
|
|
85
|
+
config[profile][API_KEY_KEY] = api_key
|
|
86
|
+
config[profile][ENDPOINT_KEY] = endpoint
|
|
87
|
+
|
|
88
|
+
save_config(config)
|
|
89
|
+
|
|
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]")
|
|
93
|
+
if endpoint != DEFAULT_ENDPOINT:
|
|
94
|
+
message(f"\t[bold]Endpoint[/bold]: [magenta]{endpoint}[/magenta]")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the configuration 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 load_config, save_config
|
|
10
|
+
from nextmv.cli.confirm import get_confirmation
|
|
11
|
+
from nextmv.cli.message import error, info, success
|
|
12
|
+
|
|
13
|
+
# Set up subcommand application.
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command()
|
|
18
|
+
def delete(
|
|
19
|
+
profile: Annotated[ # Similar to nextmv.cli.options.ProfileOption but with different help text.
|
|
20
|
+
str,
|
|
21
|
+
typer.Option(
|
|
22
|
+
"--profile",
|
|
23
|
+
"-p",
|
|
24
|
+
help="Profile name to delete.",
|
|
25
|
+
envvar="NEXTMV_PROFILE",
|
|
26
|
+
metavar="PROFILE_NAME",
|
|
27
|
+
),
|
|
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,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Delete a profile from the configuration.
|
|
40
|
+
|
|
41
|
+
Use the --yes flag to skip the confirmation prompt.
|
|
42
|
+
|
|
43
|
+
[bold][underline]Examples[/underline][/bold]
|
|
44
|
+
|
|
45
|
+
- Delete a profile named [magenta]hare[/magenta].
|
|
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]
|
|
50
|
+
"""
|
|
51
|
+
config = load_config()
|
|
52
|
+
if profile not in config:
|
|
53
|
+
error(f"Profile [magenta]{profile}[/magenta] does not exist.")
|
|
54
|
+
|
|
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
|
+
)
|
|
59
|
+
|
|
60
|
+
if not confirm:
|
|
61
|
+
info(f"Profile [magenta]{profile}[/magenta] will not be deleted.")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
del config[profile]
|
|
65
|
+
save_config(config)
|
|
66
|
+
|
|
67
|
+
success(f"Profile [magenta]{profile}[/magenta] deleted successfully.")
|