linode-cli 5.52.0__tar.gz → 5.52.2__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.
- {linode_cli-5.52.0/linode_cli.egg-info → linode_cli-5.52.2}/PKG-INFO +1 -1
- {linode_cli-5.52.0 → linode_cli-5.52.2/linode_cli.egg-info}/PKG-INFO +1 -1
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/configuration/config.py +4 -7
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/configuration/helpers.py +15 -1
- linode_cli-5.52.2/linodecli/data-3 +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/help_pages.py +2 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/overrides.py +52 -1
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/get-kubeconfig.py +27 -19
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/version.py +1 -1
- linode_cli-5.52.0/linodecli/data-3 +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/LICENSE +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/MANIFEST.in +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/README.md +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linode_cli.egg-info/SOURCES.txt +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linode_cli.egg-info/dependency_links.txt +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linode_cli.egg-info/entry_points.txt +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linode_cli.egg-info/requires.txt +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linode_cli.egg-info/top_level.txt +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/__init__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/__main__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/api_request.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/arg_helpers.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/baked/__init__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/baked/operation.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/baked/parsing.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/baked/request.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/baked/response.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/cli.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/completion.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/configuration/__init__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/configuration/auth.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/exit_codes.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/helpers.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/oauth-landing-page.html +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/output/__init__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/output/helpers.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/output/output_handler.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/__init__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/firewall-editor.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/image-upload.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/metadata.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/__init__.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/buckets.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/config.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/helpers.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/list.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/objects.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/obj/website.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/plugins.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/region-table.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/linodecli/plugins/ssh.py +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/pyproject.toml +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/setup.cfg +0 -0
- {linode_cli-5.52.0 → linode_cli-5.52.2}/setup.py +0 -0
|
@@ -290,7 +290,10 @@ class CLIConfig:
|
|
|
290
290
|
):
|
|
291
291
|
print(f"User {username} is not configured.")
|
|
292
292
|
sys.exit(ExitCodes.USERNAME_ERROR)
|
|
293
|
-
if
|
|
293
|
+
if (
|
|
294
|
+
not self.config.has_section(username)
|
|
295
|
+
and self.config.default_section is None
|
|
296
|
+
) or allowed_defaults is None:
|
|
294
297
|
return namespace
|
|
295
298
|
|
|
296
299
|
warn_dict = {}
|
|
@@ -335,12 +338,6 @@ class CLIConfig:
|
|
|
335
338
|
to save values they've set, and is used internally to update the config
|
|
336
339
|
on disk when a new user if configured.
|
|
337
340
|
"""
|
|
338
|
-
|
|
339
|
-
# Create the config path isf necessary
|
|
340
|
-
config_path = f"{os.path.expanduser('~')}/.config"
|
|
341
|
-
if not os.path.exists(config_path):
|
|
342
|
-
os.makedirs(config_path)
|
|
343
|
-
|
|
344
341
|
with open(_get_config_path(), "w", encoding="utf-8") as f:
|
|
345
342
|
self.config.write(f)
|
|
346
343
|
|
|
@@ -15,6 +15,8 @@ CONFIG_DIR = os.environ.get(
|
|
|
15
15
|
"XDG_CONFIG_HOME", f"{os.path.expanduser('~')}/.config"
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
ENV_CONFIG_FILE_PATH = "LINODE_CLI_CONFIG"
|
|
19
|
+
|
|
18
20
|
# this is a list of browser that _should_ work for web-based auth. This is mostly
|
|
19
21
|
# intended to exclude lynx and other terminal browsers which could be opened, but
|
|
20
22
|
# won't work.
|
|
@@ -38,11 +40,23 @@ def _get_config_path() -> str:
|
|
|
38
40
|
:returns: The path to the local config file.
|
|
39
41
|
:rtype: str
|
|
40
42
|
"""
|
|
43
|
+
custom_path = os.getenv(ENV_CONFIG_FILE_PATH, None)
|
|
44
|
+
|
|
45
|
+
if custom_path is not None:
|
|
46
|
+
custom_path = os.path.expanduser(custom_path)
|
|
47
|
+
if not os.path.exists(custom_path):
|
|
48
|
+
os.makedirs(os.path.dirname(custom_path), exist_ok=True)
|
|
49
|
+
return custom_path
|
|
50
|
+
|
|
41
51
|
path = f"{LEGACY_CONFIG_DIR}/{LEGACY_CONFIG_NAME}"
|
|
42
52
|
if os.path.exists(path):
|
|
43
53
|
return path
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
path = f"{CONFIG_DIR}/{CONFIG_NAME}"
|
|
56
|
+
if not os.path.exists(path):
|
|
57
|
+
os.makedirs(CONFIG_DIR, exist_ok=True)
|
|
58
|
+
|
|
59
|
+
return path
|
|
46
60
|
|
|
47
61
|
|
|
48
62
|
def _get_config(load: bool = True):
|
|
Binary file
|
|
@@ -30,6 +30,8 @@ HELP_ENV_VARS = {
|
|
|
30
30
|
"(e.g. 'v4beta')",
|
|
31
31
|
"LINODE_CLI_API_SCHEME": "Overrides the target scheme used for API requests. "
|
|
32
32
|
"(e.g. 'https')",
|
|
33
|
+
"LINODE_CLI_CONFIG": "Overrides the default configuration file path. "
|
|
34
|
+
"(e.g '~/.linode/my-cli-config')",
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
HELP_TOPICS = {
|
|
@@ -4,7 +4,7 @@ This allows us to easily alter per-command outputs, etc. without making
|
|
|
4
4
|
large changes to the OpenAPI spec.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import Dict
|
|
7
|
+
from typing import Dict, List
|
|
8
8
|
|
|
9
9
|
from rich import box
|
|
10
10
|
from rich import print as rprint
|
|
@@ -57,6 +57,15 @@ def handle_types_region_prices_list(
|
|
|
57
57
|
return linode_types_with_region_prices(operation, output_handler, json_data)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
@output_override("images", "replicate", OutputMode.table)
|
|
61
|
+
def handle_image_replicate(operation, output_handler, json_data) -> bool:
|
|
62
|
+
# pylint: disable=unused-argument
|
|
63
|
+
"""
|
|
64
|
+
Override the output of 'linode-cli images replicate'.
|
|
65
|
+
"""
|
|
66
|
+
return image_replicate_output(json_data)
|
|
67
|
+
|
|
68
|
+
|
|
60
69
|
def linode_types_with_region_prices(
|
|
61
70
|
operation, output_handler, json_data
|
|
62
71
|
) -> bool:
|
|
@@ -137,3 +146,45 @@ def format_region_prices(data: Dict[str, any]) -> any:
|
|
|
137
146
|
sub_table.add_row(*region_price_row)
|
|
138
147
|
|
|
139
148
|
return sub_table
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_replicas_output(replicas: List) -> Table:
|
|
152
|
+
"""
|
|
153
|
+
Format nested replicas list to a sub-table.
|
|
154
|
+
"""
|
|
155
|
+
replicas_output = Table(show_header=False, box=None)
|
|
156
|
+
replicas_headers = replicas[0].keys()
|
|
157
|
+
for replica in replicas:
|
|
158
|
+
row = []
|
|
159
|
+
for h in replicas_headers:
|
|
160
|
+
row.append(Align(str(replica[h]), align="left"))
|
|
161
|
+
replicas_output.add_row(*row)
|
|
162
|
+
|
|
163
|
+
return replicas_output
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def image_replicate_output(json_data) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Parse and format the image replicate output table.
|
|
169
|
+
"""
|
|
170
|
+
output = Table(
|
|
171
|
+
header_style="bold",
|
|
172
|
+
show_lines=True,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
row = []
|
|
176
|
+
for header in json_data.keys():
|
|
177
|
+
if header == "regions" and len(json_data[header]) > 0:
|
|
178
|
+
# leverage `replicas` in output for readability
|
|
179
|
+
output.add_column("replicas", justify="center")
|
|
180
|
+
row.append(build_replicas_output(json_data[header]))
|
|
181
|
+
elif json_data[header] is not None:
|
|
182
|
+
output.add_column(header, justify="center")
|
|
183
|
+
row.append(Align(str(json_data[header]), align="left"))
|
|
184
|
+
|
|
185
|
+
output.add_row(*row)
|
|
186
|
+
|
|
187
|
+
console = Console()
|
|
188
|
+
console.print(output)
|
|
189
|
+
|
|
190
|
+
return False
|
|
@@ -90,7 +90,7 @@ def call(args, context):
|
|
|
90
90
|
else cluster_config
|
|
91
91
|
)
|
|
92
92
|
if parsed.dry_run:
|
|
93
|
-
print(cluster_config)
|
|
93
|
+
print(yaml.dump(cluster_config))
|
|
94
94
|
else:
|
|
95
95
|
_dump_config(kubeconfig_path, cluster_config)
|
|
96
96
|
|
|
@@ -146,27 +146,35 @@ def _dump_config(filepath, data):
|
|
|
146
146
|
yaml.dump(data, file_descriptor)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
# Merges the lists in the provided dicts. If non-list properties of the two
|
|
150
|
-
# dicts differ, uses the value from the first dict.
|
|
151
149
|
def _merge_dict(dict_1, dict_2):
|
|
150
|
+
"""
|
|
151
|
+
Merges two dicts:
|
|
152
|
+
* Lists that are present in both dicts are merged together by their "name" key
|
|
153
|
+
(preferring duplicate values in the first dict)
|
|
154
|
+
* `None` or missing keys in the first dict are set to the second dict's value
|
|
155
|
+
* Other values are preferred from the first dict
|
|
156
|
+
"""
|
|
152
157
|
# Return a new dict to prevent any accidental mutations
|
|
153
158
|
result = {}
|
|
154
159
|
|
|
155
|
-
for key in dict_1:
|
|
156
|
-
if
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
for key, dict_1_value in dict_1.items():
|
|
161
|
+
if dict_1_value is None and (dict_2_value := dict_2.get(key)):
|
|
162
|
+
# Replace null value in previous config
|
|
163
|
+
result[key] = dict_2_value
|
|
164
|
+
elif isinstance(dict_1_value, list) and (
|
|
165
|
+
dict_2_value := dict_2.get(key)
|
|
166
|
+
):
|
|
167
|
+
merge_map = {sub["name"]: sub for sub in dict_1_value}
|
|
168
|
+
for list_2_item in dict_2_value:
|
|
169
|
+
if (list_2_name := list_2_item["name"]) not in merge_map:
|
|
170
|
+
merge_map[list_2_name] = list_2_item
|
|
171
|
+
# Convert back to a list
|
|
172
|
+
result[key] = list(merge_map.values())
|
|
173
|
+
else:
|
|
174
|
+
result[key] = dict_1_value
|
|
175
|
+
|
|
176
|
+
# Process keys missing in dict_1
|
|
177
|
+
for key in set(dict_2.keys()).difference(dict_1.keys()):
|
|
178
|
+
result[key] = dict_2[key]
|
|
171
179
|
|
|
172
180
|
return result
|
|
Binary file
|
|
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
|
|
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
|
|
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
|