oks-cli 1.14__tar.gz → 1.16__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.14 → oks_cli-1.16}/PKG-INFO +15 -20
- {oks_cli-1.14 → oks_cli-1.16}/README.md +13 -18
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/cache.py +16 -15
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/cluster.py +127 -75
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/main.py +8 -9
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/profile.py +61 -19
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/project.py +47 -33
- oks_cli-1.16/oks_cli/quotas.py +21 -0
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/utils.py +196 -32
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli.egg-info/PKG-INFO +15 -20
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli.egg-info/SOURCES.txt +2 -1
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli.egg-info/requires.txt +1 -1
- {oks_cli-1.14 → oks_cli-1.16}/setup.py +2 -2
- {oks_cli-1.14 → oks_cli-1.16}/tests/test_cache.py +1 -1
- oks_cli-1.16/tests/test_cluster.py +440 -0
- {oks_cli-1.14 → oks_cli-1.16}/tests/test_nodepool.py +1 -1
- oks_cli-1.16/tests/test_profile.py +58 -0
- oks_cli-1.16/tests/test_project.py +476 -0
- oks_cli-1.16/tests/test_quota.py +27 -0
- oks_cli-1.16/tests/test_shell_completion.py +104 -0
- oks_cli-1.14/oks_cli/quotas.py +0 -14
- oks_cli-1.14/tests/test_cluster.py +0 -158
- oks_cli-1.14/tests/test_profile.py +0 -30
- oks_cli-1.14/tests/test_project.py +0 -102
- oks_cli-1.14/tests/test_quota.py +0 -15
- {oks_cli-1.14 → oks_cli-1.16}/LICENSE +0 -0
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli/__init__.py +0 -0
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli.egg-info/dependency_links.txt +0 -0
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli.egg-info/entry_points.txt +0 -0
- {oks_cli-1.14 → oks_cli-1.16}/oks_cli.egg-info/top_level.txt +0 -0
- {oks_cli-1.14 → oks_cli-1.16}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oks-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.16
|
|
4
4
|
Author: Outscale SAS
|
|
5
5
|
Author-email: opensource@outscale.com
|
|
6
6
|
License: BSD
|
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: certifi>=2024.8.30
|
|
19
19
|
Requires-Dist: charset-normalizer>=3.3.2
|
|
20
|
-
Requires-Dist: click
|
|
20
|
+
Requires-Dist: click<8.3.0,>=8.1.7
|
|
21
21
|
Requires-Dist: colorama>=0.4.6
|
|
22
22
|
Requires-Dist: idna>=3.10
|
|
23
23
|
Requires-Dist: pyyaml>=6.0.2
|
|
@@ -102,31 +102,17 @@ Dynamic: requires-dist
|
|
|
102
102
|
### Standard Installation
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
# Clone the repository
|
|
106
|
-
git clone https://github.com/outscale/oks-cli.git
|
|
107
|
-
cd oks-cli
|
|
108
|
-
|
|
109
105
|
# Create and activate a virtual environment
|
|
110
106
|
python -m venv venv
|
|
111
107
|
source venv/bin/activate
|
|
112
108
|
|
|
113
|
-
# Install
|
|
114
|
-
pip install -
|
|
115
|
-
|
|
116
|
-
# Install the CLI in editable mode
|
|
117
|
-
pip install -e .
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### User Installation
|
|
109
|
+
# Install the CLI
|
|
110
|
+
pip install oks-cli
|
|
121
111
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
pip3.11 install -e --user .
|
|
112
|
+
# Check version of oks-cli
|
|
113
|
+
oks-cli version
|
|
126
114
|
```
|
|
127
115
|
|
|
128
|
-
> **Note:** Ensure `~/Library/Python/3.11/bin` (macOS) or the equivalent path is in your `PATH`.
|
|
129
|
-
|
|
130
116
|
---
|
|
131
117
|
|
|
132
118
|
## 🚀 Usage
|
|
@@ -207,6 +193,15 @@ oks-cli project login --project-name my-project
|
|
|
207
193
|
Install the CLI in editable mode with development dependencies
|
|
208
194
|
|
|
209
195
|
```bash
|
|
196
|
+
# Clone the repository
|
|
197
|
+
git clone https://github.com/outscale/oks-cli.git
|
|
198
|
+
cd oks-cli
|
|
199
|
+
|
|
200
|
+
# Create and activate a virtual environment
|
|
201
|
+
python -m venv venv
|
|
202
|
+
source venv/bin/activate
|
|
203
|
+
|
|
204
|
+
# CLI in editable mode
|
|
210
205
|
pip install -e ".[dev]"
|
|
211
206
|
```
|
|
212
207
|
|
|
@@ -59,31 +59,17 @@
|
|
|
59
59
|
### Standard Installation
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
# Clone the repository
|
|
63
|
-
git clone https://github.com/outscale/oks-cli.git
|
|
64
|
-
cd oks-cli
|
|
65
|
-
|
|
66
62
|
# Create and activate a virtual environment
|
|
67
63
|
python -m venv venv
|
|
68
64
|
source venv/bin/activate
|
|
69
65
|
|
|
70
|
-
# Install
|
|
71
|
-
pip install -
|
|
72
|
-
|
|
73
|
-
# Install the CLI in editable mode
|
|
74
|
-
pip install -e .
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### User Installation
|
|
66
|
+
# Install the CLI
|
|
67
|
+
pip install oks-cli
|
|
78
68
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
pip3.11 install -e --user .
|
|
69
|
+
# Check version of oks-cli
|
|
70
|
+
oks-cli version
|
|
83
71
|
```
|
|
84
72
|
|
|
85
|
-
> **Note:** Ensure `~/Library/Python/3.11/bin` (macOS) or the equivalent path is in your `PATH`.
|
|
86
|
-
|
|
87
73
|
---
|
|
88
74
|
|
|
89
75
|
## 🚀 Usage
|
|
@@ -164,6 +150,15 @@ oks-cli project login --project-name my-project
|
|
|
164
150
|
Install the CLI in editable mode with development dependencies
|
|
165
151
|
|
|
166
152
|
```bash
|
|
153
|
+
# Clone the repository
|
|
154
|
+
git clone https://github.com/outscale/oks-cli.git
|
|
155
|
+
cd oks-cli
|
|
156
|
+
|
|
157
|
+
# Create and activate a virtual environment
|
|
158
|
+
python -m venv venv
|
|
159
|
+
source venv/bin/activate
|
|
160
|
+
|
|
161
|
+
# CLI in editable mode
|
|
167
162
|
pip install -e ".[dev]"
|
|
168
163
|
```
|
|
169
164
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import click
|
|
2
|
-
from .utils import clear_cache, find_project_id_by_name, find_cluster_id_by_name, get_all_cache, get_expiration_date,
|
|
2
|
+
from .utils import clear_cache, find_project_id_by_name, find_cluster_id_by_name, get_all_cache, get_expiration_date, \
|
|
3
|
+
ctx_update, login_profile, profile_completer, cluster_completer, project_completer, print_table
|
|
4
|
+
|
|
3
5
|
import prettytable
|
|
4
6
|
|
|
5
7
|
# DEFINE THE CACHE COMMAND GROUP
|
|
6
8
|
@click.group(help="Cache related commands.")
|
|
7
|
-
@click.option('--project-name', '-p', required = False, help="Project Name")
|
|
8
|
-
@click.option('--cluster-name', '-c', required = False, help="Cluster Name")
|
|
9
|
+
@click.option('--project-name', '-p', required = False, help="Project Name", shell_complete=project_completer)
|
|
10
|
+
@click.option('--cluster-name', '--name', '-c', required = False, help="Cluster Name", shell_complete=cluster_completer)
|
|
9
11
|
@click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
|
|
10
12
|
@click.pass_context
|
|
11
13
|
def cache(ctx, project_name, cluster_name, profile):
|
|
@@ -20,8 +22,8 @@ def delete_cache(force):
|
|
|
20
22
|
clear_cache()
|
|
21
23
|
|
|
22
24
|
@cache.command('kubeconfigs', help="List cached kubeconfigs")
|
|
23
|
-
@click.option('--project-name', '-p', required=False, help="Project Name")
|
|
24
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name")
|
|
25
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
26
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
25
27
|
@click.option('--plain', is_flag=True, help="Plain table format")
|
|
26
28
|
@click.option('--msword', is_flag=True, help="Microsoft Word table format")
|
|
27
29
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
@@ -36,14 +38,8 @@ def list_kubeconfigs(ctx, project_name, cluster_name, plain, msword, profile):
|
|
|
36
38
|
|
|
37
39
|
result = get_all_cache(project_id, cluster_id, "kubeconfig")
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if plain:
|
|
43
|
-
table.set_style(prettytable.PLAIN_COLUMNS)
|
|
44
|
-
|
|
45
|
-
if msword:
|
|
46
|
-
table.set_style(prettytable.MSWORD_FRIENDLY)
|
|
41
|
+
data = list()
|
|
42
|
+
fields = [["user", "user"],["group", "group"], ["expiration date", "expires_at"]]
|
|
47
43
|
|
|
48
44
|
for element in result:
|
|
49
45
|
kubeconfig = None
|
|
@@ -57,7 +53,12 @@ def list_kubeconfigs(ctx, project_name, cluster_name, plain, msword, profile):
|
|
|
57
53
|
if kubeconfig:
|
|
58
54
|
exp = get_expiration_date(kubeconfig)
|
|
59
55
|
row = user, group, exp
|
|
56
|
+
data.append({"user": user, "group": group, "expires_at": exp})
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
style = None
|
|
59
|
+
if plain:
|
|
60
|
+
style = prettytable.PLAIN_COLUMNS
|
|
61
|
+
if msword:
|
|
62
|
+
style = prettytable.MSWORD_FRIENDLY
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
print_table(data, fields, style=style)
|
|
@@ -6,23 +6,31 @@ from nacl.encoding import Base64Encoder
|
|
|
6
6
|
|
|
7
7
|
import time
|
|
8
8
|
import os
|
|
9
|
-
import datetime
|
|
9
|
+
from datetime import datetime
|
|
10
10
|
import dateutil.parser
|
|
11
11
|
import human_readable
|
|
12
|
+
import pathlib
|
|
12
13
|
import prettytable
|
|
13
14
|
import logging
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
from .utils import cluster_completer, do_request, print_output, \
|
|
18
|
+
find_project_id_by_name, find_cluster_id_by_name, \
|
|
19
|
+
get_cache, save_cache, detect_and_parse_input, \
|
|
20
|
+
verify_certificate, shell_completions, transform_tuple, \
|
|
21
|
+
profile_list, login_profile, cluster_create_in_background, \
|
|
22
|
+
ctx_update, set_cluster_id, get_cluster_id, get_project_id, \
|
|
23
|
+
get_template, get_cluster_name, format_changed_row, \
|
|
24
|
+
is_interesting_status, profile_completer, project_completer, \
|
|
25
|
+
kubeconfig_parse_fields, print_table, get_expiration_date
|
|
16
26
|
|
|
17
27
|
from .profile import add_profile
|
|
18
28
|
from .project import project_create, project_login
|
|
19
29
|
|
|
20
30
|
# DEFINE THE CLUSTER GROUP
|
|
21
31
|
@click.group(help="Cluster related commands.")
|
|
22
|
-
@click.option('--project', '
|
|
23
|
-
@click.option('--
|
|
24
|
-
@click.option('--name', 'cluster_name', required = False, help="Cluster Name")
|
|
25
|
-
@click.option('--cluster-name', '-c', required = False, help="Cluster Name")
|
|
32
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
33
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
26
34
|
@click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
|
|
27
35
|
@click.pass_context
|
|
28
36
|
def cluster(ctx, project_name, cluster_name, profile):
|
|
@@ -31,7 +39,7 @@ def cluster(ctx, project_name, cluster_name, profile):
|
|
|
31
39
|
|
|
32
40
|
# LOGIN ON CLUSTER
|
|
33
41
|
@cluster.command('login', help="Set a default cluster")
|
|
34
|
-
@click.option('--cluster-name', '-c', required=False, help="Name of cluster")
|
|
42
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Name of cluster", shell_complete=cluster_completer)
|
|
35
43
|
@click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
|
|
36
44
|
@click.pass_context
|
|
37
45
|
def cluster_login(ctx, cluster_name, profile):
|
|
@@ -70,14 +78,13 @@ def cluster_logout(ctx, profile):
|
|
|
70
78
|
|
|
71
79
|
# LIST CLUSTERS
|
|
72
80
|
@cluster.command('list', help="List all clusters")
|
|
73
|
-
@click.option('--project-name', '-p', required=False, help="Project Name")
|
|
74
|
-
@click.option('--name', '
|
|
75
|
-
@click.option('--
|
|
76
|
-
@click.option('--deleted', is_flag=True, help="List deleted clusters")
|
|
81
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
82
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
83
|
+
@click.option('--deleted', '-x', is_flag=True, help="List deleted clusters") # x pour "deleted" / "removed"
|
|
77
84
|
@click.option('--plain', is_flag=True, help="Plain table format")
|
|
78
85
|
@click.option('--msword', is_flag=True, help="Microsoft Word table format")
|
|
79
86
|
@click.option('--watch', '-w', is_flag=True, help="Watch the changes")
|
|
80
|
-
@click.option('
|
|
87
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml", "wide"]), help="Specify output format")
|
|
81
88
|
@click.option('--profile', help="Configuration profile to use")
|
|
82
89
|
@click.pass_context
|
|
83
90
|
def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch, output, profile):
|
|
@@ -85,6 +92,8 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
85
92
|
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
|
|
86
93
|
login_profile(profile)
|
|
87
94
|
|
|
95
|
+
profile_name = os.getenv('OKS_PROFILE')
|
|
96
|
+
region_name = os.getenv('OKS_REGION')
|
|
88
97
|
project_id = find_project_id_by_name(project_name)
|
|
89
98
|
cluster_id = get_cluster_id()
|
|
90
99
|
|
|
@@ -96,7 +105,7 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
96
105
|
if deleted:
|
|
97
106
|
params['deleted'] = True
|
|
98
107
|
|
|
99
|
-
field_names = ["
|
|
108
|
+
field_names = ["CLUSTER", "PROFILE", "REGION", "CREATED", "UPDATED", "STATUS", "DEFAULT"]
|
|
100
109
|
|
|
101
110
|
data = do_request("GET", 'clusters', params=params)
|
|
102
111
|
|
|
@@ -141,9 +150,9 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
141
150
|
|
|
142
151
|
created_at = dateutil.parser.parse(cluster['statuses']['created_at'])
|
|
143
152
|
updated_at = dateutil.parser.parse(cluster['statuses']['updated_at'])
|
|
144
|
-
now = datetime.
|
|
153
|
+
now = datetime.now(tz = created_at.tzinfo)
|
|
145
154
|
|
|
146
|
-
row = [name, human_readable.date_time(now - created_at), human_readable.date_time(now - updated_at), msg, default]
|
|
155
|
+
row = [name, profile_name, region_name, human_readable.date_time(now - created_at), human_readable.date_time(now - updated_at), msg, default]
|
|
147
156
|
|
|
148
157
|
if output == "wide":
|
|
149
158
|
row.insert(0, cluster['id'])
|
|
@@ -215,10 +224,9 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
215
224
|
|
|
216
225
|
# GET CLUSTER BY NAME
|
|
217
226
|
@cluster.command('get', help="Get a cluster by name")
|
|
218
|
-
@click.option('--project-name', '-p', required
|
|
219
|
-
@click.option('--name', '
|
|
220
|
-
@click.option('--
|
|
221
|
-
@click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
227
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
228
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
229
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
222
230
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
223
231
|
@click.pass_context
|
|
224
232
|
def cluster_get_command(ctx, project_name, cluster_name, output, profile):
|
|
@@ -234,6 +242,26 @@ def cluster_get_command(ctx, project_name, cluster_name, output, profile):
|
|
|
234
242
|
print_output(data, output)
|
|
235
243
|
|
|
236
244
|
|
|
245
|
+
def prepare_cluster_template(cluster_config):
|
|
246
|
+
cluster_template = get_template("cluster")
|
|
247
|
+
|
|
248
|
+
admin_whitelist = cluster_config.get("admin_whitelist") or []
|
|
249
|
+
if isinstance(admin_whitelist, str):
|
|
250
|
+
admin_whitelist = [admin_whitelist]
|
|
251
|
+
|
|
252
|
+
final_whitelist = []
|
|
253
|
+
|
|
254
|
+
for entry in admin_whitelist:
|
|
255
|
+
if entry == "my-ip":
|
|
256
|
+
final_whitelist.extend(cluster_template.get("admin_whitelist", []))
|
|
257
|
+
else:
|
|
258
|
+
final_whitelist.append(entry)
|
|
259
|
+
|
|
260
|
+
cluster_config["admin_whitelist"] = list(dict.fromkeys(final_whitelist))
|
|
261
|
+
|
|
262
|
+
cluster_template.update(cluster_config)
|
|
263
|
+
return cluster_template
|
|
264
|
+
|
|
237
265
|
def _create_cluster(project_name, cluster_config, output):
|
|
238
266
|
"""Create a new cluster with interactive setup for missing profiles/projects."""
|
|
239
267
|
profiles = profile_list()
|
|
@@ -274,8 +302,8 @@ def _create_cluster(project_name, cluster_config, output):
|
|
|
274
302
|
project_name = project_name or "default"
|
|
275
303
|
projects = do_request("GET", 'projects', params={"name": project_name})
|
|
276
304
|
|
|
277
|
-
cluster_template =
|
|
278
|
-
cluster_template
|
|
305
|
+
cluster_template = prepare_cluster_template(cluster_config)
|
|
306
|
+
print_output(cluster_template, output)
|
|
279
307
|
|
|
280
308
|
project_name_styled = click.style(project_name, bold=True)
|
|
281
309
|
cluster_name_styled = click.style(cluster_template.get("name"), bold=True)
|
|
@@ -299,9 +327,7 @@ def _create_cluster(project_name, cluster_config, output):
|
|
|
299
327
|
else:
|
|
300
328
|
project_id = find_project_id_by_name(project_name)
|
|
301
329
|
|
|
302
|
-
cluster_template =
|
|
303
|
-
cluster_template.update(cluster_config)
|
|
304
|
-
|
|
330
|
+
cluster_template = prepare_cluster_template(cluster_config)
|
|
305
331
|
do_request("GET", f'projects/{project_id}')
|
|
306
332
|
cluster_template['project_id'] = project_id
|
|
307
333
|
|
|
@@ -311,26 +337,27 @@ def _create_cluster(project_name, cluster_config, output):
|
|
|
311
337
|
|
|
312
338
|
# CLUSTER CREATE BY NAME
|
|
313
339
|
@cluster.command('create', help="Create a new cluster")
|
|
314
|
-
@click.option('--project-name', '-p', required=False, help="Project Name")
|
|
315
|
-
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name")
|
|
316
|
-
@click.option('--description', help="Description of the cluster")
|
|
317
|
-
@click.option('--admin', help="Admin Whitelist")
|
|
318
|
-
@click.option('--version', shell_complete=shell_completions, help="Kubernetes version")
|
|
340
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
341
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
342
|
+
@click.option('--description', '-d', help="Description of the cluster")
|
|
343
|
+
@click.option('--admin', '-a', help="Admin Whitelist ips. you can use 'my-ip' to automatically use your current IP.")
|
|
344
|
+
@click.option('--version', '-v', shell_complete=shell_completions, help="Kubernetes version")
|
|
319
345
|
@click.option('--cidr-pods', help="CIDR of pods")
|
|
320
346
|
@click.option('--cidr-service', help='CIDR of services')
|
|
321
347
|
@click.option('--control-plane', shell_complete=shell_completions, help="Controlplane plan")
|
|
322
|
-
@click.option('--zone', multiple=True, shell_complete=shell_completions, help="List of Control Plane availability zones")
|
|
348
|
+
@click.option('--zone', '-z', multiple=True, shell_complete=shell_completions, help="List of Control Plane availability zones")
|
|
323
349
|
@click.option('--enable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
324
350
|
@click.option('--disable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
325
|
-
@click.option('--quirk', multiple=True, help="Quirk")
|
|
326
|
-
@click.option('--tags', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
351
|
+
@click.option('--quirk', '-q', multiple=True, help="Quirk")
|
|
352
|
+
@click.option('--tags', '-t', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
327
353
|
@click.option('--disable-api-termination', type=click.BOOL, help="Disable delete action by API")
|
|
354
|
+
@click.option('--cp-multi-az', '-m', is_flag=True, help="Enable control plane multi AZ")
|
|
328
355
|
@click.option('--dry-run', is_flag=True, help="Client dry-run, only print the object that would be sent, without sending it")
|
|
329
|
-
@click.option('
|
|
330
|
-
@click.option('
|
|
356
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
357
|
+
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to create the cluster ")
|
|
331
358
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
332
359
|
@click.pass_context
|
|
333
|
-
def cluster_create_command(ctx, project_name, cluster_name, description, admin, version, cidr_pods, cidr_service, control_plane, zone, enable_admission_plugins, disable_admission_plugins, quirk, tags, disable_api_termination, dry_run, output, filename, profile):
|
|
360
|
+
def cluster_create_command(ctx, project_name, cluster_name, description, admin, version, cidr_pods, cidr_service, control_plane, zone, enable_admission_plugins, disable_admission_plugins, quirk, tags, disable_api_termination, cp_multi_az, dry_run, output, filename, profile):
|
|
334
361
|
"""CLI command to create a new Kubernetes cluster with optional configuration parameters."""
|
|
335
362
|
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
|
|
336
363
|
login_profile(profile)
|
|
@@ -397,31 +424,32 @@ def cluster_create_command(ctx, project_name, cluster_name, description, admin,
|
|
|
397
424
|
|
|
398
425
|
if disable_api_termination is not None:
|
|
399
426
|
cluster_config["disable_api_termination"] = disable_api_termination
|
|
427
|
+
|
|
428
|
+
if cp_multi_az is not None:
|
|
429
|
+
cluster_config["cp_multi_az"] = cp_multi_az
|
|
400
430
|
|
|
401
431
|
if not dry_run:
|
|
402
432
|
_create_cluster(project_name, cluster_config, output)
|
|
403
433
|
else:
|
|
404
|
-
cluster_template =
|
|
405
|
-
cluster_template.update(cluster_config)
|
|
434
|
+
cluster_template = prepare_cluster_template(cluster_config)
|
|
406
435
|
print_output(cluster_template, output)
|
|
407
436
|
|
|
408
437
|
# UPDATE CLUSTER
|
|
409
438
|
@cluster.command('update', help="Update a cluster by name")
|
|
410
|
-
@click.option('--project-name', '-p', required=False, help="Project name")
|
|
411
|
-
@click.option('--name', '
|
|
412
|
-
@click.option('--
|
|
413
|
-
@click.option('--
|
|
414
|
-
@click.option('--
|
|
415
|
-
@click.option('--
|
|
416
|
-
@click.option('--tags', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
439
|
+
@click.option('--project-name', '-p', required=False, help="Project name", shell_complete=project_completer)
|
|
440
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster name", shell_complete=cluster_completer)
|
|
441
|
+
@click.option('--description', '-d', help="Description of the cluster")
|
|
442
|
+
@click.option('--admin', '-a', help="Admin Whitelist ips. you can use 'my-ip' to automatically use your current IP.")
|
|
443
|
+
@click.option('--version', '-v', shell_complete=shell_completions, help="Kubernetes version")
|
|
444
|
+
@click.option('--tags', '-t', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
417
445
|
@click.option('--enable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
418
446
|
@click.option('--disable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
419
|
-
@click.option('--quirk', multiple=True, help="Quirk")
|
|
447
|
+
@click.option('--quirk', '-q', multiple=True, help="Quirk")
|
|
420
448
|
@click.option('--disable-api-termination', type=click.BOOL, help="Disable delete action by API")
|
|
421
449
|
@click.option('--control-plane', shell_complete=shell_completions, help="Controlplane plan")
|
|
422
450
|
@click.option('--dry-run', is_flag=True, help="Client dry-run, only print the object that would be sent, without sending it")
|
|
423
|
-
@click.option('
|
|
424
|
-
@click.option('
|
|
451
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
452
|
+
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to update the cluster ")
|
|
425
453
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
426
454
|
@click.pass_context
|
|
427
455
|
def cluster_update_command(ctx, project_name, cluster_name, description, admin, version, tags, enable_admission_plugins, disable_admission_plugins, quirk, disable_api_termination, control_plane, dry_run, output, filename, profile):
|
|
@@ -495,10 +523,9 @@ def cluster_update_command(ctx, project_name, cluster_name, description, admin,
|
|
|
495
523
|
|
|
496
524
|
# UPGRADE CLUSTER
|
|
497
525
|
@cluster.command('upgrade', help="Upgrade a cluster by name")
|
|
498
|
-
@click.option('--project-name', '-p', required=False, help="Project name")
|
|
499
|
-
@click.option('--name', '
|
|
500
|
-
@click.option('--
|
|
501
|
-
@click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
526
|
+
@click.option('--project-name', '-p', required=False, help="Project name", shell_complete=project_completer)
|
|
527
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster name", shell_complete=cluster_completer)
|
|
528
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
502
529
|
@click.option('--force', is_flag=True, help="Force upgrade")
|
|
503
530
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
504
531
|
@click.pass_context
|
|
@@ -517,10 +544,9 @@ def cluster_update_command(ctx, project_name, cluster_name, output, force, profi
|
|
|
517
544
|
|
|
518
545
|
# DELETE CLUSTER BY NAME
|
|
519
546
|
@cluster.command('delete', help="Delete a cluster by name")
|
|
520
|
-
@click.option('--project-name', '-p', required=False, help="Project name")
|
|
521
|
-
@click.option('--name', '
|
|
522
|
-
@click.option('--
|
|
523
|
-
@click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
547
|
+
@click.option('--project-name', '-p', required=False, help="Project name", shell_complete=project_completer)
|
|
548
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster name", shell_complete=cluster_completer)
|
|
549
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
524
550
|
@click.option('--dry-run', is_flag=True, help="Run without any action")
|
|
525
551
|
@click.option('--force', is_flag=True, help="Force deletion without confirmation")
|
|
526
552
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
@@ -549,10 +575,11 @@ def cluster_delete_command(ctx, project_name, cluster_name, output, dry_run, for
|
|
|
549
575
|
|
|
550
576
|
# GET KUBECONFIG
|
|
551
577
|
@cluster.command('kubeconfig', help="Fetch the kubeconfig for a cluster")
|
|
552
|
-
@click.option('--project-name', '-p', required=False, help="Project Name")
|
|
553
|
-
@click.option('--name', '
|
|
554
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name")
|
|
578
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
579
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
555
580
|
@click.option('--print-path', is_flag=True, help="Print path to saved kubeconfig")
|
|
581
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml", "table"]), default="yaml", help="Specify output format, default is yaml")
|
|
582
|
+
@click.option('--wide', is_flag=True, help="Prints additional info, only supported for table output")
|
|
556
583
|
@click.option('--refresh', '--force', is_flag=True, help="Force refresh saved kubeconfig")
|
|
557
584
|
@click.option('--nacl', is_flag=True, help="Use public key encryption on wire (require api support)")
|
|
558
585
|
@click.option('--user', type=click.STRING, help="User")
|
|
@@ -560,7 +587,7 @@ def cluster_delete_command(ctx, project_name, cluster_name, output, dry_run, for
|
|
|
560
587
|
@click.option('--ttl', type=click.STRING, help="TTL in human readable format (5h, 1d, 1w)")
|
|
561
588
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
562
589
|
@click.pass_context
|
|
563
|
-
def cluster_kubeconfig_command(ctx, project_name, cluster_name, print_path, refresh, nacl, user, group, ttl, profile):
|
|
590
|
+
def cluster_kubeconfig_command(ctx, project_name, cluster_name, print_path, output, wide, refresh, nacl, user, group, ttl, profile):
|
|
564
591
|
"""CLI command to fetch and optionally print the kubeconfig for a specified cluster."""
|
|
565
592
|
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
|
|
566
593
|
login_profile(profile)
|
|
@@ -615,9 +642,31 @@ def cluster_kubeconfig_command(ctx, project_name, cluster_name, print_path, refr
|
|
|
615
642
|
kubeconfig_path = save_cache(project_id, cluster_id, 'kubeconfig', kubeconfig, user, group)
|
|
616
643
|
|
|
617
644
|
if print_path:
|
|
618
|
-
|
|
645
|
+
click.echo(kubeconfig_path)
|
|
619
646
|
else:
|
|
620
|
-
|
|
647
|
+
if output == 'table':
|
|
648
|
+
kubeconfig_path = pathlib.Path(kubeconfig_path).absolute()
|
|
649
|
+
if not user:
|
|
650
|
+
user = kubeconfig_path.parts[-3]
|
|
651
|
+
if not group:
|
|
652
|
+
group = kubeconfig_path.parts[-2]
|
|
653
|
+
if kubeconfig_path.is_file():
|
|
654
|
+
with kubeconfig_path.open() as f:
|
|
655
|
+
kubeconfig_str = f.read()
|
|
656
|
+
kubedata = kubeconfig_parse_fields(kubeconfig_str, cluster_name, user, group)
|
|
657
|
+
if not len(kubedata):
|
|
658
|
+
raise SystemExit("Something went wrong, could not parse kubeconfig")
|
|
659
|
+
fields = [["user", "user"], ["group", "group"], ["expiration date", "expires_at"]]
|
|
660
|
+
if wide:
|
|
661
|
+
fields.extend([["Cert subject", "cn"], ["context:name", "context_name"], ["context:user", "ctx_user"],
|
|
662
|
+
["context:cluster", "cluster_name"], ["cluster endpoint", "server_name"]])
|
|
663
|
+
print_table(kubedata, fields)
|
|
664
|
+
else:
|
|
665
|
+
raise SystemExit(f"Could not find {kubeconfig_path}")
|
|
666
|
+
elif output == 'json':
|
|
667
|
+
click.echo(json.dumps(yaml.safe_load(kubeconfig)))
|
|
668
|
+
else:
|
|
669
|
+
click.echo(kubeconfig)
|
|
621
670
|
|
|
622
671
|
|
|
623
672
|
def _run_kubectl(project_id, cluster_id, user, group, args, input=None):
|
|
@@ -642,7 +691,7 @@ def _run_kubectl(project_id, cluster_id, user, group, args, input=None):
|
|
|
642
691
|
"GET", f'clusters/{cluster_id}/kubeconfig')['data']['kubeconfig']
|
|
643
692
|
|
|
644
693
|
if not kubeconfig_raw:
|
|
645
|
-
|
|
694
|
+
click.echo("Cannot get kubeconfig")
|
|
646
695
|
raise SystemExit()
|
|
647
696
|
|
|
648
697
|
kubeconfig_path = save_cache(project_id, cluster_id, 'kubeconfig', kubeconfig_raw, user, group)
|
|
@@ -659,12 +708,12 @@ def _run_kubectl(project_id, cluster_id, user, group, args, input=None):
|
|
|
659
708
|
|
|
660
709
|
|
|
661
710
|
@cluster.command('kubectl', help='Fetch the kubeconfig for a cluster and run kubectl against it', context_settings={"ignore_unknown_options": True})
|
|
662
|
-
@click.option('--project-name', '-p', required=False, help="Project Name")
|
|
663
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name")
|
|
711
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
712
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
664
713
|
@click.option('--user', type=click.STRING, help="User")
|
|
665
714
|
@click.option('--group', type=click.STRING, help="Group")
|
|
666
|
-
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
667
715
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
716
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
668
717
|
@click.pass_context
|
|
669
718
|
def cluster_kubectl_command(ctx, project_name, cluster_name, user, group, args, profile):
|
|
670
719
|
"""CLI command to run kubectl against a specified cluster using its kubeconfig."""
|
|
@@ -677,9 +726,9 @@ def cluster_kubectl_command(ctx, project_name, cluster_name, user, group, args,
|
|
|
677
726
|
_run_kubectl(project_id, cluster_id, user, group, args)
|
|
678
727
|
|
|
679
728
|
|
|
680
|
-
@click.group(help="
|
|
681
|
-
@click.option('--project-name', '-p', required=False, help="Project Name")
|
|
682
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name")
|
|
729
|
+
@click.group(help="Nodepool related commands.")
|
|
730
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
731
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
683
732
|
@click.option('--user', type=click.STRING, help="User")
|
|
684
733
|
@click.option('--group', type=click.STRING, help="Group")
|
|
685
734
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
@@ -707,14 +756,14 @@ def nodepool_list(ctx):
|
|
|
707
756
|
'get', 'nodepool', '-o', 'wide'])
|
|
708
757
|
|
|
709
758
|
|
|
710
|
-
@nodepool.command('create')
|
|
759
|
+
@nodepool.command('create', help="Create a new nodepool")
|
|
711
760
|
@click.option('--nodepool-name', '-n', default="nodepool01", help="Nodepool Name")
|
|
712
|
-
@click.option('--count', default=2, help="Count of nodes")
|
|
713
|
-
@click.option('--type', 'vmtype', default="tinav6.c2r4p3", help="Type of VMs")
|
|
714
|
-
@click.option('--zone',
|
|
715
|
-
@click.option('
|
|
761
|
+
@click.option('--count', '-c', default=2, help="Count of nodes")
|
|
762
|
+
@click.option('--type', 'vmtype', '-t', default="tinav6.c2r4p3", help="Type of VMs")
|
|
763
|
+
@click.option('--zone', '-z', multiple=True, help="Provide zone(s)")
|
|
764
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
716
765
|
@click.option('--dry-run', is_flag=True, help="Run without any action")
|
|
717
|
-
@click.option('
|
|
766
|
+
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to create the Nodepool")
|
|
718
767
|
@click.pass_context
|
|
719
768
|
def setup_worker_pool(ctx, nodepool_name, count, vmtype, zone, output, dry_run, filename):
|
|
720
769
|
"""Create a new nodepool in the cluster, optionally from a file or parameters."""
|
|
@@ -730,6 +779,9 @@ def setup_worker_pool(ctx, nodepool_name, count, vmtype, zone, output, dry_run,
|
|
|
730
779
|
if zone:
|
|
731
780
|
nodepool['spec']["zones"] = list(zone)
|
|
732
781
|
|
|
782
|
+
if not nodepool['spec']["zones"]:
|
|
783
|
+
raise click.BadArgumentUsage("Missing option '--zone' / '-z'.")
|
|
784
|
+
|
|
733
785
|
if dry_run:
|
|
734
786
|
print_output(nodepool, output)
|
|
735
787
|
else:
|
|
@@ -9,14 +9,14 @@ from .profile import profile
|
|
|
9
9
|
from .cache import cache
|
|
10
10
|
from .quotas import quotas
|
|
11
11
|
|
|
12
|
-
from .utils import ctx_update,
|
|
12
|
+
from .utils import ctx_update, install_completions, profile_completer, cluster_completer, project_completer
|
|
13
13
|
|
|
14
14
|
# Main CLI entry point
|
|
15
15
|
@click.group(invoke_without_command=True)
|
|
16
|
-
@click.option(
|
|
17
|
-
@click.option('--project-name', '-p', required
|
|
18
|
-
@click.option('--cluster-name', '-c', required
|
|
19
|
-
@click.option('
|
|
16
|
+
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
17
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
18
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
19
|
+
@click.option('--verbose', '-v', count=True, help="Increase verbosity")
|
|
20
20
|
@click.pass_context
|
|
21
21
|
def cli(ctx, project_name, cluster_name, profile, verbose):
|
|
22
22
|
"""
|
|
@@ -64,8 +64,7 @@ cli.add_command(quotas)
|
|
|
64
64
|
def recursive_help(cmd, parent=None):
|
|
65
65
|
"""Recursively prints help for all commands and subcommands."""
|
|
66
66
|
ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
|
|
67
|
-
|
|
68
|
-
print()
|
|
67
|
+
click.echo(cmd.get_help(ctx))
|
|
69
68
|
commands = getattr(cmd, 'commands', {})
|
|
70
69
|
for sub in commands.values():
|
|
71
70
|
recursive_help(sub, ctx)
|
|
@@ -79,10 +78,10 @@ def fullhelp():
|
|
|
79
78
|
def version():
|
|
80
79
|
"""Display the current CLI version."""
|
|
81
80
|
import importlib.metadata
|
|
82
|
-
|
|
81
|
+
click.echo(importlib.metadata.version(__package__))
|
|
83
82
|
|
|
84
83
|
@cli.command("install-completion", help="Install shell completion scripts.")
|
|
85
|
-
@click.option('--type', help="Shell")
|
|
84
|
+
@click.option('--type', help="Shell, supported [bash,zsh]")
|
|
86
85
|
def install_completion(type):
|
|
87
86
|
"""Install shell completion scripts for the CLI."""
|
|
88
87
|
install_completions(type)
|