compshare-cli 0.1.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.
- compshare_cli/__init__.py +3 -0
- compshare_cli/__main__.py +4 -0
- compshare_cli/actions.py +71 -0
- compshare_cli/api.py +127 -0
- compshare_cli/cli.py +219 -0
- compshare_cli/commands/__init__.py +1 -0
- compshare_cli/commands/common.py +57 -0
- compshare_cli/commands/image.py +528 -0
- compshare_cli/commands/instance.py +1493 -0
- compshare_cli/commands/storage.py +276 -0
- compshare_cli/config.py +127 -0
- compshare_cli/errors.py +10 -0
- compshare_cli/i18n.py +506 -0
- compshare_cli/location.py +91 -0
- compshare_cli/output.py +124 -0
- compshare_cli/parsing.py +93 -0
- compshare_cli/runtime.py +34 -0
- compshare_cli/sdk.py +27 -0
- compshare_cli-0.1.0.dist-info/METADATA +283 -0
- compshare_cli-0.1.0.dist-info/RECORD +23 -0
- compshare_cli-0.1.0.dist-info/WHEEL +5 -0
- compshare_cli-0.1.0.dist-info/entry_points.txt +2 -0
- compshare_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from compshare_cli.api import call, invoke
|
|
11
|
+
from compshare_cli.commands.common import confirm, request, runtime
|
|
12
|
+
from compshare_cli.errors import UsageError
|
|
13
|
+
from compshare_cli.i18n import tr
|
|
14
|
+
from compshare_cli.location import locate_instance, region_from_zone
|
|
15
|
+
from compshare_cli.output import Renderer
|
|
16
|
+
from compshare_cli.parsing import compact, read_base64, read_text, split_csv
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(help="Manage instance images.", no_args_is_help=True)
|
|
19
|
+
|
|
20
|
+
IMAGE_COLUMNS = (
|
|
21
|
+
("CompShareImageId", "ID"),
|
|
22
|
+
("Name", "NAME"),
|
|
23
|
+
("ImageType", "TYPE"),
|
|
24
|
+
("Author", "AUTHOR"),
|
|
25
|
+
("Status", "STATUS"),
|
|
26
|
+
("Price", "PRICE/H"),
|
|
27
|
+
("VersionName", "VERSION"),
|
|
28
|
+
("Tags", "TAGS"),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _community_rows(response: Dict[str, Any]) -> Iterable[Dict[str, Any]]:
|
|
33
|
+
for group in response.get("CompshareImageGroup", []):
|
|
34
|
+
for raw in group.get("Data", []):
|
|
35
|
+
row = dict(raw)
|
|
36
|
+
row.setdefault("Name", group.get("ImageName"))
|
|
37
|
+
row.setdefault("Status", group.get("Status"))
|
|
38
|
+
row["GroupId"] = group.get("GroupId")
|
|
39
|
+
yield row
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _source(source: str) -> Tuple[str, str, bool]:
|
|
43
|
+
mapping = {
|
|
44
|
+
"platform": ("DescribeCompShareImages", "ImageSet", False),
|
|
45
|
+
"custom": ("DescribeCompShareCustomImages", "ImageSet", False),
|
|
46
|
+
"community": ("DescribeCommunityImages", "CompshareImageGroup", True),
|
|
47
|
+
"shared": ("DescribeCompShareSharingImages", "ImageSet", False),
|
|
48
|
+
"published": ("DescribeSelfCommunityImages", "CompshareImageGroup", True),
|
|
49
|
+
"user": ("DescribeUserCommunityImages", "CompshareImageGroup", True),
|
|
50
|
+
}
|
|
51
|
+
try:
|
|
52
|
+
return mapping[source.lower()]
|
|
53
|
+
except KeyError as exc:
|
|
54
|
+
raise UsageError(
|
|
55
|
+
tr("--source must be platform, custom, community, shared, published, or user.")
|
|
56
|
+
) from exc
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command("list")
|
|
60
|
+
def list_images(
|
|
61
|
+
ctx: typer.Context,
|
|
62
|
+
source: str = typer.Option(
|
|
63
|
+
"platform",
|
|
64
|
+
"--source",
|
|
65
|
+
help="Image source: platform, custom, community, shared, published or user.",
|
|
66
|
+
),
|
|
67
|
+
image: Optional[str] = typer.Option(None, "--id", help="Filter by image ID."),
|
|
68
|
+
group: Optional[str] = typer.Option(None, "--group", help="Filter by version group ID."),
|
|
69
|
+
name: Optional[str] = typer.Option(None, help="Filter by exact image name."),
|
|
70
|
+
author: Optional[str] = typer.Option(None, help="Filter by image author."),
|
|
71
|
+
query: Optional[str] = typer.Option(None, "--query", help="Fuzzy name or author search."),
|
|
72
|
+
tag: Optional[List[str]] = typer.Option(None, "--tag", help="Filter by tag; repeatable."),
|
|
73
|
+
image_type: Optional[str] = typer.Option(None, "--type", help="Filter by image type."),
|
|
74
|
+
free: Optional[bool] = typer.Option(
|
|
75
|
+
None, "--free/--paid", help="Filter free or paid community images."
|
|
76
|
+
),
|
|
77
|
+
official: Optional[bool] = typer.Option(
|
|
78
|
+
None, "--official/--unofficial", help="Filter official or community-authored images."
|
|
79
|
+
),
|
|
80
|
+
autostart: Optional[bool] = typer.Option(
|
|
81
|
+
None, "--autostart/--no-autostart", help="Filter by automatic startup support."
|
|
82
|
+
),
|
|
83
|
+
sort: Optional[str] = typer.Option(None, "--sort", help="Community sort field."),
|
|
84
|
+
ascending: bool = typer.Option(False, "--ascending", help="Sort in ascending order."),
|
|
85
|
+
user: Optional[int] = typer.Option(None, "--user", help="Organization ID for source=user."),
|
|
86
|
+
limit: int = typer.Option(20, min=1, max=100, help="Maximum number of results."),
|
|
87
|
+
offset: int = typer.Option(0, min=0, help="Number of results to skip."),
|
|
88
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
89
|
+
zone: Optional[str] = typer.Option(None, "--zone", help="Availability zone."),
|
|
90
|
+
) -> None:
|
|
91
|
+
"""List platform, custom, community, shared or published images."""
|
|
92
|
+
source = source.lower()
|
|
93
|
+
action, list_key, grouped = _source(source)
|
|
94
|
+
tags = split_csv(tag or [])
|
|
95
|
+
if source == "platform" and len(tags) > 1:
|
|
96
|
+
raise UsageError(tr("Platform image search supports only one --tag."))
|
|
97
|
+
filters = compact(
|
|
98
|
+
{
|
|
99
|
+
"CompShareImageId": image,
|
|
100
|
+
"GroupId": group,
|
|
101
|
+
"Name": name,
|
|
102
|
+
"Author": author,
|
|
103
|
+
"FuzzySearch": query,
|
|
104
|
+
"Tag": (tags[0] if source == "platform" and tags else tags or None),
|
|
105
|
+
"ImageType": image_type,
|
|
106
|
+
"IsFree": free,
|
|
107
|
+
"IsOfficial": official,
|
|
108
|
+
"IfAutoStart": autostart,
|
|
109
|
+
"SortCondition": {"Field": sort, "ASC": ascending} if sort else None,
|
|
110
|
+
"TargetTopOrganizationId": user,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
allowed = {
|
|
114
|
+
"platform": {"CompShareImageId", "Name", "Author", "Tag", "ImageType"},
|
|
115
|
+
"custom": {"CompShareImageId"},
|
|
116
|
+
"community": {
|
|
117
|
+
"CompShareImageId",
|
|
118
|
+
"GroupId",
|
|
119
|
+
"Name",
|
|
120
|
+
"Author",
|
|
121
|
+
"FuzzySearch",
|
|
122
|
+
"Tag",
|
|
123
|
+
"IsFree",
|
|
124
|
+
"IsOfficial",
|
|
125
|
+
"IfAutoStart",
|
|
126
|
+
"SortCondition",
|
|
127
|
+
},
|
|
128
|
+
"shared": {"CompShareImageId"},
|
|
129
|
+
"published": {
|
|
130
|
+
"CompShareImageId",
|
|
131
|
+
"GroupId",
|
|
132
|
+
"Name",
|
|
133
|
+
"Author",
|
|
134
|
+
"FuzzySearch",
|
|
135
|
+
"Tag",
|
|
136
|
+
"IsFree",
|
|
137
|
+
"IsOfficial",
|
|
138
|
+
"IfAutoStart",
|
|
139
|
+
"SortCondition",
|
|
140
|
+
},
|
|
141
|
+
"user": {"TargetTopOrganizationId"},
|
|
142
|
+
}[source]
|
|
143
|
+
unsupported = sorted(set(filters) - allowed)
|
|
144
|
+
if unsupported:
|
|
145
|
+
raise UsageError(
|
|
146
|
+
tr(
|
|
147
|
+
"Image source {source} does not support: {options}",
|
|
148
|
+
source=source,
|
|
149
|
+
options=", ".join(unsupported),
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
selected_zone = zone or runtime(ctx).zone
|
|
153
|
+
selected_region = region_from_zone(selected_zone) if source == "platform" else region
|
|
154
|
+
params = request(ctx, region_value=selected_region)
|
|
155
|
+
params.update({"Limit": limit, "Offset": offset, **filters})
|
|
156
|
+
if source == "platform":
|
|
157
|
+
params["Zone"] = selected_zone
|
|
158
|
+
invoke(
|
|
159
|
+
runtime(ctx),
|
|
160
|
+
action,
|
|
161
|
+
params,
|
|
162
|
+
list_key=None if grouped else list_key,
|
|
163
|
+
row_builder=_community_rows if grouped else None,
|
|
164
|
+
columns=IMAGE_COLUMNS,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@app.command("show", help="Show image details.")
|
|
169
|
+
def show(
|
|
170
|
+
ctx: typer.Context,
|
|
171
|
+
image: str,
|
|
172
|
+
source: str = typer.Option("platform", "--source", help="Image source."),
|
|
173
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
174
|
+
zone: Optional[str] = typer.Option(None, "--zone", help="Availability zone."),
|
|
175
|
+
) -> None:
|
|
176
|
+
source = source.lower()
|
|
177
|
+
if source not in {"platform", "custom", "community", "shared", "published"}:
|
|
178
|
+
raise UsageError(tr("Image source {source} cannot be queried by ID.", source=source))
|
|
179
|
+
action, _, _ = _source(source)
|
|
180
|
+
selected_zone = zone or runtime(ctx).zone
|
|
181
|
+
selected_region = region_from_zone(selected_zone) if source == "platform" else region
|
|
182
|
+
params = request(ctx, region_value=selected_region)
|
|
183
|
+
params["CompShareImageId"] = image
|
|
184
|
+
if source == "platform":
|
|
185
|
+
params["Zone"] = selected_zone
|
|
186
|
+
state = runtime(ctx)
|
|
187
|
+
response = call(state, action, params)
|
|
188
|
+
rows = (
|
|
189
|
+
list(_community_rows(response))
|
|
190
|
+
if source in {"community", "published"}
|
|
191
|
+
else response.get("ImageSet", [])
|
|
192
|
+
)
|
|
193
|
+
item = next(
|
|
194
|
+
(row for row in rows if row.get("CompShareImageId") == image),
|
|
195
|
+
rows[0] if rows else None,
|
|
196
|
+
)
|
|
197
|
+
if not item:
|
|
198
|
+
raise UsageError(tr("Image {image} was not found.", image=image))
|
|
199
|
+
Renderer(state.json_output).details(
|
|
200
|
+
"Image details",
|
|
201
|
+
[
|
|
202
|
+
("ID", item.get("CompShareImageId")),
|
|
203
|
+
("NAME", item.get("Name")),
|
|
204
|
+
("TYPE", item.get("ImageType")),
|
|
205
|
+
("STATUS", item.get("Status")),
|
|
206
|
+
("AUTHOR", item.get("Author")),
|
|
207
|
+
("VERSION", item.get("VersionName")),
|
|
208
|
+
("TAGS", item.get("Tags")),
|
|
209
|
+
("PRICE/H", item.get("Price")),
|
|
210
|
+
("DESCRIPTION", item.get("Description")),
|
|
211
|
+
],
|
|
212
|
+
response=response,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@app.command("create")
|
|
217
|
+
def create(
|
|
218
|
+
ctx: typer.Context,
|
|
219
|
+
instance: str = typer.Option(..., "--instance", help="Source instance ID."),
|
|
220
|
+
name: str = typer.Option(..., help="Custom image name."),
|
|
221
|
+
description: Optional[str] = typer.Option(None, help="Custom image description."),
|
|
222
|
+
wait: Optional[bool] = typer.Option(
|
|
223
|
+
None,
|
|
224
|
+
"--wait/--no-wait",
|
|
225
|
+
help="Wait for the operation to reach a stable state.",
|
|
226
|
+
),
|
|
227
|
+
timeout: int = typer.Option(1800, "--timeout", min=1, help="Maximum wait time in seconds."),
|
|
228
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Create a custom image from an instance."""
|
|
231
|
+
state = runtime(ctx)
|
|
232
|
+
confirm(
|
|
233
|
+
tr(
|
|
234
|
+
"Create custom image {name} from instance {instance}?",
|
|
235
|
+
name=name,
|
|
236
|
+
instance=instance,
|
|
237
|
+
),
|
|
238
|
+
yes,
|
|
239
|
+
)
|
|
240
|
+
region, zone, _ = locate_instance(state, instance)
|
|
241
|
+
params = request(ctx, zone=True, region_value=region, zone_value=zone)
|
|
242
|
+
params.update(compact({"UHostId": instance, "Name": name, "Description": description}))
|
|
243
|
+
created = call(state, "CreateCompShareCustomImage", params)
|
|
244
|
+
result: Dict[str, Any] = {"operation": created}
|
|
245
|
+
image_id = created.get("CompShareImageId") or created.get("ImageId")
|
|
246
|
+
wait_enabled = wait if wait is not None else not state.json_output and sys.stdout.isatty()
|
|
247
|
+
if wait_enabled and image_id:
|
|
248
|
+
started = time.monotonic()
|
|
249
|
+
previous: Optional[str] = None
|
|
250
|
+
while True:
|
|
251
|
+
progress_params = request(
|
|
252
|
+
ctx,
|
|
253
|
+
zone=True,
|
|
254
|
+
region_value=region,
|
|
255
|
+
zone_value=zone,
|
|
256
|
+
)
|
|
257
|
+
progress_params["CompShareImageId"] = image_id
|
|
258
|
+
current = call(state, "GetCompShareImageCreateProgress", progress_params)
|
|
259
|
+
status = str(current.get("Status") or current.get("State") or "Creating")
|
|
260
|
+
percent = current.get("Progress") or current.get("Percent")
|
|
261
|
+
display = f"{status} ({percent}%)" if percent is not None else status
|
|
262
|
+
if display != previous and not state.json_output:
|
|
263
|
+
typer.echo(tr("Waiting for image {image}: {state}", image=image_id, state=display))
|
|
264
|
+
previous = display
|
|
265
|
+
if status.casefold() in {"available", "success", "succeeded", "done"} or percent == 100:
|
|
266
|
+
result["final"] = current
|
|
267
|
+
break
|
|
268
|
+
if status.casefold() in {"failed", "error"}:
|
|
269
|
+
raise UsageError(tr("Image creation failed: {status}", status=status))
|
|
270
|
+
if time.monotonic() - started >= timeout:
|
|
271
|
+
raise UsageError(
|
|
272
|
+
tr(
|
|
273
|
+
"Timed out after {timeout}s while waiting for image {image}.",
|
|
274
|
+
timeout=timeout,
|
|
275
|
+
image=image_id,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
time.sleep(5)
|
|
279
|
+
Renderer(state.json_output).success(tr("Creating image {name}", name=name), result)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@app.command("progress", help="Get custom image creation progress.")
|
|
283
|
+
def progress(
|
|
284
|
+
ctx: typer.Context,
|
|
285
|
+
image: str,
|
|
286
|
+
zone: Optional[str] = typer.Option(None, "--zone", help="Availability zone."),
|
|
287
|
+
) -> None:
|
|
288
|
+
selected_zone = zone or runtime(ctx).zone
|
|
289
|
+
params = request(ctx, zone=True, zone_value=selected_zone)
|
|
290
|
+
params["CompShareImageId"] = image
|
|
291
|
+
invoke(runtime(ctx), "GetCompShareImageCreateProgress", params)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@app.command("update", help="Update image metadata.")
|
|
295
|
+
def update(
|
|
296
|
+
ctx: typer.Context,
|
|
297
|
+
image: str,
|
|
298
|
+
group: Optional[str] = typer.Option(None, help="Community version group ID."),
|
|
299
|
+
name: Optional[str] = typer.Option(None, help="New image name."),
|
|
300
|
+
description: Optional[str] = typer.Option(None, help="New image description."),
|
|
301
|
+
visibility: Optional[int] = typer.Option(
|
|
302
|
+
None, min=0, max=1, help="Visibility: 0 private, 1 public."
|
|
303
|
+
),
|
|
304
|
+
price: Optional[float] = typer.Option(None, min=0, help="Hourly image price."),
|
|
305
|
+
cover: Optional[Path] = typer.Option(
|
|
306
|
+
None, exists=True, dir_okay=False, help="Cover image file encoded as Base64."
|
|
307
|
+
),
|
|
308
|
+
readme: Optional[Path] = typer.Option(
|
|
309
|
+
None, exists=True, dir_okay=False, help="UTF-8 README file."
|
|
310
|
+
),
|
|
311
|
+
tag: Optional[List[str]] = typer.Option(None, "--tag", help="Image tag; repeatable."),
|
|
312
|
+
version: Optional[str] = typer.Option(None, help="Version name."),
|
|
313
|
+
version_description: Optional[str] = typer.Option(
|
|
314
|
+
None, "--version-description", help="Version description."
|
|
315
|
+
),
|
|
316
|
+
gpu: Optional[List[str]] = typer.Option(None, "--gpu", help="Supported GPU type; repeatable."),
|
|
317
|
+
autostart: Optional[bool] = typer.Option(
|
|
318
|
+
None, "--autostart/--no-autostart", help="Whether the image supports automatic startup."
|
|
319
|
+
),
|
|
320
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
321
|
+
) -> None:
|
|
322
|
+
values = compact(
|
|
323
|
+
{
|
|
324
|
+
"GroupId": group,
|
|
325
|
+
"Name": name,
|
|
326
|
+
"Description": description,
|
|
327
|
+
"Visibility": visibility,
|
|
328
|
+
"Price": price,
|
|
329
|
+
"Cover": read_base64(cover),
|
|
330
|
+
"Readme": read_text(readme),
|
|
331
|
+
"Tags": split_csv(tag or []) or None,
|
|
332
|
+
"VersionName": version,
|
|
333
|
+
"VersionDesc": version_description,
|
|
334
|
+
"SupportedGpuTypes": split_csv(gpu or []) or None,
|
|
335
|
+
"AutoStart": autostart,
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
if not values:
|
|
339
|
+
raise UsageError(tr("Specify at least one field to update."))
|
|
340
|
+
params = request(ctx, region_value=region)
|
|
341
|
+
params.update({"CompShareImageId": image, **values})
|
|
342
|
+
invoke(
|
|
343
|
+
runtime(ctx),
|
|
344
|
+
"UpdateCompShareImage",
|
|
345
|
+
params,
|
|
346
|
+
success=tr("Updated image {image}", image=image),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@app.command("delete", help="Permanently delete a custom image.")
|
|
351
|
+
def delete(
|
|
352
|
+
ctx: typer.Context,
|
|
353
|
+
image: str,
|
|
354
|
+
zone: Optional[str] = typer.Option(None, "--zone", help="Availability zone."),
|
|
355
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
|
|
356
|
+
) -> None:
|
|
357
|
+
confirm(tr("Permanently delete custom image {image}?", image=image), yes)
|
|
358
|
+
selected_zone = zone or runtime(ctx).zone
|
|
359
|
+
params = request(ctx, zone=True, zone_value=selected_zone)
|
|
360
|
+
params["CompShareImageId"] = image
|
|
361
|
+
invoke(
|
|
362
|
+
runtime(ctx),
|
|
363
|
+
"TerminateCompShareCustomImage",
|
|
364
|
+
params,
|
|
365
|
+
success=tr("Deleted image {image}", image=image),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@app.command("shares", help="List accounts an image is shared with.")
|
|
370
|
+
def shares(
|
|
371
|
+
ctx: typer.Context,
|
|
372
|
+
image: str,
|
|
373
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
374
|
+
) -> None:
|
|
375
|
+
params = request(ctx, region_value=region)
|
|
376
|
+
params["CompShareImageId"] = image
|
|
377
|
+
invoke(
|
|
378
|
+
runtime(ctx),
|
|
379
|
+
"DescribeCompShareImageShareAccounts",
|
|
380
|
+
params,
|
|
381
|
+
list_key="AccountSet",
|
|
382
|
+
columns=(("AccountId", "ACCOUNT ID"), ("AccountName", "ACCOUNT")),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _share(
|
|
387
|
+
ctx: typer.Context,
|
|
388
|
+
image: str,
|
|
389
|
+
accounts: List[int],
|
|
390
|
+
*,
|
|
391
|
+
remove: bool,
|
|
392
|
+
region: Optional[str],
|
|
393
|
+
) -> None:
|
|
394
|
+
params = request(ctx, region_value=region)
|
|
395
|
+
params["CompShareImageId"] = image
|
|
396
|
+
params["RemoveAccounts" if remove else "AddAccounts"] = accounts
|
|
397
|
+
invoke(
|
|
398
|
+
runtime(ctx),
|
|
399
|
+
"ModifyCompShareImageShareAccount",
|
|
400
|
+
params,
|
|
401
|
+
success=tr("Unshared image {image}" if remove else "Shared image {image}", image=image),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@app.command("share", help="Share an image with accounts.")
|
|
406
|
+
def share(
|
|
407
|
+
ctx: typer.Context,
|
|
408
|
+
image: str,
|
|
409
|
+
accounts: List[int] = typer.Argument(...),
|
|
410
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
411
|
+
) -> None:
|
|
412
|
+
_share(ctx, image, accounts, remove=False, region=region)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@app.command("unshare", help="Remove image sharing from accounts.")
|
|
416
|
+
def unshare(
|
|
417
|
+
ctx: typer.Context,
|
|
418
|
+
image: str,
|
|
419
|
+
accounts: List[int] = typer.Argument(...),
|
|
420
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
421
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
|
|
422
|
+
) -> None:
|
|
423
|
+
confirm(
|
|
424
|
+
tr("Remove {count} share(s) from image {image}?", count=len(accounts), image=image),
|
|
425
|
+
yes,
|
|
426
|
+
)
|
|
427
|
+
_share(ctx, image, accounts, remove=True, region=region)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@app.command("publish", help="Publish an image to the community.")
|
|
431
|
+
def publish(
|
|
432
|
+
ctx: typer.Context,
|
|
433
|
+
image: str,
|
|
434
|
+
version: str = typer.Option(..., help="Community version name."),
|
|
435
|
+
group: Optional[str] = typer.Option(None, help="Existing community version group ID."),
|
|
436
|
+
name: Optional[str] = typer.Option(None, help="Community image name."),
|
|
437
|
+
version_description: Optional[str] = typer.Option(
|
|
438
|
+
None, "--version-description", help="Version description."
|
|
439
|
+
),
|
|
440
|
+
price: float = typer.Option(0, min=0, help="Hourly image price; 0 means free."),
|
|
441
|
+
cover: Optional[Path] = typer.Option(
|
|
442
|
+
None, exists=True, dir_okay=False, help="Cover image file encoded as Base64."
|
|
443
|
+
),
|
|
444
|
+
tag: Optional[List[str]] = typer.Option(None, "--tag", help="Image tag; repeatable."),
|
|
445
|
+
description: Optional[str] = typer.Option(None, help="Community image description."),
|
|
446
|
+
readme: Optional[Path] = typer.Option(
|
|
447
|
+
None, exists=True, dir_okay=False, help="UTF-8 README file."
|
|
448
|
+
),
|
|
449
|
+
gpu: Optional[List[str]] = typer.Option(None, "--gpu", help="Supported GPU type; repeatable."),
|
|
450
|
+
autostart: Optional[bool] = typer.Option(
|
|
451
|
+
None, "--autostart/--no-autostart", help="Whether the image supports automatic startup."
|
|
452
|
+
),
|
|
453
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
454
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
|
|
455
|
+
) -> None:
|
|
456
|
+
confirm(
|
|
457
|
+
tr("Publish image {image} as community version {version}?", image=image, version=version),
|
|
458
|
+
yes,
|
|
459
|
+
)
|
|
460
|
+
params = request(ctx, region_value=region)
|
|
461
|
+
params.update(
|
|
462
|
+
compact(
|
|
463
|
+
{
|
|
464
|
+
"CompShareImageId": image,
|
|
465
|
+
"GroupId": group,
|
|
466
|
+
"CommunityImageName": name,
|
|
467
|
+
"VersionName": version,
|
|
468
|
+
"VersionDesc": version_description,
|
|
469
|
+
"Price": price,
|
|
470
|
+
"Cover": read_base64(cover),
|
|
471
|
+
"Tags": split_csv(tag or []) or None,
|
|
472
|
+
"Description": description,
|
|
473
|
+
"Readme": read_text(readme),
|
|
474
|
+
"SupportedGpuTypes": split_csv(gpu or []) or None,
|
|
475
|
+
"AutoStart": autostart,
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
)
|
|
479
|
+
invoke(
|
|
480
|
+
runtime(ctx),
|
|
481
|
+
"PublishCompShareImage",
|
|
482
|
+
params,
|
|
483
|
+
success=tr("Published image {image}", image=image),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
@app.command("favorite", help="Add an image to favorites.")
|
|
488
|
+
def favorite(
|
|
489
|
+
ctx: typer.Context,
|
|
490
|
+
image: str,
|
|
491
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
492
|
+
) -> None:
|
|
493
|
+
params = request(ctx, region_value=region)
|
|
494
|
+
params["CompShareImageId"] = image
|
|
495
|
+
invoke(
|
|
496
|
+
runtime(ctx),
|
|
497
|
+
"AddFavoriteImage",
|
|
498
|
+
params,
|
|
499
|
+
success=tr("Favorited image {image}", image=image),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@app.command("unfavorite", help="Remove an image from favorites.")
|
|
504
|
+
def unfavorite(
|
|
505
|
+
ctx: typer.Context,
|
|
506
|
+
image: str,
|
|
507
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
508
|
+
) -> None:
|
|
509
|
+
params = request(ctx, region_value=region)
|
|
510
|
+
params["CompShareImageId"] = image
|
|
511
|
+
invoke(
|
|
512
|
+
runtime(ctx),
|
|
513
|
+
"RemoveFavoriteImage",
|
|
514
|
+
params,
|
|
515
|
+
success=tr("Unfavorited image {image}", image=image),
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@app.command("tags", help="List available image tags.")
|
|
520
|
+
def tags(
|
|
521
|
+
ctx: typer.Context,
|
|
522
|
+
region: Optional[str] = typer.Option(None, "--region", help="Region for this request."),
|
|
523
|
+
) -> None:
|
|
524
|
+
invoke(
|
|
525
|
+
runtime(ctx),
|
|
526
|
+
"DescribeCompShareImageTags",
|
|
527
|
+
request(ctx, region_value=region),
|
|
528
|
+
)
|