Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/e2e-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ jobs:
steps:
- name: Notify Slack
id: main_message
uses: slackapi/slack-github-action@v2
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down Expand Up @@ -334,7 +334,7 @@ jobs:

- name: Test summary thread
if: success()
uses: slackapi/slack-github-action@v2
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v6
-
name: Run Labeler
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916
uses: crazy-max/ghaction-github-labeler@548a7c3603594ec17c819e1239f281a3b801ab4d
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
yaml-file: .github/labels.yml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly-smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:

- name: Notify Slack
if: always() && github.repository == 'linode/linode-cli' # Run even if integration tests fail and only on main repository
uses: slackapi/slack-github-action@v2
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- name: Notify Slack - Main Message
id: main_message
uses: slackapi/slack-github-action@v2
uses: slackapi/slack-github-action@v3
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
Expand Down Expand Up @@ -67,7 +67,7 @@ jobs:
result-encoding: string

- name: Build and push to DockerHub
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # pin@v7.0.0
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile
Expand Down
48 changes: 48 additions & 0 deletions linodecli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,56 @@ def _load_openapi_spec(spec_location: str) -> OpenAPI:
with CLI._get_spec_file_reader(spec_location) as f:
parsed = CLI._parse_spec_file(f)

CLI._normalize_content_parameters(parsed)

return OpenAPI(parsed)

@staticmethod
def _normalize_content_parameters(parsed: Dict[str, Any]):
"""
The openapi3 library does not support the OpenAPI 3.0 ``content``
form for Parameter objects. This method converts any such
parameters (in components and inline on paths/operations) to use
a top-level ``schema`` field so they can be parsed normally.

:param parsed: The raw spec dict to mutate in-place.
"""

def _fix_param(param):
if not isinstance(param, dict):
return
if "content" in param and "schema" not in param:
content = param.pop("content")
for media_obj in content.values():
if isinstance(media_obj, dict) and "schema" in media_obj:
param["schema"] = media_obj["schema"]
break

for param in (
parsed.get("components", {}).get("parameters", {}).values()
):
_fix_param(param)

for path_item in parsed.get("paths", {}).values():
if not isinstance(path_item, dict):
continue
for p in path_item.get("parameters", []):
_fix_param(p)
for method in (
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
):
operation = path_item.get(method)
if isinstance(operation, dict):
for p in operation.get("parameters", []):
_fix_param(p)

@staticmethod
@contextlib.contextmanager
def _get_spec_file_reader(
Expand Down
38 changes: 38 additions & 0 deletions tests/integration/linodes/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,3 +662,41 @@ def linode_with_label(linode_cloud_firewall):
res_arr = result.split(",")
linode_id = res_arr[4]
delete_target_id(target="linodes", id=linode_id)


@pytest.fixture(scope="module")
def linode_with_authorization_key(linode_cloud_firewall):
label = "cli" + get_random_text(5)
test_region = get_random_region_with_caps(
required_capabilities=["Linodes", "Disk Encryption"]
)
result = exec_test_command(
BASE_CMDS["linodes"]
+ [
"create",
"--type",
"g6-nanode-1",
"--region",
test_region,
"--image",
DEFAULT_TEST_IMAGE,
"--label",
label,
"--authorized_keys",
"ssh-rsa",
"--kernel",
"linode/latest-64bit",
"--boot_size",
"9000",
"--text",
"--delimiter",
",",
"--no-headers",
"--no-defaults",
"--format",
"id,type",
]
).split(",")

yield result
delete_target_id(target="linodes", id=result[0])
30 changes: 30 additions & 0 deletions tests/integration/linodes/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,33 @@ def get_disk_id(test_linode_instance):
).splitlines()
first_id = disk_id[0].split(",")[0]
return first_id


