From e4c14fe0cce25be9f5d4e56e6c1fb3b6a0d7d07a Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 14:16:46 -0400 Subject: [PATCH 01/21] Instantiate plugin_config in plugin_config.py. --- plugin_template/plugin_pkg_name/deploy.py | 3 +-- plugin_template/plugin_pkg_name/platform_deployer.py | 1 + plugin_template/plugin_pkg_name/plugin_config.py | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugin_template/plugin_pkg_name/deploy.py b/plugin_template/plugin_pkg_name/deploy.py index c928572..c9ca0f4 100644 --- a/plugin_template/plugin_pkg_name/deploy.py +++ b/plugin_template/plugin_pkg_name/deploy.py @@ -7,13 +7,12 @@ import django_simple_deploy from {{PluginName}}.platform_deployer import PlatformDeployer -from .plugin_config import PluginConfig +from .plugin_config import plugin_config @django_simple_deploy.hookimpl def dsd_get_plugin_config(): """Get platform-specific attributes needed by core.""" - plugin_config = PluginConfig() return plugin_config 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 From 7e7c2ea455d67d3046ea815ef044a25b7dfa8bc9 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 14:31:17 -0400 Subject: [PATCH 02/21] Adds CLI options, through cli.py and appropriate hook implementations. --- plugin_template/plugin_pkg_name/cli.py | 63 +++++++++++++++++++++++ plugin_template/plugin_pkg_name/deploy.py | 13 +++++ 2 files changed, 76 insertions(+) create mode 100644 plugin_template/plugin_pkg_name/cli.py diff --git a/plugin_template/plugin_pkg_name/cli.py b/plugin_template/plugin_pkg_name/cli.py new file mode 100644 index 0000000..3d3c150 --- /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 {{PACKAGE_NAME}}" + plugin_group = parser.add_argument_group( + title="Options for {{PACKAGE_NAME}}", + 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 c9ca0f4..87d6ab3 100644 --- a/plugin_template/plugin_pkg_name/deploy.py +++ b/plugin_template/plugin_pkg_name/deploy.py @@ -8,6 +8,7 @@ from {{PluginName}}.platform_deployer import PlatformDeployer from .plugin_config import plugin_config +from .cli import PluginCLI, validate_cli @django_simple_deploy.hookimpl @@ -16,6 +17,18 @@ def dsd_get_plugin_config(): 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.""" From 4f993ce1d42ec3797cb4273cb430d905ede9a0fc Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 14:39:06 -0400 Subject: [PATCH 03/21] Files for testing cli args. --- .../plugin_help_text_sample.txt | 6 ++++ .../integration_tests/test_custom_cli_arg.py | 22 ++++++++++++ .../integration_tests/test_help_output.py | 35 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 plugin_template/tests/integration_tests/reference_files/plugin_help_text_sample.txt create mode 100644 plugin_template/tests/integration_tests/test_custom_cli_arg.py create mode 100644 plugin_template/tests/integration_tests/test_help_output.py 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..73d2a82 --- /dev/null +++ b/plugin_template/tests/integration_tests/test_help_output.py @@ -0,0 +1,35 @@ +"""Test the help output when {{PACKAGE_NAME}} 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 {{PACKAGE_NAME}} 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 From bc723ba0c79d93ba457dd14cef99feeaaf5c3e33 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 16:54:58 -0400 Subject: [PATCH 04/21] Include new files in generation process. --- plugin_template/plugin_pkg_name/cli.py | 4 ++-- plugin_template/tests/integration_tests/test_help_output.py | 4 ++-- .../tests/integration_tests/test_platformname_config.py | 2 +- utils/generator_utils.py | 4 ++++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/plugin_template/plugin_pkg_name/cli.py b/plugin_template/plugin_pkg_name/cli.py index 3d3c150..284f074 100644 --- a/plugin_template/plugin_pkg_name/cli.py +++ b/plugin_template/plugin_pkg_name/cli.py @@ -16,9 +16,9 @@ class PluginCLI: def __init__(self, parser): """Add plugin-specific args.""" - group_desc = "Plugin-specific CLI args for {{PACKAGE_NAME}}" + group_desc = "Plugin-specific CLI args for {{PackageName}}" plugin_group = parser.add_argument_group( - title="Options for {{PACKAGE_NAME}}", + title="Options for {{PackageName}}", description = group_desc, ) diff --git a/plugin_template/tests/integration_tests/test_help_output.py b/plugin_template/tests/integration_tests/test_help_output.py index 73d2a82..60a80d0 100644 --- a/plugin_template/tests/integration_tests/test_help_output.py +++ b/plugin_template/tests/integration_tests/test_help_output.py @@ -1,4 +1,4 @@ -"""Test the help output when {{PACKAGE_NAME}} is installed. +"""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. @@ -17,7 +17,7 @@ # def test_plugin_help_output(tmp_project, request): -# """Test that {{PACKAGE_NAME}} CLI args are included in help output. +# """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 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/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", From 2714cb55f7fe203411ce2f5591ffd924859c1ab0 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 17:09:39 -0400 Subject: [PATCH 05/21] Fix comment. --- tests/e2e_tests/test_basic_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. """ From de3a5200247741a0869cf791e2617faf98580a9e Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 17:33:47 -0400 Subject: [PATCH 06/21] Starting test for custom CLI arg. --- tests/e2e_tests/test_custom_cli_arg.py | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/e2e_tests/test_custom_cli_arg.py diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py new file mode 100644 index 0000000..7b37227 --- /dev/null +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -0,0 +1,70 @@ +"""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 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) + + # 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()]) + + + + + + + if not cli_options.setup_plugins_only: + e2e_utils.run_core_plugin_tests(path_dsd, plugin_config, cli_options) \ No newline at end of file From b0b5bf8eb515c010b969b6a2383c51bf656c65b1 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 17:44:46 -0400 Subject: [PATCH 07/21] Uncomments appropriate parts of cli.py in temp test env. --- tests/e2e_tests/test_custom_cli_arg.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py index 7b37227..061bba2 100644 --- a/tests/e2e_tests/test_custom_cli_arg.py +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -61,10 +61,32 @@ def test_custom_cli_arg(get_dev_env, cli_options): # Assert these paths all exist. assert all([path_cli.exists(), path_platform_deployer.exists(), path_test_custom_cli.exists(), path_test_help.exists()]) + # cli.py + line_nums = [25, 26, 27, 28, 29, 30, 36, 37, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63] + uncomment_lines(path_cli, line_nums) if not cli_options.setup_plugins_only: - e2e_utils.run_core_plugin_tests(path_dsd, plugin_config, cli_options) \ No newline at end of file + e2e_utils.run_core_plugin_tests(path_dsd, plugin_config, cli_options) + +# --- Helper functions --- + +def uncomment_lines(path, line_nums): + """Uncomment the given lines in a file. + + # DEV: This function should parse a string like "25-30, 36-37, 44-63" + """ + lines = path.read_text().splitlines() + new_lines = [] + + for line_num, line in enumerate(lines, start=1): + if line_num in line_nums: + new_lines.append(line.replace("# ", "")) + else: + new_lines.append(line) + + new_contents = "\n".join(new_lines) + path.write_text(new_contents) From a217a7a23a9a184d8cd91735f5b458a05f5d8c49 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Fri, 19 Sep 2025 22:24:17 -0400 Subject: [PATCH 08/21] Helper function to get list of lines to uncomment. --- tests/e2e_tests/test_custom_cli_arg.py | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py index 061bba2..ac24f57 100644 --- a/tests/e2e_tests/test_custom_cli_arg.py +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -62,8 +62,8 @@ def test_custom_cli_arg(get_dev_env, cli_options): assert all([path_cli.exists(), path_platform_deployer.exists(), path_test_custom_cli.exists(), path_test_help.exists()]) # cli.py - line_nums = [25, 26, 27, 28, 29, 30, 36, 37, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63] - uncomment_lines(path_cli, line_nums) + line_num_str = "25-30, 36-37, 44-63" + uncomment_lines(path_cli, line_num_str) @@ -74,14 +74,13 @@ def test_custom_cli_arg(get_dev_env, cli_options): # --- Helper functions --- -def uncomment_lines(path, line_nums): +def uncomment_lines(path, line_num_str): """Uncomment the given lines in a file. - - # DEV: This function should parse a string like "25-30, 36-37, 44-63" """ 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("# ", "")) @@ -90,3 +89,21 @@ def uncomment_lines(path, line_nums): 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 + From 21ffb8b672d1cfd8eb1ba298428ddd053b9cda33 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sat, 20 Sep 2025 06:50:36 -0400 Subject: [PATCH 09/21] Uncomments test files. --- tests/e2e_tests/test_custom_cli_arg.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py index ac24f57..e2ab125 100644 --- a/tests/e2e_tests/test_custom_cli_arg.py +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -57,13 +57,23 @@ def test_custom_cli_arg(get_dev_env, cli_options): 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" + path_help_reference_sample = path_tests / "reference_files" / "plugin_help_text_sample.txt" # Assert these paths all exist. assert all([path_cli.exists(), path_platform_deployer.exists(), path_test_custom_cli.exists(), path_test_help.exists()]) - # cli.py - line_num_str = "25-30, 36-37, 44-63" - uncomment_lines(path_cli, line_num_str) + # 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") + + # Make a reference file for the help output. + path_help_reference = path_tests / "reference_files" / "plugin_help_text.txt" + contents = path_help_reference_sample.read_text() + new_contents = contents.replace("Fly.io", plugin_config.pkg_name) + new_contents = contents.replace("dsd-flyio", plugin_config.pkg_name) + path_help_reference.write_text(new_contents) + # DEF: Run test, fail, copy correct help output. @@ -106,4 +116,3 @@ def _get_line_nums(line_num_str): line_nums += list(range(start, end+1)) return line_nums - From ad792a1a2d15e3c318cfe1459fbbd8a5da315c48 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 09:57:12 -0400 Subject: [PATCH 10/21] Reference file for help output with a custom CLI arg. --- pytest.ini | 2 ++ .../help_output_vm_size_arg.txt | 5 ++++ tests/e2e_tests/test_custom_cli_arg.py | 25 ++++++++----------- tests/e2e_tests/utils/e2e_utils.py | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 tests/e2e_tests/reference_files/help_output_vm_size_arg.txt 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/help_output_vm_size_arg.txt b/tests/e2e_tests/reference_files/help_output_vm_size_arg.txt new file mode 100644 index 0000000..79b8a83 --- /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 BUGGER Name for a preset vm-size configuration, ie `shared- + cpu-2x`. \ No newline at end of file diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py index e2ab125..9de8576 100644 --- a/tests/e2e_tests/test_custom_cli_arg.py +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -22,6 +22,8 @@ from argparse import Namespace import subprocess import shlex +import shutil +from pathlib import Path import pytest @@ -55,29 +57,22 @@ def test_custom_cli_arg(get_dev_env, cli_options): 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_custom_cli = path_tests / "test_custom_cli_arg.py" path_test_help = path_tests / "test_help_output.py" - path_help_reference_sample = path_tests / "reference_files" / "plugin_help_text_sample.txt" # 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_custom_cli, "14-22") uncomment_lines(path_test_help, "19-35") - # Make a reference file for the help output. - path_help_reference = path_tests / "reference_files" / "plugin_help_text.txt" - contents = path_help_reference_sample.read_text() - new_contents = contents.replace("Fly.io", plugin_config.pkg_name) - new_contents = contents.replace("dsd-flyio", plugin_config.pkg_name) - path_help_reference.write_text(new_contents) - # DEF: Run test, fail, copy correct help output. - - - - + # Copy reference file for help output to new plugin. + path_help_reference = Path(__file__).parent / "reference_files" / "help_output_vm_size_arg.txt" + assert path_help_reference.exists() + path_help_reference_plugin = path_tests / "reference_files" / "plugin_help_text.txt" + shutil.copyfile(path_help_reference, path_help_reference_plugin) if not cli_options.setup_plugins_only: e2e_utils.run_core_plugin_tests(path_dsd, plugin_config, cli_options) @@ -93,7 +88,7 @@ def uncomment_lines(path, line_num_str): 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("# ", "")) + new_lines.append(line.replace("# ", "", count=1)) else: new_lines.append(line) diff --git a/tests/e2e_tests/utils/e2e_utils.py b/tests/e2e_tests/utils/e2e_utils.py index 9bad2c5..770200f 100644 --- a/tests/e2e_tests/utils/e2e_utils.py +++ b/tests/e2e_tests/utils/e2e_utils.py @@ -75,7 +75,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" return cmd From 7696b309e036f1bb166806a373468649061cda7b Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 10:14:59 -0400 Subject: [PATCH 11/21] Print output of subprocess tests. --- tests/e2e_tests/utils/e2e_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_tests/utils/e2e_utils.py b/tests/e2e_tests/utils/e2e_utils.py index 770200f..b4000a4 100644 --- a/tests/e2e_tests/utils/e2e_utils.py +++ b/tests/e2e_tests/utils/e2e_utils.py @@ -61,6 +61,7 @@ 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 check_core_plugin_tests(stdout, cli_options) From 94b5c00ea4dbc67bd4525850ced8f42e4dc42e4c Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 10:19:26 -0400 Subject: [PATCH 12/21] Check for failure in output, not '100%'. --- tests/e2e_tests/utils/e2e_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/utils/e2e_utils.py b/tests/e2e_tests/utils/e2e_utils.py index b4000a4..0aa20a3 100644 --- a/tests/e2e_tests/utils/e2e_utils.py +++ b/tests/e2e_tests/utils/e2e_utils.py @@ -63,7 +63,7 @@ def run_core_plugin_tests(path_dsd, plugin_config, cli_options): stdout = output.stdout.decode() print(stdout) - assert "[100%]" in stdout + assert "FAILURE" not in stdout check_core_plugin_tests(stdout, cli_options) From 8adb0c7f6af4c0a64e80c227c9e9ca93f0ac7542 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 10:27:48 -0400 Subject: [PATCH 13/21] FAILURES -> FAILED --- tests/e2e_tests/utils/e2e_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/utils/e2e_utils.py b/tests/e2e_tests/utils/e2e_utils.py index 0aa20a3..9876b56 100644 --- a/tests/e2e_tests/utils/e2e_utils.py +++ b/tests/e2e_tests/utils/e2e_utils.py @@ -63,7 +63,7 @@ def run_core_plugin_tests(path_dsd, plugin_config, cli_options): stdout = output.stdout.decode() print(stdout) - assert "FAILURE" not in stdout + assert "FAILED" not in stdout check_core_plugin_tests(stdout, cli_options) From 9b2ac45622a8d9740ba71f8d7af6f551bffa02d1 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 10:34:31 -0400 Subject: [PATCH 14/21] Correct reference file. --- tests/e2e_tests/reference_files/help_output_vm_size_arg.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 79b8a83..204759a 100644 --- a/tests/e2e_tests/reference_files/help_output_vm_size_arg.txt +++ b/tests/e2e_tests/reference_files/help_output_vm_size_arg.txt @@ -1,5 +1,5 @@ Options for dsd-newfly: Plugin-specific CLI args for dsd-newfly - --vm-size BUGGER Name for a preset vm-size configuration, ie `shared- + --vm-size VM_SIZE Name for a preset vm-size configuration, ie `shared- cpu-2x`. \ No newline at end of file From 0dd17b5e2dc11ef0618b33eeca7004262cc08111 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 10:39:02 -0400 Subject: [PATCH 15/21] Message clarifying in test output that generated plugin is being modified. --- tests/e2e_tests/test_custom_cli_arg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py index 9de8576..004ba60 100644 --- a/tests/e2e_tests/test_custom_cli_arg.py +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -49,6 +49,9 @@ def test_custom_cli_arg(get_dev_env, cli_options): ) 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("-", "_") From e4be221d114cde540560d233ab50e038c2c54a81 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 10:59:26 -0400 Subject: [PATCH 16/21] Modifies plugin's platform_deployer.py to exercise the example custom CLI arg. --- .../e2e_tests/reference_files/add_fly_toml.py | 8 ++++++ tests/e2e_tests/test_custom_cli_arg.py | 27 +++++++++++++++++-- tests/e2e_tests/utils/e2e_utils.py | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/e2e_tests/reference_files/add_fly_toml.py 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/test_custom_cli_arg.py b/tests/e2e_tests/test_custom_cli_arg.py index 004ba60..873ffef 100644 --- a/tests/e2e_tests/test_custom_cli_arg.py +++ b/tests/e2e_tests/test_custom_cli_arg.py @@ -68,15 +68,17 @@ def test_custom_cli_arg(get_dev_env, cli_options): # 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_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" - assert path_help_reference.exists() 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) @@ -114,3 +116,24 @@ def _get_line_nums(line_num_str): 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 9876b56..0396f17 100644 --- a/tests/e2e_tests/utils/e2e_utils.py +++ b/tests/e2e_tests/utils/e2e_utils.py @@ -76,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} -k test_help_output.py" + 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 From 5a85b9713f1b90848cdde4653ea2a67be5452274 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 13:51:49 -0400 Subject: [PATCH 17/21] Distinct name for test of plugin cli. --- tests/e2e_tests/{test_custom_cli_arg.py => test_plugin_cli.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/e2e_tests/{test_custom_cli_arg.py => test_plugin_cli.py} (100%) diff --git a/tests/e2e_tests/test_custom_cli_arg.py b/tests/e2e_tests/test_plugin_cli.py similarity index 100% rename from tests/e2e_tests/test_custom_cli_arg.py rename to tests/e2e_tests/test_plugin_cli.py From 5ef550e503612c750391a9c482abb9d960eef4bc Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 14:20:44 -0400 Subject: [PATCH 18/21] Update reference plugins for integration tests. --- .../dsd-newfly-no-space/dsd_newfly/cli.py | 63 +++++++++++++++++++ .../dsd-newfly-no-space/dsd_newfly/deploy.py | 16 ++++- .../dsd_newfly/platform_deployer.py | 1 + .../dsd_newfly/plugin_config.py | 5 ++ .../plugin_help_text_sample.txt | 6 ++ .../integration_tests/test_custom_cli_arg.py | 22 +++++++ .../integration_tests/test_help_output.py | 35 +++++++++++ .../integration_tests/test_newfly_config.py | 2 +- 8 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 tests/integration_tests/reference_files/dsd-newfly-no-space/dsd_newfly/cli.py create mode 100644 tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt create mode 100644 tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_custom_cli_arg.py create mode 100644 tests/integration_tests/reference_files/dsd-newfly-no-space/tests/integration_tests/test_help_output.py 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 From 9d3e0b7aafd298ca1da7d22df1e96acdce665be9 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 14:24:24 -0400 Subject: [PATCH 19/21] All integration test reference files updated, all integration tests pass. --- .../dsd_greenhost_advanced/cli.py | 63 +++++++++++++++++++ .../dsd_greenhost_advanced/deploy.py | 16 ++++- .../platform_deployer.py | 1 + .../dsd_greenhost_advanced/plugin_config.py | 5 ++ .../plugin_help_text_sample.txt | 6 ++ .../integration_tests/test_custom_cli_arg.py | 22 +++++++ .../test_greatgreenhost_config.py | 2 +- .../integration_tests/test_help_output.py | 35 +++++++++++ .../dsd-newfly-single-space/dsd_newfly/cli.py | 63 +++++++++++++++++++ .../dsd_newfly/deploy.py | 16 ++++- .../dsd_newfly/platform_deployer.py | 1 + .../dsd_newfly/plugin_config.py | 5 ++ .../plugin_help_text_sample.txt | 6 ++ .../integration_tests/test_custom_cli_arg.py | 22 +++++++ .../integration_tests/test_help_output.py | 35 +++++++++++ .../integration_tests/test_newfly_config.py | 2 +- 16 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 tests/integration_tests/reference_files/dsd-greenhost-advanced/dsd_greenhost_advanced/cli.py create mode 100644 tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/reference_files/plugin_help_text_sample.txt create mode 100644 tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_custom_cli_arg.py create mode 100644 tests/integration_tests/reference_files/dsd-greenhost-advanced/tests/integration_tests/test_help_output.py create mode 100644 tests/integration_tests/reference_files/dsd-newfly-single-space/dsd_newfly/cli.py create mode 100644 tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/reference_files/plugin_help_text_sample.txt create mode 100644 tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_custom_cli_arg.py create mode 100644 tests/integration_tests/reference_files/dsd-newfly-single-space/tests/integration_tests/test_help_output.py 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-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 From e88d6c19f3d957ce3023b3c88e3614e9c9fb0026 Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 14:30:54 -0400 Subject: [PATCH 20/21] Update doc of generated project structure. --- docs/plugin_structure.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 From c43ae5c2607ca0dc0ad041a1b97cc79cc81d041b Mon Sep 17 00:00:00 2001 From: Eric Matthes Date: Sun, 21 Sep 2025 14:35:28 -0400 Subject: [PATCH 21/21] Document changes to e2e test suite. --- docs/e2e_tests.md | 7 +++++++ 1 file changed, 7 insertions(+) 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.