oks-cli 1.14__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.
oks_cli/main.py ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ import click
3
+ import os
4
+ import json
5
+ import logging
6
+ from .project import project
7
+ from .cluster import cluster
8
+ from .profile import profile
9
+ from .cache import cache
10
+ from .quotas import quotas
11
+
12
+ from .utils import ctx_update, login_profile, install_completions, profile_completer
13
+
14
+ # Main CLI entry point
15
+ @click.group(invoke_without_command=True)
16
+ @click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
17
+ @click.option('--project-name', '-p', required = False, help="Project Name")
18
+ @click.option('--cluster-name', '-c', required = False, help="Cluster Name")
19
+ @click.option('-v', '--verbose', count=True)
20
+ @click.pass_context
21
+ def cli(ctx, project_name, cluster_name, profile, verbose):
22
+ """
23
+ CLI tool to manage projects and clusters.
24
+
25
+ You can run commands on two scopes: 'project' and 'cluster'.
26
+ Each scope has several actions like 'list', 'get', 'create', etc.
27
+ """
28
+ ctx_update(ctx, project_name, cluster_name, profile)
29
+
30
+ LOGLEVEL = os.environ.get('LOGLEVEL', 'WARNING').upper()
31
+
32
+ if verbose >= 1:
33
+ LOGLEVEL = "INFO"
34
+
35
+ if verbose >= 2:
36
+ LOGLEVEL = "DEBUG"
37
+
38
+ logging.basicConfig(
39
+ format='%(asctime)s %(levelname)-8s %(filename)s:%(funcName)s %(message)s',
40
+ level=LOGLEVEL,
41
+ datefmt='%Y-%m-%d %H:%M:%S'
42
+ )
43
+ logging.info("Set loglevel to %s", LOGLEVEL)
44
+
45
+ if ctx.invoked_subcommand is None:
46
+ click.echo(ctx.get_help())
47
+
48
+ if not hasattr(ctx, 'obj') or not ctx.obj:
49
+ ctx.obj = dict()
50
+
51
+ if project_name != None:
52
+ ctx.obj['project_name'] = project_name
53
+
54
+ if cluster_name != None:
55
+ ctx.obj['cluster_name'] = cluster_name
56
+
57
+ # Register the project and cluster groups with the main CLI
58
+ cli.add_command(project)
59
+ cli.add_command(cluster)
60
+ cli.add_command(profile)
61
+ cli.add_command(cache)
62
+ cli.add_command(quotas)
63
+
64
+ def recursive_help(cmd, parent=None):
65
+ """Recursively prints help for all commands and subcommands."""
66
+ ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
67
+ print(cmd.get_help(ctx))
68
+ print()
69
+ commands = getattr(cmd, 'commands', {})
70
+ for sub in commands.values():
71
+ recursive_help(sub, ctx)
72
+
73
+ @cli.command('fullhelp', help="Display detailed help information for all commands.")
74
+ def fullhelp():
75
+ """Display comprehensive help for all CLI commands."""
76
+ recursive_help(cli)
77
+
78
+ @cli.command('version', help="Show the current CLI version.")
79
+ def version():
80
+ """Display the current CLI version."""
81
+ import importlib.metadata
82
+ print(importlib.metadata.version(__package__))
83
+
84
+ @cli.command("install-completion", help="Install shell completion scripts.")
85
+ @click.option('--type', help="Shell")
86
+ def install_completion(type):
87
+ """Install shell completion scripts for the CLI."""
88
+ install_completions(type)
89
+
90
+ if __name__ == '__main__':
91
+ cli()
oks_cli/profile.py ADDED
@@ -0,0 +1,128 @@
1
+ import click
2
+ from .utils import set_profile, remove_profile, profile_list, DEFAULT_API_URL, get_profiles
3
+
4
+
5
+ # DEFINE THE PROFILE COMMAND GROUP
6
+ @click.group(help="Profile related commands.")
7
+ def profile():
8
+ """Profile management command group."""
9
+ pass
10
+
11
+
12
+ @profile.command('add', help="Add AK/SK or username/password new profile")
13
+ @click.option('--profile-name', required=False, help="Name of profile, optional", type=click.STRING)
14
+ @click.option('--access-key', required=False, help="AK of profile", type=click.STRING)
15
+ @click.option('--secret-key', required=False, help="SK of profile", type=click.STRING)
16
+ @click.option('--username', required=False, help="Username", type=click.STRING)
17
+ @click.option('--password', required=False, help="Password", type=click.STRING)
18
+ @click.option('--region', required=True, help="Region name", type=click.Choice(['eu-west-2', 'cloudgouv-eu-west-1']))
19
+ @click.option('--endpoint', required=False, help="API endpoint", type=click.STRING)
20
+ @click.option('--jwt', required=False, help="Enable jwt, by default is false", type=click.BOOL)
21
+ def add_profile(profile_name, access_key, secret_key, username, password, region, endpoint, jwt):
22
+ """Add a new profile with AK/SK or username/password authentication."""
23
+ if not profile_name:
24
+ profile_name = "default"
25
+
26
+ existing_profiles = get_profiles()
27
+ if profile_name in existing_profiles:
28
+ confirm = click.confirm(
29
+ f"The profile '{profile_name}' already exists. Do you want to replace it?",
30
+ default=False
31
+ )
32
+ if not confirm:
33
+ click.echo("Aborted.")
34
+ return
35
+
36
+ obj = {
37
+ "region_name": region
38
+ }
39
+
40
+ if endpoint:
41
+ obj["endpoint"] = endpoint
42
+
43
+ if access_key and secret_key:
44
+ obj["type"] = "ak/sk"
45
+ obj["access_key"] = access_key
46
+ obj["secret_key"] = secret_key
47
+
48
+ elif username and password:
49
+ obj["type"] = "username/password"
50
+ obj["username"] = username
51
+ obj["password"] = password
52
+ else:
53
+ raise click.UsageError("--access-key and --secret-key or --username and --password must be specified")
54
+
55
+ if jwt is not None:
56
+ obj["jwt"] = jwt
57
+
58
+ set_profile(profile_name, obj)
59
+
60
+ profile_name_styled = click.style(profile_name, bold=True)
61
+ click.echo(f"Profile {profile_name_styled} has been successfully added")
62
+
63
+ @profile.command('update', help="Update an existing profile")
64
+ @click.option('--profile-name', required=True, help="Name of profile", type=click.STRING)
65
+ @click.option('--region', required=False, help="Region name", type=click.Choice(['eu-west-2', 'cloudgouv-eu-west-1']))
66
+ @click.option('--endpoint', required=False, help="API endpoint", type=click.STRING)
67
+ @click.option('--jwt', required=False, help="Enable jwt, by default is false", type=click.BOOL)
68
+ def update_profile(profile_name, region, endpoint, jwt):
69
+ """Update configuration settings for an existing profile."""
70
+ profiles = profile_list()
71
+ if profile_name not in profiles:
72
+ raise click.ClickException(f"There no profile with name: {profile_name}")
73
+
74
+ profile = profiles[profile_name]
75
+ if region:
76
+ profile["region_name"] = region
77
+
78
+ if endpoint:
79
+ profile["endpoint"] = endpoint
80
+
81
+ if jwt is not None:
82
+ profile["jwt"] = jwt
83
+
84
+ set_profile(profile_name, profile)
85
+
86
+ profile_name = click.style(profile_name, bold=True)
87
+
88
+ click.echo(f"Profile {profile_name} has been successfully updated")
89
+
90
+ @profile.command('delete', help="Delete a profile by name")
91
+ @click.option('--profile-name', required=True, help="Name of profile", type=click.STRING)
92
+ @click.option('--force', is_flag=True, help="Force deletion without confirmation")
93
+ def delete_profile(profile_name, force):
94
+ """Delete a profile with confirmation."""
95
+ profiles = profile_list()
96
+ profile_name_bold = click.style(profile_name, bold=True)
97
+
98
+ if profile_name not in profiles:
99
+ raise click.ClickException(f"There no profile with name: {profile_name_bold}")
100
+
101
+ if force or click.confirm(f"Are you sure you want to delete the profile with name {profile_name_bold}?", abort=True):
102
+ remove_profile(profile_name)
103
+ click.echo(f"Profile {profile_name_bold} has been successfully deleted")
104
+
105
+ @profile.command('list', help="List existing profiles")
106
+ def list_profiles():
107
+ """Display all configured profiles with their settings."""
108
+ profiles = profile_list()
109
+
110
+ if not profiles:
111
+ return click.echo("There are no profiles")
112
+
113
+ profiles_keys = list(profiles.keys())
114
+
115
+ for key in profiles_keys:
116
+ if 'endpoint' not in profiles[key]:
117
+ if 'region_name' in profiles[key]:
118
+ endpoint = click.style(DEFAULT_API_URL.format(region=profiles[key]['region_name']), bold=True)
119
+ else:
120
+ endpoint = None
121
+ else:
122
+ endpoint = click.style(profiles[key]["endpoint"], bold=True)
123
+ name = click.style(key, bold=True)
124
+ account_type = click.style(profiles[key]["type"], bold=True)
125
+ region = click.style(profiles[key]["region_name"], bold=True)
126
+ jwt = click.style(profiles[key].get("jwt", False), bold=True)
127
+
128
+ click.echo(f"Profile: {name} Account type: {account_type} Region: {region} Endpoint: {endpoint} Enabled JWT auth: {jwt}")
oks_cli/project.py ADDED
@@ -0,0 +1,398 @@
1
+ import click
2
+ import time
3
+ import datetime
4
+ import dateutil.parser
5
+ import human_readable
6
+ import prettytable
7
+ import os
8
+
9
+ from .utils import do_request, print_output, find_project_id_by_name, get_project_id, set_project_id, detect_and_parse_input, transform_tuple, ctx_update, set_cluster_id, get_template, get_project_name, format_changed_row, is_interesting_status, login_profile, profile_completer
10
+
11
+ # DEIFNE THE PROJECT COMMAND GROUP
12
+ @click.group(help="Project related commands.")
13
+ @click.option('--project', 'project_name', required = False, help="Project Name")
14
+ @click.option('--project-name', '-p', required = False, help="Project Name")
15
+ @click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
16
+ @click.pass_context
17
+ def project(ctx, project_name, profile):
18
+ """Group of commands related to project management."""
19
+ ctx_update(ctx, project_name, None, profile)
20
+
21
+ # LOGIN ON PROJECT
22
+ @project.command('login', help="Set a default project by name")
23
+ @click.option('--project-name', '-p', required=False, help="Name of project", type=click.STRING)
24
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
25
+ @click.pass_context
26
+ def project_login(ctx, project_name, profile):
27
+ """Set a default project by its name and log in."""
28
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
29
+ login_profile(profile)
30
+
31
+ data = do_request("GET", 'projects', params={"name": project_name})
32
+ if len(data) != 1:
33
+ raise click.BadParameter(
34
+ f"{len(data)} projects found by name: {project_name}")
35
+ project = data.pop()
36
+
37
+ project_id = project['id']
38
+ project_name = project['name']
39
+
40
+ set_project_id(project_id)
41
+ set_cluster_id("")
42
+
43
+ project_name = click.style(project_name, bold=True)
44
+
45
+ click.echo(f"Logged into project: {project_name}")
46
+
47
+ # LOGOUT ON PROJECT
48
+ @project.command('logout', help="Unset default project")
49
+ @click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
50
+ @click.pass_context
51
+ def project_logout(ctx, profile):
52
+ """Unset the current default project and log out."""
53
+ _, _, profile = ctx_update(ctx, None, None, profile)
54
+ login_profile(profile)
55
+ set_project_id("")
56
+ set_cluster_id("")
57
+ click.echo("Logged out from the current project")
58
+
59
+ # LIST PROJECTS
60
+ @project.command('list', help="List all projects")
61
+ @click.option('--project-name', '-p', help="Name of project", type=click.STRING)
62
+ @click.option('--deleted', is_flag=True, help="List deleted projects")
63
+ @click.option('--plain', is_flag=True, help="Plain table format")
64
+ @click.option('--msword', is_flag=True, help="Microsoft Word table format")
65
+ @click.option('--uuid', is_flag=True, help="show uuid")
66
+ @click.option('--watch', '-w', is_flag=True, help="Watch the changes")
67
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
68
+ @click.option('--profile', help="Configuration profile to use")
69
+ @click.pass_context
70
+ def project_list(ctx, project_name, deleted, plain, msword, uuid, watch, output, profile):
71
+ """List projects with filtering, formatting, and live watch capabilities."""
72
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
73
+ login_profile(profile)
74
+
75
+ project_id = get_project_id()
76
+ params = {}
77
+ if project_name:
78
+ params['name'] = project_name
79
+
80
+ if deleted:
81
+ params['deleted'] = True
82
+
83
+ data = do_request("GET", 'projects', params=params)
84
+
85
+ if output:
86
+ print_output(data, output)
87
+ return
88
+
89
+ field_names = ["NAME", "CREATED", "UPDATED", "STATUS", "DEFAULT"]
90
+ if uuid:
91
+ field_names.append('UUID')
92
+
93
+ table = prettytable.PrettyTable()
94
+ table.field_names = field_names
95
+
96
+ table._min_width = {"CREATED": 13, "UPDATED": 13, "STATUS": 10}
97
+
98
+ if plain or watch:
99
+ table.set_style(prettytable.PLAIN_COLUMNS)
100
+
101
+ if msword:
102
+ table.set_style(prettytable.MSWORD_FRIENDLY)
103
+
104
+ def format_row(project):
105
+ status = project.get('status')
106
+ is_default = True if project.get('id') == project_id else False
107
+
108
+ if status == 'ready':
109
+ msg = click.style(status, fg='green')
110
+ elif status == 'failed' or status == 'deleted':
111
+ msg = click.style(status, fg='red')
112
+ elif status == 'deploying':
113
+ msg = click.style(status, fg='yellow')
114
+ else:
115
+ msg = status
116
+
117
+ name = click.style(project['name'], bold=True)
118
+ if is_default:
119
+ default = "*"
120
+ else:
121
+ default = ""
122
+
123
+ created_at = dateutil.parser.parse(project['created_at'])
124
+ updated_at = dateutil.parser.parse(project['updated_at'])
125
+ now = datetime.datetime.now(tz=created_at.tzinfo)
126
+
127
+ row = [name, human_readable.date_time(now - created_at), human_readable.date_time(now - updated_at), msg, default]
128
+ if uuid:
129
+ row.append(project['id'])
130
+
131
+ return row, status, project['name']
132
+
133
+ initial_projects = {}
134
+
135
+ for project in data:
136
+ row, _, name = format_row(project)
137
+ table.add_row(row)
138
+ initial_projects[name] = project
139
+
140
+ click.echo(table)
141
+
142
+
143
+ if watch:
144
+ total_sleep = 0
145
+ try:
146
+ while True:
147
+ time.sleep(2)
148
+ total_sleep += 2
149
+ try:
150
+ data = do_request("GET", 'projects', params=params)
151
+ except click.ClickException as err:
152
+ click.echo(f"Error during watch: {err}")
153
+ continue
154
+
155
+ current_project_names = {project['name'] for project in data}
156
+
157
+ for name, project in list(initial_projects.items()):
158
+ if name not in current_project_names:
159
+ deleted_project = project.copy()
160
+ deleted_project['status'] = 'deleted'
161
+
162
+ row, current_status, _ = format_row(deleted_project)
163
+
164
+ new_table = format_changed_row(table, row)
165
+ click.echo(new_table)
166
+
167
+ del initial_projects[name]
168
+
169
+ for project in data:
170
+ row, current_status, name = format_row(project)
171
+
172
+ if name not in initial_projects:
173
+ new_table = format_changed_row(table, row)
174
+ click.echo(new_table)
175
+ initial_projects[name] = project
176
+ continue
177
+
178
+ stored_project = initial_projects[name]
179
+ project_status = stored_project.get('status')
180
+ if project_status != current_status:
181
+ new_table = format_changed_row(table, row)
182
+ click.echo(new_table)
183
+ initial_projects[name] = project
184
+ continue
185
+
186
+ if total_sleep % 10 == 0 and is_interesting_status(current_status):
187
+ new_table = format_changed_row(table, row)
188
+ click.echo(new_table)
189
+ initial_projects[name] = project
190
+
191
+ except KeyboardInterrupt:
192
+ click.echo("\nWatch stopped.")
193
+
194
+ # CREATE PROJECT BY NAME
195
+ @project.command('create', help="Create a new project")
196
+ @click.option('--project-name', '-p', help="Name of the project")
197
+ @click.option('--description', help="Description of the project")
198
+ @click.option('--cidr', help='CIDR for the project')
199
+ @click.option('--quirk', multiple=True, help="Quirk")
200
+ @click.option('--tags', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
201
+ @click.option('--disable-api-termination', type=click.BOOL, help="Disable delete action by API")
202
+ @click.option('--dry-run', is_flag=True, help="Client dry-run, only print the object that would be sent, without sending it")
203
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml", "silent"]), help="Specify output format, by default is json")
204
+ @click.option('-f', '--filename', type=click.File("r"), help="Path to file to use to create the project ")
205
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
206
+ @click.pass_context
207
+ def project_create(ctx, project_name, description, cidr, quirk, tags, disable_api_termination, dry_run, output, filename, profile):
208
+ """Create a new project from options or file, with support for dry-run and output formatting."""
209
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
210
+ login_profile(profile)
211
+
212
+ project_config = None
213
+
214
+ if filename:
215
+ input_data = filename.read()
216
+ project_config = detect_and_parse_input(input_data)
217
+ else:
218
+ project_config = get_template("project")
219
+ if os.getenv("OKS_REGION"):
220
+ project_config["region"] = os.getenv("OKS_REGION")
221
+
222
+ if project_name:
223
+ project_config['name'] = project_name
224
+
225
+ if description:
226
+ project_config['description'] = description
227
+
228
+ if cidr:
229
+ project_config['cidr'] = cidr
230
+
231
+ if quirk:
232
+ project_config['quirks'] = transform_tuple(quirk)
233
+
234
+ if tags:
235
+ parsed_tags = {}
236
+
237
+ pairs = tags.split(',')
238
+ for pair in pairs:
239
+ if '=' not in pair:
240
+ raise click.ClickException(f"Malformed tags: '{pair}' (expected key=value)")
241
+ key, value = pair.split('=', 1)
242
+ parsed_tags[key.strip()] = value.strip()
243
+
244
+ project_config['tags'] = parsed_tags
245
+
246
+ if disable_api_termination is not None:
247
+ project_config["disable_api_termination"] = disable_api_termination
248
+
249
+ if not dry_run:
250
+ data = do_request("POST", 'projects', json=project_config)
251
+ print_output(data, output)
252
+ else:
253
+ print_output(project_config, output)
254
+
255
+ # GET PROJECT BY NAME
256
+ @project.command('get', help="Get default project or the project by name")
257
+ @click.option('--project-name', '-p', help="Name of the project")
258
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
259
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
260
+ @click.pass_context
261
+ def project_get(ctx, project_name, output, profile):
262
+ """Retrieve and display project details by name or default project."""
263
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
264
+ login_profile(profile)
265
+
266
+ project_id = find_project_id_by_name(project_name)
267
+
268
+ data = do_request("GET", f'projects/{project_id}')
269
+ print_output(data, output)
270
+
271
+ # DELETE PROJECT BY NAME
272
+ @project.command('delete', help="Delete a project by name")
273
+ @click.option('--project-name', '-p', required=False, help="Project Name")
274
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
275
+ @click.option('--dry-run', is_flag=True, help="Run without any action")
276
+ @click.option('--force', is_flag=True, help="Force deletion without confirmation")
277
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
278
+ @click.pass_context
279
+ def project_delete_command(ctx, project_name, output, dry_run, force, profile):
280
+ """Delete a project by name, with optional dry-run and confirmation."""
281
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
282
+ login_profile(profile)
283
+
284
+ project_id = find_project_id_by_name(project_name)
285
+ project_name = get_project_name(project_name)
286
+
287
+ current_project_id = get_project_id()
288
+
289
+ if dry_run:
290
+ message = {"message": "Dry run: The project would be deleted."}
291
+ print_output(message, output)
292
+ return
293
+
294
+ if force or click.confirm(f"Are you sure you want to delete the project with name {project_name}?", abort=True):
295
+ data = do_request("DELETE", f'projects/{project_id}')
296
+
297
+ if current_project_id == project_id:
298
+ set_project_id("")
299
+ set_cluster_id("")
300
+ print_output(data, output)
301
+
302
+
303
+ # UPDATE PROJECT BY NAME
304
+ @project.command('update', help="Update a project by name")
305
+ @click.option('--project-name', '-p', required=False, help="Project Name")
306
+ @click.option('--description', help="Description of the project")
307
+ @click.option('--quirk', multiple=True, help="Quirk")
308
+ @click.option('--tags', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
309
+ @click.option('--disable-api-termination', type=click.BOOL, help="Disable delete action by API")
310
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
311
+ @click.option('--dry-run', is_flag=True, help="Run without any action")
312
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
313
+ @click.pass_context
314
+ def project_update_command(ctx, project_name, description, quirk, tags, disable_api_termination, output, dry_run, profile):
315
+ """Update project details by name, supporting dry-run and output formatting."""
316
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
317
+ login_profile(profile)
318
+
319
+ project_id = find_project_id_by_name(project_name)
320
+
321
+ project_config = {}
322
+
323
+ if disable_api_termination is not None:
324
+ project_config["disable_api_termination"] = disable_api_termination
325
+
326
+ if description:
327
+ project_config['description'] = description
328
+
329
+ if quirk:
330
+ project_config['quirks'] = transform_tuple(quirk)
331
+
332
+ if tags is not None:
333
+ parsed_tags = {}
334
+
335
+ if not len(tags) == 0:
336
+ pairs = tags.split(',')
337
+ for pair in pairs:
338
+ if '=' not in pair:
339
+ raise click.ClickException(f"Malformed tags: '{pair}' (expected key=value)")
340
+ key, value = pair.split('=', 1)
341
+ parsed_tags[key.strip()] = value.strip()
342
+
343
+ project_config['tags'] = parsed_tags
344
+
345
+ if dry_run:
346
+ print_output(project_config, output)
347
+ else:
348
+ data = do_request("PATCH", f'projects/{project_id}', json = project_config)
349
+ print_output(data, output)
350
+
351
+ # GET PROJECT QUOTAS BY PROJECT NAME
352
+ @project.command('quotas', help="Get project quotas")
353
+ @click.option('--project-name', '-p', help="Name of the project")
354
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
355
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
356
+ @click.pass_context
357
+ def project_get_quotas(ctx, project_name, output, profile):
358
+ """Retrieve resource quotas for the specified project."""
359
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
360
+ login_profile(profile)
361
+
362
+ project_id = find_project_id_by_name(project_name)
363
+
364
+ data = do_request("GET", f'projects/{project_id}/quotas')["data"]
365
+ print_output(data, output)
366
+
367
+
368
+ # GET PROJECT SNAPSHOTS BY PROJECT NAME
369
+ @project.command('snapshots', help="Get project snapshots")
370
+ @click.option('--project-name', '-p', help="Name of the project")
371
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
372
+ @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
373
+ @click.pass_context
374
+ def project_get(ctx, project_name, output, profile):
375
+ """Retrieve snapshots associated with the specified project."""
376
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
377
+ login_profile(profile)
378
+
379
+ project_id = find_project_id_by_name(project_name)
380
+
381
+ response = do_request("GET", f'projects/{project_id}/snapshots')
382
+ print_output(response, output)
383
+
384
+ # GET PUBLIC IPS BY PROJECT NAME
385
+ @project.command('publicips', help="Get project public ips")
386
+ @click.option('--project-name', '-p', help="Name of the project")
387
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
388
+ @click.option('--profile', help="Configuration profile to use")
389
+ @click.pass_context
390
+ def project_get_public_ips(ctx, project_name, output, profile):
391
+ """Retrieve the list of public IPs associated with the specified project."""
392
+ project_name, _, profile = ctx_update(ctx, project_name, None, profile)
393
+ login_profile(profile)
394
+
395
+ project_id = find_project_id_by_name(project_name)
396
+
397
+ data = do_request("GET", f'projects/{project_id}/public_ips')
398
+ print_output(data, output)
oks_cli/quotas.py ADDED
@@ -0,0 +1,14 @@
1
+ import click
2
+ from .utils import do_request, print_output, ctx_update, login_profile, profile_completer
3
+
4
+ @click.command(help="Get Quotas")
5
+ @click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
6
+ @click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
7
+ @click.pass_context
8
+ def quotas(ctx, profile, output):
9
+ """Retrieve global quotas across all projects for the given profile."""
10
+ _, _, profile = ctx_update(ctx, None, None, profile)
11
+ login_profile(profile)
12
+
13
+ data = do_request("GET", 'quotas')
14
+ print_output(data, output)