From c4b5469266972b8c69b2fcb70f281e410d5cdb64 Mon Sep 17 00:00:00 2001 From: Jacob Riddle <87780794+jriddle-linode@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:12:38 -0400 Subject: [PATCH 01/14] new: adding `region-table` plugin (#525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Description **What does this PR do and why is this change necessary?** Plugin adding `region-table` command to the CLI. ```bash lin region-table ``` ## ✔️ How to Test **How do I run the relevant unit/integration tests?** `make INTEGRATION_TEST_PATH=regions testint` resolves #492 --- linodecli/plugins/region-table.py | 52 +++++++++++++++++++ .../regions/test_plugin_region_table.py | 23 ++++++++ 2 files changed, 75 insertions(+) create mode 100644 linodecli/plugins/region-table.py create mode 100644 tests/integration/regions/test_plugin_region_table.py diff --git a/linodecli/plugins/region-table.py b/linodecli/plugins/region-table.py new file mode 100644 index 000000000..d558cc5ce --- /dev/null +++ b/linodecli/plugins/region-table.py @@ -0,0 +1,52 @@ +""" +The region-table plugin displays a table output +for the capabilities of each region. +""" +import sys + +from rich.align import Align +from rich.console import Console +from rich.table import Table + + +def call(_, ctx): + """ + Invokes the region-table plugin + """ + status, regions = ctx.client.call_operation("regions", "list") + + capabilities = [ + ("Linodes", "Linodes"), + ("GPU Linodes", "GPU"), + ("NodeBalancers", "NB"), + ("Kubernetes", "K8s"), + ("Firewalls", "FW"), + ("Managed Databases", "DB"), + ("Object Storage", "OBJ"), + ("Vlans", "Vlan"), + ("Premium Plans", "Premium"), + ("Metadata", "Meta"), + ("Block Storage", "Blocks"), + ("Block Storage Migrations", "& Migration"), + ] + + if status != 200: + print("It failed :(") + sys.exit(1) + + output = Table() + headers = ["ID", "Label", "Loc"] + [x[1] for x in capabilities] + for header in headers: + output.add_column(header, justify="center") + for region in regions["data"]: + row = [ + Align(region["id"], align="left"), + Align(region["label"], align="left"), + region["country"].upper(), + ] + [ + "✔" if c[0] in region["capabilities"] else "-" for c in capabilities + ] + output.add_row(*row) + + console = Console() + console.print(output) diff --git a/tests/integration/regions/test_plugin_region_table.py b/tests/integration/regions/test_plugin_region_table.py new file mode 100644 index 000000000..fe8b45390 --- /dev/null +++ b/tests/integration/regions/test_plugin_region_table.py @@ -0,0 +1,23 @@ +import subprocess +from typing import List + +BASE_CMD = ["linode-cli", "region-table"] + + +def exec_test_command(args: List[str]): + process = subprocess.run( + args, + stdout=subprocess.PIPE, + ) + return process + + +def test_output(): + process = exec_test_command(BASE_CMD) + output = process.stdout.decode() + lines = output.split("\n") + lines = lines[3 : len(lines) - 2] + for line in lines: + assert "-" in line + assert "✔" in line + assert "|" in line From 2132cfbe593bec57f8f16ad62400a7643642ecf0 Mon Sep 17 00:00:00 2001 From: Youjung Kim <126618609+ykim-1@users.noreply.github.com> Date: Tue, 10 Oct 2023 07:49:36 -0700 Subject: [PATCH 02/14] Fix: mark execution as failed when there are failures (#531) --- .github/workflows/e2e-suite.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-suite.yml b/.github/workflows/e2e-suite.yml index cdfa2425b..27f32a592 100644 --- a/.github/workflows/e2e-suite.yml +++ b/.github/workflows/e2e-suite.yml @@ -9,6 +9,8 @@ jobs: integration-tests: name: Run integration tests runs-on: ubuntu-latest + env: + EXIT_STATUS: 0 steps: - name: Clone Repository uses: actions/checkout@v3 @@ -39,9 +41,8 @@ jobs: run: | timestamp=$(date +'%Y%m%d%H%M') report_filename="${timestamp}_cli_test_report.xml" - status=0 if ! pytest tests/integration --disable-warnings --junitxml="${report_filename}"; then - echo "Tests failed, but attempting to upload results anyway" + echo "EXIT_STATUS=1" >> $GITHUB_ENV fi env: LINODE_CLI_TOKEN: ${{ secrets.LINODE_TOKEN }} @@ -67,4 +68,13 @@ jobs: env: LINODE_CLI_TOKEN: ${{ secrets.SHARED_DX_TOKEN }} LINODE_CLI_OBJ_ACCESS_KEY: ${{ secrets.LINODE_CLI_OBJ_ACCESS_KEY }} - LINODE_CLI_OBJ_SECRET_KEY: ${{ secrets.LINODE_CLI_OBJ_SECRET_KEY }} \ No newline at end of file + LINODE_CLI_OBJ_SECRET_KEY: ${{ secrets.LINODE_CLI_OBJ_SECRET_KEY }} + + - name: Test Execution Status Handler + run: | + if [[ "$EXIT_STATUS" != 0 ]]; then + echo "Test execution contains failure(s)" + exit $EXIT_STATUS + else + echo "Tests passed!" + fi \ No newline at end of file From ac2ef3e86eb1778cf9b5ca4e88babd6d12ef0cde Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:06:14 -0400 Subject: [PATCH 03/14] new: Support specifying explicitly empty lists (#530) --- linodecli/arg_helpers.py | 5 +- linodecli/baked/operation.py | 23 ++++- tests/integration/linodes/helpers_linodes.py | 13 +-- .../integration/networking/test_networking.py | 83 ++++++++++++++++++- tests/unit/test_operation.py | 32 +++++++ wiki/Usage.md | 22 ++++- 6 files changed, 168 insertions(+), 10 deletions(-) diff --git a/linodecli/arg_helpers.py b/linodecli/arg_helpers.py index 99e0d7bde..b3e17c4b1 100644 --- a/linodecli/arg_helpers.py +++ b/linodecli/arg_helpers.py @@ -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): diff --git a/linodecli/baked/operation.py b/linodecli/baked/operation.py index 931b73adb..78e2021e1 100644 --- a/linodecli/baked/operation.py +++ b/linodecli/baked/operation.py @@ -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. @@ -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: diff --git a/tests/integration/linodes/helpers_linodes.py b/tests/integration/linodes/helpers_linodes.py index 6f2578033..35d45500f 100644 --- a/tests/integration/linodes/helpers_linodes.py +++ b/tests/integration/linodes/helpers_linodes.py @@ -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( @@ -84,7 +82,7 @@ def create_linode(): "--type", DEFAULT_LINODE_TYPE, "--region", - region, + test_region, "--image", DEFAULT_TEST_IMAGE, "--root_pass", @@ -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 @@ -160,7 +161,7 @@ def create_linode_and_wait( "--type", linode_type, "--region", - "us-east", + test_region, "--image", test_image, "--root_pass", diff --git a/tests/integration/networking/test_networking.py b/tests/integration/networking/test_networking.py index bc91a12a7..7ee64c493 100644 --- a/tests/integration/networking/test_networking.py +++ b/tests/integration/networking/test_networking.py @@ -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"] @@ -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", ","] @@ -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) diff --git a/tests/unit/test_operation.py b/tests/unit/test_operation.py index fe70f6a4e..8bb9fa8a1 100644 --- a/tests/unit/test_operation.py +++ b/tests/unit/test_operation.py @@ -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", "[]"] diff --git a/wiki/Usage.md b/wiki/Usage.md index 954ba2bb5..0510b1380 100644 --- a/wiki/Usage.md +++ b/wiki/Usage.md @@ -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`, From 2103d62b45969af3d119655e13285c00da74af7b Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:17:19 -0400 Subject: [PATCH 04/14] Drop Python 3.7 Support and Remove Usage of `pkg_resources` (#535) --- .pylintrc | 4 ++-- linodecli/__init__.py | 4 ++-- setup.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pylintrc b/.pylintrc index 356f3536d..019af5118 100644 --- a/.pylintrc +++ b/.pylintrc @@ -307,8 +307,8 @@ min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception [FORMAT] diff --git a/linodecli/__init__.py b/linodecli/__init__.py index 84d4926bb..a5af35c6b 100755 --- a/linodecli/__init__.py +++ b/linodecli/__init__.py @@ -6,9 +6,9 @@ import argparse import os import sys +from importlib.metadata import version from sys import argv -import pkg_resources from rich import print as rprint from rich.table import Table @@ -30,7 +30,7 @@ # this might not be installed at the time of building try: - VERSION = pkg_resources.require("linode-cli")[0].version + VERSION = version("linode-cli") except: VERSION = "building" diff --git a/setup.py b/setup.py index 6e91a5262..522cb5f78 100755 --- a/setup.py +++ b/setup.py @@ -100,6 +100,6 @@ def bake_version(v): ] }, data_files=get_baked_files(), - python_requires=">=3.7", + python_requires=">=3.8", include_package_data=True, ) From fa2e11abaa0d1495436096c02bcb273cc8f8f632 Mon Sep 17 00:00:00 2001 From: Jacob Riddle <87780794+jriddle-linode@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:58:22 -0400 Subject: [PATCH 05/14] fix: `region-table` Firewall column (#534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Description **What does this PR do and why is this change necessary?** Change `Firewalls` to `Cloud Firewall` which is the correct term used in the API. Remove `Block Storage Migration` from the table. Rename `Blocks` to `Block` Resolves #532 --- linodecli/plugins/region-table.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/linodecli/plugins/region-table.py b/linodecli/plugins/region-table.py index d558cc5ce..0017789ad 100644 --- a/linodecli/plugins/region-table.py +++ b/linodecli/plugins/region-table.py @@ -20,14 +20,13 @@ def call(_, ctx): ("GPU Linodes", "GPU"), ("NodeBalancers", "NB"), ("Kubernetes", "K8s"), - ("Firewalls", "FW"), + ("Cloud Firewall", "FW"), ("Managed Databases", "DB"), ("Object Storage", "OBJ"), ("Vlans", "Vlan"), ("Premium Plans", "Premium"), ("Metadata", "Meta"), - ("Block Storage", "Blocks"), - ("Block Storage Migrations", "& Migration"), + ("Block Storage", "Block"), ] if status != 200: From 14dfd2a63f0851cec95c141ce8f66656ed7671e5 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 25 Oct 2023 17:46:32 -0400 Subject: [PATCH 06/14] Support VPC Interfaces Changes (#536) --- Makefile | 7 +++-- linodecli/baked/operation.py | 4 ++- linodecli/baked/response.py | 53 ++++++++++++++++++++++-------------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 4e53c7541..b2a115a59 100644 --- a/Makefile +++ b/Makefile @@ -18,10 +18,13 @@ endif install: check-prerequisites requirements build pip3 install --force dist/*.whl -.PHONY: build -build: clean +.PHONY: bake +bake: clean python3 -m linodecli bake ${SPEC} --skip-config cp data-3 linodecli/ + +.PHONY: build +build: clean bake python3 -m build --wheel --sdist .PHONY: requirements diff --git a/linodecli/baked/operation.py b/linodecli/baked/operation.py index 78e2021e1..3441cb949 100644 --- a/linodecli/baked/operation.py +++ b/linodecli/baked/operation.py @@ -11,6 +11,8 @@ from os import environ, path from typing import List, Tuple +from openapi3.paths import Operation + from linodecli.baked.request import OpenAPIFilteringRequest, OpenAPIRequest from linodecli.baked.response import OpenAPIResponse from linodecli.overrides import OUTPUT_OVERRIDES @@ -233,7 +235,7 @@ class OpenAPIOperation: This is the class that should be pickled when building the CLI. """ - def __init__(self, command, operation, method, params): + def __init__(self, command, operation: Operation, method, params): """ Wraps an openapi3.Operation object and handles pulling out values relevant to the Linode CLI. diff --git a/linodecli/baked/response.py b/linodecli/baked/response.py index 6a57c91ed..aa8ab9935 100644 --- a/linodecli/baked/response.py +++ b/linodecli/baked/response.py @@ -1,6 +1,8 @@ """ Converting the processed OpenAPI Responses into something the CLI can work with """ +from openapi3.paths import MediaType + from .colors import colorize_string @@ -159,26 +161,35 @@ def _parse_response_model(schema, prefix=None, nested_list_depth=0): :returns: The list of parsed OpenAPIResponseAttr objects representing this schema :rtype: List[OpenAPIResponseAttr] """ - attrs = [] - if schema.properties is not None: - for k, v in schema.properties.items(): - pref = prefix + "." + k if prefix else k - - if v.type == "object": - attrs += _parse_response_model(v, prefix=pref) - elif v.type == "array" and v.items.type == "object": - attrs += _parse_response_model( - v.items, - prefix=pref, - nested_list_depth=nested_list_depth + 1, - ) - else: - attrs.append( - OpenAPIResponseAttr( - k, v, prefix=prefix, nested_list_depth=nested_list_depth - ) + if schema.type == "array": + return _parse_response_model( + schema.items, + prefix=prefix, + nested_list_depth=nested_list_depth, + ) + + attrs = [] + if schema.properties is None: + return attrs + + for k, v in schema.properties.items(): + pref = prefix + "." + k if prefix else k + + if v.type == "object": + attrs += _parse_response_model(v, prefix=pref) + elif v.type == "array" and v.items.type == "object": + attrs += _parse_response_model( + v.items, + prefix=pref, + nested_list_depth=nested_list_depth + 1, + ) + else: + attrs.append( + OpenAPIResponseAttr( + k, v, prefix=prefix, nested_list_depth=nested_list_depth ) + ) return attrs @@ -189,7 +200,7 @@ class OpenAPIResponse: responses section of an OpenAPI Operation """ - def __init__(self, response): + def __init__(self, response: MediaType): """ :param response: The response's MediaType object in the OpenAPI spec, corresponding to the application/json response type @@ -228,7 +239,9 @@ def fix_json(self, json): # Needs to go last to handle custom schemas if "pages" in json: return json["data"] - return [json] + if not isinstance(json, list): + json = [json] + return json def _fix_json_rows(self, json): """ From a8d0d52fd22ac9e165da4f3c4f3495df62668481 Mon Sep 17 00:00:00 2001 From: Ye Chen <127243817+yec-akamai@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:12:01 -0400 Subject: [PATCH 07/14] new: Display region_prices in linode types output (#533) --- linodecli/overrides.py | 93 ++++++++++++++++++++++++++++++++++++ tests/unit/test_overrides.py | 56 ++++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/linodecli/overrides.py b/linodecli/overrides.py index eea4c306d..c1c832735 100644 --- a/linodecli/overrides.py +++ b/linodecli/overrides.py @@ -4,6 +4,10 @@ large changes to the OpenAPI spec. """ +from rich.align import Align +from rich.console import Console +from rich.table import Table + from linodecli.output import OutputMode OUTPUT_OVERRIDES = {} @@ -37,3 +41,92 @@ def handle_domains_zone_file(operation, output_handler, json_data) -> bool: """ print("\n".join(json_data["zone_file"])) return False + + +@output_override("linodes", "types", OutputMode.table) +def handle_types_region_prices_list( + operation, output_handler, json_data +) -> bool: + """ + Override the output of 'linode-cli linodes types' to display regional pricing. + """ + return linode_types_with_region_prices(operation, output_handler, json_data) + + +def linode_types_with_region_prices( + operation, output_handler, json_data +) -> bool: + # pylint: disable=unused-argument + """ + Parse and reformat linode types output with region prices. + """ + if len(json_data["data"]) < 1: + return True + + output = Table() + + # To ensure the order of the headers and make sure we have region_prices as the last column + headers = sorted( + json_data["data"][0].keys() - ["addons", "price", "region_prices"], + key=len, + ) + headers += ["price.hourly", "price.monthly", "region_prices"] + + for header in headers: + output.add_column(header, justify="center") + + for linode in json_data["data"]: + row = [] + for h in headers: + if h == "region_prices": + sub_table = format_region_prices(linode[h]) + row.append(sub_table) + + elif h in ("price.hourly", "price.monthly"): + price = format_prices(h, linode) + row.append(Align(price, align="left")) + + else: + row.append(Align(str(linode[h]), align="left")) + + output.add_row(*row) + + console = Console() + console.print(output) + + print( + "See our [Pricing Page](https://www.linode.com/pricing/) for Region-specific pricing, " + + "which applies after migration is complete." + ) + + return False + + +def format_prices(prices, data: dict[str, any]) -> any: + """ + Format nested price entry. + """ + price_headers = prices.split(".") + + return str(data[price_headers[0]][price_headers[1]]) + + +def format_region_prices(data: dict[str, any]) -> any: + """ + Format nested region price entry into a sub-table. + """ + subheaders = ["id", "hourly", "monthly"] + + sub_table = Table() + + for header in subheaders: + sub_table.add_column(header, justify="center") + + for region_price in data: + region_price_row = ( + Align(str(region_price[header]), align="left") + for header in subheaders + ) + sub_table.add_row(*region_price_row) + + return sub_table diff --git a/tests/unit/test_overrides.py b/tests/unit/test_overrides.py index bb4fea69d..01e3584d2 100644 --- a/tests/unit/test_overrides.py +++ b/tests/unit/test_overrides.py @@ -56,3 +56,59 @@ def patch_func(*a): ) assert stdout_buf.getvalue() != "line 1\nline 2\n" + + def test_types_region_prices_list( + self, mock_cli, list_operation_for_overrides_test + ): + response_json = { + "data": [ + { + "addons": { + "backups": { + "price": {"hourly": 0.008, "monthly": 5}, + "region_prices": [ + { + "hourly": 0.0096, + "id": "us-east", + "monthly": 6, + } + ], + } + }, + "class": "standard", + "disk": 81920, + "gpus": 0, + "id": "g6-standard-2", + "label": "Linode 4GB", + "memory": 4096, + "network_out": 1000, + "price": {"hourly": 0.03, "monthly": 20}, + "region_prices": [ + {"hourly": 0.036, "id": "us-east", "monthly": 24} + ], + "successor": None, + "transfer": 4000, + "vcpus": 2, + } + ], + "page": 1, + "pages": 1, + "results": 1, + } + + override_signature = ("linodes", "types", OutputMode.table) + + list_operation_for_overrides_test.command = "linodes" + list_operation_for_overrides_test.action = "types" + mock_cli.output_handler.mode = OutputMode.table + + stdout_buf = io.StringIO() + + with contextlib.redirect_stdout(stdout_buf): + list_operation_for_overrides_test.process_response_json( + response_json, mock_cli.output_handler + ) + + rows = stdout_buf.getvalue().split("\n") + # assert that the overridden table has the new columns + assert len(rows[1].split(rows[1][0])) == 15 From 71a4b1187a3ad197ed056a1d6af48b83e859fddc Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:02:27 -0400 Subject: [PATCH 08/14] Fix run e2e workflow (#542) --- .github/workflows/e2e-suite-pr.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-suite-pr.yml b/.github/workflows/e2e-suite-pr.yml index 72554e44d..8c03db253 100644 --- a/.github/workflows/e2e-suite-pr.yml +++ b/.github/workflows/e2e-suite-pr.yml @@ -3,7 +3,7 @@ on: workflow_dispatch: inputs: test_path: - description: 'Test path to be tested: e.g. integration/cli' + description: "The path from 'test/integration' to the target to be tested, e.g. 'cli'" required: false sha: description: 'The hash value of the commit.' @@ -30,7 +30,7 @@ jobs: # Check out merge commit - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} @@ -56,13 +56,12 @@ jobs: owner: ${{ github.event.repository.owner.login }} repo: ${{ github.event.repository.name }} pr_num: ${{ fromJSON(inputs.pull_request_number) }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update system packages run: sudo apt-get update -y - - name: Install system deps - run: sudo apt-get install -y build-essential - - name: Setup Python uses: actions/setup-python@v4 with: @@ -124,7 +123,7 @@ jobs: # Check out merge commit - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} @@ -196,4 +195,4 @@ jobs: status: 'completed', conclusion: process.env.conclusion }); - return result; \ No newline at end of file + return result; From 2d421d10d4d166aa19033d0530d5bfd84fc077c0 Mon Sep 17 00:00:00 2001 From: Youjung Kim <126618609+ykim-1@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:35:18 -0700 Subject: [PATCH 09/14] test: fix E2E Tests, improve test fixture naming/teardown, label creation (#539) Co-authored-by: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> --- tests/integration/conftest.py | 28 +++---- .../domains/test_domain_records.py | 34 ++++---- .../integration/domains/test_domains_tags.py | 6 +- .../domains/test_master_domains.py | 22 ++--- .../integration/domains/test_slave_domains.py | 8 +- tests/integration/events/test_events.py | 8 +- tests/integration/firewalls/test_firewalls.py | 30 +++---- .../firewalls/test_firewalls_rules.py | 30 +++---- tests/integration/linodes/test_backups.py | 16 ++-- tests/integration/linodes/test_linodes.py | 24 +++--- .../integration/linodes/test_power_status.py | 10 +-- tests/integration/linodes/test_rebuild.py | 14 ++-- tests/integration/linodes/test_resize.py | 18 ++-- tests/integration/lke/test_clusters.py | 5 +- .../integration/networking/test_networking.py | 18 ++-- .../nodebalancers/test_node_balancers.py | 84 +++++++++---------- .../regions/test_plugin_region_table.py | 2 +- tests/integration/ssh/test_ssh.py | 10 +-- .../stackscripts/test_stackscripts.py | 26 +++--- tests/integration/tags/test_tags.py | 10 +-- tests/integration/users/test_users.py | 2 +- tests/integration/volumes/test_volumes.py | 28 +++---- .../volumes/test_volumes_resize.py | 16 ++-- 23 files changed, 221 insertions(+), 228 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index c7ac36610..bb137a005 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -122,8 +122,8 @@ def _generate_test_files( # test helper specific to Domains test suite @pytest.fixture -def create_master_domain(): - timestamp = str(int(time.time())) +def master_domain(): + timestamp = str(time.time_ns()) domain_id = ( exec_test_command( @@ -152,8 +152,8 @@ def create_master_domain(): @pytest.fixture -def create_slave_domain(): - timestamp = str(int(time.time()) + randint(10, 1000)) +def slave_domain(): + timestamp = str(time.time_ns()) domain_id = ( exec_test_command( @@ -184,8 +184,8 @@ def create_slave_domain(): # Test helpers specific to Linodes test suite @pytest.fixture -def create_linode_with_label(): - timestamp = str(int(time.time()) + randint(10, 1000)) +def linode_with_label(): + timestamp = str(time.time_ns()) label = "cli" + timestamp result = ( exec_test_command( @@ -193,7 +193,7 @@ def create_linode_with_label(): + [ "create", "--type", - "g6-standard-2", + "g6-nanode-1", "--region", "us-east", "--image", @@ -223,14 +223,14 @@ def create_linode_with_label(): @pytest.fixture -def create_linode_min_req(): +def linode_min_req(): result = ( exec_test_command( LINODE_BASE_CMD + [ "create", "--type", - "g6-standard-2", + "g6-nanode-1", "--region", "us-east", "--root_pass", @@ -256,7 +256,7 @@ def create_linode_min_req(): @pytest.fixture -def create_linode_wo_image(): +def linode_wo_image(): label = "cli" + str(int(time.time()) + randint(10, 1000)) linode_id = ( exec_test_command( @@ -288,7 +288,7 @@ def create_linode_wo_image(): @pytest.fixture -def create_linode_backup_enabled(): +def linode_backup_enabled(): # create linode with backups enabled linode_id = ( exec_test_command( @@ -321,8 +321,8 @@ def create_linode_backup_enabled(): @pytest.fixture -def take_snapshot_of_linode(): - timestamp = str(time.time()) +def snapshot_of_linode(): + timestamp = str(time.time_ns()) # get linode id after creation and wait for "running" status linode_id = create_linode_and_wait() new_snapshot_label = "test_snapshot" + timestamp @@ -348,7 +348,7 @@ def take_snapshot_of_linode(): # Test helpers specific to Nodebalancers test suite @pytest.fixture -def create_nodebalancer_with_default_conf(): +def nodebalancer_with_default_conf(): result = ( exec_test_command( NODEBALANCER_BASE_CMD diff --git a/tests/integration/domains/test_domain_records.py b/tests/integration/domains/test_domain_records.py index 5cc2268cc..18d184c63 100644 --- a/tests/integration/domains/test_domain_records.py +++ b/tests/integration/domains/test_domain_records.py @@ -13,8 +13,8 @@ @pytest.fixture -def domain_records_setup(): - timestamp = str(int(time.time())) +def test_domain_and_record(): + timestamp = str(time.time_ns()) # Create domain domain_id = ( exec_test_command( @@ -65,7 +65,7 @@ def domain_records_setup(): @pytest.mark.smoke -def test_create_a_domain(create_master_domain): +def test_create_a_domain(master_domain): # Current domain list process = exec_test_command( BASE_CMD + ["list", '--format="id"', "--text", "--no-header"] @@ -73,7 +73,7 @@ def test_create_a_domain(create_master_domain): output_current = process.stdout.decode() # Create domain - domain_id = create_master_domain + domain_id = master_domain process = exec_test_command( BASE_CMD + ["list", "--format=id", "--text", "--no-header"] @@ -88,8 +88,8 @@ def test_create_a_domain(create_master_domain): @pytest.mark.smoke -def test_create_domain_srv_record(domain_records_setup): - domain_id = domain_records_setup[0] +def test_create_domain_srv_record(test_domain_and_record): + domain_id = test_domain_and_record[0] process = exec_test_command( BASE_CMD @@ -117,8 +117,8 @@ def test_create_domain_srv_record(domain_records_setup): ) -def test_list_srv_record(domain_records_setup): - domain_id = domain_records_setup[0] +def test_list_srv_record(test_domain_and_record): + domain_id = test_domain_and_record[0] process = exec_test_command( BASE_CMD + [ @@ -138,9 +138,9 @@ def test_list_srv_record(domain_records_setup): @pytest.mark.smoke -def test_view_domain_record(domain_records_setup): - domain_id = domain_records_setup[0] - record_id = domain_records_setup[1] +def test_view_domain_record(test_domain_and_record): + domain_id = test_domain_and_record[0] + record_id = test_domain_and_record[1] process = exec_test_command( BASE_CMD @@ -161,9 +161,9 @@ def test_view_domain_record(domain_records_setup): ) -def test_update_domain_record(domain_records_setup): - domain_id = domain_records_setup[0] - record_id = domain_records_setup[1] +def test_update_domain_record(test_domain_and_record): + domain_id = test_domain_and_record[0] + record_id = test_domain_and_record[1] process = exec_test_command( BASE_CMD @@ -185,9 +185,9 @@ def test_update_domain_record(domain_records_setup): ) -def test_delete_a_domain_record(domain_records_setup): - domain_id = domain_records_setup[0] - record_id = domain_records_setup[1] +def test_delete_a_domain_record(test_domain_and_record): + domain_id = test_domain_and_record[0] + record_id = test_domain_and_record[1] process = exec_test_command( BASE_CMD + ["records-delete", domain_id, record_id] diff --git a/tests/integration/domains/test_domains_tags.py b/tests/integration/domains/test_domains_tags.py index b0655108d..7555c6f4d 100644 --- a/tests/integration/domains/test_domains_tags.py +++ b/tests/integration/domains/test_domains_tags.py @@ -15,7 +15,7 @@ # @pytest.mark.skip(reason="BUG 943") def test_fail_to_create_master_domain_with_invalid_tags(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) bad_tag = "*" exec_failing_test_command( @@ -38,7 +38,7 @@ def test_fail_to_create_master_domain_with_invalid_tags(): # @pytest.mark.skip(reason="BUG 943") def test_fail_to_create_slave_domain_with_invalid_tags(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) bad_tag = "*" exec_failing_test_command( @@ -61,7 +61,7 @@ def test_fail_to_create_slave_domain_with_invalid_tags(): @pytest.mark.smoke def test_create_master_domain_with_tags(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) tag = "foo" process = exec_test_command( diff --git a/tests/integration/domains/test_master_domains.py b/tests/integration/domains/test_master_domains.py index 104707940..2427c3912 100644 --- a/tests/integration/domains/test_master_domains.py +++ b/tests/integration/domains/test_master_domains.py @@ -13,8 +13,8 @@ @pytest.fixture -def setup_master_domains(): - timestamp = str(int(time.time())) +def master_test_domain(): + timestamp = str(time.time_ns()) # Create domain master_domain_id = ( exec_test_command( @@ -43,7 +43,7 @@ def setup_master_domains(): def test_create_domain_fails_without_spcified_type(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) # get debug output from linode-cli to a temporary file.. # not all output from the linode-cli goes to stdout, stderr @@ -66,7 +66,7 @@ def test_create_domain_fails_without_spcified_type(): def test_create_master_domain_fails_without_soa_email(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) result = exec_failing_test_command( BASE_CMD + [ @@ -85,17 +85,17 @@ def test_create_master_domain_fails_without_soa_email(): @pytest.mark.smoke -def test_create_master_domain(create_master_domain): - domain_id = create_master_domain +def test_create_master_domain(master_domain): + domain_id = master_domain assert re.search("[0-9]+", domain_id) -def test_update_master_domain_soa_email(setup_master_domains): +def test_update_master_domain_soa_email(master_test_domain): # Remove --master_ips param when 872 is resolved - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) new_soa_email = "pthiel_new@linode.com" - domain_id = setup_master_domains + domain_id = master_test_domain result = exec_test_command( BASE_CMD @@ -117,7 +117,7 @@ def test_update_master_domain_soa_email(setup_master_domains): assert new_soa_email in result -def test_list_master_domain(setup_master_domains): +def test_list_master_domain(master_test_domain): result = exec_test_command( BASE_CMD + [ @@ -133,7 +133,7 @@ def test_list_master_domain(setup_master_domains): assert re.search("[0-9]+,BC[0-9]+-example.com,master,active", result) -def test_show_domain_detail(setup_master_domains): +def test_show_domain_detail(master_test_domain): result = exec_test_command( BASE_CMD + [ diff --git a/tests/integration/domains/test_slave_domains.py b/tests/integration/domains/test_slave_domains.py index edb707d3a..dcc1faee7 100644 --- a/tests/integration/domains/test_slave_domains.py +++ b/tests/integration/domains/test_slave_domains.py @@ -12,7 +12,7 @@ ) BASE_CMD = ["linode-cli", "domains"] -timestamp = str(int(time.time())) +timestamp = str(time.time_ns()) @pytest.fixture @@ -61,12 +61,12 @@ def test_create_slave_domain_fails_without_master_dns_server(): @pytest.mark.smoke -def test_create_slave_domain(create_slave_domain): - domain_id = create_slave_domain +def test_create_slave_domain(slave_domain): + domain_id = slave_domain assert re.search("[0-9]+", domain_id) -def test_list_slave_domain(create_slave_domain): +def test_list_slave_domain(slave_domain): result = exec_test_command( BASE_CMD + ["list", "--text", "--no-header"] ).stdout.decode() diff --git a/tests/integration/events/test_events.py b/tests/integration/events/test_events.py index 10456edb2..bc0d4d809 100644 --- a/tests/integration/events/test_events.py +++ b/tests/integration/events/test_events.py @@ -9,8 +9,8 @@ @pytest.fixture -def events_setup(): - timestamp = str(int(time.time())) +def events_test_domain_id(): + timestamp = str(time.time_ns()) # Create domain domain_id = ( exec_test_command( @@ -185,8 +185,8 @@ def test_filter_events_by_entity_id(): @pytest.mark.skip(reason="https://github.com/linode/linode-cli/issues/500") -def test_create_domain_and_filter_domain_events(events_setup): - domain_id = events_setup +def test_create_domain_and_filter_domain_events(events_test_domain_id): + domain_id = events_test_domain_id result = exec_test_command( BASE_CMD + [ diff --git a/tests/integration/firewalls/test_firewalls.py b/tests/integration/firewalls/test_firewalls.py index 0da1e5d8a..a7ed36ecb 100644 --- a/tests/integration/firewalls/test_firewalls.py +++ b/tests/integration/firewalls/test_firewalls.py @@ -14,7 +14,7 @@ @pytest.fixture -def firewalls_setup(): +def test_firewall_id(): # Create one domain for some tests in this suite firewall_id = ( exec_test_command( @@ -43,8 +43,8 @@ def firewalls_setup(): @pytest.mark.smoke -def test_view_firewall(firewalls_setup): - firewall_id = firewalls_setup +def test_view_firewall(test_firewall_id): + firewall_id = test_firewall_id result = ( exec_test_command( @@ -65,8 +65,8 @@ def test_view_firewall(firewalls_setup): assert re.search(firewall_id + "," + FIREWALL_LABEL + ",enabled", result) -def test_list_firewall(firewalls_setup): - firewall_id = firewalls_setup +def test_list_firewall(test_firewall_id): + firewall_id = test_firewall_id result = ( exec_test_command( @@ -81,7 +81,7 @@ def test_list_firewall(firewalls_setup): @pytest.mark.smoke def test_create_firewall_with_minimum_required_args(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) firewall_label = "label-fw-test" + timestamp result = ( exec_test_command( @@ -112,8 +112,8 @@ def test_create_firewall_with_minimum_required_args(): def test_fails_to_create_firewall_without_inbound_policy(): - timestamp = str(int(time.time())) - firewall_label = "label-fw-test_in" + timestamp + timestamp = str(time.time_ns()) + firewall_label = "fw_label" + timestamp result = ( exec_failing_test_command( BASE_CMD @@ -137,8 +137,8 @@ def test_fails_to_create_firewall_without_inbound_policy(): def test_fails_to_create_firewall_without_outbound_policy(): - timestamp = str(int(time.time())) - firewall_label = "label-fw-test_out" + timestamp + timestamp = str(time.time_ns()) + firewall_label = "fw_label" + timestamp result = ( exec_failing_test_command( BASE_CMD @@ -161,7 +161,7 @@ def test_fails_to_create_firewall_without_outbound_policy(): assert "outbound_policy is required" in result -def test_firewall_label_must_be_unique_upon_creation(firewalls_setup): +def test_firewall_label_must_be_unique_upon_creation(test_firewall_id): result = ( exec_failing_test_command( BASE_CMD @@ -187,7 +187,7 @@ def test_firewall_label_must_be_unique_upon_creation(firewalls_setup): def test_create_firewall_with_inbound_and_outbound_args(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) firewall_label = "label-fw-test" + timestamp result = ( exec_test_command( @@ -221,9 +221,9 @@ def test_create_firewall_with_inbound_and_outbound_args(): delete_target_id(target="firewalls", id=firewall_id) -def test_update_firewall(firewalls_setup): - timestamp = str(int(time.time())) - firewall_id = firewalls_setup +def test_update_firewall(test_firewall_id): + timestamp = str(time.time_ns()) + firewall_id = test_firewall_id updated_tag = "updated-tag" + timestamp updated_label = "updated-" + timestamp diff --git a/tests/integration/firewalls/test_firewalls_rules.py b/tests/integration/firewalls/test_firewalls_rules.py index e83add47a..12ea3648b 100644 --- a/tests/integration/firewalls/test_firewalls_rules.py +++ b/tests/integration/firewalls/test_firewalls_rules.py @@ -11,7 +11,7 @@ @pytest.fixture -def create_firewall(): +def test_firewall_id(): firewall_id = ( exec_test_command( [ @@ -39,8 +39,8 @@ def create_firewall(): delete_target_id(target="firewalls", id=firewall_id) -def test_add_rule_to_existing_firewall(create_firewall): - firewall_id = create_firewall +def test_add_rule_to_existing_firewall(test_firewall_id): + firewall_id = test_firewall_id inbound_rule = '[{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": "accept-inbound-SSH"}]' outbound_rule = '[{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.2/32"]}, "action": "ACCEPT", "label": "accept-outbound-SSH"}]' result = json.loads( @@ -63,8 +63,8 @@ def test_add_rule_to_existing_firewall(create_firewall): assert result[0]["outbound"][0] == json.loads(outbound_rule)[0] -def test_add_multiple_rules(create_firewall): - firewall_id = create_firewall +def test_add_multiple_rules(test_firewall_id): + firewall_id = test_firewall_id inbound_rule_1 = '{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": "accept-inbound-SSH"}' inbound_rule_2 = '{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.2/32"]}, "action": "ACCEPT", "label": "accept-inbound-SSH-2"}' inbound_rules = "[" + inbound_rule_1 + "," + inbound_rule_2 + "]" @@ -81,7 +81,7 @@ def test_add_multiple_rules(create_firewall): def test_swap_rules(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) firewall_label = "label-fw-test" + timestamp inbound_rule_1 = '{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": "swap_rule_1"}' inbound_rule_2 = '{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.2/32"]}, "action": "ACCEPT", "label": "swap_rule_2"}' @@ -133,8 +133,8 @@ def test_swap_rules(): delete_target_id(target="firewalls", id=firewall_id) -def test_update_inbound_and_outbound_policy(create_firewall): - firewall_id = create_firewall +def test_update_inbound_and_outbound_policy(test_firewall_id): + firewall_id = test_firewall_id outbound_policy = "DROP" inbound_policy = "ACCEPT" result = ( @@ -160,7 +160,7 @@ def test_update_inbound_and_outbound_policy(create_firewall): def test_remove_one_rule_via_rules_update(): - timestamp = str(int(time.time())) + timestamp = str(time.time_ns()) firewall_label = "label-fw-test" + timestamp inbound_rule_1 = '{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": "test_rule_1"}' inbound_rule_2 = '{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.2/32"]}, "action": "ACCEPT", "label": "rule_to_delete"}' @@ -207,8 +207,8 @@ def test_remove_one_rule_via_rules_update(): delete_target_id(target="firewalls", id=firewall_id) -def test_list_rules(create_firewall): - firewall_id = create_firewall +def test_list_rules(test_firewall_id): + firewall_id = test_firewall_id new_label = '"rules-list-test"' inbound_rule = ( '[{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": ' @@ -246,8 +246,8 @@ def test_list_rules(create_firewall): assert new_label.replace('"', "") in result -def test_list_rules_json(create_firewall): - firewall_id = create_firewall +def test_list_rules_json(test_firewall_id): + firewall_id = test_firewall_id new_label = '"rules-list-test"' inbound_rule = ( '[{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": ' @@ -286,8 +286,8 @@ def test_list_rules_json(create_firewall): assert result[0]["inbound"][0]["addresses"]["ipv4"] == ["198.0.0.1/32"] -def test_list_rules_json_format(create_firewall): - firewall_id = create_firewall +def test_list_rules_json_format(test_firewall_id): + firewall_id = test_firewall_id new_label = '"rules-list-test"' inbound_rule = ( '[{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["198.0.0.1/32"]}, "action": "ACCEPT", "label": ' diff --git a/tests/integration/linodes/test_backups.py b/tests/integration/linodes/test_backups.py index 119e7dd64..00226630b 100755 --- a/tests/integration/linodes/test_backups.py +++ b/tests/integration/linodes/test_backups.py @@ -68,8 +68,8 @@ def test_enable_backups(create_linode_setup): assert re.search(linode_id + ",True", result) -def test_create_backup_with_backup_enabled(create_linode_backup_enabled): - linode_id = create_linode_backup_enabled +def test_create_backup_with_backup_enabled(linode_backup_enabled): + linode_id = linode_backup_enabled result = exec_test_command( BASE_CMD + [ @@ -117,10 +117,10 @@ def test_take_snapshot_of_linode(): os.environ.get("RUN_LONG_TESTS", None) != "TRUE", reason="Skipping long-running Test, to run set RUN_LONG_TESTS=TRUE", ) -def test_view_the_snapshot(take_snapshot_of_linode): +def test_view_the_snapshot(snapshot_of_linode): # get linode id after creation and wait for "running" status - linode_id = take_snapshot_of_linode[0] - new_snapshot_label = take_snapshot_of_linode[1] + linode_id = snapshot_of_linode[0] + new_snapshot_label = snapshot_of_linode[1] result = exec_test_command( BASE_CMD @@ -145,10 +145,10 @@ def test_view_the_snapshot(take_snapshot_of_linode): os.environ.get("RUN_LONG_TESTS", None) != "TRUE", reason="Skipping long-running Test, to run set RUN_LONG_TESTS=TRUE", ) -def test_cancel_backups(take_snapshot_of_linode): +def test_cancel_backups(snapshot_of_linode): # get linode id after creation and wait for "running" status - linode_id = take_snapshot_of_linode[0] - new_snapshot_label = take_snapshot_of_linode[1] + linode_id = snapshot_of_linode[0] + new_snapshot_label = snapshot_of_linode[1] result = exec_test_command( BASE_CMD diff --git a/tests/integration/linodes/test_linodes.py b/tests/integration/linodes/test_linodes.py index 18800163b..6db77d88e 100644 --- a/tests/integration/linodes/test_linodes.py +++ b/tests/integration/linodes/test_linodes.py @@ -16,7 +16,7 @@ wait_until, ) -timestamp = str(int(time.time())) +timestamp = str(time.time_ns()) linode_label = DEFAULT_LABEL + timestamp @@ -28,7 +28,7 @@ def setup_linodes(): + [ "create", "--type", - "g6-standard-2", + "g6-nanode-1", "--region", "us-east", "--image", @@ -64,11 +64,11 @@ def test_update_linode_with_a_image(): @pytest.mark.smoke -def test_create_linodes_with_a_label(create_linode_with_label): - result = create_linode_with_label +def test_create_linodes_with_a_label(linode_with_label): + result = linode_with_label assert re.search( - "^cli(.*),us-east,g6-standard-2," + DEFAULT_TEST_IMAGE, result + "^cli(.*),us-east,g6-nanode-1," + DEFAULT_TEST_IMAGE, result ) @@ -94,15 +94,15 @@ def test_view_linode_configuration(setup_linodes): linode_id + "," + linode_label - + ",us-east,g6-standard-2," + + ",us-east,g6-nanode-1," + DEFAULT_TEST_IMAGE, result, ) -def test_create_linode_with_min_required_props(create_linode_min_req): - result = create_linode_min_req - assert re.search("[0-9]+,us-east,g6-standard-2", result) +def test_create_linode_with_min_required_props(linode_min_req): + result = linode_min_req + assert re.search("[0-9]+,us-east,g6-nanode-1", result) def test_create_linodes_fails_without_a_root_pass(): @@ -111,7 +111,7 @@ def test_create_linodes_fails_without_a_root_pass(): + [ "create", "--type", - "g6-standard-2", + "g6-nanode-1", "--region", "us-east", "--image", @@ -124,8 +124,8 @@ def test_create_linodes_fails_without_a_root_pass(): assert "root_pass root_pass is required" in result -def test_create_linode_without_image_and_not_boot(create_linode_wo_image): - linode_id = create_linode_wo_image +def test_create_linode_without_image_and_not_boot(linode_wo_image): + linode_id = linode_wo_image wait_until(linode_id=linode_id, timeout=180, status="offline") diff --git a/tests/integration/linodes/test_power_status.py b/tests/integration/linodes/test_power_status.py index 5c0cadd2a..90ab42b37 100644 --- a/tests/integration/linodes/test_power_status.py +++ b/tests/integration/linodes/test_power_status.py @@ -10,7 +10,7 @@ @pytest.fixture -def setup_power_status(): +def test_linode_id(): linode_id = create_linode() yield linode_id @@ -28,8 +28,8 @@ def create_linode_in_running_state(): @pytest.mark.smoke -def test_create_linode_and_boot(setup_power_status): - linode_id = setup_power_status +def test_create_linode_and_boot(test_linode_id): + linode_id = test_linode_id # returns false if status is not running after 240s result = wait_until(linode_id=linode_id, timeout=240, status="running") @@ -52,8 +52,8 @@ def test_reboot_linode(create_linode_in_running_state): ), "Linode status has not changed to running from provisioning" -def test_shutdown_linode(setup_power_status): - linode_id = setup_power_status +def test_shutdown_linode(test_linode_id): + linode_id = test_linode_id # returns false if status is not running after 240s after reboot assert wait_until( diff --git a/tests/integration/linodes/test_rebuild.py b/tests/integration/linodes/test_rebuild.py index 1aae7e0c3..da5a56ff5 100644 --- a/tests/integration/linodes/test_rebuild.py +++ b/tests/integration/linodes/test_rebuild.py @@ -16,7 +16,7 @@ @pytest.fixture -def setup_rebuild(): +def test_linode_id(): linode_id = create_linode_and_wait() yield linode_id @@ -24,8 +24,8 @@ def setup_rebuild(): delete_target_id(target="linodes", id=linode_id) -def test_rebuild_fails_without_image(setup_rebuild): - linode_id = setup_rebuild +def test_rebuild_fails_without_image(test_linode_id): + linode_id = test_linode_id result = exec_failing_test_command( BASE_CMD @@ -43,8 +43,8 @@ def test_rebuild_fails_without_image(setup_rebuild): assert "You must specify an image" in result -def test_rebuild_fails_with_invalid_image(setup_rebuild): - linode_id = setup_rebuild +def test_rebuild_fails_with_invalid_image(test_linode_id): + linode_id = test_linode_id rebuild_image = "bad/image" result = exec_failing_test_command( @@ -68,8 +68,8 @@ def test_rebuild_fails_with_invalid_image(setup_rebuild): os.environ.get("RUN_LONG_TESTS", None) != "TRUE", reason="Skipping long-running Test, to run set RUN_LONG_TESTS=TRUE", ) -def test_rebuild_a_linode(setup_rebuild): - linode_id = setup_rebuild +def test_rebuild_a_linode(test_linode_id): + linode_id = test_linode_id rebuild_image = ( exec_test_command( [ diff --git a/tests/integration/linodes/test_resize.py b/tests/integration/linodes/test_resize.py index 4629fd517..da9cc9b3d 100644 --- a/tests/integration/linodes/test_resize.py +++ b/tests/integration/linodes/test_resize.py @@ -15,7 +15,7 @@ @pytest.fixture(scope="session") -def setup_resize(): +def test_linode_id(): plan = ( exec_test_command( [ @@ -39,8 +39,8 @@ def setup_resize(): delete_target_id(target="linodes", id=linode_id) -def test_resize_fails_to_the_same_plan(setup_resize): - linode_id = setup_resize +def test_resize_fails_to_the_same_plan(test_linode_id): + linode_id = test_linode_id linode_plan = ( exec_test_command( [ @@ -67,8 +67,8 @@ def test_resize_fails_to_the_same_plan(setup_resize): assert "Linode is already running this service plan." in result -def test_resize_fails_to_smaller_plan(setup_resize): - linode_id = setup_resize +def test_resize_fails_to_smaller_plan(test_linode_id): + linode_id = test_linode_id smaller_plan = ( exec_test_command( [ @@ -105,9 +105,9 @@ def test_resize_fails_to_smaller_plan(setup_resize): ) -def test_resize_fail_to_invalid_plan(setup_resize): +def test_resize_fail_to_invalid_plan(test_linode_id): invalid_plan = "g15-bad-plan" - linode_id = setup_resize + linode_id = test_linode_id result = exec_failing_test_command( BASE_CMD @@ -129,8 +129,8 @@ def test_resize_fail_to_invalid_plan(setup_resize): os.environ.get("RUN_LONG_TESTS", None) != "TRUE", reason="Skipping long-running Test, to run set RUN_LONG_TESTS=TRUE", ) -def test_resize_to_next_size_plan(setup_resize): - linode_id = setup_resize +def test_resize_to_next_size_plan(test_linode_id): + linode_id = test_linode_id larger_plan = ( exec_test_command( [ diff --git a/tests/integration/lke/test_clusters.py b/tests/integration/lke/test_clusters.py index feeea5904..6b1d0e8cf 100644 --- a/tests/integration/lke/test_clusters.py +++ b/tests/integration/lke/test_clusters.py @@ -1,5 +1,4 @@ import time -from random import randint import pytest @@ -9,14 +8,14 @@ @pytest.fixture(autouse=True) -def setup_test_clusters(): +def clean_up_clusters(): yield "setup" remove_lke_clusters() @pytest.mark.smoke def test_deploy_an_lke_cluster(): - timestamp = str(int(time.time()) + randint(10, 1000)) + timestamp = str(time.time_ns()) label = "cluster_test" + timestamp lke_version = ( diff --git a/tests/integration/networking/test_networking.py b/tests/integration/networking/test_networking.py index 7ee64c493..18837be27 100644 --- a/tests/integration/networking/test_networking.py +++ b/tests/integration/networking/test_networking.py @@ -13,7 +13,7 @@ @pytest.fixture(scope="package") -def setup_test_networking(): +def test_linode_id(): linode_id = create_linode_and_wait() yield linode_id @@ -22,7 +22,7 @@ def setup_test_networking(): @pytest.fixture(scope="package") -def setup_test_networking_shared_ipv4(): +def test_linode_id_shared_ipv4(): target_region = "us-southeast" linode_ids = ( @@ -47,7 +47,7 @@ def has_shared_ip(linode_id: int, ip: str) -> bool: return len([v for v in shared_ips if v["address"] == ip]) > 0 -def test_display_ips_for_available_linodes(setup_test_networking): +def test_display_ips_for_available_linodes(test_linode_id): result = exec_test_command( BASE_CMD + ["ips-list", "--text", "--no-headers", "--delimiter", ","] ).stdout.decode() @@ -65,8 +65,8 @@ def test_display_ips_for_available_linodes(setup_test_networking): @pytest.mark.smoke -def test_view_an_ip_address(setup_test_networking): - linode_id = setup_test_networking +def test_view_an_ip_address(test_linode_id): + linode_id = test_linode_id linode_ipv4 = exec_test_command( [ "linode-cli", @@ -95,8 +95,8 @@ def test_view_an_ip_address(setup_test_networking): assert re.search("^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}", result) -def test_allocate_additional_private_ipv4_address(setup_test_networking): - linode_id = setup_test_networking +def test_allocate_additional_private_ipv4_address(test_linode_id): + linode_id = test_linode_id result = exec_test_command( BASE_CMD @@ -121,8 +121,8 @@ def test_allocate_additional_private_ipv4_address(setup_test_networking): ) -def test_share_ipv4_address(setup_test_networking_shared_ipv4): - target_linode, parent_linode = setup_test_networking_shared_ipv4 +def test_share_ipv4_address(test_linode_id_shared_ipv4): + target_linode, parent_linode = test_linode_id_shared_ipv4 # Allocate an IPv4 address on the parent Linode ip_address = json.loads( diff --git a/tests/integration/nodebalancers/test_node_balancers.py b/tests/integration/nodebalancers/test_node_balancers.py index 610ce0d1d..62db12163 100644 --- a/tests/integration/nodebalancers/test_node_balancers.py +++ b/tests/integration/nodebalancers/test_node_balancers.py @@ -14,7 +14,7 @@ @pytest.fixture(scope="package") -def setup_test_node_balancers(): +def test_node_balancers(): # create a default nodebalancer nodebalancer_id = ( exec_test_command( @@ -66,7 +66,7 @@ def setup_test_node_balancers(): "--region", "us-east", "--type", - "g6-standard-2", + "g6-nanode-1", "--private_ip", "true", "--image", @@ -135,7 +135,7 @@ def create_linode_to_add(): "--region", "us-east", "--type", - "g6-standard-2", + "g6-nanode-1", "--private_ip", "true", "--image", @@ -170,13 +170,13 @@ def test_fail_to_create_nodebalancer_without_region(): @pytest.mark.smoke def test_create_nodebalancer_with_default_conf( - create_nodebalancer_with_default_conf, + nodebalancer_with_default_conf, ): - result = create_nodebalancer_with_default_conf + result = nodebalancer_with_default_conf assert re.search(nodebalancer_created, result) -def test_list_nodebalancers_and_status(setup_test_node_balancers): +def test_list_nodebalancers_and_status(test_node_balancers): result = exec_test_command( BASE_CMD + [ @@ -192,8 +192,8 @@ def test_list_nodebalancers_and_status(setup_test_node_balancers): assert re.search(nodebalancer_created, result) -def test_display_public_ipv4_for_nodebalancer(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] +def test_display_public_ipv4_for_nodebalancer(test_node_balancers): + nodebalancer_id = test_node_balancers[0] result = exec_test_command( BASE_CMD @@ -217,8 +217,8 @@ def test_fail_to_view_nodebalancer_with_invalid_id(): assert "Request failed: 404" in result -def test_create_standard_configuration_profile(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] +def test_create_standard_configuration_profile(test_node_balancers): + nodebalancer_id = test_node_balancers[0] result = exec_test_command( BASE_CMD @@ -238,9 +238,9 @@ def test_create_standard_configuration_profile(setup_test_node_balancers): ) -def test_view_configuration_profile(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] +def test_view_configuration_profile(test_node_balancers): + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] result = exec_test_command( BASE_CMD @@ -259,17 +259,15 @@ def test_view_configuration_profile(setup_test_node_balancers): ) -def test_add_node_to_conf_profile( - setup_test_node_balancers, create_linode_to_add -): +def test_add_node_to_conf_profile(test_node_balancers, create_linode_to_add): linode_create = create_linode_to_add linode_arr = linode_create.split(",") ip_arr = linode_arr[1].split(" ") node_ip = ip_arr[1] node_label = "testnode1" - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] result = exec_test_command( BASE_CMD @@ -296,11 +294,11 @@ def test_add_node_to_conf_profile( ) -def test_update_node_label(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] - node_id = setup_test_node_balancers[2] - node_ip = setup_test_node_balancers[3] +def test_update_node_label(test_node_balancers): + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] + node_id = test_node_balancers[2] + node_ip = test_node_balancers[3] new_label = "testnode1-edited" result = exec_test_command( @@ -325,11 +323,11 @@ def test_update_node_label(setup_test_node_balancers): ) -def test_update_node_port(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] - node_id = setup_test_node_balancers[2] - node_ip = setup_test_node_balancers[3] +def test_update_node_port(test_node_balancers): + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] + node_id = test_node_balancers[2] + node_ip = test_node_balancers[3] updated_port = ":23" @@ -354,10 +352,10 @@ def test_update_node_port(setup_test_node_balancers): assert "[0-9]+,.," + new_address + ",Unknown,100,accept", result -def test_fail_to_update_node_to_public_ipv4_address(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] - node_id = setup_test_node_balancers[2] +def test_fail_to_update_node_to_public_ipv4_address(test_node_balancers): + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] + node_id = test_node_balancers[2] public_ip = "8.8.8.8:80" @@ -381,19 +379,19 @@ def test_fail_to_update_node_to_public_ipv4_address(setup_test_node_balancers): assert "Must begin with 192.168" in result -def test_remove_node_from_configuration_profile(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] - node_id = setup_test_node_balancers[2] +def test_remove_node_from_configuration_profile(test_node_balancers): + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] + node_id = test_node_balancers[2] exec_test_command( BASE_CMD + ["node-delete", nodebalancer_id, config_id, node_id] ) -def test_update_the_port_of_a_configuration_profile(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] - config_id = setup_test_node_balancers[1] +def test_update_the_port_of_a_configuration_profile(test_node_balancers): + nodebalancer_id = test_node_balancers[0] + config_id = test_node_balancers[1] result = exec_test_command( BASE_CMD @@ -417,8 +415,8 @@ def test_update_the_port_of_a_configuration_profile(setup_test_node_balancers): ) -def test_add_additional_configuration_profile(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] +def test_add_additional_configuration_profile(test_node_balancers): + nodebalancer_id = test_node_balancers[0] result = exec_test_command( BASE_CMD @@ -439,8 +437,8 @@ def test_add_additional_configuration_profile(setup_test_node_balancers): ) -def test_list_multiple_configuration_profile(setup_test_node_balancers): - nodebalancer_id = setup_test_node_balancers[0] +def test_list_multiple_configuration_profile(test_node_balancers): + nodebalancer_id = test_node_balancers[0] result = exec_test_command( BASE_CMD diff --git a/tests/integration/regions/test_plugin_region_table.py b/tests/integration/regions/test_plugin_region_table.py index fe8b45390..bd50c7a96 100644 --- a/tests/integration/regions/test_plugin_region_table.py +++ b/tests/integration/regions/test_plugin_region_table.py @@ -20,4 +20,4 @@ def test_output(): for line in lines: assert "-" in line assert "✔" in line - assert "|" in line + assert "│" in line diff --git a/tests/integration/ssh/test_ssh.py b/tests/integration/ssh/test_ssh.py index 68c2fc9e0..d5acf27e3 100644 --- a/tests/integration/ssh/test_ssh.py +++ b/tests/integration/ssh/test_ssh.py @@ -15,7 +15,7 @@ @pytest.mark.skipif(platform == "win32", reason="Test N/A on Windows") @pytest.fixture(scope="package") -def test_create_a_linode_in_running_state(ssh_key_pair_generator): +def linode_in_running_state(ssh_key_pair_generator): pubkey_file, privkey_file = ssh_key_pair_generator with open(pubkey_file, "r") as f: @@ -82,9 +82,9 @@ def test_fail_to_ssh_to_nonexistent_linode(): @pytest.mark.skipif(platform == "win32", reason="Test N/A on Windows") def test_ssh_to_linode_and_get_kernel_version( - test_create_a_linode_in_running_state, ssh_key_pair_generator + linode_in_running_state, ssh_key_pair_generator ): - linode_id = test_create_a_linode_in_running_state + linode_id = linode_in_running_state pubkey_file, privkey_file = ssh_key_pair_generator linode_label = ( @@ -120,10 +120,10 @@ def test_ssh_to_linode_and_get_kernel_version( @pytest.mark.skipif(platform == "win32", reason="Test N/A on Windows") def test_check_vm_for_ipv4_connectivity( - test_create_a_linode_in_running_state, ssh_key_pair_generator + linode_in_running_state, ssh_key_pair_generator ): pubkey_file, privkey_file = ssh_key_pair_generator - linode_id = test_create_a_linode_in_running_state + linode_id = linode_in_running_state linode_label = ( exec_test_command( [ diff --git a/tests/integration/stackscripts/test_stackscripts.py b/tests/integration/stackscripts/test_stackscripts.py index 5990dfab8..73db90d52 100644 --- a/tests/integration/stackscripts/test_stackscripts.py +++ b/tests/integration/stackscripts/test_stackscripts.py @@ -39,7 +39,7 @@ def get_linode_image_lists(): @pytest.fixture(scope="package", autouse=True) -def create_stackscript(): +def test_stackscript_id(): result = exec_test_command( BASE_CMD + [ @@ -47,7 +47,7 @@ def create_stackscript(): "--script", '#!/bin/bash\n# \n $EXAMPLE_SCRIPT', "--image", - "linode/debian9", + "linode/debian10", "--label", DEF_LABEL, "--is_public=false", @@ -61,7 +61,7 @@ def create_stackscript(): assert re.search( "[0-9]+,.*," + DEF_LABEL - + ",linode/debian9,False,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+", + + ",linode/debian10,False,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+", result, ) @@ -94,7 +94,7 @@ def test_list_stackscripts(): assert re.search("[0-9]+,([A-z]|[0-9])+,True", output[0]) -def test_create_stackscript_fails_without_image(): +def test_test_stackscript_id_fails_without_image(): result = exec_failing_test_command( BASE_CMD + [ @@ -132,15 +132,15 @@ def test_view_private_stackscript(): assert re.search( "[0-9]+,.*," + DEF_LABEL - + ",linode/debian9,False,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+", + + ",linode/debian10,False,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+,[0-9]+-[0-9]+-[0-9]+T[0-9]+:[0-9]+:[0-9]+", result, ) @pytest.mark.smoke -def test_update_stackscript_compatible_image(create_stackscript): +def test_update_stackscript_compatible_image(test_stackscript_id): images = get_linode_image_lists() - private_stackscript = create_stackscript + private_stackscript = test_stackscript_id result = ( exec_test_command( BASE_CMD @@ -168,10 +168,10 @@ def test_update_stackscript_compatible_image(create_stackscript): def test_update_stackscript_to_be_compatible_with_multiple_images( - create_stackscript, + test_stackscript_id, ): images = get_linode_image_lists() - private_stackscript = create_stackscript + private_stackscript = test_stackscript_id result = exec_test_command( BASE_CMD @@ -193,9 +193,9 @@ def test_update_stackscript_to_be_compatible_with_multiple_images( def test_fail_to_deploy_stackscript_to_linode_from_incompatible_image( - create_stackscript, + test_stackscript_id, ): - private_stackscript = create_stackscript + private_stackscript = test_stackscript_id linode_plan = "g6-standard-1" linode_region = "us-east" @@ -223,8 +223,8 @@ def test_fail_to_deploy_stackscript_to_linode_from_incompatible_image( assert "Request failed: 400" in result -def test_deploy_linode_from_stackscript(create_stackscript): - private_stackscript = create_stackscript +def test_deploy_linode_from_stackscript(test_stackscript_id): + private_stackscript = test_stackscript_id images = get_linode_image_lists() linode_plan = "g6-standard-1" linode_region = "us-east" diff --git a/tests/integration/tags/test_tags.py b/tests/integration/tags/test_tags.py index a316b8875..21db3446f 100644 --- a/tests/integration/tags/test_tags.py +++ b/tests/integration/tags/test_tags.py @@ -17,7 +17,7 @@ def test_display_tags(): @pytest.fixture(scope="session") -def test_create_tag(): +def test_tag_instance(): exec_test_command( BASE_CMD + ["create", "--label", unique_tag, "--text", "--no-headers"] ).stdout.decode() @@ -28,11 +28,11 @@ def test_create_tag(): @pytest.mark.smoke -def test_view_unique_tag(test_create_tag): +def test_view_unique_tag(test_tag_instance): result = exec_test_command( BASE_CMD + ["list", "--text", "--no-headers"] ).stdout.decode() - assert test_create_tag in result + assert test_tag_instance in result def test_fail_to_create_tag_shorter_than_three_char(): @@ -42,7 +42,3 @@ def test_fail_to_create_tag_shorter_than_three_char(): ).stderr.decode() assert "Request failed: 400" in result assert "Length must be 3-50 characters" in result - - -def test_remove_tag(test_create_tag): - exec_test_command(BASE_CMD + ["delete", test_create_tag]) diff --git a/tests/integration/users/test_users.py b/tests/integration/users/test_users.py index fbb8bf14e..231310ec0 100644 --- a/tests/integration/users/test_users.py +++ b/tests/integration/users/test_users.py @@ -9,7 +9,7 @@ @pytest.fixture(scope="package", autouse=True) -def setup_test_users(): +def teardown_fixture(): yield "setup" remove_users() diff --git a/tests/integration/volumes/test_volumes.py b/tests/integration/volumes/test_volumes.py index 7d783ad57..bae9ea0ca 100644 --- a/tests/integration/volumes/test_volumes.py +++ b/tests/integration/volumes/test_volumes.py @@ -11,12 +11,12 @@ ) BASE_CMD = ["linode-cli", "volumes"] -timestamp = str(int(time.time())) -unique_tag = str(int(time.time())) + "-tag" +timestamp = str(time.time_ns()) +unique_tag = str(time.time_ns()) + "-tag" @pytest.fixture(scope="package") -def setup_test_volumes(): +def test_volume_id(): volume_id = ( exec_test_command( BASE_CMD @@ -145,7 +145,7 @@ def test_fail_to_create_volume_with_all_numberic_label(): assert "label Must begin with a letter" in result -def test_list_volume(setup_test_volumes): +def test_list_volume(test_volume_id): result = exec_test_command( BASE_CMD + ["list", "--text", "--no-headers", "--delimiter", ","] ).stdout.decode() @@ -155,8 +155,8 @@ def test_list_volume(setup_test_volumes): @pytest.mark.smoke -def test_view_single_volume(setup_test_volumes): - volume_id = setup_test_volumes +def test_view_single_volume(test_volume_id): + volume_id = test_volume_id result = exec_test_command( BASE_CMD + [ @@ -174,8 +174,8 @@ def test_view_single_volume(setup_test_volumes): assert re.search(volume_id + ",[A-Za-z0-9-]+,[0-9]+,[a-z-]+", result) -def test_update_volume_label(setup_test_volumes): - volume_id = setup_test_volumes +def test_update_volume_label(test_volume_id): + volume_id = test_volume_id new_unique_label = "label-" + str(int(time.time())) result = exec_test_command( BASE_CMD @@ -194,8 +194,8 @@ def test_update_volume_label(setup_test_volumes): assert new_unique_label in result -def test_add_new_tag_to_volume(setup_test_volumes): - volume_id = setup_test_volumes +def test_add_new_tag_to_volume(test_volume_id): + volume_id = test_volume_id result = exec_test_command( BASE_CMD + [ @@ -213,16 +213,16 @@ def test_add_new_tag_to_volume(setup_test_volumes): assert unique_tag in result -def test_view_tags_attached_to_volume(setup_test_volumes): - volume_id = setup_test_volumes +def test_view_tags_attached_to_volume(test_volume_id): + volume_id = test_volume_id exec_test_command( BASE_CMD + ["view", volume_id, "--format", "tags", "--text", "--no-headers"] ).stdout.decode() -def test_fail_to_update_volume_size(setup_test_volumes): - volume_id = setup_test_volumes +def test_fail_to_update_volume_size(test_volume_id): + volume_id = test_volume_id os.system( "linode-cli volumes update --size=15 " + volume_id diff --git a/tests/integration/volumes/test_volumes_resize.py b/tests/integration/volumes/test_volumes_resize.py index bfa97e47c..9622876d3 100644 --- a/tests/integration/volumes/test_volumes_resize.py +++ b/tests/integration/volumes/test_volumes_resize.py @@ -10,12 +10,12 @@ ) BASE_CMD = ["linode-cli", "volumes"] -timestamp = str(int(time.time())) +timestamp = str(time.time_ns()) VOLUME_CREATION_WAIT = 5 @pytest.fixture(scope="package") -def setup_test_volumes_resize(): +def test_volume_id(): volume_id = ( exec_test_command( BASE_CMD @@ -44,8 +44,8 @@ def setup_test_volumes_resize(): delete_target_id(target="volumes", id=volume_id) -def test_resize_fails_to_smaller_volume(setup_test_volumes_resize): - volume_id = setup_test_volumes_resize +def test_resize_fails_to_smaller_volume(test_volume_id): + volume_id = test_volume_id time.sleep(VOLUME_CREATION_WAIT) result = exec_failing_test_command( BASE_CMD @@ -56,8 +56,8 @@ def test_resize_fails_to_smaller_volume(setup_test_volumes_resize): assert "Storage volumes can only be resized up" in result -def test_resize_fails_to_volume_larger_than_1024gb(setup_test_volumes_resize): - volume_id = setup_test_volumes_resize +def test_resize_fails_to_volume_larger_than_1024gb(test_volume_id): + volume_id = test_volume_id result = exec_failing_test_command( BASE_CMD + [ @@ -84,8 +84,8 @@ def test_resize_fails_to_volume_larger_than_1024gb(setup_test_volumes_resize): ) -def test_resize_volume(setup_test_volumes_resize): - volume_id = setup_test_volumes_resize +def test_resize_volume(test_volume_id): + volume_id = test_volume_id exec_test_command( BASE_CMD From 5878f972104c37cd1f7ef541fe7ce711cd8a30b8 Mon Sep 17 00:00:00 2001 From: Vinay <143587840+vshanthe@users.noreply.github.com> Date: Fri, 3 Nov 2023 21:06:23 +0530 Subject: [PATCH 10/14] Integration test fixs (#540) --- .github/workflows/e2e-suite-pr-command.yml | 19 ------------------- .../firewalls/test_firewalls_rules.py | 3 +-- .../regions/test_plugin_region_table.py | 6 ++++++ 3 files changed, 7 insertions(+), 21 deletions(-) delete mode 100644 .github/workflows/e2e-suite-pr-command.yml diff --git a/.github/workflows/e2e-suite-pr-command.yml b/.github/workflows/e2e-suite-pr-command.yml deleted file mode 100644 index 3b52a695b..000000000 --- a/.github/workflows/e2e-suite-pr-command.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: AccTest Command - -on: - issue_comment: - types: [created] - -jobs: - acctest-command: - runs-on: ubuntu-latest - if: ${{ github.event.issue.pull_request }} - steps: - - name: Slash Command Dispatch - uses: peter-evans/slash-command-dispatch@v1.2.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - issue-type: pull-request - commands: acctest - named-args: true - permission: write diff --git a/tests/integration/firewalls/test_firewalls_rules.py b/tests/integration/firewalls/test_firewalls_rules.py index 12ea3648b..f3bae428f 100644 --- a/tests/integration/firewalls/test_firewalls_rules.py +++ b/tests/integration/firewalls/test_firewalls_rules.py @@ -322,5 +322,4 @@ def test_list_rules_json_format(test_firewall_id): .stdout.decode() .rstrip() ) - - assert result[0]["inbound"][0] == {"label": "rules-list-test"} + assert result[0]["inbound"][0]["label"] == "rules-list-test" diff --git a/tests/integration/regions/test_plugin_region_table.py b/tests/integration/regions/test_plugin_region_table.py index bd50c7a96..216d26d82 100644 --- a/tests/integration/regions/test_plugin_region_table.py +++ b/tests/integration/regions/test_plugin_region_table.py @@ -1,13 +1,19 @@ +import os import subprocess from typing import List BASE_CMD = ["linode-cli", "region-table"] +# Set the console width to 150 +env = os.environ.copy() +env["COLUMNS"] = "150" + def exec_test_command(args: List[str]): process = subprocess.run( args, stdout=subprocess.PIPE, + env=env, ) return process From fcbc4075dde1b10db8ab5fad138ac77e5933c0fb Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Sun, 5 Nov 2023 00:26:16 -0400 Subject: [PATCH 11/14] Fix `boto3` missing error message in obj extension (#544) --- linodecli/plugins/obj/objects.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/linodecli/plugins/obj/objects.py b/linodecli/plugins/obj/objects.py index 9a06e6c12..d0c33f048 100644 --- a/linodecli/plugins/obj/objects.py +++ b/linodecli/plugins/obj/objects.py @@ -7,8 +7,13 @@ from pathlib import Path from typing import List -from boto3.exceptions import S3UploadFailedError -from boto3.s3.transfer import MB, TransferConfig +try: + from boto3.exceptions import S3UploadFailedError + from boto3.s3.transfer import MB, TransferConfig +except: + # this has been handled in `call` function + # by print an error message + pass from linodecli.helpers import expand_globs from linodecli.plugins import inherit_plugin_args From 57d4733a9af4371c316a45e3cd389dbf70963461 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:22:23 -0500 Subject: [PATCH 12/14] Replace `distutils` with `packaging` for Py 3.12 (#546) --- pyproject.toml | 2 +- requirements-dev.txt | 1 + version | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3a1b069a5..1b1607902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools", "wheel", "packaging"] build-backend = "setuptools.build_meta" [tool.isort] diff --git a/requirements-dev.txt b/requirements-dev.txt index 4c7268f99..cf106f3ec 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,3 +8,4 @@ requests-mock==1.11.0 boto3-stubs[s3] build>=0.10.0 twine>=4.0.2 +packaging>=23.2 diff --git a/version b/version index 4181620a6..f1fd18013 100755 --- a/version +++ b/version @@ -4,7 +4,8 @@ # Prints the current version import os -from distutils.version import LooseVersion + +from packaging.version import parse ENV_LINODE_CLI_VERSION = "LINODE_CLI_VERSION" @@ -22,8 +23,7 @@ def get_version(ref="HEAD"): if version_str.startswith("v"): version_str = version_str[1:] - parts = LooseVersion(version_str).version[:3] - return tuple(parts) + return parse(version_str).release major, minor, patch = get_version() From 0ceb7803d768988ffd5446c56001447b7d4bf65f Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:06:17 -0500 Subject: [PATCH 13/14] fix: `dict[...]` -> `Dict[...]` for Python 3.8 compatibility (#547) --- linodecli/overrides.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/linodecli/overrides.py b/linodecli/overrides.py index c1c832735..8eab01b2b 100644 --- a/linodecli/overrides.py +++ b/linodecli/overrides.py @@ -3,6 +3,7 @@ This allows us to easily alter per-command outputs, etc. without making large changes to the OpenAPI spec. """ +from typing import Dict from rich.align import Align from rich.console import Console @@ -102,7 +103,7 @@ def linode_types_with_region_prices( return False -def format_prices(prices, data: dict[str, any]) -> any: +def format_prices(prices, data: Dict[str, any]) -> any: """ Format nested price entry. """ @@ -111,7 +112,7 @@ def format_prices(prices, data: dict[str, any]) -> any: return str(data[price_headers[0]][price_headers[1]]) -def format_region_prices(data: dict[str, any]) -> any: +def format_region_prices(data: Dict[str, any]) -> any: """ Format nested region price entry into a sub-table. """ From b9bba922239ec465cb5cb2ad4dea2d163f4dd073 Mon Sep 17 00:00:00 2001 From: Lena Garber <114949949+lgarber-akamai@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:25:40 -0500 Subject: [PATCH 14/14] fix: Use HEAD SHA from main branch rather than default branch (#549) --- .github/workflows/remote-release-trigger.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/remote-release-trigger.yml b/.github/workflows/remote-release-trigger.yml index 2aa9a4a84..91b449518 100644 --- a/.github/workflows/remote-release-trigger.yml +++ b/.github/workflows/remote-release-trigger.yml @@ -56,9 +56,14 @@ jobs: return "v" + cli_version_segments.join(".") + - name: Calculate the SHA of HEAD on the main branch + id: calculate_head_sha + run: echo "commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + - uses: rickstaa/action-create-tag@84c90e6ba79b47b5147dcb11ff25d6a0e06238ba # pin@v1 with: tag: ${{ steps.calculate_version.outputs.result }} + commit_sha: ${{ steps.calculate_head_sha.outputs.commit_sha }} - name: Release uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # pin@v1