oks-cli 1.15__tar.gz → 1.17__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.15 → oks_cli-1.17}/PKG-INFO +2 -2
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/cache.py +15 -14
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/cluster.py +194 -118
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/main.py +7 -8
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/profile.py +5 -5
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/project.py +45 -60
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/quotas.py +1 -1
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/utils.py +107 -11
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli.egg-info/PKG-INFO +2 -2
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli.egg-info/requires.txt +1 -1
- {oks_cli-1.15 → oks_cli-1.17}/setup.py +2 -2
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_cache.py +1 -1
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_cluster.py +159 -3
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_project.py +130 -3
- {oks_cli-1.15 → oks_cli-1.17}/LICENSE +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/README.md +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli/__init__.py +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli.egg-info/SOURCES.txt +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli.egg-info/dependency_links.txt +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli.egg-info/entry_points.txt +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/oks_cli.egg-info/top_level.txt +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/setup.cfg +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_nodepool.py +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_profile.py +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_quota.py +0 -0
- {oks_cli-1.15 → oks_cli-1.17}/tests/test_shell_completion.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oks-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.17
|
|
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
|
|
@@ -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,
|
|
3
|
-
|
|
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
|
+
|
|
5
|
+
from prettytable import TableStyle
|
|
4
6
|
|
|
5
7
|
# DEFINE THE CACHE COMMAND GROUP
|
|
6
8
|
@click.group(help="Cache related commands.")
|
|
7
9
|
@click.option('--project-name', '-p', required = False, help="Project Name", shell_complete=project_completer)
|
|
8
|
-
@click.option('--cluster-name', '-c', required = False, help="Cluster Name", shell_complete=cluster_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):
|
|
@@ -21,7 +23,7 @@ def delete_cache(force):
|
|
|
21
23
|
|
|
22
24
|
@cache.command('kubeconfigs', help="List cached kubeconfigs")
|
|
23
25
|
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
24
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name", shell_complete=cluster_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 = TableStyle.PLAIN_COLUMNS
|
|
61
|
+
if msword:
|
|
62
|
+
style = TableStyle.MSWORD_FRIENDLY
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
print_table(data, fields, style=style)
|
|
@@ -6,23 +6,32 @@ 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 prettytable import TableStyle
|
|
18
|
+
from .utils import cluster_completer, do_request, print_output, \
|
|
19
|
+
find_project_id_by_name, find_cluster_id_by_name, \
|
|
20
|
+
get_cache, save_cache, detect_and_parse_input, \
|
|
21
|
+
verify_certificate, shell_completions, transform_tuple, \
|
|
22
|
+
profile_list, login_profile, cluster_create_in_background, \
|
|
23
|
+
ctx_update, set_cluster_id, get_cluster_id, get_project_id, \
|
|
24
|
+
get_template, get_cluster_name, format_changed_row, \
|
|
25
|
+
is_interesting_status, profile_completer, project_completer, \
|
|
26
|
+
kubeconfig_parse_fields, print_table, format_row
|
|
16
27
|
|
|
17
28
|
from .profile import add_profile
|
|
18
29
|
from .project import project_create, project_login
|
|
19
30
|
|
|
20
31
|
# DEFINE THE CLUSTER GROUP
|
|
21
32
|
@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", shell_complete=cluster_completer)
|
|
33
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
34
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
26
35
|
@click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
|
|
27
36
|
@click.pass_context
|
|
28
37
|
def cluster(ctx, project_name, cluster_name, profile):
|
|
@@ -31,7 +40,7 @@ def cluster(ctx, project_name, cluster_name, profile):
|
|
|
31
40
|
|
|
32
41
|
# LOGIN ON CLUSTER
|
|
33
42
|
@cluster.command('login', help="Set a default cluster")
|
|
34
|
-
@click.option('--cluster-name', '-c', required=False, help="Name of cluster", shell_complete=cluster_completer)
|
|
43
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Name of cluster", shell_complete=cluster_completer)
|
|
35
44
|
@click.option("--profile", help="Configuration profile to use", shell_complete=profile_completer)
|
|
36
45
|
@click.pass_context
|
|
37
46
|
def cluster_login(ctx, cluster_name, profile):
|
|
@@ -71,34 +80,48 @@ def cluster_logout(ctx, profile):
|
|
|
71
80
|
# LIST CLUSTERS
|
|
72
81
|
@cluster.command('list', help="List all clusters")
|
|
73
82
|
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
74
|
-
@click.option('--name', '
|
|
75
|
-
@click.option('--
|
|
76
|
-
@click.option('--deleted', is_flag=True, help="List deleted clusters")
|
|
83
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
84
|
+
@click.option('--deleted', '-x', is_flag=True, help="List deleted clusters") # x pour "deleted" / "removed"
|
|
77
85
|
@click.option('--plain', is_flag=True, help="Plain table format")
|
|
78
86
|
@click.option('--msword', is_flag=True, help="Microsoft Word table format")
|
|
79
87
|
@click.option('--watch', '-w', is_flag=True, help="Watch the changes")
|
|
80
|
-
@click.option('
|
|
88
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml", "wide"]), help="Specify output format")
|
|
81
89
|
@click.option('--profile', help="Configuration profile to use")
|
|
90
|
+
@click.option('--all', '-A', is_flag=True, help="List clusters from all projects")
|
|
82
91
|
@click.pass_context
|
|
83
|
-
def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch, output, profile):
|
|
92
|
+
def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch, output, profile, all):
|
|
84
93
|
"""Display clusters with optional filtering and real-time monitoring."""
|
|
85
94
|
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
|
|
86
95
|
login_profile(profile)
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
profile_name = os.getenv('OKS_PROFILE')
|
|
98
|
+
region_name = os.getenv('OKS_REGION')
|
|
91
99
|
params = {}
|
|
92
|
-
|
|
100
|
+
|
|
101
|
+
if not all:
|
|
102
|
+
project_id = find_project_id_by_name(project_name)
|
|
103
|
+
params['project_id'] = project_id
|
|
104
|
+
|
|
105
|
+
cluster_id = get_cluster_id()
|
|
93
106
|
|
|
94
107
|
if cluster_name:
|
|
95
108
|
params['name'] = cluster_name
|
|
96
109
|
if deleted:
|
|
97
110
|
params['deleted'] = True
|
|
98
111
|
|
|
99
|
-
field_names = ["
|
|
112
|
+
field_names = ["CLUSTER", "PROFILE", "REGION", "CREATED", "UPDATED", "STATUS", "DEFAULT"]
|
|
113
|
+
|
|
114
|
+
if all:
|
|
115
|
+
field_names.insert(0, "PROJECT")
|
|
100
116
|
|
|
101
|
-
|
|
117
|
+
projects = {project["id"]: project for project in do_request("GET", "projects")}
|
|
118
|
+
data = do_request("GET", "clusters/all", params=params)
|
|
119
|
+
|
|
120
|
+
for cluster in data:
|
|
121
|
+
project = projects.get(cluster.get("project_id"))
|
|
122
|
+
cluster["project_name"] = project.get("name")
|
|
123
|
+
else:
|
|
124
|
+
data = do_request("GET", "clusters", params=params)
|
|
102
125
|
|
|
103
126
|
if output == "wide":
|
|
104
127
|
field_names.insert(0, "ID")
|
|
@@ -114,49 +137,27 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
114
137
|
table._min_width = {"CREATED": 13, "UPDATED": 13, "STATUS": 10}
|
|
115
138
|
|
|
116
139
|
if plain or watch:
|
|
117
|
-
table.set_style(
|
|
140
|
+
table.set_style(TableStyle.PLAIN_COLUMNS)
|
|
118
141
|
|
|
119
142
|
if msword:
|
|
120
143
|
table.set_style(prettytable.MSWORD_FRIENDLY)
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
status = cluster['statuses']['status']
|
|
124
|
-
|
|
125
|
-
is_default = True if cluster.get('id') == cluster_id else False
|
|
126
|
-
|
|
127
|
-
if status == 'ready':
|
|
128
|
-
msg = click.style(status, fg='green')
|
|
129
|
-
elif status == 'failed' or status == 'deleted':
|
|
130
|
-
msg = click.style(status, fg='red')
|
|
131
|
-
elif status == 'deploying':
|
|
132
|
-
msg = click.style(status, fg='yellow')
|
|
133
|
-
else:
|
|
134
|
-
msg = status
|
|
135
|
-
|
|
136
|
-
name = click.style(cluster['name'], bold=True)
|
|
137
|
-
if is_default:
|
|
138
|
-
default = "*"
|
|
139
|
-
else:
|
|
140
|
-
default = ""
|
|
141
|
-
|
|
142
|
-
created_at = dateutil.parser.parse(cluster['statuses']['created_at'])
|
|
143
|
-
updated_at = dateutil.parser.parse(cluster['statuses']['updated_at'])
|
|
144
|
-
now = datetime.datetime.now(tz = created_at.tzinfo)
|
|
145
|
-
|
|
146
|
-
row = [name, human_readable.date_time(now - created_at), human_readable.date_time(now - updated_at), msg, default]
|
|
145
|
+
initial_clusters = {}
|
|
147
146
|
|
|
147
|
+
for cluster in data:
|
|
148
|
+
row, _, name = format_row(cluster.get('statuses'), cluster.get('name'), cluster_id == cluster.get('id'))
|
|
149
|
+
row.insert(1, profile_name)
|
|
150
|
+
row.insert(2, region_name)
|
|
151
|
+
if all:
|
|
152
|
+
project_name = click.style(cluster.get("project_name"), bold=True)
|
|
153
|
+
row.insert(0, project_name)
|
|
148
154
|
if output == "wide":
|
|
149
|
-
row.insert(0, cluster
|
|
150
|
-
row.append(cluster
|
|
151
|
-
row.append(cluster
|
|
155
|
+
row.insert(0, cluster.get('id'))
|
|
156
|
+
row.append(cluster.get('version'))
|
|
157
|
+
row.append(cluster.get('control_planes'))
|
|
152
158
|
|
|
153
|
-
return row, status, cluster['name']
|
|
154
|
-
|
|
155
|
-
initial_clusters = {}
|
|
156
|
-
for cluster in data:
|
|
157
|
-
row, _, name = format_row(cluster)
|
|
158
159
|
table.add_row(row)
|
|
159
|
-
initial_clusters[
|
|
160
|
+
initial_clusters[cluster.get("id")] = cluster
|
|
160
161
|
|
|
161
162
|
click.echo(table)
|
|
162
163
|
|
|
@@ -168,46 +169,66 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
168
169
|
total_sleep += 2
|
|
169
170
|
|
|
170
171
|
try:
|
|
171
|
-
|
|
172
|
+
if all:
|
|
173
|
+
projects = {project["id"]: project for project in do_request("GET", "projects")}
|
|
174
|
+
data = do_request("GET", "clusters/all", params=params)
|
|
175
|
+
|
|
176
|
+
for cluster in data:
|
|
177
|
+
project = projects.get(cluster.get("project_id"))
|
|
178
|
+
cluster["project_name"] = project.get("name")
|
|
179
|
+
else:
|
|
180
|
+
data = do_request("GET", 'clusters', params=params)
|
|
172
181
|
except click.ClickException as err:
|
|
173
182
|
click.echo(f"Error during watch: {err}")
|
|
174
183
|
continue
|
|
175
184
|
|
|
176
|
-
|
|
185
|
+
current_cluster_ids = {cluster.get('id') for cluster in data}
|
|
177
186
|
|
|
178
|
-
for
|
|
179
|
-
if
|
|
187
|
+
for id, cluster in list(initial_clusters.items()):
|
|
188
|
+
if id not in current_cluster_ids:
|
|
180
189
|
deleted_cluster = cluster.copy()
|
|
181
190
|
deleted_cluster['statuses']['status'] = 'deleted'
|
|
182
191
|
|
|
183
|
-
row, current_status, _ = format_row(deleted_cluster)
|
|
192
|
+
row, current_status, _ = format_row(deleted_cluster.get('statuses'), deleted_cluster.get('name'), cluster_id == deleted_cluster.get('id'))
|
|
193
|
+
row.insert(1, profile_name)
|
|
194
|
+
row.insert(2, region_name)
|
|
195
|
+
if all:
|
|
196
|
+
project_name = click.style(cluster.get("project_name"), bold=True)
|
|
197
|
+
row.insert(0, project_name)
|
|
184
198
|
|
|
185
199
|
new_table = format_changed_row(table, row)
|
|
186
200
|
click.echo(new_table)
|
|
187
201
|
|
|
188
|
-
del initial_clusters[
|
|
202
|
+
del initial_clusters[id]
|
|
189
203
|
|
|
190
204
|
for cluster in data:
|
|
191
|
-
row, current_status, name = format_row(cluster)
|
|
205
|
+
row, current_status, name = format_row(cluster.get('statuses'), cluster.get('name'), cluster_id == cluster.get('id'))
|
|
206
|
+
row.insert(1, profile_name)
|
|
207
|
+
row.insert(2, region_name)
|
|
208
|
+
if all:
|
|
209
|
+
project_name = click.style(cluster.get("project_name"), bold=True)
|
|
210
|
+
row.insert(0, project_name)
|
|
192
211
|
|
|
193
|
-
|
|
212
|
+
cl_id = cluster.get('id')
|
|
213
|
+
|
|
214
|
+
if cl_id not in initial_clusters:
|
|
194
215
|
new_table = format_changed_row(table, row)
|
|
195
216
|
click.echo(new_table)
|
|
196
|
-
initial_clusters[
|
|
217
|
+
initial_clusters[cl_id] = cluster
|
|
197
218
|
continue
|
|
198
219
|
|
|
199
|
-
stored_cluster = initial_clusters[
|
|
220
|
+
stored_cluster = initial_clusters[cl_id]
|
|
200
221
|
cluster_status = stored_cluster.get('statuses').get('status')
|
|
201
222
|
if cluster_status != current_status:
|
|
202
223
|
new_table = format_changed_row(table, row)
|
|
203
224
|
click.echo(new_table)
|
|
204
|
-
initial_clusters[
|
|
225
|
+
initial_clusters[cl_id] = cluster
|
|
205
226
|
continue
|
|
206
227
|
|
|
207
228
|
if total_sleep % 10 == 0 and is_interesting_status(current_status):
|
|
208
229
|
new_table = format_changed_row(table, row)
|
|
209
230
|
click.echo(new_table)
|
|
210
|
-
initial_clusters[
|
|
231
|
+
initial_clusters[cl_id] = cluster
|
|
211
232
|
|
|
212
233
|
except KeyboardInterrupt:
|
|
213
234
|
click.echo("\nWatch stopped.")
|
|
@@ -215,10 +236,9 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch,
|
|
|
215
236
|
|
|
216
237
|
# GET CLUSTER BY NAME
|
|
217
238
|
@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")
|
|
239
|
+
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
240
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
241
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
222
242
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
223
243
|
@click.pass_context
|
|
224
244
|
def cluster_get_command(ctx, project_name, cluster_name, output, profile):
|
|
@@ -234,6 +254,26 @@ def cluster_get_command(ctx, project_name, cluster_name, output, profile):
|
|
|
234
254
|
print_output(data, output)
|
|
235
255
|
|
|
236
256
|
|
|
257
|
+
def prepare_cluster_template(cluster_config):
|
|
258
|
+
cluster_template = get_template("cluster")
|
|
259
|
+
|
|
260
|
+
admin_whitelist = cluster_config.get("admin_whitelist") or []
|
|
261
|
+
if isinstance(admin_whitelist, str):
|
|
262
|
+
admin_whitelist = [admin_whitelist]
|
|
263
|
+
|
|
264
|
+
final_whitelist = []
|
|
265
|
+
|
|
266
|
+
for entry in admin_whitelist:
|
|
267
|
+
if entry == "my-ip":
|
|
268
|
+
final_whitelist.extend(cluster_template.get("admin_whitelist", []))
|
|
269
|
+
else:
|
|
270
|
+
final_whitelist.append(entry)
|
|
271
|
+
|
|
272
|
+
cluster_config["admin_whitelist"] = list(dict.fromkeys(final_whitelist))
|
|
273
|
+
|
|
274
|
+
cluster_template.update(cluster_config)
|
|
275
|
+
return cluster_template
|
|
276
|
+
|
|
237
277
|
def _create_cluster(project_name, cluster_config, output):
|
|
238
278
|
"""Create a new cluster with interactive setup for missing profiles/projects."""
|
|
239
279
|
profiles = profile_list()
|
|
@@ -274,8 +314,8 @@ def _create_cluster(project_name, cluster_config, output):
|
|
|
274
314
|
project_name = project_name or "default"
|
|
275
315
|
projects = do_request("GET", 'projects', params={"name": project_name})
|
|
276
316
|
|
|
277
|
-
cluster_template =
|
|
278
|
-
cluster_template
|
|
317
|
+
cluster_template = prepare_cluster_template(cluster_config)
|
|
318
|
+
print_output(cluster_template, output)
|
|
279
319
|
|
|
280
320
|
project_name_styled = click.style(project_name, bold=True)
|
|
281
321
|
cluster_name_styled = click.style(cluster_template.get("name"), bold=True)
|
|
@@ -299,9 +339,7 @@ def _create_cluster(project_name, cluster_config, output):
|
|
|
299
339
|
else:
|
|
300
340
|
project_id = find_project_id_by_name(project_name)
|
|
301
341
|
|
|
302
|
-
cluster_template =
|
|
303
|
-
cluster_template.update(cluster_config)
|
|
304
|
-
|
|
342
|
+
cluster_template = prepare_cluster_template(cluster_config)
|
|
305
343
|
do_request("GET", f'projects/{project_id}')
|
|
306
344
|
cluster_template['project_id'] = project_id
|
|
307
345
|
|
|
@@ -313,22 +351,22 @@ def _create_cluster(project_name, cluster_config, output):
|
|
|
313
351
|
@cluster.command('create', help="Create a new cluster")
|
|
314
352
|
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
315
353
|
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
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")
|
|
354
|
+
@click.option('--description', '-d', help="Description of the cluster")
|
|
355
|
+
@click.option('--admin', '-a', help="Admin Whitelist ips. you can use 'my-ip' to automatically use your current IP.")
|
|
356
|
+
@click.option('--version', '-v', shell_complete=shell_completions, help="Kubernetes version")
|
|
319
357
|
@click.option('--cidr-pods', help="CIDR of pods")
|
|
320
358
|
@click.option('--cidr-service', help='CIDR of services')
|
|
321
359
|
@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")
|
|
360
|
+
@click.option('--zone', '-z', multiple=True, shell_complete=shell_completions, help="List of Control Plane availability zones")
|
|
323
361
|
@click.option('--enable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
324
362
|
@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'")
|
|
363
|
+
@click.option('--quirk', '-q', multiple=True, help="Quirk")
|
|
364
|
+
@click.option('--tags', '-t', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
327
365
|
@click.option('--disable-api-termination', type=click.BOOL, help="Disable delete action by API")
|
|
328
|
-
@click.option('--cp-multi-az', is_flag=True, help="
|
|
366
|
+
@click.option('--cp-multi-az', '-m', is_flag=True, help="Enable control plane multi AZ")
|
|
329
367
|
@click.option('--dry-run', is_flag=True, help="Client dry-run, only print the object that would be sent, without sending it")
|
|
330
|
-
@click.option('
|
|
331
|
-
@click.option('
|
|
368
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
369
|
+
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to create the cluster ")
|
|
332
370
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
333
371
|
@click.pass_context
|
|
334
372
|
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):
|
|
@@ -405,27 +443,25 @@ def cluster_create_command(ctx, project_name, cluster_name, description, admin,
|
|
|
405
443
|
if not dry_run:
|
|
406
444
|
_create_cluster(project_name, cluster_config, output)
|
|
407
445
|
else:
|
|
408
|
-
cluster_template =
|
|
409
|
-
cluster_template.update(cluster_config)
|
|
446
|
+
cluster_template = prepare_cluster_template(cluster_config)
|
|
410
447
|
print_output(cluster_template, output)
|
|
411
448
|
|
|
412
449
|
# UPDATE CLUSTER
|
|
413
450
|
@cluster.command('update', help="Update a cluster by name")
|
|
414
451
|
@click.option('--project-name', '-p', required=False, help="Project name", shell_complete=project_completer)
|
|
415
|
-
@click.option('--name', '
|
|
416
|
-
@click.option('--
|
|
417
|
-
@click.option('--
|
|
418
|
-
@click.option('--
|
|
419
|
-
@click.option('--
|
|
420
|
-
@click.option('--tags', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
452
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster name", shell_complete=cluster_completer)
|
|
453
|
+
@click.option('--description', '-d', help="Description of the cluster")
|
|
454
|
+
@click.option('--admin', '-a', help="Admin Whitelist ips. you can use 'my-ip' to automatically use your current IP.")
|
|
455
|
+
@click.option('--version', '-v', shell_complete=shell_completions, help="Kubernetes version")
|
|
456
|
+
@click.option('--tags', '-t', help="Comma-separated list of tags, example: 'key1=value1,key2=value2'")
|
|
421
457
|
@click.option('--enable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
422
458
|
@click.option('--disable-admission-plugins', help="List of admission plugins, separated by commas")
|
|
423
|
-
@click.option('--quirk', multiple=True, help="Quirk")
|
|
459
|
+
@click.option('--quirk', '-q', multiple=True, help="Quirk")
|
|
424
460
|
@click.option('--disable-api-termination', type=click.BOOL, help="Disable delete action by API")
|
|
425
461
|
@click.option('--control-plane', shell_complete=shell_completions, help="Controlplane plan")
|
|
426
462
|
@click.option('--dry-run', is_flag=True, help="Client dry-run, only print the object that would be sent, without sending it")
|
|
427
|
-
@click.option('
|
|
428
|
-
@click.option('
|
|
463
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
464
|
+
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to update the cluster ")
|
|
429
465
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
430
466
|
@click.pass_context
|
|
431
467
|
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):
|
|
@@ -449,7 +485,23 @@ def cluster_update_command(ctx, project_name, cluster_name, description, admin,
|
|
|
449
485
|
if len(admin) == 0:
|
|
450
486
|
cluster_config['admin_whitelist'] = []
|
|
451
487
|
else:
|
|
452
|
-
|
|
488
|
+
admin_list = admin.split(',')
|
|
489
|
+
resolved_ips = []
|
|
490
|
+
for ip in admin_list:
|
|
491
|
+
ip = ip.strip()
|
|
492
|
+
if ip == "my-ip":
|
|
493
|
+
try:
|
|
494
|
+
data = do_request("GET", "myip")
|
|
495
|
+
if isinstance(data, dict) and "x_real_ip" in data:
|
|
496
|
+
resolved_ip = data["x_real_ip"]
|
|
497
|
+
resolved_ips.append(f"{resolved_ip}/32")
|
|
498
|
+
else:
|
|
499
|
+
raise click.ClickException(f"Unexpected response format from 'myip': {data}")
|
|
500
|
+
except Exception as e:
|
|
501
|
+
raise click.ClickException(f"Unable to resolve 'my-ip': {e}")
|
|
502
|
+
else:
|
|
503
|
+
resolved_ips.append(ip)
|
|
504
|
+
cluster_config['admin_whitelist'] = resolved_ips
|
|
453
505
|
|
|
454
506
|
if version is not None:
|
|
455
507
|
cluster_config['version'] = version
|
|
@@ -500,9 +552,8 @@ def cluster_update_command(ctx, project_name, cluster_name, description, admin,
|
|
|
500
552
|
# UPGRADE CLUSTER
|
|
501
553
|
@cluster.command('upgrade', help="Upgrade a cluster by name")
|
|
502
554
|
@click.option('--project-name', '-p', required=False, help="Project name", shell_complete=project_completer)
|
|
503
|
-
@click.option('--name', '
|
|
504
|
-
@click.option('--
|
|
505
|
-
@click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
555
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster name", shell_complete=cluster_completer)
|
|
556
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
506
557
|
@click.option('--force', is_flag=True, help="Force upgrade")
|
|
507
558
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
508
559
|
@click.pass_context
|
|
@@ -522,9 +573,8 @@ def cluster_update_command(ctx, project_name, cluster_name, output, force, profi
|
|
|
522
573
|
# DELETE CLUSTER BY NAME
|
|
523
574
|
@cluster.command('delete', help="Delete a cluster by name")
|
|
524
575
|
@click.option('--project-name', '-p', required=False, help="Project name", shell_complete=project_completer)
|
|
525
|
-
@click.option('--name', '
|
|
526
|
-
@click.option('--
|
|
527
|
-
@click.option('-o', '--output', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
576
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster name", shell_complete=cluster_completer)
|
|
577
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
528
578
|
@click.option('--dry-run', is_flag=True, help="Run without any action")
|
|
529
579
|
@click.option('--force', is_flag=True, help="Force deletion without confirmation")
|
|
530
580
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
@@ -554,9 +604,10 @@ def cluster_delete_command(ctx, project_name, cluster_name, output, dry_run, for
|
|
|
554
604
|
# GET KUBECONFIG
|
|
555
605
|
@cluster.command('kubeconfig', help="Fetch the kubeconfig for a cluster")
|
|
556
606
|
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
557
|
-
@click.option('--name', '
|
|
558
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
607
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
559
608
|
@click.option('--print-path', is_flag=True, help="Print path to saved kubeconfig")
|
|
609
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml", "table"]), default="yaml", help="Specify output format, default is yaml")
|
|
610
|
+
@click.option('--wide', is_flag=True, help="Prints additional info, only supported for table output")
|
|
560
611
|
@click.option('--refresh', '--force', is_flag=True, help="Force refresh saved kubeconfig")
|
|
561
612
|
@click.option('--nacl', is_flag=True, help="Use public key encryption on wire (require api support)")
|
|
562
613
|
@click.option('--user', type=click.STRING, help="User")
|
|
@@ -564,7 +615,7 @@ def cluster_delete_command(ctx, project_name, cluster_name, output, dry_run, for
|
|
|
564
615
|
@click.option('--ttl', type=click.STRING, help="TTL in human readable format (5h, 1d, 1w)")
|
|
565
616
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
566
617
|
@click.pass_context
|
|
567
|
-
def cluster_kubeconfig_command(ctx, project_name, cluster_name, print_path, refresh, nacl, user, group, ttl, profile):
|
|
618
|
+
def cluster_kubeconfig_command(ctx, project_name, cluster_name, print_path, output, wide, refresh, nacl, user, group, ttl, profile):
|
|
568
619
|
"""CLI command to fetch and optionally print the kubeconfig for a specified cluster."""
|
|
569
620
|
project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile)
|
|
570
621
|
login_profile(profile)
|
|
@@ -619,9 +670,31 @@ def cluster_kubeconfig_command(ctx, project_name, cluster_name, print_path, refr
|
|
|
619
670
|
kubeconfig_path = save_cache(project_id, cluster_id, 'kubeconfig', kubeconfig, user, group)
|
|
620
671
|
|
|
621
672
|
if print_path:
|
|
622
|
-
|
|
673
|
+
click.echo(kubeconfig_path)
|
|
623
674
|
else:
|
|
624
|
-
|
|
675
|
+
if output == 'table':
|
|
676
|
+
kubeconfig_path = pathlib.Path(kubeconfig_path).absolute()
|
|
677
|
+
if not user:
|
|
678
|
+
user = kubeconfig_path.parts[-3]
|
|
679
|
+
if not group:
|
|
680
|
+
group = kubeconfig_path.parts[-2]
|
|
681
|
+
if kubeconfig_path.is_file():
|
|
682
|
+
with kubeconfig_path.open() as f:
|
|
683
|
+
kubeconfig_str = f.read()
|
|
684
|
+
kubedata = kubeconfig_parse_fields(kubeconfig_str, cluster_name, user, group)
|
|
685
|
+
if not len(kubedata):
|
|
686
|
+
raise SystemExit("Something went wrong, could not parse kubeconfig")
|
|
687
|
+
fields = [["user", "user"], ["group", "group"], ["expiration date", "expires_at"]]
|
|
688
|
+
if wide:
|
|
689
|
+
fields.extend([["Cert subject", "cn"], ["context:name", "context_name"], ["context:user", "ctx_user"],
|
|
690
|
+
["context:cluster", "cluster_name"], ["cluster endpoint", "server_name"]])
|
|
691
|
+
print_table(kubedata, fields)
|
|
692
|
+
else:
|
|
693
|
+
raise SystemExit(f"Could not find {kubeconfig_path}")
|
|
694
|
+
elif output == 'json':
|
|
695
|
+
click.echo(json.dumps(yaml.safe_load(kubeconfig)))
|
|
696
|
+
else:
|
|
697
|
+
click.echo(kubeconfig)
|
|
625
698
|
|
|
626
699
|
|
|
627
700
|
def _run_kubectl(project_id, cluster_id, user, group, args, input=None):
|
|
@@ -646,7 +719,7 @@ def _run_kubectl(project_id, cluster_id, user, group, args, input=None):
|
|
|
646
719
|
"GET", f'clusters/{cluster_id}/kubeconfig')['data']['kubeconfig']
|
|
647
720
|
|
|
648
721
|
if not kubeconfig_raw:
|
|
649
|
-
|
|
722
|
+
click.echo("Cannot get kubeconfig")
|
|
650
723
|
raise SystemExit()
|
|
651
724
|
|
|
652
725
|
kubeconfig_path = save_cache(project_id, cluster_id, 'kubeconfig', kubeconfig_raw, user, group)
|
|
@@ -664,11 +737,11 @@ def _run_kubectl(project_id, cluster_id, user, group, args, input=None):
|
|
|
664
737
|
|
|
665
738
|
@cluster.command('kubectl', help='Fetch the kubeconfig for a cluster and run kubectl against it', context_settings={"ignore_unknown_options": True})
|
|
666
739
|
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
667
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
740
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
668
741
|
@click.option('--user', type=click.STRING, help="User")
|
|
669
742
|
@click.option('--group', type=click.STRING, help="Group")
|
|
670
|
-
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
671
743
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
744
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
672
745
|
@click.pass_context
|
|
673
746
|
def cluster_kubectl_command(ctx, project_name, cluster_name, user, group, args, profile):
|
|
674
747
|
"""CLI command to run kubectl against a specified cluster using its kubeconfig."""
|
|
@@ -681,9 +754,9 @@ def cluster_kubectl_command(ctx, project_name, cluster_name, user, group, args,
|
|
|
681
754
|
_run_kubectl(project_id, cluster_id, user, group, args)
|
|
682
755
|
|
|
683
756
|
|
|
684
|
-
@click.group(help="
|
|
757
|
+
@click.group(help="Nodepool related commands.")
|
|
685
758
|
@click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer)
|
|
686
|
-
@click.option('--cluster-name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
759
|
+
@click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer)
|
|
687
760
|
@click.option('--user', type=click.STRING, help="User")
|
|
688
761
|
@click.option('--group', type=click.STRING, help="Group")
|
|
689
762
|
@click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer)
|
|
@@ -711,14 +784,14 @@ def nodepool_list(ctx):
|
|
|
711
784
|
'get', 'nodepool', '-o', 'wide'])
|
|
712
785
|
|
|
713
786
|
|
|
714
|
-
@nodepool.command('create')
|
|
787
|
+
@nodepool.command('create', help="Create a new nodepool")
|
|
715
788
|
@click.option('--nodepool-name', '-n', default="nodepool01", help="Nodepool Name")
|
|
716
|
-
@click.option('--count', default=2, help="Count of nodes")
|
|
717
|
-
@click.option('--type', 'vmtype', default="tinav6.c2r4p3", help="Type of VMs")
|
|
718
|
-
@click.option('--zone',
|
|
719
|
-
@click.option('
|
|
789
|
+
@click.option('--count', '-c', default=2, help="Count of nodes")
|
|
790
|
+
@click.option('--type', 'vmtype', '-t', default="tinav6.c2r4p3", help="Type of VMs")
|
|
791
|
+
@click.option('--zone', '-z', multiple=True, help="Provide zone(s)")
|
|
792
|
+
@click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json")
|
|
720
793
|
@click.option('--dry-run', is_flag=True, help="Run without any action")
|
|
721
|
-
@click.option('
|
|
794
|
+
@click.option('--filename', '-f', type=click.File("r"), help="Path to file to use to create the Nodepool")
|
|
722
795
|
@click.pass_context
|
|
723
796
|
def setup_worker_pool(ctx, nodepool_name, count, vmtype, zone, output, dry_run, filename):
|
|
724
797
|
"""Create a new nodepool in the cluster, optionally from a file or parameters."""
|
|
@@ -734,6 +807,9 @@ def setup_worker_pool(ctx, nodepool_name, count, vmtype, zone, output, dry_run,
|
|
|
734
807
|
if zone:
|
|
735
808
|
nodepool['spec']["zones"] = list(zone)
|
|
736
809
|
|
|
810
|
+
if not nodepool['spec']["zones"]:
|
|
811
|
+
raise click.BadArgumentUsage("Missing option '--zone' / '-z'.")
|
|
812
|
+
|
|
737
813
|
if dry_run:
|
|
738
814
|
print_output(nodepool, output)
|
|
739
815
|
else:
|