Skip to content

Commit

Permalink
Merge branch 'dev' into zhiwei/remove-pkg-resources
Browse files Browse the repository at this point in the history
  • Loading branch information
zliang-akamai authored Oct 24, 2023
2 parents b66b91a + ac2ef3e commit b5948ab
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 10 deletions.
5 changes: 4 additions & 1 deletion linodecli/arg_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,10 @@ def action_help(cli, command, action):
if op.method in {"post", "put"} and arg.required
else ""
)
print(f" --{arg.path}: {is_required}{arg.description}")
nullable_fmt = " (nullable)" if arg.nullable else ""
print(
f" --{arg.path}: {is_required}{arg.description}{nullable_fmt}"
)


def bake_command(cli, spec_loc):
Expand Down
23 changes: 22 additions & 1 deletion linodecli/baked/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ def type_func(value):
return type_func


class ArrayAction(argparse.Action):
"""
This action is intended to be used only with array arguments.
This purpose of this action is to allow users to specify explicitly
empty lists using a singular "[]" argument value.
"""

def __call__(self, parser, namespace, values, option_string=None):
if getattr(namespace, self.dest) is None:
setattr(namespace, self.dest, [])

output_list = getattr(namespace, self.dest)

# If the output list is empty and the user specifies a []
# argument, keep the list empty
if values == "[]" and len(output_list) < 1:
return

output_list.append(values)


