diff --git a/docs/e2e_tests.md b/docs/e2e_tests.md index 1e14597..05c45a2 100644 --- a/docs/e2e_tests.md +++ b/docs/e2e_tests.md @@ -75,3 +75,10 @@ test_newplatform_config.py .................. [100%] This is really helpful for maintaining the test suite, and for development work. **Note:** This can get a little flaky, because we're working in a temp environment that pytest will destroy when more resources are created. If you want to keep working with these resources, you can copy the entire `e2e_new_plugin_test0` directory to a more stable location. You'll have to rebuild the `django-simple-deploy/.venv` environment, but you'll have a useful, stable development environment to work with. You'll be able to run any tests you want from that environment. + +Testing custom plugin CLI args +--- + +A plugin can extend the core django-simple-deploy CLI by defining its own custom CLI args. The code for doing this is included in the generated plugin, but code that would actually define a custom CLI arg is commented out. + +The e2e test suite for this plugin generator includes `e2e_tests/test_plugin_cli.py`. This test uncomments the relevant code from a generated plugin. It inserts a block of code that uses the custom CLI arg in a `deploy` call, and asserts that the expected custom behavior works successfully. It also tests that `manage.py deploy --help` includes output documenting usage of the custom CLI arg. diff --git a/docs/plugin_structure.md b/docs/plugin_structure.md index 221abee..bfd177b 100644 --- a/docs/plugin_structure.md +++ b/docs/plugin_structure.md @@ -34,6 +34,7 @@ dsd-greenhost-high-traffic │   └── README.md ├── dsd_greenhost_high_traffic │   ├── __init__.py +│   ├── cli.py │   ├── deploy.py │   ├── deploy_messages.py │   ├── platform_deployer.py @@ -50,9 +51,11 @@ dsd-greenhost-high-traffic │   └── utils.py └── integration_tests ├── reference_files - └── test_greenhost_config.py + ├── test_custom_cli_arg.py + ├── test_greenhost_config.py + └── test_help_output.py -8 directories, 18 files +8 directories, 21 files ``` Notes diff --git a/plugin_template/plugin_pkg_name/cli.py b/plugin_template/plugin_pkg_name/cli.py new file mode 100644 index 0000000..284f074 --- /dev/null +++ b/plugin_template/plugin_pkg_name/cli.py @@ -0,0 +1,63 @@ +"""Extends the core django-simple-deploy CLI.""" + +import json +import shlex +import subprocess + +from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config +from django_simple_deploy.management.commands.utils.command_errors import ( + DSDCommandError, +) + +from .plugin_config import plugin_config + + +class PluginCLI: + + def __init__(self, parser): + """Add plugin-specific args.""" + group_desc = "Plugin-specific CLI args for {{PackageName}}" + plugin_group = parser.add_argument_group( + title="Options for {{PackageName}}", + description = group_desc, + ) + + # plugin_group.add_argument( + # "--vm-size", + # type=str, + # help="Name for a preset vm-size configuration, ie `shared-cpu-2x`.", + # default="", + # ) + + +def validate_cli(options): + """Validate options that were passed to CLI.""" + + # vm_size = options["vm_size"] + # _validate_vm_size(vm_size) + + pass + + +# --- Helper functions --- + +# def _validate_vm_size(vm_size): +# """Validate the vm size arg that was passed.""" +# if not vm_size: +# return + +# if not dsd_config.unit_testing: +# cmd = "fly platform vm-sizes --json" +# cmd_parts = shlex.split(cmd) +# output = subprocess.run(cmd_parts, capture_output=True) +# allowed_sizes = list(json.loads(output.stdout).keys()) +# else: +# allowed_sizes = ["shared-cup-1x", "shared-cpu-2x"] + +# if vm_size not in allowed_sizes: +# msg = f"The vm-size {vm_size} requested is not available." +# msg += f"\n Allowed sizes: {' '.join(allowed_sizes)}" +# raise DSDCommandError(msg) + +# # vm_size is valid. Set the relevant plugin_config attribute. +# plugin_config.vm_size = vm_size \ No newline at end of file diff --git a/plugin_template/plugin_pkg_name/deploy.py b/plugin_template/plugin_pkg_name/deploy.py index c928572..87d6ab3 100644 --- a/plugin_template/plugin_pkg_name/deploy.py +++ b/plugin_template/plugin_pkg_name/deploy.py @@ -7,16 +7,28 @@ import django_simple_deploy from {{PluginName}}.platform_deployer import PlatformDeployer -from .plugin_config import PluginConfig +from .plugin_config import plugin_config +from .cli import PluginCLI, validate_cli @django_simple_deploy.hookimpl def dsd_get_plugin_config(): """Get platform-specific attributes needed by core.""" - plugin_config = PluginConfig() return plugin_config +@django_simple_deploy.hookimpl +def dsd_get_plugin_cli(parser): + """Get plugin's CLI extension.""" + plugin_cli = PluginCLI(parser) + + +@django_simple_deploy.hookimpl +def dsd_validate_cli(options): + """Validate and parse plugin-specific CLI args.""" + validate_cli(options) + + @django_simple_deploy.hookimpl def dsd_deploy(): """Carry out platform-specific deployment steps.""" diff --git a/plugin_template/plugin_pkg_name/platform_deployer.py b/plugin_template/plugin_pkg_name/platform_deployer.py index ea09bff..9e71285 100644 --- a/plugin_template/plugin_pkg_name/platform_deployer.py +++ b/plugin_template/plugin_pkg_name/platform_deployer.py @@ -51,6 +51,7 @@ def _add_requirements(self): import requests from . import deploy_messages as platform_msgs +from .plugin_config import plugin_config from django_simple_deploy.management.commands.utils import plugin_utils from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config diff --git a/plugin_template/plugin_pkg_name/plugin_config.py b/plugin_template/plugin_pkg_name/plugin_config.py index 927d3ee..3634fd5 100644 --- a/plugin_template/plugin_pkg_name/plugin_config.py +++ b/plugin_template/plugin_pkg_name/plugin_config.py @@ -25,3 +25,8 @@ def __init__(self): self.automate_all_supported = {{AutomateAllSupported}} self.confirm_automate_all_msg = platform_msgs.confirm_automate_all self.platform_name = "{{PlatformName}}" + + +# Create plugin_config once right here. This approach keeps from having to pass the config +# instance between core, plugins, and these utility functions. +plugin_config = PluginConfig() \ No newline at end of file diff --git a/plugin_template/tests/integration_tests/reference_files/plugin_help_text_sample.txt b/plugin_template/tests/integration_tests/reference_files/plugin_help_text_sample.txt new file mode 100644 index 0000000..ef17f9c --- /dev/null +++ b/plugin_template/tests/integration_tests/reference_files/plugin_help_text_sample.txt @@ -0,0 +1,6 @@ +Options for dsd-flyio: + Full documentation: https://django-simple-deploy.readthedocs.io/en/latest/ + quick_starts/quick_start_flyio/#customizing-the-deployment + + --vm-size VM_SIZE Name for a preset vm-size configuration on Fly.io, ie + `shared-cpu-2x`. \ No newline at end of file diff --git a/plugin_template/tests/integration_tests/test_custom_cli_arg.py b/plugin_template/tests/integration_tests/test_custom_cli_arg.py new file mode 100644 index 0000000..c1fcae9 --- /dev/null +++ b/plugin_template/tests/integration_tests/test_custom_cli_arg.py @@ -0,0 +1,22 @@ +"""Test a custom plugin-specific CLI arg. +""" + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_vm_size_arg(tmp_project, request): +# """Test that a custom vm size is written to fly.toml.""" +# cmd = "python manage.py deploy --vm-size shared-cpu-2x" +# msp.call_deploy(tmp_project, cmd, platform="fly_io") + +# path = tmp_project / "fly.toml" +# contents_fly_toml = path.read_text() + +# assert 'size = "shared-cpu-2x"' in contents_fly_toml \ No newline at end of file diff --git a/plugin_template/tests/integration_tests/test_help_output.py b/plugin_template/tests/integration_tests/test_help_output.py new file mode 100644 index 0000000..60a80d0 --- /dev/null +++ b/plugin_template/tests/integration_tests/test_help_output.py @@ -0,0 +1,35 @@ +"""Test the help output when {{PackageName}} is installed. + +The core django-simple-deploy library tests its own help output. +This test checks that plugin-specific options are included in the help output. +""" + +from pathlib import Path + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_plugin_help_output(tmp_project, request): +# """Test that {{PackageName}} CLI args are included in help output. + +# Note: When updating this, run `manage.py deploy --help` in a terminal set +# to 80 characters wide. That splits help text at the same places as the +# test environment. +# On macOS, you can simply run: +# $ COLUMNS=80 python manage.py deploy --help +# """ +# cmd = "python manage.py deploy --help" +# stdout, stderr = msp.call_deploy(tmp_project, cmd) + +# path_reference = Path(__file__).parent / "reference_files" / "plugin_help_text.txt" +# help_lines = path_reference.read_text().splitlines() + +# for line in help_lines: +# assert line in stdout diff --git a/plugin_template/tests/integration_tests/test_platformname_config.py b/plugin_template/tests/integration_tests/test_platformname_config.py index 3d74c83..3f3b889 100644 --- a/plugin_template/tests/integration_tests/test_platformname_config.py +++ b/plugin_template/tests/integration_tests/test_platformname_config.py @@ -1,4 +1,4 @@ -"""Integration tests for django-simple-deploy, targeting Fly.io.""" +"""Integration tests for django-simple-deploy, targeting {{PlatformName}}.""" import sys from pathlib import Path diff --git a/pytest.ini b/pytest.ini index 62ff2f7..5692240 100644 --- a/pytest.ini +++ b/pytest.ini @@ -12,3 +12,5 @@ norecursedirs = addopts = --import-mode=importlib pythonpath = . + +tmp_path_retention_count = 10 diff --git a/tests/e2e_tests/reference_files/add_fly_toml.py b/tests/e2e_tests/reference_files/add_fly_toml.py new file mode 100644 index 0000000..15c2f79 --- /dev/null +++ b/tests/e2e_tests/reference_files/add_fly_toml.py @@ -0,0 +1,8 @@ + # This is a dummy block to write a fly.toml file with only the + # text needed to pass the test_custom_cli_arg.py test. It is + # not a valid fly.toml file. + path = dsd_config.project_root / "fly.toml" + + contents = "Dummy fly.toml file." + contents += '\n\nThe text \n\nsize = "shared-cpu-2x"\n\n appears in this file.\n' + plugin_utils.add_file(path, contents) \ No newline at end of file diff --git a/tests/e2e_tests/reference_files/help_output_vm_size_arg.txt b/tests/e2e_tests/reference_files/help_output_vm_size_arg.txt new file mode 100644 index 0000000..204759a --- /dev/null +++ b/tests/e2e_tests/reference_files/help_output_vm_size_arg.txt @@ -0,0 +1,5 @@ +Options for dsd-newfly: + Plugin-specific CLI args for dsd-newfly + + --vm-size VM_SIZE Name for a preset vm-size configuration, ie `shared- + cpu-2x`. \ No newline at end of file diff --git a/tests/e2e_tests/test_basic_plugin.py b/tests/e2e_tests/test_basic_plugin.py index e50b7a3..aea9b5f 100644 --- a/tests/e2e_tests/test_basic_plugin.py +++ b/tests/e2e_tests/test_basic_plugin.py @@ -10,7 +10,7 @@ - This makes an editable install of both django-simple-deploy and the new plugin. - If there are issues, you can go the test env and modify both core and the new plugin to troubleshoot. -- If you want to do this, ou may need to set run_core_plugin_tests to False, otherwise +- If you want to do this, you may need to use `--setup-plugins-only`, otherwise the pytest temp dir will be garbage collected because so many temp dirs are being made. """ diff --git a/tests/e2e_tests/test_plugin_cli.py b/tests/e2e_tests/test_plugin_cli.py new file mode 100644 index 0000000..873ffef --- /dev/null +++ b/tests/e2e_tests/test_plugin_cli.py @@ -0,0 +1,139 @@ +"""Test a plugin that uses a custom CLI arg. + +Some of the code that supports a custom CLI is commented out in the generated plugin. +So, we need to uncomment that code, then run the tests. + +This test: +- Generates a new plugin. +- Modifies the generated files to enable a custom CLI arg. +- Modifies platform_deployer.py to use the custom CLI arg in a testable way. +- Sets up a development environment for django-simple-deploy core. +- Installs the new plugin to the development environment. +- Runs the plugin's integration tests, using a `deploy` call that includes the custom CLI arg. + +Notes: +- This makes an editable install of both django-simple-deploy and the new plugin. +- If there are issues, you can go the test env and modify both core and the new + plugin to troubleshoot. +- If you want to do this, you may need to use `--setup-plugins-only`, otherwise + the pytest temp dir will be garbage collected because so many temp dirs are being made. +""" + +from argparse import Namespace +import subprocess +import shlex +import shutil +from pathlib import Path + +import pytest + +from utils.plugin_config import PluginConfig +from tests.e2e_tests.utils import e2e_utils + + +# Skip these tests if uv is not available. +pytestmark = pytest.mark.skipif( + not e2e_utils.uv_available(), reason="uv must be installed in order to run e2e tests." +) + + +def test_custom_cli_arg(get_dev_env, cli_options): + """Test a simple plugin config.""" + dev_env_dir, path_to_python, path_dsd = get_dev_env + + plugin_config = PluginConfig( + platform_name = "NewFly", + pkg_name = "dsd-newfly", + support_automate_all = True, + license_name = "eric", + ) + e2e_utils.generate_plugin(get_dev_env, plugin_config) + + msg = "\n*** Modifying plugin code to use the custom CLI arg that's commented out by default. ***\n" + print(msg) + + # Uncomment CLI-related code. + path_plugin_dir = dev_env_dir / plugin_config.pkg_name + path_main_dir = path_plugin_dir / plugin_config.pkg_name.replace("-", "_") + + path_cli = path_main_dir / "cli.py" + path_platform_deployer = path_main_dir / "platform_deployer.py" + + path_tests = path_plugin_dir / "tests" / "integration_tests" + path_test_custom_cli = path_tests / "test_custom_cli_arg.py" + path_test_help = path_tests / "test_help_output.py" + + # Assert these paths all exist. + assert all([path_cli.exists(), path_platform_deployer.exists(), path_test_custom_cli.exists(), path_test_help.exists()]) + + # Uncomment lines from relevant files. + uncomment_lines(path_cli, "25-30, 36-37, 44-63") + uncomment_lines(path_test_custom_cli, "14-22") + uncomment_lines(path_test_help, "19-35") + + # Copy reference file for help output to new plugin. + path_help_reference = Path(__file__).parent / "reference_files" / "help_output_vm_size_arg.txt" + path_help_reference_plugin = path_tests / "reference_files" / "plugin_help_text.txt" + shutil.copyfile(path_help_reference, path_help_reference_plugin) + + # Modify plugin's platform_deployer.py file to pass the uncommented test_custom_cli_arg.py file. + _write_add_fly_toml(path_platform_deployer) + + if not cli_options.setup_plugins_only: + e2e_utils.run_core_plugin_tests(path_dsd, plugin_config, cli_options) + +# --- Helper functions --- + +def uncomment_lines(path, line_num_str): + """Uncomment the given lines in a file. + """ + lines = path.read_text().splitlines() + new_lines = [] + + line_nums = _get_line_nums(line_num_str) + for line_num, line in enumerate(lines, start=1): + if line_num in line_nums: + new_lines.append(line.replace("# ", "", count=1)) + else: + new_lines.append(line) + + new_contents = "\n".join(new_lines) + path.write_text(new_contents) + + +def _get_line_nums(line_num_str): + """Get list of lines from a string like "25-30, 36-37, 44-63".""" + range_strs = line_num_str.split(",") + + line_nums = [] + for range_str in range_strs: + if "-" not in range_str: + line_nums.append(int(range_str)) + continue + + start, end = range_str.split("-") + start, end = int(start), int(end) + line_nums += list(range(start, end+1)) + + return line_nums + + +def _write_add_fly_toml(path_platform_deployer): + """Add code to platform_deployer that writes a fly.toml file to pass test_custom_cli_arg.py.""" + target_string = "# Configure project for deployment to NewFly" + + lines = path_platform_deployer.read_text().splitlines() + + new_lines = [] + for line in lines: + if target_string not in line: + new_lines.append(line) + continue + + # This only runs once in the loop. + path_fly_toml_block = Path(__file__).parent / "reference_files" / "add_fly_toml.py" + fly_toml_lines = path_fly_toml_block.read_text().splitlines() + new_lines += fly_toml_lines + + new_contents = "\n".join(new_lines) + path_platform_deployer.write_text(new_contents) diff --git a/tests/e2e_tests/utils/e2e_utils.py b/tests/e2e_tests/utils/e2e_utils.py index 9bad2c5..0396f17 100644 --- a/tests/e2e_tests/utils/e2e_utils.py +++ b/tests/e2e_tests/utils/e2e_utils.py @@ -61,8 +61,9 @@ def run_core_plugin_tests(path_dsd, plugin_config, cli_options): output = subprocess.run(cmd, capture_output=True,shell=True) stdout = output.stdout.decode() + print(stdout) - assert "[100%]" in stdout + assert "FAILED" not in stdout check_core_plugin_tests(stdout, cli_options) @@ -75,7 +76,7 @@ def get_core_plugin_test_cmd(path_dsd, cli_options, platform_name_lower): cmd = f"cd {path_dsd.as_posix()} && source .venv/bin/activate && pytest" else: # Only run the new plugin's integration tests. - cmd = f"cd {path_dsd.as_posix()} && source .venv/bin/activate && pytest -k {test_filename}" + cmd = f"cd {path_dsd.as_posix()} && source .venv/bin/activate && pytest -k {test_filename} -k test_help_output.py -k test_custom_cli_arg.py" return cmd diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/cli.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/cli.py new file mode 100644 index 0000000..684c855 --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/cli.py @@ -0,0 +1,63 @@ +"""Extends the core django-simple-deploy CLI.""" + +import json +import shlex +import subprocess + +from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config +from django_simple_deploy.management.commands.utils.command_errors import ( + DSDCommandError, +) + +from .plugin_config import plugin_config + + +class PluginCLI: + + def __init__(self, parser): + """Add plugin-specific args.""" + group_desc = "Plugin-specific CLI args for dsd-greenhost-advanced" + plugin_group = parser.add_argument_group( + title="Options for dsd-greenhost-advanced", + description = group_desc, + ) + + # plugin_group.add_argument( + # "--vm-size", + # type=str, + # help="Name for a preset vm-size configuration, ie `shared-cpu-2x`.", + # default="", + # ) + + +def validate_cli(options): + """Validate options that were passed to CLI.""" + + # vm_size = options["vm_size"] + # _validate_vm_size(vm_size) + + pass + + +# --- Helper functions --- + +# def _validate_vm_size(vm_size): +# """Validate the vm size arg that was passed.""" +# if not vm_size: +# return + +# if not dsd_config.unit_testing: +# cmd = "fly platform vm-sizes --json" +# cmd_parts = shlex.split(cmd) +# output = subprocess.run(cmd_parts, capture_output=True) +# allowed_sizes = list(json.loads(output.stdout).keys()) +# else: +# allowed_sizes = ["shared-cup-1x", "shared-cpu-2x"] + +# if vm_size not in allowed_sizes: +# msg = f"The vm-size {vm_size} requested is not available." +# msg += f"\n Allowed sizes: {' '.join(allowed_sizes)}" +# raise DSDCommandError(msg) + +# # vm_size is valid. Set the relevant plugin_config attribute. +# plugin_config.vm_size = vm_size \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/deploy.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/deploy.py index 21a537a..0ec8d6f 100644 --- a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/deploy.py +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/deploy.py @@ -7,16 +7,28 @@ import django_simple_deploy from dsd_greenhost_advanced.platform_deployer import PlatformDeployer -from .plugin_config import PluginConfig +from .plugin_config import plugin_config +from .cli import PluginCLI, validate_cli @django_simple_deploy.hookimpl def dsd_get_plugin_config(): """Get platform-specific attributes needed by core.""" - plugin_config = PluginConfig() return plugin_config +@django_simple_deploy.hookimpl +def dsd_get_plugin_cli(parser): + """Get plugin's CLI extension.""" + plugin_cli = PluginCLI(parser) + + +@django_simple_deploy.hookimpl +def dsd_validate_cli(options): + """Validate and parse plugin-specific CLI args.""" + validate_cli(options) + + @django_simple_deploy.hookimpl def dsd_deploy(): """Carry out platform-specific deployment steps.""" diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/platform_deployer.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/platform_deployer.py index e1efc21..24f29cf 100644 --- a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/platform_deployer.py +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/platform_deployer.py @@ -51,6 +51,7 @@ def _add_requirements(self): import requests from . import deploy_messages as platform_msgs +from .plugin_config import plugin_config from django_simple_deploy.management.commands.utils import plugin_utils from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/plugin_config.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/plugin_config.py index 9c6fdcc..45e0f7e 100644 --- a/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/plugin_config.py +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/plugin_config.py @@ -25,3 +25,8 @@ def __init__(self): self.automate_all_supported = True self.confirm_automate_all_msg = platform_msgs.confirm_automate_all self.platform_name = "Great Green Host" + + +# Create plugin_config once right here. This approach keeps from having to pass the config +# instance between core, plugins, and these utility functions. +plugin_config = PluginConfig() \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/reference_files/plugin_help_text_sample.txt b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/reference_files/plugin_help_text_sample.txt new file mode 100644 index 0000000..ef17f9c --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/reference_files/plugin_help_text_sample.txt @@ -0,0 +1,6 @@ +Options for dsd-flyio: + Full documentation: https://django-simple-deploy.readthedocs.io/en/latest/ + quick_starts/quick_start_flyio/#customizing-the-deployment + + --vm-size VM_SIZE Name for a preset vm-size configuration on Fly.io, ie + `shared-cpu-2x`. \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_custom_cli_arg.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_custom_cli_arg.py new file mode 100644 index 0000000..c1fcae9 --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_custom_cli_arg.py @@ -0,0 +1,22 @@ +"""Test a custom plugin-specific CLI arg. +""" + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_vm_size_arg(tmp_project, request): +# """Test that a custom vm size is written to fly.toml.""" +# cmd = "python manage.py deploy --vm-size shared-cpu-2x" +# msp.call_deploy(tmp_project, cmd, platform="fly_io") + +# path = tmp_project / "fly.toml" +# contents_fly_toml = path.read_text() + +# assert 'size = "shared-cpu-2x"' in contents_fly_toml \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_greatgreenhost_config.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_greatgreenhost_config.py index 128f4a6..4d446e6 100644 --- a/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_greatgreenhost_config.py +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_greatgreenhost_config.py @@ -1,4 +1,4 @@ -"""Integration tests for django-simple-deploy, targeting Fly.io.""" +"""Integration tests for django-simple-deploy, targeting Great Green Host.""" import sys from pathlib import Path diff --git a/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_help_output.py b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_help_output.py new file mode 100644 index 0000000..9e0e338 --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_help_output.py @@ -0,0 +1,35 @@ +"""Test the help output when dsd-greenhost-advanced is installed. + +The core django-simple-deploy library tests its own help output. +This test checks that plugin-specific options are included in the help output. +""" + +from pathlib import Path + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_plugin_help_output(tmp_project, request): +# """Test that dsd-greenhost-advanced CLI args are included in help output. + +# Note: When updating this, run `manage.py deploy --help` in a terminal set +# to 80 characters wide. That splits help text at the same places as the +# test environment. +# On macOS, you can simply run: +# $ COLUMNS=80 python manage.py deploy --help +# """ +# cmd = "python manage.py deploy --help" +# stdout, stderr = msp.call_deploy(tmp_project, cmd) + +# path_reference = Path(__file__).parent / "reference_files" / "plugin_help_text.txt" +# help_lines = path_reference.read_text().splitlines() + +# for line in help_lines: +# assert line in stdout diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/cli.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/cli.py new file mode 100644 index 0000000..a438a9a --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/cli.py @@ -0,0 +1,63 @@ +"""Extends the core django-simple-deploy CLI.""" + +import json +import shlex +import subprocess + +from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config +from django_simple_deploy.management.commands.utils.command_errors import ( + DSDCommandError, +) + +from .plugin_config import plugin_config + + +class PluginCLI: + + def __init__(self, parser): + """Add plugin-specific args.""" + group_desc = "Plugin-specific CLI args for dsd-newfly" + plugin_group = parser.add_argument_group( + title="Options for dsd-newfly", + description = group_desc, + ) + + # plugin_group.add_argument( + # "--vm-size", + # type=str, + # help="Name for a preset vm-size configuration, ie `shared-cpu-2x`.", + # default="", + # ) + + +def validate_cli(options): + """Validate options that were passed to CLI.""" + + # vm_size = options["vm_size"] + # _validate_vm_size(vm_size) + + pass + + +# --- Helper functions --- + +# def _validate_vm_size(vm_size): +# """Validate the vm size arg that was passed.""" +# if not vm_size: +# return + +# if not dsd_config.unit_testing: +# cmd = "fly platform vm-sizes --json" +# cmd_parts = shlex.split(cmd) +# output = subprocess.run(cmd_parts, capture_output=True) +# allowed_sizes = list(json.loads(output.stdout).keys()) +# else: +# allowed_sizes = ["shared-cup-1x", "shared-cpu-2x"] + +# if vm_size not in allowed_sizes: +# msg = f"The vm-size {vm_size} requested is not available." +# msg += f"\n Allowed sizes: {' '.join(allowed_sizes)}" +# raise DSDCommandError(msg) + +# # vm_size is valid. Set the relevant plugin_config attribute. +# plugin_config.vm_size = vm_size \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/deploy.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/deploy.py index f822cc1..f3897df 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/deploy.py +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/deploy.py @@ -7,16 +7,28 @@ import django_simple_deploy from dsd_newfly.platform_deployer import PlatformDeployer -from .plugin_config import PluginConfig +from .plugin_config import plugin_config +from .cli import PluginCLI, validate_cli @django_simple_deploy.hookimpl def dsd_get_plugin_config(): """Get platform-specific attributes needed by core.""" - plugin_config = PluginConfig() return plugin_config +@django_simple_deploy.hookimpl +def dsd_get_plugin_cli(parser): + """Get plugin's CLI extension.""" + plugin_cli = PluginCLI(parser) + + +@django_simple_deploy.hookimpl +def dsd_validate_cli(options): + """Validate and parse plugin-specific CLI args.""" + validate_cli(options) + + @django_simple_deploy.hookimpl def dsd_deploy(): """Carry out platform-specific deployment steps.""" diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/platform_deployer.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/platform_deployer.py index 0609911..2a8b6a1 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/platform_deployer.py +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/platform_deployer.py @@ -51,6 +51,7 @@ def _add_requirements(self): import requests from . import deploy_messages as platform_msgs +from .plugin_config import plugin_config from django_simple_deploy.management.commands.utils import plugin_utils from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/plugin_config.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/plugin_config.py index 30538aa..5dd5003 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/plugin_config.py +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/plugin_config.py @@ -25,3 +25,8 @@ def __init__(self): self.automate_all_supported = True self.confirm_automate_all_msg = platform_msgs.confirm_automate_all self.platform_name = "NewFly" + + +# Create plugin_config once right here. This approach keeps from having to pass the config +# instance between core, plugins, and these utility functions. +plugin_config = PluginConfig() \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt new file mode 100644 index 0000000..ef17f9c --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt @@ -0,0 +1,6 @@ +Options for dsd-flyio: + Full documentation: https://django-simple-deploy.readthedocs.io/en/latest/ + quick_starts/quick_start_flyio/#customizing-the-deployment + + --vm-size VM_SIZE Name for a preset vm-size configuration on Fly.io, ie + `shared-cpu-2x`. \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_custom_cli_arg.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_custom_cli_arg.py new file mode 100644 index 0000000..c1fcae9 --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_custom_cli_arg.py @@ -0,0 +1,22 @@ +"""Test a custom plugin-specific CLI arg. +""" + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_vm_size_arg(tmp_project, request): +# """Test that a custom vm size is written to fly.toml.""" +# cmd = "python manage.py deploy --vm-size shared-cpu-2x" +# msp.call_deploy(tmp_project, cmd, platform="fly_io") + +# path = tmp_project / "fly.toml" +# contents_fly_toml = path.read_text() + +# assert 'size = "shared-cpu-2x"' in contents_fly_toml \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_help_output.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_help_output.py new file mode 100644 index 0000000..e6a68fa --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_help_output.py @@ -0,0 +1,35 @@ +"""Test the help output when dsd-newfly is installed. + +The core django-simple-deploy library tests its own help output. +This test checks that plugin-specific options are included in the help output. +""" + +from pathlib import Path + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_plugin_help_output(tmp_project, request): +# """Test that dsd-newfly CLI args are included in help output. + +# Note: When updating this, run `manage.py deploy --help` in a terminal set +# to 80 characters wide. That splits help text at the same places as the +# test environment. +# On macOS, you can simply run: +# $ COLUMNS=80 python manage.py deploy --help +# """ +# cmd = "python manage.py deploy --help" +# stdout, stderr = msp.call_deploy(tmp_project, cmd) + +# path_reference = Path(__file__).parent / "reference_files" / "plugin_help_text.txt" +# help_lines = path_reference.read_text().splitlines() + +# for line in help_lines: +# assert line in stdout diff --git a/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_newfly_config.py b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_newfly_config.py index b1e6c3a..11cec1e 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_newfly_config.py +++ b/tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_newfly_config.py @@ -1,4 +1,4 @@ -"""Integration tests for django-simple-deploy, targeting Fly.io.""" +"""Integration tests for django-simple-deploy, targeting NewFly.""" import sys from pathlib import Path diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/cli.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/cli.py new file mode 100644 index 0000000..a438a9a --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/cli.py @@ -0,0 +1,63 @@ +"""Extends the core django-simple-deploy CLI.""" + +import json +import shlex +import subprocess + +from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config +from django_simple_deploy.management.commands.utils.command_errors import ( + DSDCommandError, +) + +from .plugin_config import plugin_config + + +class PluginCLI: + + def __init__(self, parser): + """Add plugin-specific args.""" + group_desc = "Plugin-specific CLI args for dsd-newfly" + plugin_group = parser.add_argument_group( + title="Options for dsd-newfly", + description = group_desc, + ) + + # plugin_group.add_argument( + # "--vm-size", + # type=str, + # help="Name for a preset vm-size configuration, ie `shared-cpu-2x`.", + # default="", + # ) + + +def validate_cli(options): + """Validate options that were passed to CLI.""" + + # vm_size = options["vm_size"] + # _validate_vm_size(vm_size) + + pass + + +# --- Helper functions --- + +# def _validate_vm_size(vm_size): +# """Validate the vm size arg that was passed.""" +# if not vm_size: +# return + +# if not dsd_config.unit_testing: +# cmd = "fly platform vm-sizes --json" +# cmd_parts = shlex.split(cmd) +# output = subprocess.run(cmd_parts, capture_output=True) +# allowed_sizes = list(json.loads(output.stdout).keys()) +# else: +# allowed_sizes = ["shared-cup-1x", "shared-cpu-2x"] + +# if vm_size not in allowed_sizes: +# msg = f"The vm-size {vm_size} requested is not available." +# msg += f"\n Allowed sizes: {' '.join(allowed_sizes)}" +# raise DSDCommandError(msg) + +# # vm_size is valid. Set the relevant plugin_config attribute. +# plugin_config.vm_size = vm_size \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/deploy.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/deploy.py index 9510dd1..7281ab2 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/deploy.py +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/deploy.py @@ -7,16 +7,28 @@ import django_simple_deploy from dsd_newfly.platform_deployer import PlatformDeployer -from .plugin_config import PluginConfig +from .plugin_config import plugin_config +from .cli import PluginCLI, validate_cli @django_simple_deploy.hookimpl def dsd_get_plugin_config(): """Get platform-specific attributes needed by core.""" - plugin_config = PluginConfig() return plugin_config +@django_simple_deploy.hookimpl +def dsd_get_plugin_cli(parser): + """Get plugin's CLI extension.""" + plugin_cli = PluginCLI(parser) + + +@django_simple_deploy.hookimpl +def dsd_validate_cli(options): + """Validate and parse plugin-specific CLI args.""" + validate_cli(options) + + @django_simple_deploy.hookimpl def dsd_deploy(): """Carry out platform-specific deployment steps.""" diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/platform_deployer.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/platform_deployer.py index e431022..081dcb9 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/platform_deployer.py +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/platform_deployer.py @@ -51,6 +51,7 @@ def _add_requirements(self): import requests from . import deploy_messages as platform_msgs +from .plugin_config import plugin_config from django_simple_deploy.management.commands.utils import plugin_utils from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/plugin_config.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/plugin_config.py index a6a14b7..6977f96 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/plugin_config.py +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/plugin_config.py @@ -25,3 +25,8 @@ def __init__(self): self.automate_all_supported = True self.confirm_automate_all_msg = platform_msgs.confirm_automate_all self.platform_name = "New Fly" + + +# Create plugin_config once right here. This approach keeps from having to pass the config +# instance between core, plugins, and these utility functions. +plugin_config = PluginConfig() \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt new file mode 100644 index 0000000..ef17f9c --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt @@ -0,0 +1,6 @@ +Options for dsd-flyio: + Full documentation: https://django-simple-deploy.readthedocs.io/en/latest/ + quick_starts/quick_start_flyio/#customizing-the-deployment + + --vm-size VM_SIZE Name for a preset vm-size configuration on Fly.io, ie + `shared-cpu-2x`. \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_custom_cli_arg.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_custom_cli_arg.py new file mode 100644 index 0000000..c1fcae9 --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_custom_cli_arg.py @@ -0,0 +1,22 @@ +"""Test a custom plugin-specific CLI arg. +""" + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_vm_size_arg(tmp_project, request): +# """Test that a custom vm size is written to fly.toml.""" +# cmd = "python manage.py deploy --vm-size shared-cpu-2x" +# msp.call_deploy(tmp_project, cmd, platform="fly_io") + +# path = tmp_project / "fly.toml" +# contents_fly_toml = path.read_text() + +# assert 'size = "shared-cpu-2x"' in contents_fly_toml \ No newline at end of file diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_help_output.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_help_output.py new file mode 100644 index 0000000..e6a68fa --- /dev/null +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_help_output.py @@ -0,0 +1,35 @@ +"""Test the help output when dsd-newfly is installed. + +The core django-simple-deploy library tests its own help output. +This test checks that plugin-specific options are included in the help output. +""" + +from pathlib import Path + +import pytest + +from tests.integration_tests.conftest import tmp_project +from tests.integration_tests.utils import manage_sample_project as msp + +# Skip the default module-level `manage.py deploy call`, so we can call +# `deploy` with our own set of plugin-specific CLI args. +pytestmark = pytest.mark.skip_auto_dsd_call + + +# def test_plugin_help_output(tmp_project, request): +# """Test that dsd-newfly CLI args are included in help output. + +# Note: When updating this, run `manage.py deploy --help` in a terminal set +# to 80 characters wide. That splits help text at the same places as the +# test environment. +# On macOS, you can simply run: +# $ COLUMNS=80 python manage.py deploy --help +# """ +# cmd = "python manage.py deploy --help" +# stdout, stderr = msp.call_deploy(tmp_project, cmd) + +# path_reference = Path(__file__).parent / "reference_files" / "plugin_help_text.txt" +# help_lines = path_reference.read_text().splitlines() + +# for line in help_lines: +# assert line in stdout diff --git a/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_newfly_config.py b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_newfly_config.py index 3ce024a..a9dbebc 100644 --- a/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_newfly_config.py +++ b/tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_newfly_config.py @@ -1,4 +1,4 @@ -"""Integration tests for django-simple-deploy, targeting Fly.io.""" +"""Integration tests for django-simple-deploy, targeting New Fly.""" import sys from pathlib import Path diff --git a/utils/generator_utils.py b/utils/generator_utils.py index c872165..ae19701 100644 --- a/utils/generator_utils.py +++ b/utils/generator_utils.py @@ -153,11 +153,13 @@ def build_new_plugin(args, plugin_config): "developer_resources/README.md", "plugin_pkg_name/__init__.py", "tests/e2e_tests/__init__.py", + "tests/integration_tests/test_custom_cli_arg.py", "tests/integration_tests/reference_files/.gitignore", "tests/integration_tests/reference_files/Pipfile", "tests/integration_tests/reference_files/pyproject.toml", "tests/integration_tests/reference_files/requirements.txt", "tests/integration_tests/reference_files/settings.py", + "tests/integration_tests/reference_files/plugin_help_text_sample.txt", ] for target_file in target_files: @@ -174,6 +176,7 @@ def build_new_plugin(args, plugin_config): "pyproject.toml", "tests/conftest.py", "tests/integration_tests/test_platformname_config.py", + "tests/integration_tests/test_help_output.py", "tests/e2e_tests/utils.py", "tests/e2e_tests/test_deployment.py", "MANIFEST.in", @@ -181,6 +184,7 @@ def build_new_plugin(args, plugin_config): "CHANGELOG.md", "LICENSE", "plugin_pkg_name/platform_deployer.py", + "plugin_pkg_name/cli.py", "plugin_pkg_name/deploy.py", "plugin_pkg_name/plugin_config.py", "plugin_pkg_name/templates/dockerfile_example",