def wait_for_disk_status(
linode_id: "str", disk_id: "str", timeout, status: "str", period=10
):
must_end = time.time() + timeout
while time.time() < must_end:
time.sleep(period)
try:
result = exec_test_command(
[
"linode-cli",
"linodes",
"disk-view",
linode_id,
disk_id,
"--format",
"status",
"--text",
"--no-headers",
]
)
except RuntimeError as response_error:
if "Not found" in str(response_error):
continue
else:
raise RuntimeError(response_error)
if status == result:
return True
return False
128 changes: 108 additions & 20 deletions tests/integration/linodes/test_linodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
exec_failing_test_command,
exec_test_command,
get_random_region_with_caps,
get_random_text,
retry_exec_test_command_with_delay,
)
from tests.integration.linodes.fixtures import ( # noqa: F401
linode_min_req,
linode_with_authorization_key,
linode_with_label,
linode_wo_image,
test_linode_instance,
Expand All @@ -23,6 +26,7 @@
DEFAULT_TEST_IMAGE,
create_linode,
get_disk_id,
wait_for_disk_status,
wait_until,
)

Expand All @@ -42,6 +46,110 @@ def test_create_linodes_with_a_label(linode_with_label):
)


def test_expected_error_if_fields_authorized_users_authorized_keys_root_pass_are_not_set():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 tests require API SPEC release. Could you let them know about that?

test_region = get_random_region_with_caps(
required_capabilities=["Linodes", "Disk Encryption"]
)
result = exec_failing_test_command(
BASE_CMDS["linodes"]
+ [
"create",
"--type",
"g6-nanode-1",
"--region",
test_region,
"--image",
DEFAULT_TEST_IMAGE,
"--label",
"cli-negative-test-case",
"--kernel",
"linode/latest-64bit",
"--boot_size",
"9000",
"--text",
"--delimiter",
",",
"--no-headers",
"--format",
"label,region,type,image,id",
"--no-defaults",
],
expected_code=ExitCodes.REQUEST_FAILED,
)
assert "Request failed: 400" in result
assert (
"Must provide valid root_pass, authorized_keys, or authorized_users"
in result
)


def test_create_linode_with_kernel_and_boot_size_then_add_disk_and_rebuild(
linode_with_authorization_key,
):
result_create = linode_with_authorization_key
assert result_create[1] == "g6-nanode-1"
assert wait_until(
linode_id=result_create[0], timeout=180, status="running"
), "linode failed to change status to running from creating.."

response_create_disk = (
retry_exec_test_command_with_delay(
BASE_CMDS["linodes"]
+ [
"disk-create",
result_create[0],
"--size",
"2000",
"--label",
"cli" + get_random_text(5),
"--image",
"linode/debian12",
"--root_pass",
"aComplex@Password123",
"--text",
"--no-headers",
"--delimiter",
",",
],
retries=3,
delay=10,
)
.splitlines()[0]
.split(",")
)
assert "not ready" in response_create_disk
assert wait_for_disk_status(
linode_id=result_create[0],
disk_id=response_create_disk[0],
timeout=90,
status="ready",
), "linode failed to change disk status to ready after disk creation.."

result_rebuild = (
exec_test_command(
BASE_CMDS["linodes"]
+ [
"rebuild",
"--image",
DEFAULT_TEST_IMAGE,
"--authorized_keys",
"ssh-rsa-sha2-512",
"--text",
"--no-headers",
"--delimiter",
",",
result_create[0],
]
)
.splitlines()[0]
.split(",")
)
assert DEFAULT_TEST_IMAGE in result_rebuild
assert wait_until(
linode_id=result_create[0], timeout=180, status="running"
), "linode failed to change status to running from rebuilding.."


@pytest.mark.smoke
def test_view_linode_configuration(test_linode_instance):
linode_id = test_linode_instance
Expand Down Expand Up @@ -75,26 +183,6 @@ def test_create_linode_with_min_required_props(linode_min_req):
assert re.search("[0-9]+,us-ord,g6-nanode-1", result)


def test_create_linodes_fails_without_a_root_pass():
result = exec_failing_test_command(
BASE_CMDS["linodes"]
+ [
"create",
"--type",
"g6-nanode-1",
"--region",
"us-ord",
"--image",
DEFAULT_TEST_IMAGE,
"--text",
"--no-headers",
],
ExitCodes.REQUEST_FAILED,
)
assert "Request failed: 400" in result
assert "root_pass root_pass is required" in result


def test_create_linode_without_image_and_not_boot(linode_wo_image):
linode_id = linode_wo_image

Expand Down
Loading