oks-cli 1.17__tar.gz → 1.18__tar.gz
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-1.17 → oks_cli-1.18}/PKG-INFO +1 -1
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/cluster.py +3 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/profile.py +1 -1
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/project.py +18 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/utils.py +19 -4
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli.egg-info/PKG-INFO +1 -1
- {oks_cli-1.17 → oks_cli-1.18}/setup.py +1 -1
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_profile.py +27 -1
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_project.py +74 -0
- {oks_cli-1.17 → oks_cli-1.18}/LICENSE +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/README.md +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/__init__.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/cache.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/main.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli/quotas.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli.egg-info/SOURCES.txt +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli.egg-info/dependency_links.txt +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli.egg-info/entry_points.txt +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli.egg-info/requires.txt +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/oks_cli.egg-info/top_level.txt +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/setup.cfg +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_cache.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_cluster.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_nodepool.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_quota.py +0 -0
- {oks_cli-1.17 → oks_cli-1.18}/tests/test_shell_completion.py +0 -0
|
@@ -257,6 +257,9 @@ def cluster_get_command(ctx, project_name, cluster_name, output, profile):
|
|
|
257
257
|
def prepare_cluster_template(cluster_config):
|
|
258
258
|
cluster_template = get_template("cluster")
|
|
259
259
|
|
|
260
|
+
if cluster_template.get("project_id") == "":
|
|
261
|
+
cluster_template.pop("project_id", None)
|
|
262
|
+
|
|
260
263
|
admin_whitelist = cluster_config.get("admin_whitelist") or []
|
|
261
264
|
if isinstance(admin_whitelist, str):
|
|
262
265
|
admin_whitelist = [admin_whitelist]
|
|
@@ -18,7 +18,7 @@ def profile():
|
|
|
18
18
|
@click.option('--password', required=False, help="Password", type=click.STRING)
|
|
19
19
|
@click.option('--region', required=True, help="Region name", type=click.Choice(['eu-west-2', 'cloudgouv-eu-west-1']))
|
|
20
20
|
@click.option('--endpoint', required=False, help="API endpoint", type=click.STRING)
|
|
21
|
-
@click.option('--jwt', help="Enable JWT, by default is false")
|
|
21
|
+
@click.option('--jwt', help="Enable JWT, by default is false", type=click.BOOL)
|
|
22
22
|
def add_profile(profile_name, access_key, secret_key, username, password, region, endpoint, jwt):
|
|
23
23
|
"""Add a new profile with AK/SK or username/password authentication."""
|
|
24
24
|
if not profile_name:
|
|
@@ -391,4 +391,22 @@ def project_get_public_ips(ctx, project_name, output, profile):
|
|
|
391
391
|
project_id = find_project_id_by_name(project_name)
|
|
392
392
|
|
|
393
393
|
data = do_request("GET", f'projects/{project_id}/public_ips')
|
|
394
|
+
print_output(data, output)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# GET NETS BY PROJECT NAME
|
|
399
|
+
@project.command('nets', help="Get project nets")
|
|
400
|
+
@click.option('--project-name', '-p', help="Name of the project", shell_complete=project_completer)
|
|
401
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
402
|
+
@click.option('--profile',help="Configuration profile to use")
|
|
403
|
+
@click.pass_context
|
|
404
|
+
def project_get_public_ips(ctx, project_name, output, profile):
|
|
405
|
+
"""Retrieve the list of Nets associated with the specified project."""
|
|
406
|
+
project_name, _, profile = ctx_update(ctx, project_name, None, profile)
|
|
407
|
+
login_profile(profile)
|
|
408
|
+
|
|
409
|
+
project_id = find_project_id_by_name(project_name)
|
|
410
|
+
|
|
411
|
+
data = do_request("GET", f'projects/{project_id}/nets')
|
|
394
412
|
print_output(data, output)
|
|
@@ -77,6 +77,8 @@ def find_response_object(data):
|
|
|
77
77
|
return response["PublicIps"]
|
|
78
78
|
elif key == "IP":
|
|
79
79
|
return response["IP"]
|
|
80
|
+
elif key == "Nets":
|
|
81
|
+
return response["Nets"]
|
|
80
82
|
|
|
81
83
|
raise click.ClickException("The API response format is incorrect.")
|
|
82
84
|
|
|
@@ -108,10 +110,13 @@ def do_request(method, path, *args, **kwargs):
|
|
|
108
110
|
obj = find_response_object(data)
|
|
109
111
|
return obj
|
|
110
112
|
except requests.exceptions.HTTPError as err:
|
|
111
|
-
otp_response = handle_otp_error(err,
|
|
113
|
+
otp_response = handle_otp_error(err, method, path, args, kwargs)
|
|
112
114
|
if otp_response is not None:
|
|
113
115
|
return otp_response
|
|
114
116
|
|
|
117
|
+
if os.environ.get("OKS_OTP_CODE") is not None:
|
|
118
|
+
raise JSONClickException(err.response.text)
|
|
119
|
+
|
|
115
120
|
jwt_response = handle_jwt_error(err, method, path, args, kwargs)
|
|
116
121
|
if jwt_response is not None:
|
|
117
122
|
return jwt_response
|
|
@@ -210,7 +215,7 @@ def print_table(data, table_fields, align="l", style=None):
|
|
|
210
215
|
table.add_row([d[v] if v in d else "" for v in values])
|
|
211
216
|
click.echo(table)
|
|
212
217
|
|
|
213
|
-
def handle_otp_error(err,
|
|
218
|
+
def handle_otp_error(err, method, path, args, kwargs):
|
|
214
219
|
"""Handle OTP authentication error by prompting the user and retrying the request."""
|
|
215
220
|
try:
|
|
216
221
|
response_body = json.loads(err.response.text)
|
|
@@ -218,7 +223,11 @@ def handle_otp_error(err, callback):
|
|
|
218
223
|
otp_code = click.prompt('Enter your OTP code', type=int)
|
|
219
224
|
os.environ["OKS_OTP_CODE"] = str(otp_code)
|
|
220
225
|
|
|
221
|
-
|
|
226
|
+
logging.info("Retrying request with user-provided OTP...")
|
|
227
|
+
return do_request(method, path, *args, **kwargs)
|
|
228
|
+
|
|
229
|
+
except JSONClickException:
|
|
230
|
+
raise
|
|
222
231
|
except Exception:
|
|
223
232
|
return None
|
|
224
233
|
|
|
@@ -257,8 +266,14 @@ def find_cluster_id_by_name(project_id, cluster_name):
|
|
|
257
266
|
"""Retrieve the cluster ID by name within a given project, or use the default cluster if none is provided."""
|
|
258
267
|
if not cluster_name:
|
|
259
268
|
cluster_id = get_cluster_id()
|
|
269
|
+
errors = {"Error": "--cluster-name must be specified, or a default cluster must be set"}
|
|
270
|
+
|
|
260
271
|
if not cluster_id:
|
|
261
|
-
raise
|
|
272
|
+
raise JSONClickException(json.dumps(errors))
|
|
273
|
+
|
|
274
|
+
cluster = do_request("GET", f'clusters/{cluster_id}')
|
|
275
|
+
if cluster['project_id'] != project_id:
|
|
276
|
+
raise JSONClickException(json.dumps(errors))
|
|
262
277
|
else:
|
|
263
278
|
data = do_request("GET", 'clusters', params={"project_id": project_id, "name": cluster_name})
|
|
264
279
|
if len(data) != 1:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from click.testing import CliRunner
|
|
2
2
|
from oks_cli.main import cli
|
|
3
|
-
|
|
3
|
+
from unittest.mock import patch
|
|
4
4
|
|
|
5
5
|
def test_profile_list_command():
|
|
6
6
|
runner = CliRunner()
|
|
@@ -38,6 +38,32 @@ def test_profile_add_command():
|
|
|
38
38
|
assert result.exit_code == 0
|
|
39
39
|
assert "Profile default has been successfully added" in result.output
|
|
40
40
|
|
|
41
|
+
def test_profile_add_jwt_boolean():
|
|
42
|
+
runner = CliRunner()
|
|
43
|
+
|
|
44
|
+
with patch("oks_cli.profile.set_profile") as mock_set_profile:
|
|
45
|
+
result = runner.invoke(
|
|
46
|
+
cli,
|
|
47
|
+
[
|
|
48
|
+
"profile", "add",
|
|
49
|
+
"--region", "eu-west-2",
|
|
50
|
+
"--access-key", "AK",
|
|
51
|
+
"--secret-key", "SK",
|
|
52
|
+
"--jwt", "true"
|
|
53
|
+
],
|
|
54
|
+
input="y\n"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert result.exit_code == 0
|
|
58
|
+
|
|
59
|
+
mock_set_profile.assert_called_once()
|
|
60
|
+
profile_name, obj = mock_set_profile.call_args[0]
|
|
61
|
+
|
|
62
|
+
assert profile_name == "default"
|
|
63
|
+
|
|
64
|
+
assert isinstance(obj["jwt"], bool)
|
|
65
|
+
assert obj["jwt"] is True
|
|
66
|
+
|
|
41
67
|
def test_profile_update_command(add_default_profile):
|
|
42
68
|
runner = CliRunner()
|
|
43
69
|
result = runner.invoke(cli, ["profile", "update", "--profile-name", "default", "--region", "cloudgouv-eu-west-1"])
|
|
@@ -476,6 +476,80 @@ def test_project_publicips_yaml(mock_request, add_default_profile):
|
|
|
476
476
|
# END PROJECT PUBLICIPS COMMAND
|
|
477
477
|
|
|
478
478
|
|
|
479
|
+
# START PROJECT NETS COMMAND
|
|
480
|
+
# Test the "project nets" command: verifies fetching project nets
|
|
481
|
+
nets = [{
|
|
482
|
+
"DhcpOptionsSetId": "dopt-12345678",
|
|
483
|
+
"IpRange": "10.50.0.0/16",
|
|
484
|
+
"NetId": "vpc-12345678",
|
|
485
|
+
"State": "available",
|
|
486
|
+
"Tags": [
|
|
487
|
+
{
|
|
488
|
+
"Key": "Name",
|
|
489
|
+
"Value": "default"
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
}]
|
|
493
|
+
|
|
494
|
+
@patch("oks_cli.utils.requests.request")
|
|
495
|
+
def test_project_nets_command(mock_request, add_default_profile):
|
|
496
|
+
mock_request.side_effect = [
|
|
497
|
+
MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}),
|
|
498
|
+
MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Nets": nets })
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
runner = CliRunner()
|
|
502
|
+
result = runner.invoke(cli, ["project", "nets", "-p", "test"])
|
|
503
|
+
assert result.exit_code == 0
|
|
504
|
+
|
|
505
|
+
data = json.loads(result.output)
|
|
506
|
+
assert isinstance(data, list)
|
|
507
|
+
assert data == nets
|
|
508
|
+
|
|
509
|
+
@patch("oks_cli.utils.requests.request")
|
|
510
|
+
def test_project_nets_json(mock_request, add_default_profile):
|
|
511
|
+
mock_request.side_effect = [
|
|
512
|
+
MagicMock(status_code=200, headers={}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}),
|
|
513
|
+
MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Nets": nets })
|
|
514
|
+
]
|
|
515
|
+
|
|
516
|
+
runner = CliRunner()
|
|
517
|
+
result = runner.invoke(cli, [
|
|
518
|
+
"project", "nets",
|
|
519
|
+
"-p", "test-project",
|
|
520
|
+
"-o", "json",
|
|
521
|
+
"--profile", "default"
|
|
522
|
+
])
|
|
523
|
+
|
|
524
|
+
assert result.exit_code == 0
|
|
525
|
+
|
|
526
|
+
data = json.loads(result.output)
|
|
527
|
+
assert isinstance(data, list)
|
|
528
|
+
assert data == nets
|
|
529
|
+
|
|
530
|
+
@patch("oks_cli.utils.requests.request")
|
|
531
|
+
def test_project_nets_yaml(mock_request, add_default_profile):
|
|
532
|
+
mock_request.side_effect = [
|
|
533
|
+
MagicMock(status_code=200, headers={}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}),
|
|
534
|
+
MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Nets": nets })
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
runner = CliRunner()
|
|
538
|
+
result = runner.invoke(cli, [
|
|
539
|
+
"project", "nets",
|
|
540
|
+
"-p", "test-project",
|
|
541
|
+
"-o", "yaml",
|
|
542
|
+
"--profile", "default"
|
|
543
|
+
])
|
|
544
|
+
|
|
545
|
+
assert result.exit_code == 0
|
|
546
|
+
|
|
547
|
+
data = yaml.safe_load(result.output)
|
|
548
|
+
assert isinstance(data, list)
|
|
549
|
+
assert data == nets
|
|
550
|
+
# END PROJECT NETS COMMAND
|
|
551
|
+
|
|
552
|
+
|
|
479
553
|
# Test the "project list" command with --watch option
|
|
480
554
|
@patch("oks_cli.utils.requests.request")
|
|
481
555
|
@patch("time.sleep")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|