Skip to content

Commit

Permalink
Merge pull request #732 from linode/dev
Browse files Browse the repository at this point in the history
v5.56.3
  • Loading branch information
zliang-akamai authored Feb 26, 2025
2 parents c5cecf9 + fafe73e commit 556685d
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 65 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.11-slim AS builder
FROM python:3.13-slim AS builder

ARG linode_cli_version

Expand All @@ -15,11 +15,11 @@ RUN make requirements

RUN LINODE_CLI_VERSION=$linode_cli_version GITHUB_TOKEN=$github_token make build

FROM python:3.11-slim
FROM python:3.13-slim

COPY --from=builder /src/dist /dist

RUN pip3 install /dist/*.whl boto3
RUN pip3 install --no-cache-dir /dist/*.whl boto3

RUN useradd -ms /bin/bash cli
USER cli:cli
Expand Down
38 changes: 25 additions & 13 deletions linodecli/baked/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
Request details for a CLI Operation
"""

from typing import List, Optional

from openapi3.paths import MediaType
from openapi3.schemas import Schema

from linodecli.baked.parsing import simplify_description
from linodecli.baked.response import OpenAPIResponse
from linodecli.baked.util import _aggregate_schema_properties


Expand All @@ -13,16 +17,16 @@ class OpenAPIRequestArg:
A single argument to a request as defined by a Schema in the OpenAPI spec
"""

def __init__(
def __init__( # pylint: disable=too-many-arguments
self,
name,
schema,
required,
prefix=None,
is_parent=False,
parent=None,
depth=0,
): # pylint: disable=too-many-arguments
name: str,
schema: Schema,
required: bool,
prefix: Optional[str] = None,
is_parent: bool = False,
parent: Optional[str] = None,
depth: int = 0,
) -> None:
"""
Parses a single Schema node into a argument the CLI can use when making
requests.
Expand Down Expand Up @@ -120,9 +124,14 @@ def __init__(
)


def _parse_request_model(schema, prefix=None, parent=None, depth=0):
def _parse_request_model(
schema: Schema,
prefix: Optional[str] = None,
parent: Optional[str] = None,
depth: int = 0,
) -> List[OpenAPIRequestArg]:
"""
Parses a schema into a list of OpenAPIRequest objects
Parses an OpenAPI schema into a list of OpenAPIRequest objects
:param schema: The schema to parse as a request model
:type schema: openapi3.Schema
:param prefix: The prefix to add to all keys in this schema, as a json path
Expand All @@ -143,6 +152,7 @@ def _parse_request_model(schema, prefix=None, parent=None, depth=0):
return args

for k, v in properties.items():
# Handle nested objects which aren't read-only and have properties
if (
v.type == "object"
and not v.readOnly
Expand All @@ -159,6 +169,8 @@ def _parse_request_model(schema, prefix=None, parent=None, depth=0):
# parent arguments.
depth=depth,
)

# Handle arrays of objects that not marked as JSON
elif (
v.type == "array"
and v.items
Expand Down Expand Up @@ -209,7 +221,7 @@ class OpenAPIRequest:
on the MediaType object of a requestBody portion of an OpenAPI Operation
"""

def __init__(self, request):
def __init__(self, request: MediaType) -> None:
"""
:param request: The request's MediaType object in the OpenAPI spec,
corresponding to the application/json data the endpoint
Expand Down Expand Up @@ -256,7 +268,7 @@ class OpenAPIFilteringRequest:
endpoints where filters are accepted.
"""

def __init__(self, response_model):
def __init__(self, response_model: OpenAPIResponse) -> None:
"""
:param response_model: The parsed response model whose properties may be
filterable.
Expand Down
28 changes: 25 additions & 3 deletions linodecli/baked/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
Converting the processed OpenAPI Responses into something the CLI can work with
"""

from typing import Optional

from openapi3.paths import MediaType
from openapi3.schemas import Schema

from linodecli.baked.util import _aggregate_schema_properties

Expand Down Expand Up @@ -30,7 +33,13 @@ class OpenAPIResponseAttr:
from it.
"""

def __init__(self, name, schema, prefix=None, nested_list_depth=0):
def __init__(
self,
name: str,
schema: Schema,
prefix: Optional[str] = None,
nested_list_depth: int = 0,
) -> None:
"""
:param name: The key that held this schema in the properties list, representing
its name in a response.
Expand Down Expand Up @@ -84,10 +93,13 @@ def __init__(self, name, schema, prefix=None, nested_list_depth=0):
self.item_type = schema.items.type

@property
def path(self):
def path(self) -> str:
"""
This is a helper for filterable fields to return the json path to this
element in a response.
:returns: The json path to the element in a response.
:rtype: str
"""
return self.name

Expand Down Expand Up @@ -129,6 +141,7 @@ def render_value(self, model, colorize=True):
value = str(value)
color = self.color_map.get(value) or self.color_map["default_"]
value = f"[{color}]{value}[/]"
# Convert None value to an empty string for better display
if value is None:
# Prints the word None if you don't change it
value = ""
Expand Down Expand Up @@ -194,12 +207,14 @@ def _parse_response_model(schema, prefix=None, nested_list_depth=0):
elif v.type == "object":
attrs += _parse_response_model(v, prefix=pref)
elif v.type == "array" and v.items.type == "object":
# Parse arrays for objects recursively and increase the nesting depth
attrs += _parse_response_model(
v.items,
prefix=pref,
nested_list_depth=nested_list_depth + 1,
)
else:
# Handle any other simple types
attrs.append(
OpenAPIResponseAttr(
k, v, prefix=prefix, nested_list_depth=nested_list_depth
Expand All @@ -215,7 +230,7 @@ class OpenAPIResponse:
responses section of an OpenAPI Operation
"""

def __init__(self, response: MediaType):
def __init__(self, response: MediaType) -> None:
"""
:param response: The response's MediaType object in the OpenAPI spec,
corresponding to the application/json response type
Expand Down Expand Up @@ -287,15 +302,22 @@ def _fix_nested_list(self, json):

nested_lists = [c.strip() for c in self.nested_list.split(",")]
result = []

for nested_list in nested_lists:
path_parts = nested_list.split(".")

if not isinstance(json, list):
json = [json]

for cur in json:
# Get the nested list using the path
nlist_path = cur
for p in path_parts:
nlist_path = nlist_path.get(p)
nlist = nlist_path

# For each item in the nested list,
# combine the parent properties with the nested item
for item in nlist:
cobj = {k: v for k, v in cur.items() if k != path_parts[0]}
cobj["_split"] = path_parts[-1]
Expand Down
50 changes: 16 additions & 34 deletions linodecli/configuration/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ def default_username(self) -> str:
:returns: The `default-user` username or an empty string.
:rtype: str
"""
if self.config.has_option("DEFAULT", "default-user"):
return self.config.get("DEFAULT", "default-user")

return ""
return self.config.get("DEFAULT", "default-user", fallback="")

def set_user(self, username: str):
"""
Expand Down Expand Up @@ -153,15 +150,11 @@ def get_token(self) -> str:
:rtype: str
"""
if self.used_env_token:
return os.environ.get(ENV_TOKEN_NAME, None)
return os.getenv(ENV_TOKEN_NAME, None)

if self.config.has_option(
self.username or self.default_username(), "token"
):
return self.config.get(
self.username or self.default_username(), "token"
)
return ""
return self.config.get(
self.username or self.default_username(), "token", fallback=""
)

def get_value(self, key: str) -> Optional[Any]:
"""
Expand All @@ -180,12 +173,9 @@ def get_value(self, key: str) -> Optional[Any]:
current user.
:rtype: any
"""
username = self.username or self.default_username()

if not self.config.has_option(username, key):
return None

return self.config.get(username, key)
return self.config.get(
self.username or self.default_username(), key, fallback=None
)

def get_bool(self, key: str) -> bool:
"""
Expand All @@ -204,12 +194,10 @@ def get_bool(self, key: str) -> bool:
current user.
:rtype: any
"""
username = self.username or self.default_username()

if not self.config.has_option(username, key):
return False

return self.config.getboolean(username, key)
return self.config.getboolean(
self.username or self.default_username(), key, fallback=False
)

# plugin methods - these are intended for plugins to utilize to store their
# own persistent config information
Expand Down Expand Up @@ -255,13 +243,10 @@ def plugin_get_value(self, key: str) -> Optional[Any]:
"No running plugin to retrieve configuration for!"
)

username = self.username or self.default_username()
username = self.username or self.default_username() or "DEFAULT"
full_key = f"plugin-{self.running_plugin}-{key}"

if not self.config.has_option(username, full_key):
return None

return self.config.get(username, full_key)
return self.config.get(username, full_key, fallback=None)

# TODO: this is more of an argparsing function than it is a config function
# might be better to move this to argparsing during refactor and just have
Expand Down Expand Up @@ -308,11 +293,8 @@ def update(
# these don't get included in the updated namespace
if key.startswith("plugin-"):
continue
value = None
if self.config.has_option(username, key):
value = self.config.get(username, key)
else:
value = ns_dict[key]

value = self.config.get(username, key, fallback=ns_dict.get(key))

if not value:
continue
Expand Down Expand Up @@ -553,7 +535,7 @@ def _handle_no_default_user(self): # pylint: disable=too-many-branches

if len(users) == 0:
# config is new or _really_ old
token = self.config.get("DEFAULT", "token")
token = self.config.get("DEFAULT", "token", fallback=None)

if token is not None:
# there's a token in the config - configure that user
Expand Down
4 changes: 1 addition & 3 deletions linodecli/configuration/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,4 @@ def _config_get_with_default(
:returns: The value pulled from the config or the default value.
:rtype: Any
"""
return (
config.get(user, field) if config.has_option(user, field) else default
)
return config.get(user, field, fallback=default)
17 changes: 9 additions & 8 deletions wiki/Output.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
By default, the CLI displays on some pre-selected fields for a given type of
response. If you want to see everything, just ask::
```bash
linode-cli linodes list --all
linode-cli linodes list --all-columns
```

Using `--all` will cause the CLI to display all returned columns of output.
Note that this will probably be hard to read on normal-sized screens for most
actions.
Using `--all-columns` will cause the CLI to display all returned columns of
output. Note that this will probably be hard to read on normal-sized screens
for most actions.

If you want even finer control over your output, you can request specific columns
be displayed::
Expand Down Expand Up @@ -48,11 +48,12 @@ linode-cli linodes list --no-headers --text

To get JSON output from the CLI, simple request it::
```bash
linode-cli linodes list --json --all
linode-cli linodes list --json --all-columns
```

While the `--all` is optional, you probably want to see all output fields in
your JSON output. If you want your JSON pretty-printed, we can do that too::
While the `--all-columns` is optional, you probably want to see all output
fields in your JSON output. If you want your JSON pretty-printed, we can do
that too::
```bash
linode-cli linodes list --json --pretty --all
linode-cli linodes list --json --pretty --all-columns
```
13 changes: 13 additions & 0 deletions wiki/Uninstallation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Uninstallation

## PyPi

```bash
pip3 uninstall linode-cli
```

If you would like to remove the config file (easy to re-create) you must do so manually.

```bash
rm $HOME/.config/linode-cli
```
3 changes: 2 additions & 1 deletion wiki/_Sidebar.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
- [Installation](./Installation)
- [Uninstallation](./Uninstallation)
- [Configuration](./Configuration)
- [Usage](./Usage)
- [Output](./Output)
Expand All @@ -7,4 +8,4 @@
- [Overview](./Development%20-%20Overview)
- [Skeleton](./Development%20-%20Skeleton)
- [Setup](./Development%20-%20Setup)
- [Testing](./Development%20-%20Testing)
- [Testing](./Development%20-%20Testing)

0 comments on commit 556685d

Please sign in to comment.