class ListArgumentAction(argparse.Action):
"""
This action is intended to be used only with list arguments.
Expand Down Expand Up @@ -380,7 +401,7 @@ def _add_args_post_put(self, parser) -> List[Tuple[str, str]]:
parser.add_argument(
"--" + arg.path,
metavar=arg.name,
action="append",
action=ArrayAction,
type=arg_type_handler,
)
elif arg.list_item:
Expand Down
13 changes: 7 additions & 6 deletions tests/integration/linodes/helpers_linodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ def wait_until(linode_id: "str", timeout, status: "str", period=5):
return False


def create_linode():
region = "us-east"

def create_linode(test_region=DEFAULT_REGION):
# create linode
linode_id = (
exec_test_command(
Expand All @@ -84,7 +82,7 @@ def create_linode():
"--type",
DEFAULT_LINODE_TYPE,
"--region",
region,
test_region,
"--image",
DEFAULT_TEST_IMAGE,
"--root_pass",
Expand Down Expand Up @@ -142,7 +140,10 @@ def remove_linodes():


def create_linode_and_wait(
test_plan=DEFAULT_LINODE_TYPE, test_image=DEFAULT_TEST_IMAGE, ssh_key=""
test_plan=DEFAULT_LINODE_TYPE,
test_image=DEFAULT_TEST_IMAGE,
ssh_key="",
test_region=DEFAULT_REGION,
):
linode_type = test_plan

Expand All @@ -160,7 +161,7 @@ def create_linode_and_wait(
"--type",
linode_type,
"--region",
"us-east",
test_region,
"--image",
test_image,
"--root_pass",
Expand Down
83 changes: 82 additions & 1 deletion tests/integration/networking/test_networking.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import json
import re

import pytest

from tests.integration.helpers import delete_target_id, exec_test_command
from tests.integration.linodes.helpers_linodes import create_linode_and_wait
from tests.integration.linodes.helpers_linodes import (
create_linode,
create_linode_and_wait,
)

BASE_CMD = ["linode-cli", "networking"]

Expand All @@ -17,6 +21,32 @@ def setup_test_networking():
delete_target_id(target="linodes", id=linode_id)


@pytest.fixture(scope="package")
def setup_test_networking_shared_ipv4():
target_region = "us-southeast"

linode_ids = (
create_linode(test_region=target_region),
create_linode(test_region=target_region),
)

yield linode_ids

for id in linode_ids:
delete_target_id(target="linodes", id=id)


def has_shared_ip(linode_id: int, ip: str) -> bool:
shared_ips = json.loads(
exec_test_command(
["linode-cli", "linodes", "ips-list", "--json", linode_id]
).stdout.decode()
)[0]["ipv4"]["shared"]

# Ensure there is a matching shared IP
return len([v for v in shared_ips if v["address"] == ip]) > 0


def test_display_ips_for_available_linodes(setup_test_networking):
result = exec_test_command(
BASE_CMD + ["ips-list", "--text", "--no-headers", "--delimiter", ","]
Expand Down Expand Up @@ -89,3 +119,54 @@ def test_allocate_additional_private_ipv4_address(setup_test_networking):
assert re.search(
"ipv4,False,.*,[0-9][0-9][0-9][0-9][0-9][0-9][0-9]*", result
)


def test_share_ipv4_address(setup_test_networking_shared_ipv4):
target_linode, parent_linode = setup_test_networking_shared_ipv4

# Allocate an IPv4 address on the parent Linode
ip_address = json.loads(
exec_test_command(
BASE_CMD
+ [
"ip-add",
"--type",
"ipv4",
"--linode_id",
parent_linode,
"--json",
"--public",
"true",
]
).stdout.decode()
)[0]["address"]

# Share the IP address to the target Linode
exec_test_command(
BASE_CMD
+ [
"ip-share",
"--ips",
ip_address,
"--linode_id",
target_linode,
"--json",
]
)

assert has_shared_ip(target_linode, ip_address)

# Remove the IP shares
exec_test_command(
BASE_CMD
+ [
"ip-share",
"--ips",
"[]",
"--linode_id",
target_linode,
"--json",
]
)

assert not has_shared_ip(target_linode, ip_address)
32 changes: 32 additions & 0 deletions tests/unit/test_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,35 @@ def test_parse_args_nullable_float(self, create_operation):

result = create_operation.parse_args(["--nullable_float", "456.123"])
assert result.nullable_float == 456.123

def test_array_arg_action_basic(self):
"""
Tests a basic array argument condition..
"""

parser = argparse.ArgumentParser(
prog=f"foo",
)

parser.add_argument(
"--foo",
metavar="foo",
action=operation.ArrayAction,
type=str,
)

# User specifies a normal list
result = parser.parse_args(["--foo", "foo", "--foo", "bar"])
assert getattr(result, "foo") == ["foo", "bar"]

# User wants an explicitly empty list
result = parser.parse_args(["--foo", "[]"])
assert getattr(result, "foo") == []

# User doesn't specify the list
result = parser.parse_args([])
assert getattr(result, "foo") is None

# User specifies a normal value and an empty list value
result = parser.parse_args(["--foo", "foo", "--foo", "[]"])
assert getattr(result, "foo") == ["foo", "[]"]
22 changes: 21 additions & 1 deletion wiki/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,31 @@ linode-cli linodes create \
When running certain commands, you may need to specify an argument that is nested
in another field. These arguments can be specified using a `.` delimited path to
the argument. For example, to create a firewall with an inbound policy of `DROP`
and an outbound policy of `ACCEPT`, you can execute the following::
and an outbound policy of `ACCEPT`, you can execute the following:
```bash
linode-cli firewalls create --label example-firewall --rules.outbound_policy ACCEPT --rules.inbound_policy DROP
```

## Special Arguments

In some cases, certain values for arguments may have unique functionality.

### Null Values

Arguments marked as nullable can be passed the value `null` to send an explicit null value to the Linode API:

```bash
linode-cli networking ip-update --rdns null 127.0.0.1
```

### Empty Lists

List arguments can be passed the value `[]` to send an explicit empty list value to the Linode API:

```bash
linode-cli networking ip-share --linode_id 12345 --ips []
```

## Suppressing Defaults

If you configured default values for `image`, `authorized_users`, `region`,
Expand Down

0 comments on commit b5948ab

Please sign in to comment.