nextmv 1.0.0.dev9__py3-none-any.whl → 1.1.0.dev0__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/cli/CONTRIBUTING.md +31 -11
- nextmv/cli/cloud/__init__.py +2 -0
- nextmv/cli/cloud/acceptance/delete.py +1 -4
- nextmv/cli/cloud/account/__init__.py +5 -3
- nextmv/cli/cloud/account/create.py +4 -1
- nextmv/cli/cloud/account/delete.py +1 -1
- nextmv/cli/cloud/app/delete.py +1 -1
- nextmv/cli/cloud/app/push.py +23 -42
- nextmv/cli/cloud/batch/delete.py +1 -4
- nextmv/cli/cloud/ensemble/delete.py +1 -4
- nextmv/cli/cloud/input_set/__init__.py +2 -0
- nextmv/cli/cloud/input_set/delete.py +64 -0
- nextmv/cli/cloud/instance/delete.py +1 -1
- nextmv/cli/cloud/managed_input/delete.py +1 -1
- nextmv/cli/cloud/run/create.py +4 -9
- nextmv/cli/cloud/scenario/delete.py +1 -4
- nextmv/cli/cloud/secrets/delete.py +1 -4
- nextmv/cli/cloud/shadow/delete.py +1 -4
- nextmv/cli/cloud/shadow/stop.py +14 -2
- nextmv/cli/cloud/sso/__init__.py +32 -0
- nextmv/cli/cloud/sso/create.py +97 -0
- nextmv/cli/cloud/sso/delete.py +58 -0
- nextmv/cli/cloud/sso/disable.py +56 -0
- nextmv/cli/cloud/sso/enable.py +56 -0
- nextmv/cli/cloud/sso/get.py +61 -0
- nextmv/cli/cloud/sso/update.py +78 -0
- nextmv/cli/cloud/switchback/delete.py +1 -4
- nextmv/cli/cloud/switchback/stop.py +14 -2
- nextmv/cli/cloud/version/delete.py +1 -1
- nextmv/cli/community/__init__.py +1 -1
- nextmv/cli/community/clone.py +11 -198
- nextmv/cli/community/list.py +51 -116
- nextmv/cli/configuration/config.py +27 -0
- nextmv/cli/configuration/create.py +4 -4
- nextmv/cli/configuration/delete.py +1 -1
- nextmv/cli/main.py +2 -3
- nextmv/cli/message.py +71 -54
- nextmv/cloud/__init__.py +5 -0
- nextmv/cloud/account.py +12 -10
- nextmv/cloud/application/__init__.py +12 -204
- nextmv/cloud/application/_acceptance.py +13 -8
- nextmv/cloud/application/_input_set.py +42 -6
- nextmv/cloud/application/_run.py +2 -2
- nextmv/cloud/application/_shadow.py +9 -3
- nextmv/cloud/application/_switchback.py +11 -2
- nextmv/cloud/batch_experiment.py +3 -1
- nextmv/cloud/community.py +446 -0
- nextmv/cloud/integration.py +7 -4
- nextmv/cloud/shadow.py +25 -0
- nextmv/cloud/sso.py +248 -0
- nextmv/cloud/switchback.py +2 -0
- nextmv/model.py +40 -4
- nextmv/options.py +1 -1
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/METADATA +3 -1
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/RECORD +59 -49
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/entry_points.txt +0 -0
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/cli/community/clone.py
CHANGED
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
This module defines the community clone command for the Nextmv CLI.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
|
-
import shutil
|
|
7
|
-
import tarfile
|
|
8
|
-
import tempfile
|
|
9
|
-
from collections.abc import Callable
|
|
10
5
|
from typing import Annotated
|
|
11
6
|
|
|
12
|
-
import rich
|
|
13
7
|
import typer
|
|
14
8
|
|
|
15
|
-
from nextmv.cli.
|
|
16
|
-
from nextmv.cli.message import error
|
|
9
|
+
from nextmv.cli.configuration.config import build_client
|
|
10
|
+
from nextmv.cli.message import error
|
|
17
11
|
from nextmv.cli.options import ProfileOption
|
|
12
|
+
from nextmv.cloud.community import clone_community_app
|
|
18
13
|
|
|
19
14
|
# Set up subcommand application.
|
|
20
15
|
app = typer.Typer()
|
|
@@ -77,197 +72,15 @@ def clone(
|
|
|
77
72
|
$ [dim]nextmv community clone --app go-nextroute --profile hare[/dim]
|
|
78
73
|
"""
|
|
79
74
|
|
|
80
|
-
manifest = download_manifest(profile=profile)
|
|
81
|
-
app_obj = find_app(manifest, app)
|
|
82
|
-
|
|
83
75
|
if version is not None and version == "":
|
|
84
76
|
error("The --version flag cannot be an empty string.")
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
raise typer.Exit(code=1)
|
|
95
|
-
|
|
96
|
-
original_version = version
|
|
97
|
-
if version == LATEST_VERSION:
|
|
98
|
-
version = app_obj.get("latest_app_version")
|
|
99
|
-
|
|
100
|
-
# Clean and normalize directory path in an OS-independent way
|
|
101
|
-
if directory is not None and directory != "":
|
|
102
|
-
destination = os.path.normpath(directory)
|
|
103
|
-
else:
|
|
104
|
-
destination = app
|
|
105
|
-
|
|
106
|
-
full_destination = get_valid_path(destination, os.stat)
|
|
107
|
-
os.makedirs(full_destination, exist_ok=True)
|
|
108
|
-
|
|
109
|
-
tarball = f"{app}_{version}.tar.gz"
|
|
110
|
-
s3_file_path = f"{app}/{version}/{tarball}"
|
|
111
|
-
downloaded_object = download_object(
|
|
112
|
-
file=s3_file_path,
|
|
113
|
-
path="community-apps",
|
|
114
|
-
output_dir=full_destination,
|
|
115
|
-
output_file=tarball,
|
|
116
|
-
profile=profile,
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
# Extract the tarball to a temporary directory to handle nested structure
|
|
120
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
121
|
-
with tarfile.open(downloaded_object, "r:gz") as tar:
|
|
122
|
-
tar.extractall(path=temp_dir, filter=None)
|
|
123
|
-
|
|
124
|
-
# Find the extracted directory (typically the app name)
|
|
125
|
-
extracted_items = os.listdir(temp_dir)
|
|
126
|
-
if len(extracted_items) == 1 and os.path.isdir(os.path.join(temp_dir, extracted_items[0])):
|
|
127
|
-
# Move contents from the extracted directory to full_destination
|
|
128
|
-
extracted_dir = os.path.join(temp_dir, extracted_items[0])
|
|
129
|
-
for item in os.listdir(extracted_dir):
|
|
130
|
-
shutil.move(os.path.join(extracted_dir, item), full_destination)
|
|
131
|
-
else:
|
|
132
|
-
# If structure is unexpected, move everything directly
|
|
133
|
-
for item in extracted_items:
|
|
134
|
-
shutil.move(os.path.join(temp_dir, item), full_destination)
|
|
135
|
-
|
|
136
|
-
# Remove the tarball after extraction
|
|
137
|
-
os.remove(downloaded_object)
|
|
138
|
-
|
|
139
|
-
success(
|
|
140
|
-
f"Successfully cloned the [magenta]{app}[/magenta] community app, "
|
|
141
|
-
f"using version [magenta]{original_version}[/magenta] in path: [magenta]{full_destination}[/magenta]."
|
|
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,
|
|
142
86
|
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def app_has_version(app_obj: dict, version: str) -> bool:
|
|
146
|
-
"""
|
|
147
|
-
Check if the given app object has the specified version.
|
|
148
|
-
|
|
149
|
-
Parameters
|
|
150
|
-
----------
|
|
151
|
-
app_obj : dict
|
|
152
|
-
The community app object.
|
|
153
|
-
version : str
|
|
154
|
-
The version to check.
|
|
155
|
-
|
|
156
|
-
Returns
|
|
157
|
-
-------
|
|
158
|
-
bool
|
|
159
|
-
True if the app has the specified version, False otherwise.
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
if version == LATEST_VERSION:
|
|
163
|
-
version = app_obj.get("latest_app_version")
|
|
164
|
-
|
|
165
|
-
if version in app_obj.get("app_versions", []):
|
|
166
|
-
return True
|
|
167
|
-
|
|
168
|
-
return False
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def get_valid_path(path: str, stat_fn: Callable[[str], os.stat_result], ending: str = "") -> str:
|
|
172
|
-
"""
|
|
173
|
-
Validates and returns a non-existing path. If the path exists,
|
|
174
|
-
it will append a number to the path and return it. If the path does not
|
|
175
|
-
exist, it will return the path as is.
|
|
176
|
-
|
|
177
|
-
The ending parameter is used to check if the path ends with a specific
|
|
178
|
-
string. This is useful to specify if it is a file (like foo.json, in which
|
|
179
|
-
case the next iteration is foo-1.json) or a directory (like foo, in which
|
|
180
|
-
case the next iteration is foo-1).
|
|
181
|
-
|
|
182
|
-
Parameters
|
|
183
|
-
----------
|
|
184
|
-
path : str
|
|
185
|
-
The initial path to validate.
|
|
186
|
-
stat_fn : Callable[[str], os.stat_result]
|
|
187
|
-
A function that takes a path and returns its stat result.
|
|
188
|
-
ending : str, optional
|
|
189
|
-
The expected ending of the path (e.g., file extension), by default "".
|
|
190
|
-
|
|
191
|
-
Returns
|
|
192
|
-
-------
|
|
193
|
-
str
|
|
194
|
-
A valid, non-existing path.
|
|
195
|
-
|
|
196
|
-
Raises
|
|
197
|
-
------
|
|
198
|
-
Exception
|
|
199
|
-
If an unexpected error occurs during path validation
|
|
200
|
-
"""
|
|
201
|
-
base_name = os.path.basename(path)
|
|
202
|
-
name_without_ending = base_name.removesuffix(ending) if ending else base_name
|
|
203
|
-
|
|
204
|
-
while True:
|
|
205
|
-
try:
|
|
206
|
-
stat_fn(path)
|
|
207
|
-
# If we get here, the path exists
|
|
208
|
-
# Get folder/file name number, increase it and create new path
|
|
209
|
-
name = os.path.basename(path)
|
|
210
|
-
|
|
211
|
-
# Get folder/file name number
|
|
212
|
-
parts = name.split("-")
|
|
213
|
-
last = parts[-1].removesuffix(ending) if ending else parts[-1]
|
|
214
|
-
|
|
215
|
-
# Save last folder name index to be changed
|
|
216
|
-
i = path.rfind(name)
|
|
217
|
-
|
|
218
|
-
try:
|
|
219
|
-
num = int(last)
|
|
220
|
-
# Increase number and create new path
|
|
221
|
-
if ending:
|
|
222
|
-
temp_path = path[:i] + f"{name_without_ending}-{num + 1}{ending}"
|
|
223
|
-
else:
|
|
224
|
-
temp_path = path[:i] + f"{base_name}-{num + 1}"
|
|
225
|
-
path = temp_path
|
|
226
|
-
except ValueError:
|
|
227
|
-
# If there is no number, add it
|
|
228
|
-
if ending:
|
|
229
|
-
temp_path = path[:i] + f"{name_without_ending}-1{ending}"
|
|
230
|
-
else:
|
|
231
|
-
temp_path = path[:i] + f"{name}-1"
|
|
232
|
-
path = temp_path
|
|
233
|
-
|
|
234
|
-
except FileNotFoundError:
|
|
235
|
-
# Path doesn't exist, we can use it
|
|
236
|
-
return path
|
|
237
|
-
except Exception:
|
|
238
|
-
# Re-raise unexpected errors
|
|
239
|
-
error(f"An unexpected error occurred while validating the path: {path}")
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def download_object(file: str, path: str, output_dir: str, output_file: str, profile: str | None = None) -> str:
|
|
243
|
-
"""
|
|
244
|
-
Downloads an object from the internal bucket and saves it to the specified
|
|
245
|
-
output directory.
|
|
246
|
-
|
|
247
|
-
Parameters
|
|
248
|
-
----------
|
|
249
|
-
file : str
|
|
250
|
-
The name of the file to download.
|
|
251
|
-
path : str
|
|
252
|
-
The directory in the bucket where the file is located.
|
|
253
|
-
output_dir : str
|
|
254
|
-
The local directory where the file will be saved.
|
|
255
|
-
output_file : str
|
|
256
|
-
The name of the output file.
|
|
257
|
-
profile : str | None
|
|
258
|
-
The profile name to use. If None, the default profile is used.
|
|
259
|
-
|
|
260
|
-
Returns
|
|
261
|
-
-------
|
|
262
|
-
str
|
|
263
|
-
The path to the downloaded file.
|
|
264
|
-
"""
|
|
265
|
-
|
|
266
|
-
response = download_file(directory=path, file=file, profile=profile)
|
|
267
|
-
file_name = os.path.join(output_dir, output_file)
|
|
268
|
-
|
|
269
|
-
with open(file_name, "wb") as f:
|
|
270
|
-
f.write(response.content)
|
|
271
|
-
|
|
272
|
-
return file_name
|
|
273
|
-
return file_name
|
nextmv/cli/community/list.py
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
This module defines the community list command for the Nextmv CLI.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Annotated
|
|
5
|
+
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
import requests
|
|
8
7
|
import rich
|
|
9
8
|
import typer
|
|
10
|
-
import yaml
|
|
11
9
|
from rich.console import Console
|
|
12
10
|
from rich.table import Table
|
|
13
11
|
|
|
14
12
|
from nextmv.cli.configuration.config import build_client
|
|
15
13
|
from nextmv.cli.message import error
|
|
16
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
17
|
|
|
18
18
|
# Set up subcommand application.
|
|
19
19
|
app = typer.Typer()
|
|
@@ -62,96 +62,73 @@ def list(
|
|
|
62
62
|
if app is not None and app == "":
|
|
63
63
|
error("The --app flag cannot be an empty string.")
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
client = build_client(profile)
|
|
66
66
|
if flat and app is None:
|
|
67
|
-
|
|
67
|
+
_apps_list(client)
|
|
68
68
|
raise typer.Exit()
|
|
69
69
|
elif not flat and app is None:
|
|
70
|
-
|
|
70
|
+
_apps_table(client)
|
|
71
71
|
raise typer.Exit()
|
|
72
72
|
elif flat and app is not None and app != "":
|
|
73
|
-
|
|
73
|
+
_versions_list(client, app)
|
|
74
74
|
raise typer.Exit()
|
|
75
75
|
elif not flat and app is not None and app != "":
|
|
76
|
-
|
|
76
|
+
_versions_table(client, app)
|
|
77
77
|
raise typer.Exit()
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def
|
|
80
|
+
def _apps_table(client: Client) -> None:
|
|
81
81
|
"""
|
|
82
|
-
|
|
82
|
+
This function prints a table of community apps.
|
|
83
83
|
|
|
84
84
|
Parameters
|
|
85
85
|
----------
|
|
86
|
-
|
|
87
|
-
The
|
|
88
|
-
|
|
89
|
-
Returns
|
|
90
|
-
-------
|
|
91
|
-
dict
|
|
92
|
-
The community apps manifest as a dictionary.
|
|
93
|
-
|
|
94
|
-
Raises
|
|
95
|
-
requests.HTTPError
|
|
96
|
-
If the response status code is not 2xx.
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
response = download_file(directory="community-apps", file="manifest.yml", profile=profile)
|
|
100
|
-
manifest = yaml.safe_load(response.text)
|
|
101
|
-
|
|
102
|
-
return manifest
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def apps_table(manifest: dict[str, Any]) -> None:
|
|
106
|
-
"""
|
|
107
|
-
This function prints a table of community apps from the manifest.
|
|
108
|
-
|
|
109
|
-
Parameters
|
|
110
|
-
----------
|
|
111
|
-
manifest : dict[str, Any]
|
|
112
|
-
The community apps manifest.
|
|
86
|
+
client : Client
|
|
87
|
+
The Nextmv Cloud client to use for the request.
|
|
113
88
|
"""
|
|
114
89
|
|
|
90
|
+
apps = list_community_apps(client)
|
|
115
91
|
table = Table("Name", "Type", "Latest", "Description", border_style="cyan", header_style="cyan")
|
|
116
|
-
for app in
|
|
92
|
+
for app in apps:
|
|
117
93
|
table.add_row(
|
|
118
|
-
app.
|
|
119
|
-
app.
|
|
120
|
-
app.
|
|
121
|
-
app.
|
|
94
|
+
app.name,
|
|
95
|
+
app.app_type,
|
|
96
|
+
app.latest_app_version if app.latest_app_version is not None else "",
|
|
97
|
+
app.description,
|
|
122
98
|
)
|
|
123
99
|
|
|
124
100
|
console.print(table)
|
|
125
101
|
|
|
126
102
|
|
|
127
|
-
def
|
|
103
|
+
def _apps_list(client: Client) -> None:
|
|
128
104
|
"""
|
|
129
|
-
This function prints a flat list of community app names
|
|
105
|
+
This function prints a flat list of community app names.
|
|
130
106
|
|
|
131
107
|
Parameters
|
|
132
108
|
----------
|
|
133
|
-
|
|
134
|
-
The
|
|
109
|
+
client : Client
|
|
110
|
+
The Nextmv Cloud client to use for the request.
|
|
135
111
|
"""
|
|
136
112
|
|
|
137
|
-
|
|
113
|
+
apps = list_community_apps(client)
|
|
114
|
+
names = [app.name for app in apps]
|
|
138
115
|
print("\n".join(names))
|
|
139
116
|
|
|
140
117
|
|
|
141
|
-
def
|
|
118
|
+
def _versions_table(client: Client, app: str) -> None:
|
|
142
119
|
"""
|
|
143
120
|
This function prints a table of versions for a specific community app.
|
|
144
121
|
|
|
145
122
|
Parameters
|
|
146
123
|
----------
|
|
147
|
-
|
|
148
|
-
The
|
|
124
|
+
client : Client
|
|
125
|
+
The Nextmv Cloud client to use for the request.
|
|
149
126
|
app : str
|
|
150
127
|
The name of the community app.
|
|
151
128
|
"""
|
|
152
129
|
|
|
153
|
-
|
|
154
|
-
latest_version =
|
|
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 ""
|
|
155
132
|
|
|
156
133
|
# Add the latest version with indicator
|
|
157
134
|
table = Table("Version", "Latest?", border_style="cyan", header_style="cyan")
|
|
@@ -159,7 +136,7 @@ def versions_table(manifest: dict[str, Any], app: str) -> None:
|
|
|
159
136
|
table.add_row("", "") # Empty row to separate latest from others.
|
|
160
137
|
|
|
161
138
|
# Add all other versions (excluding the latest)
|
|
162
|
-
versions =
|
|
139
|
+
versions = comm_app.app_versions if comm_app.app_versions is not None else []
|
|
163
140
|
for version in versions:
|
|
164
141
|
if version != latest_version:
|
|
165
142
|
table.add_row(version, "")
|
|
@@ -167,99 +144,57 @@ def versions_table(manifest: dict[str, Any], app: str) -> None:
|
|
|
167
144
|
console.print(table)
|
|
168
145
|
|
|
169
146
|
|
|
170
|
-
def
|
|
147
|
+
def _versions_list(client: Client, app: str) -> None:
|
|
171
148
|
"""
|
|
172
149
|
This function prints a flat list of versions for a specific community app.
|
|
173
150
|
|
|
174
151
|
Parameters
|
|
175
152
|
----------
|
|
176
|
-
|
|
177
|
-
The
|
|
153
|
+
client : Client
|
|
154
|
+
The Nextmv Cloud client to use for the request.
|
|
178
155
|
app : str
|
|
179
156
|
The name of the community app.
|
|
180
157
|
"""
|
|
181
158
|
|
|
182
|
-
|
|
183
|
-
versions =
|
|
159
|
+
comm_app = _find_app(client, app)
|
|
160
|
+
versions = comm_app.app_versions if comm_app.app_versions is not None else []
|
|
184
161
|
|
|
185
162
|
versions_output = ""
|
|
186
163
|
for version in versions:
|
|
187
164
|
versions_output += f"{version}\n"
|
|
188
165
|
|
|
189
|
-
print("\n"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def download_file(
|
|
193
|
-
directory: str,
|
|
194
|
-
file: str,
|
|
195
|
-
profile: str | None = None,
|
|
196
|
-
) -> requests.Response:
|
|
197
|
-
"""
|
|
198
|
-
Gets a file from an internal bucket and return it.
|
|
166
|
+
print(versions_output.rstrip("\n"))
|
|
199
167
|
|
|
200
|
-
Parameters
|
|
201
|
-
----------
|
|
202
|
-
directory : str
|
|
203
|
-
The directory in the bucket where the file is located.
|
|
204
|
-
file : str
|
|
205
|
-
The name of the file to download.
|
|
206
|
-
profile : str | None
|
|
207
|
-
The profile name to use. If None, the default profile is used.
|
|
208
168
|
|
|
209
|
-
|
|
210
|
-
-------
|
|
211
|
-
requests.Response
|
|
212
|
-
The response object containing the file data.
|
|
213
|
-
|
|
214
|
-
Raises
|
|
215
|
-
requests.HTTPError
|
|
216
|
-
If the response status code is not 2xx.
|
|
217
|
-
"""
|
|
218
|
-
|
|
219
|
-
client = build_client(profile)
|
|
220
|
-
|
|
221
|
-
# Request the download URL for the file.
|
|
222
|
-
response = client.request(
|
|
223
|
-
method="GET",
|
|
224
|
-
endpoint="v0/internal/tools",
|
|
225
|
-
headers=client.headers | {"request-source": "cli"}, # Pass `client.headers` to preserve auth.
|
|
226
|
-
query_params={"file": f"{directory}/{file}"},
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
# Use the URL obtained to download the file.
|
|
230
|
-
body = response.json()
|
|
231
|
-
download_response = client.request(
|
|
232
|
-
method="GET",
|
|
233
|
-
endpoint=body.get("url"),
|
|
234
|
-
headers={"Content-Type": "application/json"},
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
return download_response
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def find_app(manifest: dict[str, Any], app: str) -> dict[str, Any] | None:
|
|
169
|
+
def _find_app(client: Client, app: str) -> CommunityApp:
|
|
241
170
|
"""
|
|
242
171
|
Finds and returns a community app from the manifest by its name.
|
|
243
172
|
|
|
244
173
|
Parameters
|
|
245
174
|
----------
|
|
246
|
-
|
|
247
|
-
The
|
|
175
|
+
client : Client
|
|
176
|
+
The Nextmv Cloud client to use for the request.
|
|
248
177
|
app : str
|
|
249
178
|
The name of the community app to find.
|
|
250
179
|
|
|
251
180
|
Returns
|
|
252
181
|
-------
|
|
253
|
-
|
|
254
|
-
The community app
|
|
182
|
+
CommunityApp
|
|
183
|
+
The community app if found.
|
|
184
|
+
|
|
185
|
+
Raises
|
|
186
|
+
------
|
|
187
|
+
ValueError
|
|
188
|
+
If the community app is not found.
|
|
255
189
|
"""
|
|
256
190
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
191
|
+
comm_apps = list_community_apps(client)
|
|
192
|
+
for comm_app in comm_apps:
|
|
193
|
+
if comm_app.name == app:
|
|
194
|
+
return comm_app
|
|
260
195
|
|
|
261
196
|
# We don't use error() here to allow printing something before exiting.
|
|
262
197
|
rich.print(f"[red]Error:[/red] Community app [magenta]{app}[/magenta] was not found. Here are the available apps:")
|
|
263
|
-
|
|
198
|
+
_apps_table(client)
|
|
264
199
|
|
|
265
200
|
raise typer.Exit(code=1)
|
|
@@ -12,6 +12,7 @@ from nextmv.cli.message import error, success, warning
|
|
|
12
12
|
from nextmv.cloud.account import Account
|
|
13
13
|
from nextmv.cloud.application import Application
|
|
14
14
|
from nextmv.cloud.client import Client
|
|
15
|
+
from nextmv.cloud.sso import SSOConfiguration
|
|
15
16
|
|
|
16
17
|
# Some useful constants.
|
|
17
18
|
CONFIG_DIR = Path.home() / ".nextmv"
|
|
@@ -207,6 +208,32 @@ def build_account(account_id: str | None = None, profile: str | None = None) ->
|
|
|
207
208
|
return Account(account_id=account_id, client=client)
|
|
208
209
|
|
|
209
210
|
|
|
211
|
+
def build_sso_config(profile: str | None = None) -> SSOConfiguration:
|
|
212
|
+
"""
|
|
213
|
+
Builds a `cloud.SSOConfiguration` using the API key and endpoint for the given
|
|
214
|
+
profile. If no profile is given, the default profile is used.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
profile : str | None
|
|
219
|
+
The profile name to use. If None, the default profile is used.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
SSOConfiguration
|
|
224
|
+
An SSOConfiguration object for the configured profile.
|
|
225
|
+
|
|
226
|
+
Raises
|
|
227
|
+
------
|
|
228
|
+
typer.Exit
|
|
229
|
+
If the configuration is invalid or missing.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
client = build_client(profile)
|
|
233
|
+
|
|
234
|
+
return SSOConfiguration(client=client)
|
|
235
|
+
|
|
236
|
+
|
|
210
237
|
def obscure_api_key(api_key: str) -> str:
|
|
211
238
|
"""
|
|
212
239
|
Obscure an API key for display purposes.
|
|
@@ -14,7 +14,7 @@ from nextmv.cli.configuration.config import (
|
|
|
14
14
|
obscure_api_key,
|
|
15
15
|
save_config,
|
|
16
16
|
)
|
|
17
|
-
from nextmv.cli.message import error,
|
|
17
|
+
from nextmv.cli.message import error, message, success
|
|
18
18
|
|
|
19
19
|
# Set up subcommand application.
|
|
20
20
|
app = typer.Typer()
|
|
@@ -88,7 +88,7 @@ def create(
|
|
|
88
88
|
save_config(config)
|
|
89
89
|
|
|
90
90
|
success("Configuration saved successfully.")
|
|
91
|
-
|
|
92
|
-
|
|
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
93
|
if endpoint != DEFAULT_ENDPOINT:
|
|
94
|
-
|
|
94
|
+
message(f"\t[bold]Endpoint[/bold]: [magenta]{endpoint}[/magenta]")
|
nextmv/cli/main.py
CHANGED
|
@@ -101,11 +101,10 @@ def handle_go_cli() -> None:
|
|
|
101
101
|
return
|
|
102
102
|
|
|
103
103
|
info(
|
|
104
|
-
|
|
104
|
+
"You can delete the [italic red]deprecated[/italic red] Nextmv CLI later by removing "
|
|
105
105
|
f"[magenta]{GO_CLI_PATH}[/magenta]. "
|
|
106
106
|
"Make sure you also clean up your [code]PATH[/code], "
|
|
107
|
-
f"by removing references to [magenta]{CONFIG_DIR}[/magenta] from it."
|
|
108
|
-
emoji=":bulb:",
|
|
107
|
+
f"by removing references to [magenta]{CONFIG_DIR}[/magenta] from it."
|
|
109
108
|
)
|
|
110
109
|
|
|
111
110
|
|