From 82de37c6fe96e2d9e27f0376270edbe829cf66ff Mon Sep 17 00:00:00 2001 From: Yash Dholakia Date: Thu, 12 Oct 2023 10:21:55 -0700 Subject: [PATCH] Release Changes for v1.4.3 --- CHANGELOG.md | 8 + NOTICE.txt | 8 + README.md | 4 +- source/aws_lambda/prepare_input/handler.py | 2 +- .../aws_lambda/shared/personalize_service.py | 140 +++++++++--------- .../resource_hash/hash.py | 12 ++ .../resource_name/name.py | 12 ++ .../src/custom_resources/requirements.txt | 2 +- .../cdk/aws_lambda/java/bundling.py | 2 +- .../cdk/aws_lambda/python/bundling.py | 2 +- .../cdk/scripts/build_s3_cdk_dist.py | 19 +-- .../helpers_cdk/aws_solutions/cdk/stack.py | 12 ++ .../cdk/stepfunctions/solutionstep.py | 4 +- .../aws_solutions/cdk/synthesizers.py | 6 +- .../helpers_cdk/setup.py | 2 +- .../aws_solutions/core/config.py | 5 +- .../requirements-dev.txt | 14 +- source/images/solution-architecture.jpg | Bin 324249 -> 0 bytes source/infrastructure/aspects/app_registry.py | 9 +- source/infrastructure/cdk.json | 2 +- source/infrastructure/deploy.py | 12 ++ .../functions/create_batch_inference_job.py | 13 ++ .../functions/create_batch_segment_job.py | 13 ++ .../aws_lambda/functions/create_config.py | 4 +- .../functions/create_dataset_import_job.py | 13 ++ .../aws_lambda/functions/s3_event.py | 4 +- source/infrastructure/personalize/stack.py | 41 +++++ .../scheduled_dataset_import.py | 1 + .../scheduled_solution_maintenance.py | 1 + source/infrastructure/setup.py | 2 +- source/requirements-dev.txt | 11 +- .../schedule_sfn_task/ScheduleEvent.java | 3 - .../aws_solutions/scheduler/cdk/construct.py | 10 +- source/scheduler/cdk/setup.py | 2 +- .../aspects/test_personalize_app_stack.py | 4 +- .../aws_lambda/python/fixtures/pyproject.toml | 1 + .../test_build_s3_cdk_dist.py | 11 -- .../test_solution_config.py | 19 +-- source/tests/test_deploy.py | 9 +- 39 files changed, 277 insertions(+), 162 deletions(-) delete mode 100644 source/images/solution-architecture.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index fc45343..ca32432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.3] - 2023-10-12 + +### Changed + +- Upgrade aws-cdk to 2.88.0 +- Upgrade deprecated methods in App-registry +- Address or Fix all SonarQube issues + ## [1.4.2] - 2023-06-22 ### Changed diff --git a/NOTICE.txt b/NOTICE.txt index 2d4d383..1865502 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -37,5 +37,13 @@ requests-mock under the Apache License Version 2.0 rich under the Massachusetts Institute of Technology (MIT) license tenacity under the Apache License Version 2.0 quartz-scheduler under the Apache License Version 2.0 +parsedatetime under the Apache License Version 2.0 +urllib3 under the Massachusetts Institute of Technology (MIT) license +setuptools under the Massachusetts Institute of Technology (MIT) license +pipenv under the Massachusetts Institute of Technology (MIT) license +virtualenv under the Massachusetts Institute of Technology (MIT) license +tox under the Massachusetts Institute of Technology (MIT) license +tox-pyenv under the Apache License Version 2.0 +poetry under the Massachusetts Institute of Technology (MIT) license The Apache License Version Version 2.0 is included in LICENSE.txt. \ No newline at end of file diff --git a/README.md b/README.md index b75a38e..8869564 100644 --- a/README.md +++ b/README.md @@ -609,7 +609,7 @@ The following procedures assumes that all the OS-level configuration has been co - [AWS Command Line Interface](https://aws.amazon.com/cli/) - [Python](https://www.python.org/) 3.9 or newer - [Node.js](https://nodejs.org/en/) 16.x or newer -- [AWS CDK](https://aws.amazon.com/cdk/) 2.75.0 or newer +- [AWS CDK](https://aws.amazon.com/cdk/) 2.88.0 or newer - [Amazon Corretto OpenJDK](https://docs.aws.amazon.com/corretto/) 17.0.4.1 > **Please ensure you test the templates before updating any production deployments.** @@ -707,7 +707,7 @@ After running the command, you can deploy the template: ## Collection of operational metrics This solution collects anonymous operational metrics to help AWS improve the quality of features of the solution. -For more information, including how to disable this capability, please see the [implementation guide](https://docs.aws.amazon.com/solutions/latest/maintaining-personalized-experiences-with-ml/collection-of-operational-metrics.html). +For more information, including how to disable this capability, please see the [implementation guide](https://docs.aws.amazon.com/solutions/latest/maintaining-personalized-experiences-with-ml/reference.html). --- diff --git a/source/aws_lambda/prepare_input/handler.py b/source/aws_lambda/prepare_input/handler.py index 159fde0..3b61252 100644 --- a/source/aws_lambda/prepare_input/handler.py +++ b/source/aws_lambda/prepare_input/handler.py @@ -23,7 +23,7 @@ metrics = Metrics() -def lambda_handler(event: Dict[str, Any], context: LambdaContext) -> Dict: +def lambda_handler(event: Dict[str, Any], _) -> Dict: """Add timeStarted to the workflowConfig of all items :param event: AWS Lambda Event :param context: AWS Lambda Context diff --git a/source/aws_lambda/shared/personalize_service.py b/source/aws_lambda/shared/personalize_service.py index bd96b9f..2aea466 100644 --- a/source/aws_lambda/shared/personalize_service.py +++ b/source/aws_lambda/shared/personalize_service.py @@ -65,7 +65,19 @@ ("timeStarted", Resource), ("solutionVersionArn", SolutionVersion), ) - +RESOURCE_TYPES = [ + "datasetGroup", + "datasetImport", + "dataset", + "eventTracker", + "solution", + "solutionVersion", + "filter", + "recommender", + "campaign", + "batchJob", + "segmentJob" +] def get_duplicates(items): if isinstance(items, str): @@ -714,71 +726,60 @@ def _validate_filters(self, path="filters[].serviceConfig"): self._fill_default_vals("filter", _filter) def _validate_type(self, var, typ, err: str): - validates = isinstance(var, typ) + validates = isinstance(var, typ) and var is not None + if not validates: self._configuration_errors.append(err) + return validates - def _validate_solutions(self, path="solutions[]"): + def _validate_solutions(self, path="solutions[]"): solutions = jmespath.search(path, self.config_dict) or {} - for idx, _solution in enumerate(solutions): - campaigns = _solution.get("campaigns", []) - if self._validate_type(campaigns, list, f"solutions[{idx}].campaigns must be a list"): - self._validate_campaigns(f"solutions[{idx}].campaigns", campaigns) - - batch_inference_jobs = _solution.get("batchInferenceJobs", []) - if batch_inference_jobs and self._validate_type( - batch_inference_jobs, - list, - f"solutions[{idx}].batchInferenceJobs must be a list", - ): - self._validate_batch_inference_jobs( - path=f"solutions[{idx}].batchInferenceJobs", - solution_name=_solution.get("serviceConfig", {}).get("name", ""), - batch_inference_jobs=batch_inference_jobs, - ) - batch_segment_jobs = _solution.get("batchSegmentJobs", []) - if batch_segment_jobs and self._validate_type( - batch_segment_jobs, - list, - f"solutions[{idx}].batchSegmentJobs must be a list", - ): - self._validate_batch_segment_jobs( - path=f"solutions[{idx}].batchSegmentJobs", - solution_name=_solution.get("serviceConfig", {}).get("name", ""), - batch_segment_jobs=batch_segment_jobs, - ) + for idx, _solution in enumerate(solutions): + # Validate campaigns and batch jobs + self._validate_campaigns(f"solutions[{idx}].campaigns", _solution.get("campaigns", [])) + self._validate_batch_inference_jobs( + path=f"solutions[{idx}].batchInferenceJobs", + solution_name=_solution.get("serviceConfig", {}).get("name", ""), + batch_inference_jobs=_solution.get("batchInferenceJobs", []), + ) + self._validate_batch_segment_jobs( + path=f"solutions[{idx}].batchSegmentJobs", + solution_name=_solution.get("serviceConfig", {}).get("name", ""), + batch_segment_jobs=_solution.get("batchSegmentJobs", []), + ) - _solution = _solution.get("serviceConfig") + # Validate service configuration + _service_config = _solution.get("serviceConfig") - if not self._validate_type(_solution, dict, f"solutions[{idx}].serviceConfig must be an object"): + if not self._validate_type(_service_config, dict, f"solutions[{idx}].serviceConfig must be an object"): continue # `performAutoML` is currently returned from InputValidator.validate() as a valid field # Once the botocore Stubber is updated to not have this param anymore in `create_solution` call, # this check can be deleted. - if "performAutoML" in _solution: - del _solution["performAutoML"] + if "performAutoML" in _service_config: + del _service_config["performAutoML"] logger.error( "performAutoML is not a valid configuration parameter - proceeding to create the " "solution without this feature. For more details, refer to the Maintaining Personalized Experiences " "Github project's README.md file." ) - _solution["datasetGroupArn"] = DatasetGroup().arn("validation") - if "solutionVersion" in _solution: - # To pass solution through InputValidator - solution_version_config = _solution["solutionVersion"] - del _solution["solutionVersion"] - self._validate_resource(Solution(), _solution) - _solution["solutionVersion"] = solution_version_config + _service_config["datasetGroupArn"] = DatasetGroup().arn("validation") + if "solutionVersion" in _service_config: + # To pass solution through InputValidator + solution_version_config = _service_config["solutionVersion"] + del _service_config["solutionVersion"] + self._validate_resource(Solution(), _service_config) + _service_config["solutionVersion"] = solution_version_config else: - self._validate_resource(Solution(), _solution) + self._validate_resource(Solution(), _service_config) - self._fill_default_vals("solution", _solution) - self._validate_solution_version(_solution) + self._fill_default_vals("solution", _service_config) + self._validate_solution_version(_service_config) def _validate_solution_version(self, solution_config): allowed_sol_version_keys = ["trainingMode", "tags"] @@ -819,6 +820,8 @@ def _validate_solution_update(self): ) def _validate_campaigns(self, path, campaigns: List[Dict]): + self._validate_type(campaigns, list, f"{path} must be a list") + for idx, campaign_config in enumerate(campaigns): current_path = f"{path}.campaigns[{idx}]" @@ -832,6 +835,12 @@ def _validate_campaigns(self, path, campaigns: List[Dict]): self._fill_default_vals("campaign", campaign) def _validate_batch_inference_jobs(self, path, solution_name, batch_inference_jobs: List[Dict]): + self._validate_type( + batch_inference_jobs, + list, + f"solutions[{path} must be a list", + ) + for idx, batch_job_config in enumerate(batch_inference_jobs): current_path = f"{path}.batchInferenceJobs[{idx}]" @@ -860,6 +869,12 @@ def _validate_batch_inference_jobs(self, path, solution_name, batch_inference_jo self._fill_default_vals("batchJob", batch_job) def _validate_batch_segment_jobs(self, path, solution_name, batch_segment_jobs: List[Dict]): + self._validate_type( + batch_segment_jobs, + list, + f"solutions[{path} must be a list", + ) + for idx, batch_job_config in enumerate(batch_segment_jobs): current_path = f"{path}.batchSegmentJobs[{idx}]" @@ -1108,42 +1123,23 @@ def _validate_naming(self): self._validate_no_duplicates(name="campaign names", path="solutions[].campaigns[].serviceConfig.name") self._validate_no_duplicates(name="solution names", path="solutions[].serviceConfig.name") - def _fill_default_vals(self, resource_type, resource_dict): - """Insert default values for tags and other fields whenever not supplied""" - - if ( - resource_type - in [ - "datasetGroup", - "datasetImport", - "dataset", - "eventTracker", - "solution", - "solutionVersion", - "filter", - "recommender", - "campaign", - "batchJob", - "segmentJob", - ] - and "tags" not in resource_dict - ): + def _fill_resource_dict_tags(self, resource_type, resource_dict): + if resource_type in RESOURCE_TYPES and "tags" not in resource_dict: if self.pass_root_tags: resource_dict["tags"] = self.config_dict["tags"] else: resource_dict["tags"] = [] + def _fill_default_vals(self, resource_type, resource_dict): + """Insert default values for tags and other fields whenever not supplied""" + self._fill_resource_dict_tags(resource_type, resource_dict) + if resource_type == "datasetImport": if "importMode" not in resource_dict: resource_dict["importMode"] = "FULL" + if "publishAttributionMetricsToS3" not in resource_dict: resource_dict["publishAttributionMetricsToS3"] = False - if resource_type == "solutionVersion": - if "tags" not in resource_dict: - if self.pass_root_tags: - resource_dict["tags"] = self.config_dict["tags"] - else: - resource_dict["tags"] = [] - if "trainingMode" not in resource_dict: - resource_dict["trainingMode"] = "FULL" + if resource_type == "solutionVersion" and "trainingMode" not in resource_dict: + resource_dict["trainingMode"] = "FULL" diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/hash.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/hash.py index 9b0819e..a7e7418 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/hash.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/hash.py @@ -21,6 +21,9 @@ from aws_solutions.cdk.aws_lambda.python.function import SolutionsPythonFunction from aws_solutions.cdk.cfn_nag import add_cfn_nag_suppressions, CfnNagSuppression +from cdk_nag import NagSuppressions +from cdk_nag import NagPackSuppression + class ResourceHash(Construct): """Used to create unique resource names based on the hash of the stack ID""" @@ -56,6 +59,15 @@ def __init__( ], ) + NagSuppressions.add_resource_suppressions(self._resource_name_function.role, [ + NagPackSuppression( + id='AwsSolutions-IAM5', + reason='All IAM policies defined in this solution' + 'grant only least-privilege permissions. Wild ' + 'card for resources is used only for services ' + 'which do not have a resource arn')], + apply_to_children=True) + properties = { "ServiceToken": self._resource_name_function.function_arn, "Purpose": purpose, diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/name.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/name.py index 02198ed..d540f79 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/name.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/name.py @@ -23,6 +23,9 @@ from aws_solutions.cdk.aws_lambda.python.function import SolutionsPythonFunction from aws_solutions.cdk.cfn_nag import add_cfn_nag_suppressions, CfnNagSuppression +from cdk_nag import NagSuppressions +from cdk_nag import NagPackSuppression + class ResourceName(Construct): """Used to create unique resource names of the format {stack_name}-{purpose}-{id}""" @@ -59,6 +62,15 @@ def __init__( ], ) + NagSuppressions.add_resource_suppressions(self._resource_name_function.role, [ + NagPackSuppression( + id='AwsSolutions-IAM5', + reason='All IAM policies defined in this solution' + 'grant only least-privilege permissions. Wild ' + 'card for resources is used only for services ' + 'which do not have a resource arn')], + apply_to_children=True) + properties = { "ServiceToken": self._resource_name_function.function_arn, "Purpose": purpose, diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/requirements.txt b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/requirements.txt index 293fe27..0e04dcc 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/requirements.txt +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/requirements.txt @@ -1,3 +1,3 @@ requests==2.31.0 -urllib3==1.26.16 +urllib3==1.26.17 crhelper==2.0.11 diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/bundling.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/bundling.py index 242074a..e151cd7 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/bundling.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/bundling.py @@ -44,7 +44,7 @@ def __init__( self.gradle_test = gradle_test self.distribution_path = distribution_path - def try_bundle(self, output_dir: str, options: BundlingOptions) -> bool: + def try_bundle(self, output_dir: str, options: BundlingOptions) -> bool: #NOSONAR - Options are required for method header source = Path(self.to_bundle).absolute() is_gradle_build = (source / "gradlew").exists() diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/bundling.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/bundling.py index c86985c..bff7081 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/bundling.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/bundling.py @@ -55,7 +55,7 @@ def platform_supports_bundling(self): logger.info("local bundling %s supported for %s" % ("is" if os_platform_can_bundle else "is not", os_platform)) return os_platform_can_bundle - def try_bundle(self, output_dir: str, options: BundlingOptions) -> bool: + def try_bundle(self, output_dir: str, options: BundlingOptions) -> bool: #NOSONAR - Options are required for method header if not self.platform_supports_bundling: raise SolutionsPythonBundlingException("this platform does not support bundling") diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/build_s3_cdk_dist.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/build_s3_cdk_dist.py index 1d63b81..3ed63ce 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/build_s3_cdk_dist.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/build_s3_cdk_dist.py @@ -173,22 +173,6 @@ def __init__(self, build_env: BuildEnvironment): def package(self): logger.info("packaging global assets") - -def validate_version_code(ctx, param, value): - """ - Version codes are validated as semantic versions prefixed by a v, e.g. v1.2.3 - :param ctx: the click context - :param param: the click parameter - :param value: the parameter value - :return: the validated value - """ - re_semver = r"^v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" - if re.match(re_semver, value): - return value - else: - raise click.BadParameter("please specifiy major, minor and patch versions, e.g. v1.0.0") - - @click.group() @click.option( "--log-level", @@ -309,8 +293,7 @@ def source_code_package(ctx, ignore, solution_name): @click.option( "--version-code", help="The version of the package.", - required=True, - callback=validate_version_code, + required=True ) @click.option( "--cdk-app-path", diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stack.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stack.py index 4af2b71..2d5e7c2 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stack.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stack.py @@ -23,6 +23,9 @@ from aws_solutions.cdk.interfaces import TemplateOptions from aws_solutions.cdk.mappings import Mappings +from cdk_nag import NagSuppressions +from cdk_nag import NagPackSuppression + RE_SOLUTION_ID = re.compile(r"^SO\d+$") RE_TEMPLATE_FILENAME = re.compile(r"^[a-z]+(?:-[a-z]+)*\.template$") # NOSONAR @@ -51,6 +54,15 @@ def visit(self, node: IConstruct): if node == self.stack: self.stack.metrics = Metrics(self.stack, "Metrics", self.stack.metrics) + NagSuppressions.add_resource_suppressions(self.stack.metrics, [ + NagPackSuppression( + id='AwsSolutions-IAM5', + reason='All IAM policies defined in this solution' + 'grant only least-privilege permissions. Wild ' + 'card for resources is used only for services ' + 'which do not have a resource arn')], + apply_to_children=True) + class SolutionStack(Stack): def __init__( diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solutionstep.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solutionstep.py index 7683502..415bb8b 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solutionstep.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solutionstep.py @@ -123,7 +123,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs): if libraries and any(not l.exists() for l in libraries): raise ValueError(f"libraries provided, but do not exist at {libraries}") - function = kwargs.pop("function") + function_name = kwargs.pop("function") kwargs["layers"] = kwargs.get("layers", []) kwargs["tracing"] = Tracing.ACTIVE kwargs["timeout"] = Duration.seconds(15) @@ -133,7 +133,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs): scope, construct_id, entrypoint, - function, + function_name, libraries=libraries, **kwargs, ) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py index 705a90a..13b7cd5 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py @@ -19,7 +19,7 @@ from dataclasses import dataclass, field from fileinput import FileInput from pathlib import Path -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Iterable import jsii from aws_cdk import DefaultStackSynthesizer, IStackSynthesizer, ISynthesisSession @@ -273,10 +273,10 @@ def _template_names(self, session: ISynthesisSession) -> List[Path]: templates.append(assembly_output_path.joinpath(child_template)) return templates - def _templates(self, session: ISynthesisSession) -> Tuple[Path, Dict]: + def _templates(self, session: ISynthesisSession) -> Iterable[Tuple[Path, Dict]]: assembly_output_path = Path(session.assembly.outdir) - assets = {} + try: assets = json.loads(next(assembly_output_path.glob(self._stack.stack_name + "*.assets.json")).read_text()) except StopIteration: diff --git a/source/cdk_solution_helper_py/helpers_cdk/setup.py b/source/cdk_solution_helper_py/helpers_cdk/setup.py index 546de5a..f59c70b 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/setup.py +++ b/source/cdk_solution_helper_py/helpers_cdk/setup.py @@ -50,7 +50,7 @@ def get_version(): }, install_requires=[ "pip>=22.3.1", - "aws_cdk_lib==2.75.0", + "aws_cdk_lib==2.88.0", "Click==8.1.3", "boto3==1.26.47", "requests==2.31.0", diff --git a/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/config.py b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/config.py index b9dde60..7349c48 100644 --- a/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/config.py +++ b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/config.py @@ -23,9 +23,6 @@ SOLUTION_ID_RE = re.compile(r"^SO(?P\d+)(?P[a-zA-Z]*)$") # NOSONAR -SOLUTION_VERSION_RE = re.compile( - r"^v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" # NOSONAR -) class SolutionConfigEnv: @@ -55,7 +52,7 @@ class Config: """Stores information about the current solution""" id = SolutionConfigEnv("SOLUTION_ID", regex=SOLUTION_ID_RE) - version = SolutionConfigEnv("SOLUTION_VERSION", regex=SOLUTION_VERSION_RE) + version = SolutionConfigEnv("SOLUTION_VERSION", regex=None) _botocore_config = None @property diff --git a/source/cdk_solution_helper_py/requirements-dev.txt b/source/cdk_solution_helper_py/requirements-dev.txt index 5dc819e..5637c22 100644 --- a/source/cdk_solution_helper_py/requirements-dev.txt +++ b/source/cdk_solution_helper_py/requirements-dev.txt @@ -1,17 +1,17 @@ -aws_cdk_lib==2.75.0 -aws-cdk.aws-servicecatalogappregistry-alpha==2.75.0a0 +aws_cdk_lib==2.88.0 +aws-cdk.aws-servicecatalogappregistry-alpha==2.88.0a0 black boto3==1.26.47 requests==2.31.0 crhelper==2.0.11 -Click -moto +Click~=8.1.3 +moto~=2.3.0 pipenv -poetry -pytest>=7.2.0 +poetry~=1.6.1 +pytest>=7.4.2 pytest-cov>=4.0.0 pytest-mock>=3.10.0 -tox +tox~=2.9.1 tox-pyenv -e ./source/cdk_solution_helper_py/helpers_cdk -e ./source/cdk_solution_helper_py/helpers_common diff --git a/source/images/solution-architecture.jpg b/source/images/solution-architecture.jpg deleted file mode 100644 index 7634f22bb2da94ea734ad0aea7693a3a25d4e9df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324249 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