linode-cli 5.68.0__tar.gz → 5.69.0__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.68.0/linode_cli.egg-info → linode_cli-5.69.0}/PKG-INFO +13 -4
- {linode_cli-5.68.0 → linode_cli-5.69.0}/README.md +12 -3
- {linode_cli-5.68.0 → linode_cli-5.69.0/linode_cli.egg-info}/PKG-INFO +13 -4
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/baked/request.py +9 -1
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/baked/response.py +11 -2
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/baked/util.py +75 -1
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/configuration/auth.py +6 -5
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/configuration/helpers.py +4 -3
- linode_cli-5.69.0/linodecli/data-3 +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/get-kubeconfig.py +27 -2
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/version.py +1 -1
- linode_cli-5.68.0/linodecli/data-3 +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/LICENSE +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/MANIFEST.in +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linode_cli.egg-info/SOURCES.txt +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linode_cli.egg-info/dependency_links.txt +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linode_cli.egg-info/entry_points.txt +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linode_cli.egg-info/requires.txt +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linode_cli.egg-info/top_level.txt +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/__init__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/__main__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/api_request.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/arg_helpers.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/baked/__init__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/baked/operation.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/baked/parsing.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/cli.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/completion.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/configuration/__init__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/configuration/config.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/exit_codes.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/help_formatter.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/help_pages.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/helpers.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/oauth-landing-page.html +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/output/__init__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/output/helpers.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/output/output_handler.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/overrides.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/__init__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/firewall-editor.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/image-upload.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/metadata.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/monitor-api.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/__init__.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/buckets.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/config.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/helpers.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/list.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/objects.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/obj/website.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/plugins.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/region-table.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/linodecli/plugins/ssh.py +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/pyproject.toml +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/setup.cfg +0 -0
- {linode_cli-5.68.0 → linode_cli-5.69.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linode-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.69.0
|
|
4
4
|
Summary: The official command-line interface for interacting with the Linode API.
|
|
5
5
|
Author-email: "Akamai Technologies Inc." <developers@linode.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -45,12 +45,21 @@ Visit the [Wiki](../../wiki) for more information.
|
|
|
45
45
|
|
|
46
46
|
## Install
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
We recommend installing `linode-cli` with `pipx`, which installs each Python CLI tool into its own isolated environment and works on distributions where `pip install` fails because the system Python is marked as externally managed ([PEP 668](https://peps.python.org/pep-0668/)). If pipx isn't installed yet, follow the [pipx installation guide](https://pipx.pypa.io/latest/how-to/install-pipx.html).
|
|
49
|
+
|
|
50
|
+
To install:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pipx install linode-cli
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To upgrade:
|
|
57
|
+
|
|
49
58
|
```bash
|
|
50
|
-
|
|
59
|
+
pipx upgrade linode-cli
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
The [Wiki](https://github.com/linode/linode-cli/wiki/Installation) covers other installation methods, including the Docker image, the GitHub Action, and building from source.
|
|
54
63
|
|
|
55
64
|
## Contributing
|
|
56
65
|
|
|
@@ -12,12 +12,21 @@ Visit the [Wiki](../../wiki) for more information.
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
We recommend installing `linode-cli` with `pipx`, which installs each Python CLI tool into its own isolated environment and works on distributions where `pip install` fails because the system Python is marked as externally managed ([PEP 668](https://peps.python.org/pep-0668/)). If pipx isn't installed yet, follow the [pipx installation guide](https://pipx.pypa.io/latest/how-to/install-pipx.html).
|
|
16
|
+
|
|
17
|
+
To install:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pipx install linode-cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To upgrade:
|
|
24
|
+
|
|
16
25
|
```bash
|
|
17
|
-
|
|
26
|
+
pipx upgrade linode-cli
|
|
18
27
|
```
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
The [Wiki](https://github.com/linode/linode-cli/wiki/Installation) covers other installation methods, including the Docker image, the GitHub Action, and building from source.
|
|
21
30
|
|
|
22
31
|
## Contributing
|
|
23
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linode-cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.69.0
|
|
4
4
|
Summary: The official command-line interface for interacting with the Linode API.
|
|
5
5
|
Author-email: "Akamai Technologies Inc." <developers@linode.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -45,12 +45,21 @@ Visit the [Wiki](../../wiki) for more information.
|
|
|
45
45
|
|
|
46
46
|
## Install
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
We recommend installing `linode-cli` with `pipx`, which installs each Python CLI tool into its own isolated environment and works on distributions where `pip install` fails because the system Python is marked as externally managed ([PEP 668](https://peps.python.org/pep-0668/)). If pipx isn't installed yet, follow the [pipx installation guide](https://pipx.pypa.io/latest/how-to/install-pipx.html).
|
|
49
|
+
|
|
50
|
+
To install:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pipx install linode-cli
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To upgrade:
|
|
57
|
+
|
|
49
58
|
```bash
|
|
50
|
-
|
|
59
|
+
pipx upgrade linode-cli
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
The [Wiki](https://github.com/linode/linode-cli/wiki/Installation) covers other installation methods, including the Docker image, the GitHub Action, and building from source.
|
|
54
63
|
|
|
55
64
|
## Contributing
|
|
56
65
|
|
|
@@ -182,12 +182,20 @@ def _parse_request_model(
|
|
|
182
182
|
depth=depth,
|
|
183
183
|
)
|
|
184
184
|
|
|
185
|
-
# Handle arrays of objects that not marked as JSON
|
|
185
|
+
# Handle arrays of objects that are not marked as JSON.
|
|
186
|
+
# NOTE: We only expand an array of objects into individual child
|
|
187
|
+
# arguments when it is not already nested under another list
|
|
188
|
+
# (i.e. parent is None). The CLI can only associate one level of
|
|
189
|
+
# nested list objects, so a list of objects nested within another
|
|
190
|
+
# list must be treated as JSON instead.
|
|
191
|
+
# Otherwise, we would generate child arguments that can never
|
|
192
|
+
# be used because they always conflict with their implicit JSON parent.
|
|
186
193
|
elif (
|
|
187
194
|
v.type == "array"
|
|
188
195
|
and v.items
|
|
189
196
|
and v.items.type == "object"
|
|
190
197
|
and v.extensions.get("linode-cli-format") != "json"
|
|
198
|
+
and parent is None
|
|
191
199
|
):
|
|
192
200
|
# handle lists of objects as a special case, where each property
|
|
193
201
|
# of the object in the list is its own argument
|
|
@@ -210,8 +210,10 @@ def _parse_response_model(schema, prefix=None, nested_list_depth=0):
|
|
|
210
210
|
)
|
|
211
211
|
elif v.type == "object":
|
|
212
212
|
attrs += _parse_response_model(v, prefix=pref)
|
|
213
|
-
elif v.type == "array" and
|
|
214
|
-
|
|
213
|
+
elif v.type == "array" and (
|
|
214
|
+
v.items.type == "object"
|
|
215
|
+
or bool(_aggregate_schema_properties(v.items)[0])
|
|
216
|
+
):
|
|
215
217
|
attrs += _parse_response_model(
|
|
216
218
|
v.items,
|
|
217
219
|
prefix=pref,
|
|
@@ -316,8 +318,15 @@ class OpenAPIResponse:
|
|
|
316
318
|
for cur in json:
|
|
317
319
|
# Get the nested list using the path
|
|
318
320
|
nlist_path = cur
|
|
321
|
+
|
|
319
322
|
for p in path_parts:
|
|
320
323
|
nlist_path = nlist_path.get(p)
|
|
324
|
+
|
|
325
|
+
# To avoid errors when nested list is not documented in
|
|
326
|
+
# the OpenAPI spec, but it is present in the API response
|
|
327
|
+
if nlist_path is None:
|
|
328
|
+
continue
|
|
329
|
+
|
|
321
330
|
nlist = nlist_path
|
|
322
331
|
|
|
323
332
|
# For each item in the nested list,
|
|
@@ -8,6 +8,67 @@ from typing import Any, Dict, List, Set, Tuple
|
|
|
8
8
|
|
|
9
9
|
from openapi3.schemas import Schema
|
|
10
10
|
|
|
11
|
+
# The maximum schema nesting depth `_schema_richness` will traverse before
|
|
12
|
+
# bailing out. This is purely a safety valve to guarantee termination on
|
|
13
|
+
# self-referential or pathologically deep schemas (which the recursion would
|
|
14
|
+
# otherwise follow forever); it is far deeper than any real Linode API response
|
|
15
|
+
# model, so it never affects scoring in practice.
|
|
16
|
+
_MAX_RICHNESS_DEPTH = 32
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _schema_richness(schema: Any, _depth: int = 0) -> int:
|
|
20
|
+
"""
|
|
21
|
+
Estimates how complete a schema definition is, used to decide which
|
|
22
|
+
definition to keep when the same property appears in multiple composition
|
|
23
|
+
(oneOf/allOf/anyOf) branches.
|
|
24
|
+
|
|
25
|
+
A branch that nulls a property out (e.g. ``{"type": "object", "nullable":
|
|
26
|
+
true}`` with no properties) should never overwrite a branch that fully
|
|
27
|
+
defines that property's nested structure. The score is a recursive measure
|
|
28
|
+
of how much structure a schema actually contains, so a fuller definition always outscores a
|
|
29
|
+
sparser one regardless of the
|
|
30
|
+
order the branches appear in.
|
|
31
|
+
|
|
32
|
+
:param schema: The schema (or raw schema dict) to score.
|
|
33
|
+
:return: A non-negative integer; higher means more complete.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Guard against pathologically deep or self-referential schemas.
|
|
37
|
+
if _depth > _MAX_RICHNESS_DEPTH:
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
def get(source: Any, attr: str) -> Any:
|
|
41
|
+
if isinstance(source, dict):
|
|
42
|
+
return source.get(attr)
|
|
43
|
+
return getattr(source, attr, None)
|
|
44
|
+
|
|
45
|
+
score = 0
|
|
46
|
+
|
|
47
|
+
# Count each defined property, plus the richness of its own definition so
|
|
48
|
+
# that deeply-nested structure contributes to the total.
|
|
49
|
+
properties = get(schema, "properties")
|
|
50
|
+
if properties:
|
|
51
|
+
for _, prop in properties.items():
|
|
52
|
+
score += 1 + _schema_richness(prop, _depth + 1)
|
|
53
|
+
|
|
54
|
+
# Account for composite (oneOf/allOf/anyOf) definitions by summing the
|
|
55
|
+
# richness of each branch.
|
|
56
|
+
for composition_field in ("oneOf", "allOf", "anyOf"):
|
|
57
|
+
for branch in get(schema, composition_field) or []:
|
|
58
|
+
score += 1 + _schema_richness(branch, _depth + 1)
|
|
59
|
+
|
|
60
|
+
# Account for array item schemas so arrays of objects are scored by their
|
|
61
|
+
# element structure.
|
|
62
|
+
array_items = get(schema, "items")
|
|
63
|
+
if array_items is not None:
|
|
64
|
+
score += _schema_richness(array_items, _depth + 1)
|
|
65
|
+
|
|
66
|
+
additional_properties = get(schema, "additionalProperties")
|
|
67
|
+
if additional_properties is not None:
|
|
68
|
+
score += 1 + _schema_richness(additional_properties, _depth + 1)
|
|
69
|
+
|
|
70
|
+
return score
|
|
71
|
+
|
|
11
72
|
|
|
12
73
|
def _aggregate_schema_properties(
|
|
13
74
|
schema: Schema,
|
|
@@ -48,7 +109,20 @@ def _aggregate_schema_properties(
|
|
|
48
109
|
return
|
|
49
110
|
|
|
50
111
|
# This is a valid option
|
|
51
|
-
properties.
|
|
112
|
+
for key, value in entry.properties.items():
|
|
113
|
+
# When the same property is defined in multiple composition
|
|
114
|
+
# branches (e.g. a oneOf of interface variants that each define
|
|
115
|
+
# `public`, `vpc`, `vlan`, etc.), keep the most complete
|
|
116
|
+
# definition instead of letting a later, emptier branch overwrite
|
|
117
|
+
# it. Otherwise nested fields like `public.ipv6.ranges.range`
|
|
118
|
+
# would be silently dropped when a subsequent branch nulls the
|
|
119
|
+
# property out.
|
|
120
|
+
if key in properties and _schema_richness(
|
|
121
|
+
value
|
|
122
|
+
) <= _schema_richness(properties[key]):
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
properties[key] = value
|
|
52
126
|
|
|
53
127
|
nonlocal schema_count
|
|
54
128
|
schema_count += 1
|
|
@@ -214,11 +214,12 @@ def _get_token_terminal(base_url: str) -> Tuple[str, str]:
|
|
|
214
214
|
:returns: A tuple containing the user's username and token.
|
|
215
215
|
:rtype: Tuple[str, str]
|
|
216
216
|
"""
|
|
217
|
-
print(
|
|
218
|
-
First, we need a Personal Access Token. To get one, please visit
|
|
219
|
-
{TOKEN_GENERATION_URL} and click
|
|
220
|
-
"Create a Personal Access Token". The CLI needs access to everything
|
|
221
|
-
on your account to work correctly."
|
|
217
|
+
print(
|
|
218
|
+
"First, we need a Personal Access Token. To get one, please visit\n"
|
|
219
|
+
f"{TOKEN_GENERATION_URL} and click\n"
|
|
220
|
+
'"Create a Personal Access Token". The CLI needs access to everything\n'
|
|
221
|
+
"on your account to work correctly."
|
|
222
|
+
)
|
|
222
223
|
|
|
223
224
|
while True:
|
|
224
225
|
token = input("Personal Access Token: ")
|
|
@@ -97,9 +97,10 @@ def _check_browsers() -> bool:
|
|
|
97
97
|
|
|
98
98
|
# pylint: disable-next=protected-access
|
|
99
99
|
if not KNOWN_GOOD_BROWSERS.intersection(webbrowser._tryorder):
|
|
100
|
-
print(
|
|
101
|
-
This tool defaults to web-based authentication
|
|
102
|
-
however no known-working browsers were found."
|
|
100
|
+
print(
|
|
101
|
+
"This tool defaults to web-based authentication,\n"
|
|
102
|
+
"however no known-working browsers were found."
|
|
103
|
+
)
|
|
103
104
|
while True:
|
|
104
105
|
r = input("Try it anyway? [y/N]: ")
|
|
105
106
|
if r.lower() in "yn ":
|
|
Binary file
|
|
@@ -8,6 +8,7 @@ Usage:
|
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
10
10
|
import base64
|
|
11
|
+
import os
|
|
11
12
|
import sys
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
@@ -19,6 +20,11 @@ from linodecli.plugins import inherit_plugin_args
|
|
|
19
20
|
|
|
20
21
|
PLUGIN_BASE = "linode-cli get-kubeconfig"
|
|
21
22
|
|
|
23
|
+
# Kubeconfigs contain credentials, so they should only be
|
|
24
|
+
# accessible by the user that created them.
|
|
25
|
+
KUBECONFIG_FILE_MODE = 0o600
|
|
26
|
+
KUBECONFIG_DIR_MODE = 0o700
|
|
27
|
+
|
|
22
28
|
|
|
23
29
|
def call(args, context):
|
|
24
30
|
"""
|
|
@@ -147,8 +153,27 @@ def _load_config(filepath):
|
|
|
147
153
|
|
|
148
154
|
# Dumps data to a yaml file
|
|
149
155
|
def _dump_config(filepath, data):
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
filepath.parent.mkdir(mode=KUBECONFIG_DIR_MODE, parents=True, exist_ok=True)
|
|
157
|
+
|
|
158
|
+
# Create the file with restrictive permissions rather than chmod-ing it
|
|
159
|
+
# afterwards, so its contents are never briefly readable by other users.
|
|
160
|
+
# NOTE: The mode is only applied when the file is created.
|
|
161
|
+
def opener(path, flags):
|
|
162
|
+
return os.open(path, flags, mode=KUBECONFIG_FILE_MODE)
|
|
163
|
+
|
|
164
|
+
with open(
|
|
165
|
+
filepath, "w", encoding="utf-8", opener=opener
|
|
166
|
+
) as file_descriptor:
|
|
167
|
+
# Tighten the permissions of pre-existing files that are readable or
|
|
168
|
+
# writable by users other than the owner.
|
|
169
|
+
# NOTE: os.fchmod is not available on Windows, where POSIX file modes
|
|
170
|
+
# are not meaningful anyway.
|
|
171
|
+
if (
|
|
172
|
+
hasattr(os, "fchmod")
|
|
173
|
+
and os.fstat(file_descriptor.fileno()).st_mode & 0o077
|
|
174
|
+
):
|
|
175
|
+
os.fchmod(file_descriptor.fileno(), KUBECONFIG_FILE_MODE)
|
|
176
|
+
|
|
152
177
|
yaml.dump(data, file_descriptor)
|
|
153
178
|
|
|
154
179
|
|
|
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
|
|
File without changes
|