From 2132c45fce362d83762e21cf1f58fed88cfcbe4a Mon Sep 17 00:00:00 2001 From: Derek Graeber Date: Mon, 3 Feb 2025 17:40:22 +0000 Subject: [PATCH 1/4] ignore changes to dependabot --- .github/dependabot.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 95c7e76..f540155 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -13,6 +13,12 @@ updates: - dependency-name: "myst_parser" versions: - "4.0.0" + - dependency-name: "pydantic" + versions: + - "2.10.5" + - dependency-name: "pydantic-core" + versions: + - "2.27.2" - package-ecosystem: "github-actions" directory: "/" From 5ff26a5c6f26212ec5c69175fe3668747ed1e227 Mon Sep 17 00:00:00 2001 From: Derek Graeber Date: Tue, 4 Feb 2025 14:19:59 +0000 Subject: [PATCH 2/4] Adding taint support, fix typos in pydoc --- CHANGELOG.md | 1 + seedfarmer/__main__.py | 3 +- seedfarmer/cli_groups/__init__.py | 3 +- seedfarmer/cli_groups/_store_group.py | 12 +- seedfarmer/cli_groups/_taint_group.py | 167 ++++++++++++++++++++++++++ test/unit-test/test_cli_arg.py | 36 +++++- 6 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 seedfarmer/cli_groups/_taint_group.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f04ad..249caa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch ## Unreleased ### New +- adding support to taint modules to force a redeploy ### Changes diff --git a/seedfarmer/__main__.py b/seedfarmer/__main__.py index d750c04..ec137ca 100644 --- a/seedfarmer/__main__.py +++ b/seedfarmer/__main__.py @@ -20,7 +20,7 @@ import seedfarmer from seedfarmer import DEBUG_LOGGING_FORMAT, commands, config, enable_debug -from seedfarmer.cli_groups import bootstrap, bundle, init, list, metadata, projectpolicy, remove, store +from seedfarmer.cli_groups import bootstrap, bundle, init, list, metadata, projectpolicy, remove, store, taint from seedfarmer.output_utils import print_bolded from seedfarmer.utils import load_dotenv_files @@ -294,5 +294,6 @@ def main() -> int: cli.add_command(projectpolicy) cli.add_command(metadata) cli.add_command(bundle) + cli.add_command(taint) cli() return 0 diff --git a/seedfarmer/cli_groups/__init__.py b/seedfarmer/cli_groups/__init__.py index 420816f..6dc1268 100644 --- a/seedfarmer/cli_groups/__init__.py +++ b/seedfarmer/cli_groups/__init__.py @@ -20,5 +20,6 @@ from seedfarmer.cli_groups._project_group import projectpolicy from seedfarmer.cli_groups._remove_group import remove from seedfarmer.cli_groups._store_group import store +from seedfarmer.cli_groups._taint_group import taint -__all__ = ["bootstrap", "init", "list", "remove", "store", "projectpolicy", "metadata", "bundle"] +__all__ = ["bootstrap", "init", "list", "remove", "store", "projectpolicy", "metadata", "bundle", "taint"] diff --git a/seedfarmer/cli_groups/_store_group.py b/seedfarmer/cli_groups/_store_group.py index 00c75e2..3115352 100644 --- a/seedfarmer/cli_groups/_store_group.py +++ b/seedfarmer/cli_groups/_store_group.py @@ -110,7 +110,7 @@ def store() -> None: @click.option( "--target-account-id", default=None, - help="""Account Id of the target accout to store deployspec, if specifed --target-region is required + help="""Account Id of the target account to store deployspec, if specified --target-region is required You SHOULD NOT use this parameter as this command will leverage the SeedFarmer session manager! It is meant for development purposes. """, @@ -119,7 +119,7 @@ def store() -> None: @click.option( "--target-region", default=None, - help="""Region of the target accout to store deployspec, if specifed --target-account-id is required + help="""Region of the target account to store deployspec, if specified --target-account-id is required You SHOULD NOT use this parameter as this command will leverage the SeedFarmer session manager! It is meant for development purposes. """, @@ -228,13 +228,13 @@ def store_deployspec( @click.option( "--target-account-id", default=None, - help="Account Id of the target accout to store module metadata, if specifed --target-region is required", + help="Account Id of the target account to store module metadata, if specified --target-region is required", show_default=True, ) @click.option( "--target-region", default=None, - help="Region of the target accout to store module metadata, if specifed --target-account-id is required", + help="Region of the target account to store module metadata, if specified --target-account-id is required", show_default=True, ) @click.option( @@ -344,13 +344,13 @@ def store_module_metadata( @click.option( "--target-account-id", default=None, - help="Account Id of the target accout to store md5, if specifed --target-region is required", + help="Account Id of the target account to store md5, if specified --target-region is required", show_default=True, ) @click.option( "--target-region", default=None, - help="Region of the target accout to store md5, if specifed --target-account-id is required", + help="Region of the target account to store md5, if specified --target-account-id is required", show_default=True, ) @click.option( diff --git a/seedfarmer/cli_groups/_taint_group.py b/seedfarmer/cli_groups/_taint_group.py new file mode 100644 index 0000000..350935f --- /dev/null +++ b/seedfarmer/cli_groups/_taint_group.py @@ -0,0 +1,167 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import List, Optional + +import click + +import seedfarmer.mgmt.deploy_utils as du +import seedfarmer.mgmt.module_info as mi +from seedfarmer import DEBUG_LOGGING_FORMAT, config, enable_debug +from seedfarmer.output_utils import print_bolded +from seedfarmer.services.session_manager import ISessionManager, SessionManager +from seedfarmer.utils import load_dotenv_files + +_logger: logging.Logger = logging.getLogger(__name__) + + +def _load_project() -> str: + try: + _logger.info("No --project provided, attempting load from seedfarmer.yaml") + return config.PROJECT + except FileNotFoundError: + print_bolded("Unable to determine project to bootstrap, one of --project or a seedfarmer.yaml is required") + raise click.ClickException("Failed to determine project identifier") + + +def _error_messaging(deployment: str, group: Optional[str] = None, module: Optional[str] = None) -> None: + if group and module: + print(f"No module info found for {deployment}-{group}-{module}") + print_bolded(f"To see all deployed modules in {deployment}, run seedfarmer list modules -d {deployment}") + else: + print(f"No module info found for {deployment}") + print_bolded("To see all deployments, run seedfarmer list deployments") + + +@click.group(name="taint", help="Top Level command to support adding a taint to a deployed module") +def taint() -> None: + """Taint module""" + pass + + +@taint.command( + name="module", + help="""This command will mark a module as needing + redeploy of a module on the next deployment. + Do not use this unless you are sure of the ramifications! + """, +) +@click.option( + "--deployment", + "-d", + type=str, + help="The Deployment Name", + required=True, +) +@click.option( + "--group", + "-g", + type=str, + help="The Group Name", + required=True, +) +@click.option( + "--module", + "-m", + type=str, + help="The Module Name", + required=True, +) +@click.option( + "--project", + "-p", + help="Project identifier", + required=False, + default=None, +) +@click.option( + "--profile", + default=None, + help="The AWS profile used to create a session to assume the toolchain role", + required=False, +) +@click.option( + "--region", + default=None, + help="The AWS region used to create a session to assume the toolchain role", + required=False, +) +@click.option( + "--qualifier", + default=None, + help="""A qualifier to use with the seedfarmer roles. + Use only if bootstrapped with this qualifier""", + required=False, +) +@click.option( + "--env-file", + "env_files", + default=[".env"], + help="""A relative path to the .env file to load environment variables from. + Multple files can be passed in by repeating this flag, and the order will be + preserved when overriding duplicate values. + """, + multiple=True, + required=False, +) +@click.option( + "--debug/--no-debug", + default=False, + help="Enable detailed logging.", + show_default=True, +) +def taint_module( + deployment: str, + group: str, + module: str, + project: Optional[str], + profile: Optional[str], + region: Optional[str], + qualifier: Optional[str], + env_files: List[str], + debug: bool, +) -> None: + if debug: + enable_debug(format=DEBUG_LOGGING_FORMAT) + _logger.debug("We are removing module data for %s of group %s in %s", module, group, deployment) + + if project is None: + project = _load_project() + + load_dotenv_files(config.OPS_ROOT, env_files=env_files) + + session_manager: ISessionManager = SessionManager().get_or_create( + project_name=project, profile=profile, region_name=region, qualifier=qualifier + ) + dep_manifest = du.generate_deployed_manifest(deployment_name=deployment, skip_deploy_spec=True) + + dep_manifest.validate_and_set_module_defaults() # type: ignore + try: + session = session_manager.get_deployment_session( + account_id=dep_manifest.get_module(group=group, module=module).get_target_account_id(), # type: ignore + region_name=dep_manifest.get_module(group=group, module=module).target_region, # type: ignore + ) + except Exception: + _error_messaging(deployment, group, module) + return + + mi.remove_module_md5( + deployment=deployment, + group=group, + module=module, + type=mi.ModuleConst.BUNDLE, + session=session, + ) + _logger.debug("Module %s-%s-%s marked for redeploy", module, group, deployment) diff --git a/test/unit-test/test_cli_arg.py b/test/unit-test/test_cli_arg.py index db738bf..cef590e 100644 --- a/test/unit-test/test_cli_arg.py +++ b/test/unit-test/test_cli_arg.py @@ -21,7 +21,7 @@ from moto import mock_aws from seedfarmer import config -from seedfarmer.__main__ import apply, bootstrap, destroy, init, metadata, projectpolicy, remove, store, version +from seedfarmer.__main__ import apply, bootstrap, destroy, init, metadata, projectpolicy, remove, store, taint, version from seedfarmer.__main__ import list as list from seedfarmer.models._deploy_spec import DeploySpec from seedfarmer.models.manifests import DeploymentManifest @@ -1550,3 +1550,37 @@ def test_metadata_add_kv_missing_value(mocker): mocker.patch("seedfarmer.cli_groups._manage_metadata_group.metadata_support.add_kv_output", return_value=None) mocker.patch("seedfarmer.cli_groups._manage_metadata_group.metadata_support.add_json_output", return_value=None) _test_command(sub_command=metadata, options=["add", "--key", "adfdf"], exit_code=1) + + +@pytest.mark.metadata +def test_taint(mocker): + mocker.patch("seedfarmer.cli_groups._taint_group.mi.remove_module_md5", return_value=None) + _test_command( + sub_command=taint, + options=[ + "module", + "-d", + "deployment_name", + "-g", + "test_group", + "-m", + "test_module", + ], + exit_code=1, + ) + + +@pytest.mark.metadata +def test_taint_missing_param(mocker): + mocker.patch("seedfarmer.cli_groups._taint_group.mi.remove_module_md5", return_value=None) + _test_command( + sub_command=taint, + options=[ + "module", + "-d", + "deployment-name", + "-g", + "test-group", + ], + exit_code=2, + ) From 45ead5ad9fbd9b5314b55799b276e54cb3647cb3 Mon Sep 17 00:00:00 2001 From: Derek Graeber Date: Fri, 7 Feb 2025 14:47:55 +0000 Subject: [PATCH 3/4] catch if no deployment or module exists --- seedfarmer/cli_groups/_taint_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seedfarmer/cli_groups/_taint_group.py b/seedfarmer/cli_groups/_taint_group.py index 350935f..8c2fb2e 100644 --- a/seedfarmer/cli_groups/_taint_group.py +++ b/seedfarmer/cli_groups/_taint_group.py @@ -145,10 +145,10 @@ def taint_module( session_manager: ISessionManager = SessionManager().get_or_create( project_name=project, profile=profile, region_name=region, qualifier=qualifier ) - dep_manifest = du.generate_deployed_manifest(deployment_name=deployment, skip_deploy_spec=True) - dep_manifest.validate_and_set_module_defaults() # type: ignore try: + dep_manifest = du.generate_deployed_manifest(deployment_name=deployment, skip_deploy_spec=True) + dep_manifest.validate_and_set_module_defaults() # type: ignore session = session_manager.get_deployment_session( account_id=dep_manifest.get_module(group=group, module=module).get_target_account_id(), # type: ignore region_name=dep_manifest.get_module(group=group, module=module).target_region, # type: ignore From b049b4eeec5e3c5121c70d842c2fb3ee1172c09b Mon Sep 17 00:00:00 2001 From: Derek Graeber Date: Fri, 7 Feb 2025 14:56:36 +0000 Subject: [PATCH 4/4] fix exit code --- test/unit-test/test_cli_arg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-test/test_cli_arg.py b/test/unit-test/test_cli_arg.py index cef590e..ad315b5 100644 --- a/test/unit-test/test_cli_arg.py +++ b/test/unit-test/test_cli_arg.py @@ -1566,7 +1566,7 @@ def test_taint(mocker): "-m", "test_module", ], - exit_code=1, + exit_code=0, )