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
compshare_cli/actions.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Public CompShare API actions covered by the first CLI release."""
|
|
2
|
+
|
|
3
|
+
INSTANCE_ACTIONS = frozenset(
|
|
4
|
+
{
|
|
5
|
+
"CheckCompShareNetOptimizer",
|
|
6
|
+
"CheckCompShareResourceCapacity",
|
|
7
|
+
"CreateCompShareInstance",
|
|
8
|
+
"DeleteCompShareStopScheduler",
|
|
9
|
+
"DescribeAvailableCompShareInstanceTypes",
|
|
10
|
+
"DescribeCompShareInstance",
|
|
11
|
+
"DescribeCompShareMachineTypeFamilies",
|
|
12
|
+
"DescribeCompShareSoftwarePort",
|
|
13
|
+
"DescribeCompShareSupportZone",
|
|
14
|
+
"DescribeModelRepositoryModels",
|
|
15
|
+
"GetCompShareInstanceMonitor",
|
|
16
|
+
"GetCompShareInstancePrice",
|
|
17
|
+
"GetCompShareInstanceUpgradePrice",
|
|
18
|
+
"GetCompShareInstanceUserPrice",
|
|
19
|
+
"GetCompShareRefundPrice",
|
|
20
|
+
"GetSoftwareURL",
|
|
21
|
+
"ModifyCompShareInstanceName",
|
|
22
|
+
"RebootCompShareInstance",
|
|
23
|
+
"ReinstallCompShareInstance",
|
|
24
|
+
"ResetCompShareInstancePassword",
|
|
25
|
+
"ResizeCompShareInstance",
|
|
26
|
+
"StartCompShareInstance",
|
|
27
|
+
"StopCompShareInstance",
|
|
28
|
+
"SwitchChargeType",
|
|
29
|
+
"TerminateCompShareInstance",
|
|
30
|
+
"UpdateCompShareInstancePorts",
|
|
31
|
+
"UpdateCompShareStopScheduler",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
IMAGE_ACTIONS = frozenset(
|
|
36
|
+
{
|
|
37
|
+
"AddFavoriteImage",
|
|
38
|
+
"CreateCompShareCustomImage",
|
|
39
|
+
"DescribeCommunityImages",
|
|
40
|
+
"DescribeCompShareCustomImages",
|
|
41
|
+
"DescribeCompShareImages",
|
|
42
|
+
"DescribeCompShareImageShareAccounts",
|
|
43
|
+
"DescribeCompShareImageTags",
|
|
44
|
+
"DescribeCompShareSharingImages",
|
|
45
|
+
"DescribeSelfCommunityImages",
|
|
46
|
+
"DescribeUserCommunityImages",
|
|
47
|
+
"GetCompShareImageCreateProgress",
|
|
48
|
+
"ModifyCompShareImageShareAccount",
|
|
49
|
+
"PublishCompShareImage",
|
|
50
|
+
"RemoveFavoriteImage",
|
|
51
|
+
"TerminateCompShareCustomImage",
|
|
52
|
+
"UpdateCompShareImage",
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
STORAGE_ACTIONS = frozenset(
|
|
57
|
+
{
|
|
58
|
+
"AttachCompshareDisk",
|
|
59
|
+
"AttachUS3",
|
|
60
|
+
"CreateAndAttachCompshareDisk",
|
|
61
|
+
"DeleteCompshareDisk",
|
|
62
|
+
"DetachCompshareDisk",
|
|
63
|
+
"GetCompShareAttachedDiskUpgradePrice",
|
|
64
|
+
"ResizeCompShareDisk",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
PUBLIC_ACTIONS = INSTANCE_ACTIONS | IMAGE_ACTIONS | STORAGE_ACTIONS
|
|
69
|
+
|
|
70
|
+
# Documented publicly but unavailable in the production API (RetCode 161).
|
|
71
|
+
UNAVAILABLE_ACTIONS = frozenset({"DescribeFavoriteImages"})
|
compshare_cli/api.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Tuple
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from ucloud.core import exc as ucloud_exc
|
|
8
|
+
|
|
9
|
+
from compshare_cli.errors import CLIError
|
|
10
|
+
from compshare_cli.i18n import tr
|
|
11
|
+
from compshare_cli.output import Renderer
|
|
12
|
+
from compshare_cli.runtime import Runtime
|
|
13
|
+
from compshare_cli.sdk import CompShareSDK
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def invoke(
|
|
17
|
+
runtime: Runtime,
|
|
18
|
+
action: str,
|
|
19
|
+
params: Dict[str, Any],
|
|
20
|
+
*,
|
|
21
|
+
list_key: Optional[str] = None,
|
|
22
|
+
row_builder: Optional[Callable[[Dict[str, Any]], Iterable[Dict[str, Any]]]] = None,
|
|
23
|
+
columns: Optional[Sequence[Tuple[str, str]]] = None,
|
|
24
|
+
success: Optional[str] = None,
|
|
25
|
+
) -> Dict[str, Any]:
|
|
26
|
+
renderer = Renderer(runtime.json_output)
|
|
27
|
+
retries = 3 if action == "DeleteCompshareDisk" else 0
|
|
28
|
+
attempt = 0
|
|
29
|
+
try:
|
|
30
|
+
while True:
|
|
31
|
+
try:
|
|
32
|
+
response = CompShareSDK(runtime.profile, runtime.region).invoke(action, params)
|
|
33
|
+
break
|
|
34
|
+
except ucloud_exc.RetCodeException as error:
|
|
35
|
+
if error.code == 8434 and attempt < retries:
|
|
36
|
+
attempt += 1
|
|
37
|
+
if not runtime.json_output:
|
|
38
|
+
renderer.console.print(
|
|
39
|
+
tr(
|
|
40
|
+
"Resource is still detaching; retrying ({attempt}/{total})...",
|
|
41
|
+
attempt=attempt,
|
|
42
|
+
total=retries,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
time.sleep(2 * attempt)
|
|
46
|
+
continue
|
|
47
|
+
raise
|
|
48
|
+
except CLIError as error:
|
|
49
|
+
renderer.error(str(error))
|
|
50
|
+
raise typer.Exit(1) from error
|
|
51
|
+
except ucloud_exc.RetCodeException as error:
|
|
52
|
+
hint = error_hint(error.action or action, error.code)
|
|
53
|
+
renderer.error(
|
|
54
|
+
_with_hint(error.message or str(error), hint),
|
|
55
|
+
details={
|
|
56
|
+
"action": error.action,
|
|
57
|
+
"ret_code": error.code,
|
|
58
|
+
"request_uuid": error.request_uuid,
|
|
59
|
+
"hint": hint,
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
raise typer.Exit(1) from error
|
|
63
|
+
except ucloud_exc.UCloudException as error:
|
|
64
|
+
renderer.error(str(error))
|
|
65
|
+
raise typer.Exit(1) from error
|
|
66
|
+
except Exception as error: # SDK transport exceptions are not all UCloudException subclasses.
|
|
67
|
+
renderer.error(str(error))
|
|
68
|
+
raise typer.Exit(1) from error
|
|
69
|
+
|
|
70
|
+
if success:
|
|
71
|
+
renderer.success(success, response)
|
|
72
|
+
elif list_key is None and row_builder is None:
|
|
73
|
+
renderer.data(response)
|
|
74
|
+
else:
|
|
75
|
+
rows = row_builder(response) if row_builder else response.get(list_key or "", [])
|
|
76
|
+
renderer.data(response, rows=rows, columns=columns)
|
|
77
|
+
return response
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def call(runtime: Runtime, action: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
81
|
+
"""Invoke without rendering, for commands that combine multiple API calls."""
|
|
82
|
+
try:
|
|
83
|
+
return CompShareSDK(runtime.profile, runtime.region).invoke(action, params)
|
|
84
|
+
except CLIError as error:
|
|
85
|
+
Renderer(runtime.json_output).error(str(error))
|
|
86
|
+
raise typer.Exit(1) from error
|
|
87
|
+
except ucloud_exc.RetCodeException as error:
|
|
88
|
+
hint = error_hint(error.action or action, error.code)
|
|
89
|
+
Renderer(runtime.json_output).error(
|
|
90
|
+
_with_hint(error.message or str(error), hint),
|
|
91
|
+
details={
|
|
92
|
+
"action": error.action,
|
|
93
|
+
"ret_code": error.code,
|
|
94
|
+
"request_uuid": error.request_uuid,
|
|
95
|
+
"hint": hint,
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
raise typer.Exit(1) from error
|
|
99
|
+
except ucloud_exc.UCloudException as error:
|
|
100
|
+
Renderer(runtime.json_output).error(str(error))
|
|
101
|
+
raise typer.Exit(1) from error
|
|
102
|
+
except Exception as error:
|
|
103
|
+
Renderer(runtime.json_output).error(str(error))
|
|
104
|
+
raise typer.Exit(1) from error
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def error_hint(action: str, code: int) -> Optional[str]:
|
|
108
|
+
hints = {
|
|
109
|
+
("DeleteCompshareDisk", 8434): "The disk is still detaching. Wait a moment and retry.",
|
|
110
|
+
("AttachUS3", 8433): "Confirm that US3 is enabled for the selected region and account.",
|
|
111
|
+
("GetCompShareInstanceMonitor", 210): (
|
|
112
|
+
"This production endpoint currently rejects instance IDs; "
|
|
113
|
+
"use the console for monitoring."
|
|
114
|
+
),
|
|
115
|
+
("GetCompShareInstanceMonitor", 230): (
|
|
116
|
+
"This production endpoint is currently incompatible; use the console for monitoring."
|
|
117
|
+
),
|
|
118
|
+
("GetSoftwareURL", 230): (
|
|
119
|
+
"This production endpoint currently rejects its action; "
|
|
120
|
+
"use instance show or the console."
|
|
121
|
+
),
|
|
122
|
+
}
|
|
123
|
+
return tr(hints[(action, code)]) if (action, code) in hints else None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _with_hint(message: str, hint: Optional[str]) -> str:
|
|
127
|
+
return f"{message} {tr('Hint')}: {hint}" if hint else message
|
compshare_cli/cli.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import typer
|
|
8
|
+
from typer.main import get_command
|
|
9
|
+
|
|
10
|
+
from compshare_cli import __version__
|
|
11
|
+
from compshare_cli.commands import image, instance, storage
|
|
12
|
+
from compshare_cli.config import DEFAULT_PROFILE, ConfigStore, Profile
|
|
13
|
+
from compshare_cli.errors import CLIError
|
|
14
|
+
from compshare_cli.i18n import configured_language, localize_command, normalize_language, tr
|
|
15
|
+
from compshare_cli.output import Renderer
|
|
16
|
+
from compshare_cli.runtime import Runtime
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(
|
|
19
|
+
name="compshare",
|
|
20
|
+
help="Manage CompShare GPU compute from the terminal.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
add_completion=False,
|
|
23
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
24
|
+
)
|
|
25
|
+
app.add_typer(instance.app, name="instance")
|
|
26
|
+
app.add_typer(image.app, name="image")
|
|
27
|
+
app.add_typer(storage.app, name="storage")
|
|
28
|
+
config_app = typer.Typer(
|
|
29
|
+
help="Manage credential profiles.",
|
|
30
|
+
invoke_without_command=True,
|
|
31
|
+
no_args_is_help=False,
|
|
32
|
+
)
|
|
33
|
+
app.add_typer(config_app, name="config")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.callback()
|
|
37
|
+
def root(
|
|
38
|
+
ctx: typer.Context,
|
|
39
|
+
profile: Optional[str] = typer.Option(
|
|
40
|
+
None,
|
|
41
|
+
"--profile",
|
|
42
|
+
metavar="NAME",
|
|
43
|
+
help="Credential profile.",
|
|
44
|
+
),
|
|
45
|
+
json_output: bool = typer.Option(
|
|
46
|
+
False,
|
|
47
|
+
"--json",
|
|
48
|
+
help="Emit machine-readable JSON.",
|
|
49
|
+
),
|
|
50
|
+
) -> None:
|
|
51
|
+
"""CompShare CLI."""
|
|
52
|
+
ctx.obj = Runtime(
|
|
53
|
+
json_output=json_output,
|
|
54
|
+
profile_name=profile,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@config_app.callback()
|
|
59
|
+
def config(
|
|
60
|
+
ctx: typer.Context,
|
|
61
|
+
name: str = typer.Option(
|
|
62
|
+
DEFAULT_PROFILE,
|
|
63
|
+
"--name",
|
|
64
|
+
help="Credential profile name.",
|
|
65
|
+
),
|
|
66
|
+
public_key: Optional[str] = typer.Option(None, "--public-key", help="API public key."),
|
|
67
|
+
private_key: Optional[str] = typer.Option(None, "--private-key", hidden=True),
|
|
68
|
+
activate: bool = typer.Option(
|
|
69
|
+
True,
|
|
70
|
+
"--activate/--no-activate",
|
|
71
|
+
help="Make this profile the default.",
|
|
72
|
+
),
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Save a CompShare API credential profile."""
|
|
75
|
+
if ctx.invoked_subcommand is not None:
|
|
76
|
+
return
|
|
77
|
+
public = public_key or typer.prompt(tr("Public key"))
|
|
78
|
+
private = private_key or typer.prompt(tr("Private key"), hide_input=True)
|
|
79
|
+
ConfigStore().save_profile(
|
|
80
|
+
name,
|
|
81
|
+
Profile(public_key=public, private_key=private),
|
|
82
|
+
activate=activate,
|
|
83
|
+
)
|
|
84
|
+
Renderer(ctx.find_root().obj.json_output).success(
|
|
85
|
+
tr("Saved credential profile {name}", name=name),
|
|
86
|
+
{"ok": True, "profile": name, "active": activate},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@config_app.command("set")
|
|
91
|
+
def config_set(
|
|
92
|
+
ctx: typer.Context,
|
|
93
|
+
name: str = typer.Option(DEFAULT_PROFILE, "--name", help="Credential profile name."),
|
|
94
|
+
public_key: Optional[str] = typer.Option(None, "--public-key", help="API public key."),
|
|
95
|
+
private_key: Optional[str] = typer.Option(None, "--private-key", hidden=True),
|
|
96
|
+
activate: bool = typer.Option(
|
|
97
|
+
True, "--activate/--no-activate", help="Make this profile the default."
|
|
98
|
+
),
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Create or update a credential profile."""
|
|
101
|
+
public = public_key or typer.prompt(tr("Public key"))
|
|
102
|
+
private = private_key or typer.prompt(tr("Private key"), hide_input=True)
|
|
103
|
+
ConfigStore().save_profile(
|
|
104
|
+
name,
|
|
105
|
+
Profile(public_key=public, private_key=private),
|
|
106
|
+
activate=activate,
|
|
107
|
+
)
|
|
108
|
+
Renderer(ctx.find_root().obj.json_output).success(
|
|
109
|
+
tr("Saved credential profile {name}", name=name),
|
|
110
|
+
{"ok": True, "profile": name, "active": activate},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@config_app.command("list")
|
|
115
|
+
def config_list(ctx: typer.Context) -> None:
|
|
116
|
+
"""List credential profiles."""
|
|
117
|
+
store = ConfigStore()
|
|
118
|
+
current = store.current_profile()
|
|
119
|
+
profiles = [{"Profile": name, "Active": name == current} for name in store.list_profiles()]
|
|
120
|
+
Renderer(ctx.find_root().obj.json_output).data(
|
|
121
|
+
{"current_profile": current, "profiles": profiles},
|
|
122
|
+
rows=profiles,
|
|
123
|
+
columns=(("Profile", "PROFILE"), ("Active", "ACTIVE")),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@config_app.command("use")
|
|
128
|
+
def config_use(ctx: typer.Context, name: str) -> None:
|
|
129
|
+
"""Set the default credential profile."""
|
|
130
|
+
ConfigStore().use_profile(name)
|
|
131
|
+
Renderer(ctx.find_root().obj.json_output).success(
|
|
132
|
+
tr("Using credential profile {name}", name=name),
|
|
133
|
+
{"ok": True, "profile": name},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@config_app.command("delete")
|
|
138
|
+
def config_delete(
|
|
139
|
+
ctx: typer.Context,
|
|
140
|
+
name: str,
|
|
141
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Delete a credential profile."""
|
|
144
|
+
if not yes and not typer.confirm(tr("Delete credential profile {name}?", name=name)):
|
|
145
|
+
raise typer.Abort()
|
|
146
|
+
ConfigStore().delete_profile(name)
|
|
147
|
+
Renderer(ctx.find_root().obj.json_output).success(
|
|
148
|
+
tr("Deleted credential profile {name}", name=name),
|
|
149
|
+
{"ok": True, "profile": name},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@config_app.command("path")
|
|
154
|
+
def config_path_command(ctx: typer.Context) -> None:
|
|
155
|
+
"""Show the configuration file path."""
|
|
156
|
+
path = str(ConfigStore().path)
|
|
157
|
+
if ctx.find_root().obj.json_output:
|
|
158
|
+
Renderer(True).data({"path": path})
|
|
159
|
+
else:
|
|
160
|
+
typer.echo(path)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@app.command("version")
|
|
164
|
+
def version(ctx: typer.Context) -> None:
|
|
165
|
+
"""Print the CLI version."""
|
|
166
|
+
if ctx.find_root().obj.json_output:
|
|
167
|
+
Renderer(True).data({"version": __version__})
|
|
168
|
+
else:
|
|
169
|
+
typer.echo(__version__)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.command("lang")
|
|
173
|
+
def lang(
|
|
174
|
+
ctx: typer.Context,
|
|
175
|
+
language: Optional[str] = typer.Argument(None, help="Language: zh or en."),
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Set or show the default help language."""
|
|
178
|
+
renderer = Renderer(ctx.find_root().obj.json_output)
|
|
179
|
+
if language is None:
|
|
180
|
+
current = configured_language()
|
|
181
|
+
message = (
|
|
182
|
+
f"当前默认帮助语言:{current}"
|
|
183
|
+
if current == "zh"
|
|
184
|
+
else f"Default help language: {current}"
|
|
185
|
+
)
|
|
186
|
+
renderer.success(message, {"language": current})
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
selected = normalize_language(language)
|
|
190
|
+
ConfigStore().save_language(selected)
|
|
191
|
+
message = (
|
|
192
|
+
"默认帮助语言已切换为中文(zh)"
|
|
193
|
+
if selected == "zh"
|
|
194
|
+
else "Default help language set to English (en)"
|
|
195
|
+
)
|
|
196
|
+
renderer.success(message, {"ok": True, "language": selected})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def main(args: Optional[List[str]] = None) -> None:
|
|
200
|
+
argv = list(sys.argv[1:] if args is None else args)
|
|
201
|
+
try:
|
|
202
|
+
language = configured_language()
|
|
203
|
+
command = localize_command(get_command(app), language)
|
|
204
|
+
result = command.main(args=argv, prog_name="compshare", standalone_mode=False)
|
|
205
|
+
except CLIError as error:
|
|
206
|
+
Renderer("--json" in argv).error(str(error))
|
|
207
|
+
raise SystemExit(2) from error
|
|
208
|
+
except click.ClickException as error:
|
|
209
|
+
Renderer("--json" in argv).error(error.format_message())
|
|
210
|
+
raise SystemExit(error.exit_code) from error
|
|
211
|
+
except click.Abort as error:
|
|
212
|
+
Renderer("--json" in argv).error(tr("Aborted"))
|
|
213
|
+
raise SystemExit(1) from error
|
|
214
|
+
if isinstance(result, int) and result:
|
|
215
|
+
raise SystemExit(result)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command groups."""
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from compshare_cli.i18n import tr
|
|
8
|
+
from compshare_cli.location import region_from_zone
|
|
9
|
+
from compshare_cli.output import Renderer
|
|
10
|
+
from compshare_cli.runtime import Runtime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def runtime(ctx: typer.Context) -> Runtime:
|
|
14
|
+
value = ctx.find_root().obj
|
|
15
|
+
if not isinstance(value, Runtime):
|
|
16
|
+
raise RuntimeError("CLI runtime is not initialized")
|
|
17
|
+
return value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def request(
|
|
21
|
+
ctx: typer.Context,
|
|
22
|
+
*,
|
|
23
|
+
zone: bool = False,
|
|
24
|
+
project_id: Optional[str] = None,
|
|
25
|
+
region_value: Optional[str] = None,
|
|
26
|
+
zone_value: Optional[str] = None,
|
|
27
|
+
) -> Dict[str, Any]:
|
|
28
|
+
state = runtime(ctx)
|
|
29
|
+
resolved_zone = zone_value or (state.zone if zone else None)
|
|
30
|
+
resolved_region = region_value or (
|
|
31
|
+
region_from_zone(resolved_zone) if resolved_zone else state.region
|
|
32
|
+
)
|
|
33
|
+
payload: Dict[str, Any] = {"Region": resolved_region}
|
|
34
|
+
if zone:
|
|
35
|
+
payload["Zone"] = resolved_zone
|
|
36
|
+
if project_id:
|
|
37
|
+
payload["ProjectId"] = project_id
|
|
38
|
+
return payload
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def confirm(message: str, yes: bool) -> None:
|
|
42
|
+
if yes:
|
|
43
|
+
return
|
|
44
|
+
if not typer.confirm(tr(message)):
|
|
45
|
+
raise typer.Abort()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def confirm_details(
|
|
49
|
+
state: Runtime,
|
|
50
|
+
title: str,
|
|
51
|
+
fields: list[tuple[str, Any]],
|
|
52
|
+
prompt: str,
|
|
53
|
+
yes: bool,
|
|
54
|
+
) -> None:
|
|
55
|
+
if not state.json_output:
|
|
56
|
+
Renderer(False).details(title, fields)
|
|
57
|
+
confirm(prompt, yes)
|