From 6304107d4a8af549ad9c56ed6a4cc75a72909ac3 Mon Sep 17 00:00:00 2001 From: thyagram-aws Date: Mon, 30 Jan 2023 09:38:32 -0800 Subject: [PATCH 1/3] inital commit --- .cfnnag_global_disable | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 52 ++ .github/ISSUE_TEMPLATE/feature_request.md | 17 + .github/PULL_REQUEST_TEMPLATE.md | 5 + .gitignore | 74 ++ .viperlightignore | 18 + .viperlightrc | 4 + CHANGELOG.md | 12 + CODE_OF_CONDUCT.md | 4 + CONTRIBUTING.md | 62 ++ Config | 26 + LICENSE.txt | 175 ++++ NOTICE.txt | 87 ++ README.md | 151 +++ buildspec.yml | 56 ++ deployment/run-unit-tests.sh | 91 ++ deployment/venv_check.py | 10 + docs/ConstructModel.drawio | 1 + docs/DataConnectors.drawio | 1 + docs/Databrew-Recipe-Samples/README.md | 21 + .../deterministic-encrypt/recipe.json | 11 + .../deterministic-encrypt/sample-data.csv | 31 + .../transformed-sample-data.csv | 31 + .../encrypt/recipe.json | 11 + .../encrypt/sample-data.csv | 31 + .../encrypt/transformed-sample-data.csv | 31 + docs/Databrew-Recipe-Samples/hash/recipe.json | 12 + .../hash/sample-data.csv | 31 + .../hash/transformed-sample-data.csv | 31 + .../redact-mask-date/recipe.json | 13 + .../redact-mask-date/sample-data.csv | 31 + .../transformed-sample-data.csv | 31 + .../redact-mask-range/recipe.json | 15 + .../redact-mask-range/sample-data.csv | 31 + .../transformed-sample-data.csv | 31 + .../substitute/recipe.json | 12 + .../substitute/sample-data.csv | 31 + .../substitute/transformed-sample-data.csv | 31 + .../transform-sample-recipe.json | 97 ++ docs/Use-Cases.drawio | 76 ++ docs/Use-Cases.jpg | Bin 0 -> 37699 bytes docs/architecture-1.jpg | Bin 0 -> 197906 bytes docs/architecture-2.jpg | Bin 0 -> 166180 bytes sonar-project.properties | 53 ++ source/.coveragerc | 15 + .../automatic_brew_job_launch/__init__.py | 0 .../lambda_function.py | 147 +++ source/aws_lambda/brew_run_job/__init__.py | 12 + .../brew_run_job/lambda_function.py | 66 ++ source/aws_lambda/connectors/__init__.py | 12 + .../connectors/salesforce/__init__.py | 12 + .../salesforce/connector_profile.py | 100 ++ source/aws_lambda/custom_resource/__init__.py | 12 + .../custom_resource/salesforce/__init__.py | 12 + .../salesforce/connector_profile.py | 84 ++ .../salesforce/requirements.txt | 3 + .../string_manipulation/__init__.py | 12 + .../string_manipulation/requirements.txt | 1 + .../string_manipulation/string_manip.py | 44 + .../custom_resource/transform/__init__.py | 12 + .../custom_resource/transform/empty-file.txt | 0 .../transform/recipe_from_s3.py | 137 +++ .../transform/requirements.txt | 2 + source/aws_lambda/setup.py | 40 + .../aws_lambda/shared/util/requirements.txt | 1 + source/aws_lambda/shared/util/setup.py | 38 + .../aws_lambda/shared/util/shared/__init__.py | 12 + .../shared/util/shared/connectors/__init__.py | 12 + .../shared/connectors/salesforce/__init__.py | 12 + .../shared/connectors/salesforce/connector.py | 128 +++ .../shared/connectors/salesforce/token.py | 61 ++ .../shared/util/shared/requirements.txt | 1 + .../util/shared/secrets_manager/__init__.py | 28 + .../util/shared/stepfunctions/__init__.py | 62 ++ .../step_function_call_back/__init__.py | 14 + .../lambda_function.py | 53 ++ source/cdk_solution_helper_py/CHANGELOG.md | 14 + source/cdk_solution_helper_py/README.md | 122 +++ .../helpers_cdk/aws_solutions/cdk/__init__.py | 41 + .../helpers_cdk/aws_solutions/cdk/aspects.py | 29 + .../aws_solutions/cdk/aws_lambda/__init__.py | 12 + .../cfn_custom_resources/__init__.py | 12 + .../resource_hash/__init__.py | 16 + .../resource_hash/hash.py | 84 ++ .../resource_hash/src/__init__.py | 12 + .../src/custom_resources/__init__.py | 12 + .../src/custom_resources/hash.py | 89 ++ .../src/custom_resources/requirements.txt | 1 + .../resource_name/__init__.py | 16 + .../resource_name/name.py | 90 ++ .../resource_name/src/__init__.py | 12 + .../src/custom_resources/__init__.py | 12 + .../src/custom_resources/name.py | 74 ++ .../src/custom_resources/requirements.txt | 1 + .../solutions_metrics/__init__.py | 16 + .../solutions_metrics/metrics.py | 102 +++ .../solutions_metrics/src/__init__.py | 12 + .../src/custom_resources/__init__.py | 12 + .../src/custom_resources/metrics.py | 81 ++ .../src/custom_resources/requirements.txt | 2 + .../cdk/aws_lambda/environment.py | 48 + .../cdk/aws_lambda/environment_variable.py | 31 + .../cdk/aws_lambda/java/__init__.py | 12 + .../cdk/aws_lambda/java/bundling.py | 117 +++ .../cdk/aws_lambda/java/function.py | 117 +++ .../cdk/aws_lambda/layers/__init__.py | 12 + .../layers/aws_lambda_powertools/__init__.py | 16 + .../layers/aws_lambda_powertools/layer.py | 34 + .../requirements/requirements.txt | 1 + .../cdk/aws_lambda/python/__init__.py | 12 + .../cdk/aws_lambda/python/bundling.py | 224 +++++ .../cdk/aws_lambda/python/function.py | 191 ++++ .../cdk/aws_lambda/python/layer.py | 85 ++ .../helpers_cdk/aws_solutions/cdk/cfn_nag.py | 59 ++ .../helpers_cdk/aws_solutions/cdk/context.py | 84 ++ .../aws_solutions/cdk/helpers/__init__.py | 14 + .../aws_solutions/cdk/helpers/copytree.py | 57 ++ .../aws_solutions/cdk/helpers/loader.py | 94 ++ .../aws_solutions/cdk/helpers/logger.py | 35 + .../aws_solutions/cdk/interfaces.py | 115 +++ .../helpers_cdk/aws_solutions/cdk/mappings.py | 55 ++ .../aws_solutions/cdk/scripts/__init__.py | 12 + .../cdk/scripts/build_s3_cdk_dist.py | 401 ++++++++ .../helpers_cdk/aws_solutions/cdk/stack.py | 78 ++ .../cdk/stepfunctions/__init__.py | 12 + .../cdk/stepfunctions/solution_fragment.py | 82 ++ .../cdk/stepfunctions/solutionstep.py | 145 +++ .../aws_solutions/cdk/synthesizers.py | 305 +++++++ .../aws_solutions/cdk/tools/__init__.py | 14 + .../aws_solutions/cdk/tools/cleaner.py | 77 ++ .../helpers_cdk/setup.py | 78 ++ .../aws_solutions/core/__init__.py | 24 + .../aws_solutions/core/config.py | 75 ++ .../aws_solutions/core/helpers.py | 90 ++ .../aws_solutions/core/logging.py | 58 ++ .../helpers_common/setup.py | 63 ++ .../requirements-dev.txt | 17 + source/images/solution-architecture.jpg | Bin 0 -> 324249 bytes source/infrastructure/__init__.py | 12 + source/infrastructure/cdk.json | 12 + .../data_connectors/__init__.py | 12 + .../data_connectors/app_registry.py | 95 ++ .../data_connectors/appflow_pull_stack.py | 26 + .../automatic_databrew_job_launch.py | 174 ++++ .../data_connectors/aws_lambda/__init__.py | 16 + .../aws_lambda/layers/__init__.py | 14 + .../layers/aws_solutions/__init__.py | 14 + .../aws_lambda/layers/aws_solutions/layer.py | 34 + .../requirements/requirements.txt | 4 + .../data_connectors/base_connector_stack.py | 174 ++++ .../data_connectors/connector_buckets.py | 210 +++++ .../google_analytics_pull_stack.py | 41 + .../data_connectors/orchestration/__init__.py | 12 + .../orchestration/async_callback_construct.py | 194 ++++ .../orchestration/stepfunctions/__init__.py | 12 + .../orchestration/stepfunctions/base.py | 167 ++++ .../stepfunctions/workflows/__init__.py | 12 + .../workflows/salesforce_workflow.py | 90 ++ .../data_connectors/s3_push_stack.py | 65 ++ .../data_connectors/salesforce_pull_stack.py | 654 +++++++++++++ .../data_connectors/transform/__init__.py | 12 + .../transform/databrew_transform.py | 856 ++++++++++++++++++ .../data_connectors/transform/sample-file.txt | 1 + source/infrastructure/deploy.py | 61 ++ source/infrastructure/setup.py | 53 ++ source/pytest.ini | 13 + source/requirements-dev.txt | 28 + source/tests/__init__.py | 12 + .../test_lambda_function.py | 120 +++ .../test_brew_run_job_lambda_function.py | 107 +++ .../salesforce/test_connector_profile.py | 95 ++ .../test_custom_resource_connector_profile.py | 78 ++ .../transform/test_recipe_from_s3.py | 117 +++ .../test_call_back_lambda_function.py | 80 ++ source/tests/conftest.py | 88 ++ .../infrastructure/data_connectors/cdk.json | 12 + .../workflows/test_salesforce_workflow.py | 12 + .../orchestration/test_async_callback.py | 182 ++++ .../test_base_connector_stack.py | 91 ++ .../data_connectors/test_connector_buckets.py | 115 +++ .../test_google_analytics_pull_stack.py | 52 ++ .../data_connectors/test_s3_push_stack.py | 75 ++ .../test_salesforce_pull_stack.py | 103 +++ 183 files changed, 10910 insertions(+) create mode 100644 .cfnnag_global_disable create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .viperlightignore create mode 100644 .viperlightrc create mode 100755 CHANGELOG.md create mode 100755 CODE_OF_CONDUCT.md create mode 100755 CONTRIBUTING.md create mode 100644 Config create mode 100755 LICENSE.txt create mode 100755 NOTICE.txt create mode 100755 README.md create mode 100755 buildspec.yml create mode 100755 deployment/run-unit-tests.sh create mode 100644 deployment/venv_check.py create mode 100644 docs/ConstructModel.drawio create mode 100644 docs/DataConnectors.drawio create mode 100644 docs/Databrew-Recipe-Samples/README.md create mode 100644 docs/Databrew-Recipe-Samples/deterministic-encrypt/recipe.json create mode 100644 docs/Databrew-Recipe-Samples/deterministic-encrypt/sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/deterministic-encrypt/transformed-sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/encrypt/recipe.json create mode 100644 docs/Databrew-Recipe-Samples/encrypt/sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/encrypt/transformed-sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/hash/recipe.json create mode 100644 docs/Databrew-Recipe-Samples/hash/sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/hash/transformed-sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/redact-mask-date/recipe.json create mode 100644 docs/Databrew-Recipe-Samples/redact-mask-date/sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/redact-mask-date/transformed-sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/redact-mask-range/recipe.json create mode 100644 docs/Databrew-Recipe-Samples/redact-mask-range/sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/redact-mask-range/transformed-sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/substitute/recipe.json create mode 100644 docs/Databrew-Recipe-Samples/substitute/sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/substitute/transformed-sample-data.csv create mode 100644 docs/Databrew-Recipe-Samples/transform-sample-recipe.json create mode 100644 docs/Use-Cases.drawio create mode 100644 docs/Use-Cases.jpg create mode 100644 docs/architecture-1.jpg create mode 100644 docs/architecture-2.jpg create mode 100644 sonar-project.properties create mode 100644 source/.coveragerc create mode 100644 source/aws_lambda/automatic_brew_job_launch/__init__.py create mode 100644 source/aws_lambda/automatic_brew_job_launch/lambda_function.py create mode 100644 source/aws_lambda/brew_run_job/__init__.py create mode 100644 source/aws_lambda/brew_run_job/lambda_function.py create mode 100644 source/aws_lambda/connectors/__init__.py create mode 100644 source/aws_lambda/connectors/salesforce/__init__.py create mode 100644 source/aws_lambda/connectors/salesforce/connector_profile.py create mode 100644 source/aws_lambda/custom_resource/__init__.py create mode 100644 source/aws_lambda/custom_resource/salesforce/__init__.py create mode 100644 source/aws_lambda/custom_resource/salesforce/connector_profile.py create mode 100644 source/aws_lambda/custom_resource/salesforce/requirements.txt create mode 100644 source/aws_lambda/custom_resource/string_manipulation/__init__.py create mode 100644 source/aws_lambda/custom_resource/string_manipulation/requirements.txt create mode 100644 source/aws_lambda/custom_resource/string_manipulation/string_manip.py create mode 100644 source/aws_lambda/custom_resource/transform/__init__.py create mode 100644 source/aws_lambda/custom_resource/transform/empty-file.txt create mode 100644 source/aws_lambda/custom_resource/transform/recipe_from_s3.py create mode 100644 source/aws_lambda/custom_resource/transform/requirements.txt create mode 100644 source/aws_lambda/setup.py create mode 100644 source/aws_lambda/shared/util/requirements.txt create mode 100644 source/aws_lambda/shared/util/setup.py create mode 100644 source/aws_lambda/shared/util/shared/__init__.py create mode 100644 source/aws_lambda/shared/util/shared/connectors/__init__.py create mode 100644 source/aws_lambda/shared/util/shared/connectors/salesforce/__init__.py create mode 100644 source/aws_lambda/shared/util/shared/connectors/salesforce/connector.py create mode 100644 source/aws_lambda/shared/util/shared/connectors/salesforce/token.py create mode 100644 source/aws_lambda/shared/util/shared/requirements.txt create mode 100644 source/aws_lambda/shared/util/shared/secrets_manager/__init__.py create mode 100644 source/aws_lambda/shared/util/shared/stepfunctions/__init__.py create mode 100644 source/aws_lambda/step_function_call_back/__init__.py create mode 100644 source/aws_lambda/step_function_call_back/lambda_function.py create mode 100644 source/cdk_solution_helper_py/CHANGELOG.md create mode 100644 source/cdk_solution_helper_py/README.md create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aspects.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/hash.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/hash.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/requirements.txt create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/name.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/name.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/requirements.txt create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/metrics.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/metrics.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/requirements.txt create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment_variable.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/bundling.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/function.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/layer.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/bundling.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/function.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/layer.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/cfn_nag.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/context.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/copytree.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/loader.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/logger.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/interfaces.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/mappings.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/build_s3_cdk_dist.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stack.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solution_fragment.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solutionstep.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/cleaner.py create mode 100644 source/cdk_solution_helper_py/helpers_cdk/setup.py create mode 100644 source/cdk_solution_helper_py/helpers_common/aws_solutions/core/__init__.py create mode 100644 source/cdk_solution_helper_py/helpers_common/aws_solutions/core/config.py create mode 100644 source/cdk_solution_helper_py/helpers_common/aws_solutions/core/helpers.py create mode 100644 source/cdk_solution_helper_py/helpers_common/aws_solutions/core/logging.py create mode 100644 source/cdk_solution_helper_py/helpers_common/setup.py create mode 100644 source/cdk_solution_helper_py/requirements-dev.txt create mode 100644 source/images/solution-architecture.jpg create mode 100644 source/infrastructure/__init__.py create mode 100644 source/infrastructure/cdk.json create mode 100644 source/infrastructure/data_connectors/__init__.py create mode 100644 source/infrastructure/data_connectors/app_registry.py create mode 100644 source/infrastructure/data_connectors/appflow_pull_stack.py create mode 100644 source/infrastructure/data_connectors/automatic_databrew_job_launch.py create mode 100644 source/infrastructure/data_connectors/aws_lambda/__init__.py create mode 100644 source/infrastructure/data_connectors/aws_lambda/layers/__init__.py create mode 100644 source/infrastructure/data_connectors/aws_lambda/layers/aws_solutions/__init__.py create mode 100644 source/infrastructure/data_connectors/aws_lambda/layers/aws_solutions/layer.py create mode 100644 source/infrastructure/data_connectors/aws_lambda/layers/aws_solutions/requirements/requirements.txt create mode 100644 source/infrastructure/data_connectors/base_connector_stack.py create mode 100644 source/infrastructure/data_connectors/connector_buckets.py create mode 100644 source/infrastructure/data_connectors/google_analytics_pull_stack.py create mode 100644 source/infrastructure/data_connectors/orchestration/__init__.py create mode 100644 source/infrastructure/data_connectors/orchestration/async_callback_construct.py create mode 100644 source/infrastructure/data_connectors/orchestration/stepfunctions/__init__.py create mode 100644 source/infrastructure/data_connectors/orchestration/stepfunctions/base.py create mode 100644 source/infrastructure/data_connectors/orchestration/stepfunctions/workflows/__init__.py create mode 100644 source/infrastructure/data_connectors/orchestration/stepfunctions/workflows/salesforce_workflow.py create mode 100644 source/infrastructure/data_connectors/s3_push_stack.py create mode 100644 source/infrastructure/data_connectors/salesforce_pull_stack.py create mode 100644 source/infrastructure/data_connectors/transform/__init__.py create mode 100644 source/infrastructure/data_connectors/transform/databrew_transform.py create mode 100644 source/infrastructure/data_connectors/transform/sample-file.txt create mode 100644 source/infrastructure/deploy.py create mode 100644 source/infrastructure/setup.py create mode 100644 source/pytest.ini create mode 100644 source/requirements-dev.txt create mode 100644 source/tests/__init__.py create mode 100644 source/tests/aws_lambda/automatic_brew_job_launch/test_lambda_function.py create mode 100644 source/tests/aws_lambda/brew_run_job/test_brew_run_job_lambda_function.py create mode 100644 source/tests/aws_lambda/connectors/salesforce/test_connector_profile.py create mode 100644 source/tests/aws_lambda/custom_resource/salesforce/test_custom_resource_connector_profile.py create mode 100644 source/tests/aws_lambda/custom_resource/transform/test_recipe_from_s3.py create mode 100644 source/tests/aws_lambda/step_function_call_back/test_call_back_lambda_function.py create mode 100644 source/tests/conftest.py create mode 100644 source/tests/infrastructure/data_connectors/cdk.json create mode 100644 source/tests/infrastructure/data_connectors/orchestration/stepfunctions/workflows/test_salesforce_workflow.py create mode 100644 source/tests/infrastructure/data_connectors/orchestration/test_async_callback.py create mode 100644 source/tests/infrastructure/data_connectors/test_base_connector_stack.py create mode 100644 source/tests/infrastructure/data_connectors/test_connector_buckets.py create mode 100644 source/tests/infrastructure/data_connectors/test_google_analytics_pull_stack.py create mode 100644 source/tests/infrastructure/data_connectors/test_s3_push_stack.py create mode 100644 source/tests/infrastructure/data_connectors/test_salesforce_pull_stack.py diff --git a/.cfnnag_global_disable b/.cfnnag_global_disable new file mode 100644 index 0000000..4e018e2 --- /dev/null +++ b/.cfnnag_global_disable @@ -0,0 +1 @@ +using cdk-nag instead of cfn-nag - jtthario@amazon.com diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..909f3f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,52 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Please complete the following information about the solution:** +- [ ] Version: [e.g. v1.0.0] + +To get the version of the solution, you can look at the description of the created CloudFormation stack. For example, "_(SO0021) - Video On Demand workflow with AWS Step Functions, MediaConvert, MediaPackage, S3, CloudFront and DynamoDB. Version **v5.0.0**_". If you have not yet installed the stack, or are unable to install due to a problem, you can find the version and solution ID in the template with a text editor. Open the .template file and search for `SOLUTION_VERSION` in the content. You will find several matches and they will all be the same value: + +```json + "Environment": { + "Variables": { + "SOLUTION_ID": "SO0221", + "SOLUTION_VERSION": "v1.0.0" + } + }, +``` + +This information is also provided in `source/infrastructure/cdk.json`: + +```json + "SOLUTION_ID": "SO0221", + "SOLUTION_VERSION": "v1.0.0", +``` + + + +- [ ] Region: [e.g. us-east-1] +- [ ] Was the solution modified from the version published on this repository? +- [ ] If the answer to the previous question was yes, are the changes available on GitHub? +- [ ] Have you checked your [service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) for the sevices this solution uses? +- [ ] Were there any errors in the CloudWatch Logs? + +**Screenshots** +If applicable, add screenshots to help explain your problem (please **DO NOT include sensitive information**). + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..d3d209f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this solution +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the feature you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..de50e4d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +*Issue #, if available:* + +*Description of changes:* + +By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec02e2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# Modified based on https://www.gitignore.io/api/visualstudiocode,python + +# compiled output +**/global-s3-assets +**/regional-s3-assets +**/build-s3-assets +**/open-source +**/tmp + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Python Distribution / packaging +*.egg-info/ +*.egg + +# Python Virtual Environments +**/venv* +**/.venv* +!deployment/venv_check.py +.python-version + +## Python Testing +**/.pytest_cache +**/.coverage +**/coverage-reports/ +**/.coverage* + +# linting, scanning configurations, sonarqube +.scannerwork/ + +### VisualStudioCode ### +.vscode/* + +### IntelliJ/ PyCharm ### +**/.idea/* + +# System Files +**/.DS_Store + +# CDK +**/cdk.out + +# Glue +.glue/* + +# Generated test assets +source/infrastructure/tests/assets/* +!source/infrastructure/tests/assets/.keep +source/scheduler/cdk/aws_solutions/scheduler/cdk/aws_lambda/get_next_scheduled_event/build +source/scheduler/cdk/aws_solutions/scheduler/cdk/aws_lambda/get_next_scheduled_event/.gradle +source/scheduler/cdk/aws_solutions/scheduler/cdk/aws_lambda/get_next_scheduled_event/.idea + +# gradle build files +**/.gradle/* + +# java build files +**/java/**/build + +# python build files +source/cdk_solution_helper_py/helpers_cdk/build/* +source/cdk_solution_helper_py/helpers_common/build/* +source/scheduler/common/build/* +source/scheduler/cdk/build/* +source/aws_lambda/shared/util/build/* + +# various temporary file extensions +*.bkp +*.tmp +*.sav +*.dtmp diff --git a/.viperlightignore b/.viperlightignore new file mode 100644 index 0000000..8fab093 --- /dev/null +++ b/.viperlightignore @@ -0,0 +1,18 @@ +# ignore Config used with code.amazon.com +Config + +# these are email addresses used in functional test +tests/ci/taskcat1.yml +.venv +cdk.out +source/infrastructure/regional-s3-assets + +# all sample data that intentionally includes fake PII +docs/Databrew-Recipe-Samples + +[python-pipoutdated] +aws-lambda=0.2.0 +infrastructure=1.0.0 +aws-sam-cli=1.58.0 +pip=21.1.2 +charset-normalizer=2.0.12 diff --git a/.viperlightrc b/.viperlightrc new file mode 100644 index 0000000..34036e5 --- /dev/null +++ b/.viperlightrc @@ -0,0 +1,4 @@ +{ + "all": true, + "failOn": "medium" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..3e02b20 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Change Log + +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.0.0] - 2023-01-31 + +### Added + +- All files, initial version diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100755 index 0000000..3b64466 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 0000000..ee4bace --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional +documentation, we greatly value feedback and contributions from our community. + +Please read through this document before submitting any issues or pull requests to ensure we have all the necessary +information to effectively respond to your bug report or contribution. + + +## Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features. + +When filing an issue, please check [existing open](https://github.com/aws-solutions/data-connectors-for-aws-clean-rooms/issues), or [recently closed](https://github.com/aws-solutions/data-connectors-for-aws-clean-rooms/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already +reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +* A reproducible test case or series of steps +* The version of our code being used +* Any modifications you've made relevant to the bug +* Anything unusual about your environment or deployment + + +## Contributing via Pull Requests +Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: + +1. You are working against the latest source on the *main* branch. +2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +3. You open an issue to discuss any significant work - we would hate for your time to be wasted. + +To send us a pull request, please: + +1. Fork the repository. +2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +3. Ensure all build processes execute successfully (see README.md for additional guidance). +4. Ensure all unit, integration, and/or snapshot tests pass, as applicable. +5. Commit to your fork using clear commit messages. +6. Send us a pull request, answering any default questions in the pull request interface. +7. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + +GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and +[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). + + +## Finding contributions to work on +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-solutions/data-connectors-for-aws-clean-rooms/labels/help%20wanted) issues is a great place to start. + + +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. + + +## Security issue notifications +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue. + + +## Licensing + +See the [LICENSE](https://github.com/aws-solutions/data-connectors-for-aws-clean-rooms/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. + +We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/Config b/Config new file mode 100644 index 0000000..f21d0fd --- /dev/null +++ b/Config @@ -0,0 +1,26 @@ +package.Solution-adtech-madison-connectors = { + interfaces = (1.0); + + # Use NoOpBuild. See https://w.amazon.com/index.php/BrazilBuildSystem/NoOpBuild + build-system = no-op; + build-tools = { + 1.0 = { + NoOpBuild = 1.0; + }; + }; + + # Use runtime-dependencies for when you want to bring in additional + # packages when deploying. + # Use dependencies instead if you intend for these dependencies to + # be exported to other packages that build against you. + dependencies = { + 1.0 = { + }; + }; + + runtime-dependencies = { + 1.0 = { + }; + }; + +}; diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100755 index 0000000..19dc35b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100755 index 0000000..2b65339 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,87 @@ +Data Connectors for AWS Clean Rooms + +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. A copy of the License is located at http://www.apache.org/licenses/ +or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the +specific language governing permissions and limitations under the License. + +********************** +THIRD PARTY COMPONENTS +********************** +This software includes third party software subject to the following copyrights: + + Name Version License + Jinja2 3.1.2 BSD License + MarkupSafe 2.1.1 BSD License + PyYAML 6.0 MIT License + Werkzeug 2.1.2 BSD License + attrs 22.1.0 MIT License + avro 1.11.1 Apache Software License + aws-cdk-lib 2.51.0 Apache-2.0 + aws-cdk.asset-awscli-v1 2.2.11 Apache-2.0 + aws-cdk.asset-kubectl-v20 2.1.1 Apache-2.0 + aws-cdk.asset-node-proxy-agent-v5 2.0.17 Apache-2.0 + aws-lambda 0.2.0 Apache Software License + aws-lambda-powertools 1.31.0 MIT License; Other/Proprietary License + aws-solutions-cdk 2.0.0 Apache Software License + aws-solutions-constructs.aws-eventbridge-lambda 2.25.0 Apache-2.0 + aws-solutions-constructs.aws-lambda-dynamodb 2.25.0 Apache-2.0 + aws-solutions-constructs.aws-lambda-sns 2.25.0 Apache-2.0 + aws-solutions-constructs.core 2.25.0 Apache-2.0 + aws-solutions-python 2.0.0 Apache Software License + aws-xray-sdk 2.11.0 Apache Software License + black 22.10.0 MIT License + boto3 1.26.12 Apache Software License + botocore 1.29.12 Apache Software License + cattrs 22.2.0 MIT License + cdk-nag 2.18.47 Apache-2.0 + certifi 2022.9.24 Mozilla Public License 2.0 (MPL 2.0) + cffi 1.15.1 MIT License + charset-normalizer 2.0.12 MIT License + click 8.1.3 BSD License + constructs 10.1.163 Apache-2.0 + coverage 6.5.0 Apache Software License + crhelper 2.0.11 Apache Software License + cronex 0.1.3.1 MIT License + cryptography 38.0.3 Apache Software License; BSD License + docker 6.0.0 Apache Software License + exceptiongroup 1.0.4 MIT License + fastjsonschema 2.16.2 BSD License + idna 3.4 BSD License + infrastructure 1.0.0 Apache Software License + iniconfig 1.1.1 MIT License + jmespath 1.0.1 MIT License + jsii 1.71.0 Apache Software License + moto 4.0.3 Apache Software License + mypy-extensions 0.4.3 MIT License + packaging 21.3 Apache Software License; BSD License + parsedatetime 2.6 Apache Software License + pathspec 0.10.2 Mozilla Public License 2.0 (MPL 2.0) + platformdirs 2.5.4 MIT License + pluggy 1.0.0 MIT License + publication 0.0.3 MIT License + pycparser 2.21 BSD License + pyparsing 3.0.9 MIT License + pytest 7.2.0 MIT License + pytest-cov 4.0.0 MIT License + pytest-env 0.8.1 MIT License + pytest-mock 3.10.0 MIT License + python-dateutil 2.8.2 Apache Software License; BSD License + pytz 2022.6 MIT License + requests 2.28.1 Apache Software License + responses 0.17.0 Apache 2.0 + s3transfer 0.6.0 Apache Software License + shared 0.0.0 Apache Software License + six 1.16.0 MIT License + tenacity 8.1.0 Apache Software License + tomli 2.0.1 MIT License + typeguard 2.13.3 MIT License + typing_extensions 4.4.0 Python Software Foundation License + urllib3 1.26.12 MIT License + websocket-client 1.4.2 Apache Software License + wrapt 1.14.1 BSD License + xmltodict 0.13.0 MIT License + wheel 0.38.4 MIT License + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..2859901 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# Data Connectors for AWS Clean Rooms + +Data Connectors for AWS Clean Rooms + +## Table of Contents + + +## Architecture + +The following image shows the architecture of the solution for pulling data from a provider using Amazon AppFlow. + +![Pull Data Through AppFlow](docs/architecture-1.jpg) + +The following image shows the architecture of the solution for pushing S3 data from a provider into an S3 bucket. + +![Push Data to S3 Bucket](docs/architecture-2.jpg) + + +**Note**: From v1.0.0, AWS CloudFormation template resources are created by the [AWS CDK](https://aws.amazon.com/cdk/) +and [AWS Solutions Constructs](https://aws.amazon.com/solutions/constructs/). + +### AWS CDK Constructs + +[AWS CDK Solutions Constructs](https://aws.amazon.com/solutions/constructs/) make it easier to consistently create +well-architected applications. All AWS Solutions Constructs are reviewed by AWS and use best practices established by +the AWS Well-Architected Framework. + +## Deployment + +You can launch this solution with one click from [AWS Solutions Implementations](https://aws.amazon.com/solutions/implementations/). + +To customize the solution, or to contribute to the solution, see [Creating a custom build](#creating-a-custom-build) + +## Configuration + + +## Creating a custom build +To customize the solution, follow the steps below: + +### Prerequisites +The following procedures assumes that all the OS-level configuration has been completed. They are: + +* [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.7.0 or newer +* [Amazon Corretto OpenJDK](https://docs.aws.amazon.com/corretto/) 11 + +> **Please ensure you test the templates before updating any production deployments.** + +### 1. Download or clone this repo +``` +git clone https://github.com/aws-solutions/data-connectors-for-aws-clean-rooms +``` + +### 2. Create a Python virtual environment for development +```bash +python -m venv .venv +source ./.venv/bin/activate +cd ./source +pip install -r requirements-dev.txt +``` + +### 2. After introducing changes, run the unit tests to make sure the customizations don't break existing functionality +```bash +pytest --cov +``` + +### 3. Build the solution for deployment + +#### Using AWS CDK (recommended) +Packaging and deploying the solution with the AWS CDK allows for the most flexibility in development +```bash +cd ./source/infrastructure + +# set environment variables required by the solution +export BUCKET_NAME="my-bucket-name" + +# bootstrap CDK (required once - deploys a CDK bootstrap CloudFormation stack for assets) +cdk bootstrap --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess + +# build the solution +cdk synth + +# build and deploy the solution +cdk deploy +``` + +#### Using the solution build tools +It is highly recommended to use the AWS CDK to deploy this solution (using the instructions above). While CDK is used to +develop the solution, to package the solution for release as a CloudFormation template, use the `build-s3-cdk-dist` +build tool: + +```bash +cd ./deployment + +export DIST_BUCKET_PREFIX=my-bucket-name +export SOLUTION_NAME=my-solution-name +export VERSION=my-version +export REGION_NAME=my-region + +build-s3-cdk-dist deploy \ + --source-bucket-name DIST_BUCKET_PREFIX \ + --solution-name SOLUTION_NAME \ + --version_code VERSION \ + --cdk-app-path ../source/infrastructure/deploy.py \ + --cdk-app-entrypoint deploy:build_app \ + --region REGION_NAME \ + --sync +``` + +**Parameter Details** +- `$DIST_BUCKET_PREFIX` - The S3 bucket name prefix. A randomized value is recommended. You will need to create an + S3 bucket where the name is `-`. The solution's CloudFormation template will expect the + source code to be located in the bucket matching that name. +- `$SOLUTION_NAME` - The name of This solution (example: solution-customization) +- `$VERSION` - The version number to use (example: v0.0.1) +- `$REGION_NAME` - The region name to use (example: us-east-1) + +This will result in all global assets being pushed to the `DIST_BUCKET_PREFIX`, and all regional assets being pushed to +`DIST_BUCKET_PREFIX-`. If your `REGION_NAME` is us-east-1, and the `DIST_BUCKET_PREFIX` is +`my-bucket-name`, ensure that both `my-bucket-name` and `my-bucket-name-us-east-1` exist and are owned by you. + +After running the command, you can deploy the template: + +* Get the link of the `SOLUTION_NAME.template` uploaded to your Amazon S3 bucket +* Deploy the solution to your account by launching a new AWS CloudFormation stack using the link of the template above. + +> **Note:** `build-s3-cdk-dist` will use your current configured `AWS_REGION` and `AWS_PROFILE`. To set your defaults, install the [AWS Command Line Interface](https://aws.amazon.com/cli/) and run `aws configure`. + +> **Note:** You can drop `--sync` from the command to only perform the build and synthesis of the template without uploading to a remote location. This is helpful when testing new changes to the code. + +## 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/TBD/collection-of-operational-metrics.html). + +*** + +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. \ No newline at end of file diff --git a/buildspec.yml b/buildspec.yml new file mode 100755 index 0000000..34f6d46 --- /dev/null +++ b/buildspec.yml @@ -0,0 +1,56 @@ +version: 0.2 + +phases: + install: + runtime-versions: + python: 3.9 + commands: + - echo "nothing to do in install" + pre_build: + commands: + - echo "Installing dependencies and executing unit tests - `pwd`" + - cd deployment + - ./run-unit-tests.sh + - echo "Installing dependencies and executing unit tests completed `date`" + build: + commands: + - echo "Starting build `date` in `pwd`" + - . ../.venv-temp/bin/activate + - |- + build-s3-cdk-dist --log-level DEBUG deploy \ + --source-bucket-name $DIST_OUTPUT_BUCKET \ + --solution-name $SOLUTION_NAME \ + --version-code $VERSION \ + --cdk-app-path ../source/infrastructure/deploy.py \ + --cdk-app-entrypoint deploy:build_app + - echo "Build completed `date`" + - echo "Cleanup build generated files that are not needed for next steps/stages of build/pipeline" + - build-s3-cdk-dist clean-for-scan + - echo "Starting source-code-package `date` in `pwd`" + - build-s3-cdk-dist source-code-package --ignore "**/build/*" --solution-name $SOLUTION_NAME + - echo "Source Code Package completed `date`" + post_build: + commands: + - echo "Retrieving next stage buildspec `date` in `pwd`" + - aws s3 cp s3://${SOLUTIONS_BUILD_ASSETS_BUCKET:-solutions-build-assets}/changelog-spec.yml ../buildspec.yml + - echo "Retrieving next stage buildspec complete" + - echo "Post build completed on `date`" + +artifacts: + exclude-paths: + - source/cdk_solution_helper_py/helpers_cdk/build/* + - source/cdk_solution_helper_py/helpers_common/build/* + files: + - .cfnnag_global_disable + - .gitignore + - CHANGELOG.md + - CODE_OF_CONDUCT.md + - CONTRIBUTING.md + - LICENSE.txt + - NOTICE.txt + - README.md + - buildspec.yml + - sonar-project.properties + - .github/**/* + - deployment/**/* + - source/**/* diff --git a/deployment/run-unit-tests.sh b/deployment/run-unit-tests.sh new file mode 100755 index 0000000..599df27 --- /dev/null +++ b/deployment/run-unit-tests.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# +# 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. +# + +# +# This assumes all of the OS-level configuration has been completed and git repo has already been cloned +# +# This script should be run from the repo's deployment directory +# cd deployment +# ./run-unit-tests.sh +# + +[ "$DEBUG" == 'true' ] && set -x +# set -e + +# Get reference for all important folders +template_dir="$PWD" +source_dir="$(cd $template_dir/../source; pwd -P)" +root_dir="$template_dir/.." +venv_folder=".venv-temp" + + +# check if we need a new testing venv, or use active (workstation testing) +python3 ./venv_check.py +if [ $? == 1 ]; then + echo "------------------------------------------------------------------------------" + echo "[Env] Create clean virtual environment and install dependencies" + echo "------------------------------------------------------------------------------" + cd $root_dir + if [ -d $venv_folder ]; then + rm -rf $venv_folder + fi + python3 -m venv $venv_folder + source $venv_folder/bin/activate + using_test_venv=1 + # configure the environment + cd $source_dir + pip install --upgrade pip + pip install -r $source_dir/requirements-dev.txt +else + using_test_venv=0 + echo "------------------------------------------------------------------------------" + echo "[Env] Using active virtual environment for tests" + echo "------------------------------------------------------------------------------" + echo '' +fi + + +echo "------------------------------------------------------------------------------" +echo "[Test] Run pytest with coverage" +echo "------------------------------------------------------------------------------" +cd $source_dir +# setup coverage report path +coverage_report_path=$source_dir/tests/coverage-reports/source.coverage.xml +echo "coverage report path set to $coverage_report_path" + +pytest --cov --cov-report term-missing --cov-report term --cov-report "xml:$coverage_report_path" + +# The pytest --cov with its parameters and .coveragerc generates a xml cov-report with `coverage/sources` list +# with absolute path for the source directories. To avoid dependencies of tools (such as SonarQube) on different +# absolute paths for source directories, this substitution is used to convert each absolute source directory +# path to the corresponding project relative path. The $source_dir holds the absolute path for source directory. +sed -i -e "s,$source_dir,source,g" $coverage_report_path + +if [ $using_test_venv == 1 ]; then + echo "------------------------------------------------------------------------------" + echo "[Env] Deactivating test virtual environment" + echo "------------------------------------------------------------------------------" + echo '' + # deactivate the virtual environment + deactivate +else + echo "------------------------------------------------------------------------------" + echo "[Env] Leaving virtual environment active" + echo "------------------------------------------------------------------------------" + echo '' + +fi + +cd $template_dir + diff --git a/deployment/venv_check.py b/deployment/venv_check.py new file mode 100644 index 0000000..97936e5 --- /dev/null +++ b/deployment/venv_check.py @@ -0,0 +1,10 @@ +""" +This program returns 0 if the current environment is a virtual environment. +""" +import sys + +# compare the python prefixes, same == not venv +IN_VENV = (getattr(sys, "base_prefix", None) or getattr( + sys, "real_prefix", None) or sys.prefix) != sys.prefix +# return success (0) if in a venv +sys.exit(IN_VENV is False) diff --git a/docs/ConstructModel.drawio b/docs/ConstructModel.drawio new file mode 100644 index 0000000..f760b52 --- /dev/null +++ b/docs/ConstructModel.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/DataConnectors.drawio b/docs/DataConnectors.drawio new file mode 100644 index 0000000..4ea5d47 --- /dev/null +++ b/docs/DataConnectors.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/README.md b/docs/Databrew-Recipe-Samples/README.md new file mode 100644 index 0000000..a175584 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/README.md @@ -0,0 +1,21 @@ + +## Databrew recipe samples + +In DataBrew, a recipe is a set of data transformation steps. You can apply these steps to a sample of your data, or apply that same recipe to a dataset. +Even if you're not a programmer, it's helpful to understand the structure of a recipe and how DataBrew organizes the recipe actions. + +The examples present in these folders serve as a quick reference and a guidance on how to construct databrew recipe steps. +The folder names indicate the type of transformation step applied to the original dataset. + +The folder contents are organized as follows: +- Sample data +- Recipe step in json file format +- Transformed data after the application of the recipe step + +Additionally there is also 1 complete recipe file (transform-sample-recipe.json) containing multiple steps. + +You can start building your recipe by adding data transformations to the source columns of your dataset. + +For a complete reference on Recipe steps and function. + +https://docs.aws.amazon.com/databrew/latest/dg/recipe-actions-reference.html diff --git a/docs/Databrew-Recipe-Samples/deterministic-encrypt/recipe.json b/docs/Databrew-Recipe-Samples/deterministic-encrypt/recipe.json new file mode 100644 index 0000000..a7354c4 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/deterministic-encrypt/recipe.json @@ -0,0 +1,11 @@ +[ + { + "Action": { + "Operation": "DETERMINISTIC_ENCRYPT", + "Parameters": { + "secretId": "arn:aws:secretsmanager:us-east-1:0123456789:secret:AwsGlueDataBrew-transform-secret", + "sourceColumns": "[\"address\"]" + } + } + } +] \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/deterministic-encrypt/sample-data.csv b/docs/Databrew-Recipe-Samples/deterministic-encrypt/sample-data.csv new file mode 100644 index 0000000..1221187 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/deterministic-encrypt/sample-data.csv @@ -0,0 +1,31 @@ +address +10932 Bigge Rd +4469 Sherman Street +309 63rd St. #411 +2183 Roy Alley +3181 White Oak Drive +3097 Better Street +539 Kyle Street +570 Nancy Street +1074 Small Street +4222 Bedford Street +3414 Gore Street +515 Hillside Drive +4032 Arron Smith Drive +2865 Driftwood Road +3500 Diane Street +4986 Chapel Street +456 Oral Lake Road +582 Thrash Trail +1620 Maxwell Street +2489 O Conner Street +4696 Retreat Avenue +173 Lunetta Street +1843 Olive Street +4899 University Hill Road +1517 Gambler Lane +4254 Walkers Ridge Way +3396 Nancy Street +4213 High Meadow Lane +2877 Glen Street +784 Beechwood Avenue \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/deterministic-encrypt/transformed-sample-data.csv b/docs/Databrew-Recipe-Samples/deterministic-encrypt/transformed-sample-data.csv new file mode 100644 index 0000000..0241498 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/deterministic-encrypt/transformed-sample-data.csv @@ -0,0 +1,31 @@ +address +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MagAqOtDdqHtmVtaJc0zM7Jc804Flh+iqirCSywi0Q== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MQkCAXyA+la9P0jnpGPLzpu6NG4hLvv7EnyH3NjEc+wUK7Ra +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MUjLHF8Q6IbvQF7npsTDQOlsWWDoEXkeNeKtjI92xi2EEA== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MW5T6PmeDM9Hq+gavH/HqqGtL8T1u46a8YdFIwEHtA== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0McijKYzxxo/plwloWGZ7KjYxZwCv6u9qdcS2Qm/Uarfqz5GqGw== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MTAjW5hTehYetqFjPat8j1mYGTCEEz1FvMQg4YsI8R8WesU= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0Maq/13td+vmcq4R9mJJiukzwhjwmfmJfwRCwzdVURIM= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MU6bpqwR3tMBNr2Tsy79uKtXatzcJ5zhlwL7p5HcGeU1 +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MST92Ipl+Csa4SfesPyQ+WaEo363flcVy+IoNnjKdVaKyQ== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MSoKWyliLRZQmmZqMikpSegmV6zxqJcenqs4Wt8uKogWTjmC +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MZ7EsHD7RfP267wOtwiDEVtzE91PApvSmxR7Limif8a0 +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MYaJS36mLOW+8eHpr6Qz4Kq5wu3wNJBtDMaXeDeA3Oc/3PY= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MSdPiE8wV9aufuCSS0/2vSFgngeqqnx2Zyhh9xRkkW6njdGq6XcJ +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MVUagfKkwwitz9ujgB6XfHB2otB9te+FmOmr/S390Tc7PRUo +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MQC0u4MF8NkZDBl0lEgGhnOm1zN0sdUkS1ijXylgQ8g2cA== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MaG9w/TEe9k6F5G7y0bao9lbBYWS5AM7TRh4rr5Ct36UOTo= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MTBYK+eRL7+ZGIGPRpc7irPcyMYR/rdGNTAFiQGJ3hYuWfo= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MaGUbfT7bUB44xhbOyIESjTDEnExY9/y7QSWWBD6RIPj +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MaAYMeCxljQSDb9NM4Vj6AIm+1CY+CgRo2CtT13GOM9EGN5i +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MZ1ZojtKN4Jgfrxfd3oO1A6tfc22FwzLeO/BVmPCGJApcCFjxg== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MawNgqpYIVrD1zoBvS3hmbDzxhPUQj4K89BgOMcQUdL3k5GG +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MbnpoX5gyvGTa/pmG799evctVvhPU9BH0ou7cfBF68h19eg= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MSIIa37EL7CNKJPki7BJA4iQQKDGldv3pUHwEoFp/uJCHw== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MQLi6Us5yk1ftVsJv5rS4ft2J6H1bF/CENhnlgGJI7HdAWgI+hOGv5/4 +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MSMEs5rMi7VSwDyhUiH7sLPxh+C5CIvJUPWnRk2tHcRjgw== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MUv39QvShkJDxUhn+B1iJhIWEPQX0V4zrt1D6uqbkCwNIYO5jrUU +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0Mf/pGkwpBCUPdtqOsacUjCQ8PyGNkN4+Dkx2tbDSMBRpmA== +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MQrrmyx2+61erEs8o4zUSxVe+V8ah5ul28CBrQLkbvXfIfpgQp8= +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0MWwhaYiEvwKmm1mliWZpHMJKY6jCpcoQv9U/oUmwiVn/ +AQBiYXJuOmF3czpzZWNyZXRzbWFuYWdlcjp1cy1lYXN0LTE6Njg5MzU0Mzc2MzAyOnNlY3JldDpBd3NHbHVlRGF0YUJyZXctZGF0YWJyZXctc2FtcGxlLXNlY3JldC1VWWZUZ1cAJDhiNGY4MTJhLTIzYzgtNDQxYS1hNjkyLTY4MzYxNWYwMDQ0Med+rEONcPPaslU88XU+0uCD1Z+PMdd6EsPTj4hxK+VS8AJ8xg== \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/encrypt/recipe.json b/docs/Databrew-Recipe-Samples/encrypt/recipe.json new file mode 100644 index 0000000..a10cee2 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/encrypt/recipe.json @@ -0,0 +1,11 @@ +[ + { + "Action": { + "Operation": "ENCRYPT", + "Parameters": { + "kmsKeyArn": "arn:aws:kms:us-east-1:123456780:key/", + "sourceColumns": "[\"fname\",\"lname\"]" + } + } + } +] \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/encrypt/sample-data.csv b/docs/Databrew-Recipe-Samples/encrypt/sample-data.csv new file mode 100644 index 0000000..8dcdc40 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/encrypt/sample-data.csv @@ -0,0 +1,31 @@ +lname,fname +White,Johnson +Borden,Ashley +Green,Marjorie +Munsch,Jerome +Aragon,Robert +Russell,Jacki +Venson,Lillian +Conley,Thomas +Jackson,Charles +Davis,Susan +Watson,Gail +Garrison,Lisa +Renfro,Julie +Heard,James +Reyes,Danny +Hall,Mark +Mceachern,Monte +Diaz,Christopher +Lowe,Tim +Oyola,Lynette +Morrison,Adriane +Santos,Thomas +Faulkner,Victor +Iorio,Albert +Kaminski,Teresa +Edwards,Rick +Peacock,Stacey +Nelson,Agnes +Townsend,Mireille +Zwick,Rebecca \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/encrypt/transformed-sample-data.csv b/docs/Databrew-Recipe-Samples/encrypt/transformed-sample-data.csv new file mode 100644 index 0000000..1e11334 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/encrypt/transformed-sample-data.csv @@ -0,0 +1,31 @@ +lname,fname +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 \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/hash/recipe.json b/docs/Databrew-Recipe-Samples/hash/recipe.json new file mode 100644 index 0000000..bad8db5 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/hash/recipe.json @@ -0,0 +1,12 @@ +[ + { + "Action": { + "Operation": "CRYPTOGRAPHIC_HASH", + "Parameters": { + "entityTypeFilter": "[\"PHONE_NUMBER\"]", + "secretId": "arn:aws:secretsmanager:us-east-1:0123456789:secret:AwsGlueDataBrew-transform-secret", + "sourceColumns": "[\"phone\"]" + } + } + } +] \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/hash/sample-data.csv b/docs/Databrew-Recipe-Samples/hash/sample-data.csv new file mode 100644 index 0000000..cf27f48 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/hash/sample-data.csv @@ -0,0 +1,31 @@ +phone +408 496-7223 +785-939-6046 +415 986-7020 +303-901-6123 +816-645-6936 +913-227-6106 +308-583-8759 +919-656-6779 +212-847-4915 +205-221-9156 +713-547-3414 +337-965-2982 +808-560-1638 +408-370-0031 +805-369-0464 +281-597-5517 +952-412-3707 +903-624-9156 +860-755-0293 +228-938-2056 +205-276-1807 +940-859-1393 +419-340-3832 +217-615-6419 +281-906-2148 +626-991-3620 +919-571-2339 +570-480-8704 +270-408-7254 +908-814-6733 \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/hash/transformed-sample-data.csv b/docs/Databrew-Recipe-Samples/hash/transformed-sample-data.csv new file mode 100644 index 0000000..af48107 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/hash/transformed-sample-data.csv @@ -0,0 +1,31 @@ +phone +GBZZZqrDb1FzL8W8PWxIVexzF2Z/M+Bvm3J5ejNUdWA= +/tN302iPpY5AwOFfXBn7KBWd+LFBKlEFhGt8D3bPvEA= +XdJbKoapkFHxoAzKrjDE23idegiAOLyKkTYtxsOWLvk= +undyeAphId4BkpTrXzvQvVZVLuw4rZo8iH8NLnxuJvQ= +hhqlyX9BgH0DcwOmPiBZcJuFsnlk45qLlh96EisxSEo= +d8GznIhNryvejkhYpQ6DcPoPczszNVFpI6DlM6An8+M= +4DsYB7ctPySB7/c6+7nECg9HOR5zL8Qq6z7VIHm+DQ4= +417Mov65o/WT7hlKhM4H/OXscKQFpDGQllQLE/ApV+s= +TnWqj9hssiK0n4QgPwxep0frDiLRK78itd7kWeHWlCI= +wGQhhg8gugj7W5V4bR97yPNU6kOLYjGQqy8uC/Udwac= +3jlIy8S247WMwXnlpwzuLJlvGkHeKyE81nnl/em4FJQ= +4D+unHEA3B8po2Ldjoj4Sp16Uz+dme0J6MsBtCAf4MM= +11jX1IWpRJE3Ere2EK09oD2T3nPwWyliZimAowohF2I= +1CuI+yJcB5FZJ7XdAYeFclVOprbQ8CUebgIoawaY/LI= +4hHOHYZ7xATZtkBnsZKLUZgu7kfWX2bpGl5f0zVHAIs= +5TrCOc+77uQEoqklqiABH/eGLzR3pIef1GAjtuym5dg= +yM8Ko4KgUlTD87XW7Y8qYkNZYZKABX29MWokfTrbrxA= +aCucHVWfgIJX6fxdSnzyGr6XodUee1rY5wZhn9fX0MU= +vDPzR30nUZ1vZ95Y7gEYT9O6wOB6US/3LcTi9aFOo/g= +IoHvoQocnOeIwRfxaBGoQzFRLkgsbgQnx/FDVAeNH2I= +V5Bg/q6SI2j1yxiAuFqhh2tZ/mHcNWtu1UKXLtmypao= +IZY5oN5EupSYi/wwGZRSCBXx5ay0BxDWJ8tX8d4QtIo= +sH4nqNZPaWeijc+MDe34RRd9Jb3PwL3sxlz6fjPHegE= +m4xEViKFBDq/ndnlPV5ullT7QYB1DoUqPktdxMds3zo= +AL1DgPvd+Vx3BAxwZ9bB31qif1rZ5GujWxWnR7kZjpU= +vdJl+R+vKMHLl9vc6SQzjVKyuuhaH1MKP9q/3Kb3gpk= +Hs2lCEfjYMLBMLdmpdF4eZ9ZmjPzy2SvmROIhboIJ2E= +HHec288DY5e+4JRT3xIdUcxysXjNbkSvN48Q/ZbaeRE= ++IO+GghX8nEg5f8NulBmxNeMqNeWsnNM2q28jZcSt/4= +B4nVmgJn77Now7A0u2yrg3MaZYTqMd3eZU1mA6caOPg= \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/redact-mask-date/recipe.json b/docs/Databrew-Recipe-Samples/redact-mask-date/recipe.json new file mode 100644 index 0000000..ba588c8 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/redact-mask-date/recipe.json @@ -0,0 +1,13 @@ +[ + { + "Action": { + "Operation": "MASK_DATE", + "Parameters": { + "locale": "en", + "maskSymbol": "#", + "redact": "[\"YEAR\",\"DAY\"]", + "sourceColumns": "[\"birthdate\"]" + } + } + } +] \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/redact-mask-date/sample-data.csv b/docs/Databrew-Recipe-Samples/redact-mask-date/sample-data.csv new file mode 100644 index 0000000..84c0a05 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/redact-mask-date/sample-data.csv @@ -0,0 +1,31 @@ +birthdate +1958-04-21 +1944-12-22 +1958-04-21 +1962-03-25 +1964-09-06 +1986-05-27 +1963-09-23 +1969-10-02 +1978-01-12 +1980-04-09 +1975-01-04 +1953-07-11 +1968-02-16 +1952-01-20 +1980-01-16 +1982-06-14 +1961-03-10 +1955-09-20 +1967-05-28 +1958-10-24 +1953-07-17 +1950-06-09 +1965-02-10 +1977-08-19 +1964-06-20 +1979-08-18 +1976-05-24 +1950-03-26 +1984-09-21 +1952-11-19 \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/redact-mask-date/transformed-sample-data.csv b/docs/Databrew-Recipe-Samples/redact-mask-date/transformed-sample-data.csv new file mode 100644 index 0000000..3bf36d1 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/redact-mask-date/transformed-sample-data.csv @@ -0,0 +1,31 @@ +birthdate +####/04/## +####/12/## +####/04/## +####/03/## +####/##/06 +####/05/## +####/09/## +####/##/02 +####/##/12 +####/##/09 +####/##/04 +####/##/11 +####/02/## +####/01/## +####/01/## +####/06/## +####/##/10 +####/09/## +####/05/## +####/10/## +####/07/## +####/##/09 +####/##/10 +####/08/## +####/06/## +####/08/## +####/05/## +####/03/## +####/09/## +####/11/## \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/redact-mask-range/recipe.json b/docs/Databrew-Recipe-Samples/redact-mask-range/recipe.json new file mode 100644 index 0000000..e64138c --- /dev/null +++ b/docs/Databrew-Recipe-Samples/redact-mask-range/recipe.json @@ -0,0 +1,15 @@ +[ + { + "Action": { + "Operation": "MASK_RANGE", + "Parameters": { + "alphabet": "[\"WHITESPACE\",\"SYMBOLS\"]", + "firstN": "5", + "maskMode": "MASK_FIRST_N", + "maskSymbol": "#", + "sourceColumns": "[\"email\"]", + "stop": "5" + } + } + } +] \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/redact-mask-range/sample-data.csv b/docs/Databrew-Recipe-Samples/redact-mask-range/sample-data.csv new file mode 100644 index 0000000..4987a97 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/redact-mask-range/sample-data.csv @@ -0,0 +1,31 @@ +email +jwhite@domain.com +aborden@domain.com +mgreen@domain.com +jmunsch@domain.com +raragon@domain.com +jrussell@domain.com +lvenson@domain.com +tconley@domain.com +cjackson@domain.com +sdavis@domain.com +gwatson@domain.com +lgarrison@domain.com +jrenfro@domain.com +jheard@domain.com +dreyes@domain.com +mhall@domain.com +mmceachern@domain.com +cdiaz@domain.com +tlowe@domain.com +loyola@domain.com +amorrison@domain.com +tsantos@domain.com +vfaulkner@domain.com +aiorio@domain.com +tkaminski@domain.com +redwards@domain.com +speacock@domain.com +anelson@domain.com +mtownsend@domain.com +rzwick@domain.com \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/redact-mask-range/transformed-sample-data.csv b/docs/Databrew-Recipe-Samples/redact-mask-range/transformed-sample-data.csv new file mode 100644 index 0000000..6a12acd --- /dev/null +++ b/docs/Databrew-Recipe-Samples/redact-mask-range/transformed-sample-data.csv @@ -0,0 +1,31 @@ +email +#####e@domain.com +#####en@domain.com +#####n@domain.com +#####ch@domain.com +#####on@domain.com +#####ell@domain.com +#####on@domain.com +#####ey@domain.com +#####son@domain.com +#####s@domain.com +#####on@domain.com +#####ison@domain.com +#####ro@domain.com +#####d@domain.com +#####s@domain.com +#####@domain.com +#####chern@domain.com +#####@domain.com +#####@domain.com +#####a@domain.com +#####ison@domain.com +#####os@domain.com +#####kner@domain.com +#####o@domain.com +#####nski@domain.com +#####rds@domain.com +#####ock@domain.com +#####on@domain.com +#####send@domain.com +#####k@domain.com \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/substitute/recipe.json b/docs/Databrew-Recipe-Samples/substitute/recipe.json new file mode 100644 index 0000000..d00adec --- /dev/null +++ b/docs/Databrew-Recipe-Samples/substitute/recipe.json @@ -0,0 +1,12 @@ +[ + { + "Action": { + "Operation": "REPLACE_WITH_RANDOM_BETWEEN", + "Parameters": { + "lowerBound": "100", + "sourceColumns": "[\"cc_cvc\"]", + "upperBound": "600" + } + } + } +] \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/substitute/sample-data.csv b/docs/Databrew-Recipe-Samples/substitute/sample-data.csv new file mode 100644 index 0000000..236aa20 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/substitute/sample-data.csv @@ -0,0 +1,31 @@ +cc_cvc +123 +713 +258 +612 +911 +232 +471 +731 +892 +33 +694 +680 +238 +311 +713 +953 +889 +584 +616 +991 +322 +767 +276 +347 +721 +701 +436 +496 +710 +173 \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/substitute/transformed-sample-data.csv b/docs/Databrew-Recipe-Samples/substitute/transformed-sample-data.csv new file mode 100644 index 0000000..4880468 --- /dev/null +++ b/docs/Databrew-Recipe-Samples/substitute/transformed-sample-data.csv @@ -0,0 +1,31 @@ +cc_cvc +277 +112 +273 +519 +183 +491 +146 +499 +414 +247 +229 +426 +172 +121 +549 +552 +580 +325 +507 +156 +503 +494 +171 +170 +358 +560 +164 +449 +502 +546 \ No newline at end of file diff --git a/docs/Databrew-Recipe-Samples/transform-sample-recipe.json b/docs/Databrew-Recipe-Samples/transform-sample-recipe.json new file mode 100644 index 0000000..2f706af --- /dev/null +++ b/docs/Databrew-Recipe-Samples/transform-sample-recipe.json @@ -0,0 +1,97 @@ +[ + { + "Action": { + "Operation": "FILL_WITH_MOST_FREQUENT", + "Parameters": { + "sourceColumn": "cc_type" + } + } + }, + { + "Action": { + "Operation": "YEAR", + "Parameters": { + "dateTimeFormat": "yyyy-mm-dd*HH:MM:SS", + "functionStepType": "YEAR", + "sourceColumn": "cc_expiredate", + "targetColumn": "cc_expiredate_YEAR" + } + } + }, + { + "Action": { + "Operation": "FLAG_COLUMN_FROM_PATTERN", + "Parameters": { + "pattern": "f", + "sourceColumn": "gender", + "targetColumn": "gender_flagged" + } + } + }, + { + "Action": { + "Operation": "GROUP_BY", + "Parameters": { + "groupByAggFunctionOptions": "[{\"sourceColumnName\":\"cc_type\",\"targetColumnName\":\"cc_type_count\",\"targetColumnDataType\":\"string\",\"functionName\":\"COUNT\"}]", + "sourceColumns": "[\"state\"]", + "useNewDataFrame": "false" + } + } + }, + { + "Action": { + "Operation": "CATEGORICAL_MAPPING", + "Parameters": { + "categoryMap": "{\"Houston\":\"Houston\",\"Kansas City\":\"Kansas City\",\"Goff\":\"Goff\",\"Centennial\":\"Centennial\",\"Wood River\":\"Wood River\"}", + "deleteOtherRows": "false", + "mapType": "TEXT", + "mappingOption": "TOP_X_VALUES", + "other": "Others", + "sourceColumn": "city", + "targetColumn": "city_mapped" + } + } + }, + { + "Action": { + "Operation": "MASK_RANGE", + "Parameters": { + "alphabet": "[\"WHITESPACE\",\"SYMBOLS\"]", + "firstN": "5", + "maskMode": "MASK_FIRST_N", + "maskSymbol": "#", + "sourceColumns": "[\"email\"]", + "stop": "5" + } + } + }, + { + "Action": { + "Operation": "MASK_DATE", + "Parameters": { + "maskSymbol": "#", + "redact": "[\"YEAR\"]", + "sourceColumns": "[\"birthdate\"]" + } + } + }, + { + "Action": { + "Operation": "REPLACE_WITH_RANDOM_BETWEEN", + "Parameters": { + "lowerBound": "1", + "sourceColumns": "[\"cc_cvc\"]", + "upperBound": "200" + } + } + }, + { + "Action": { + "Operation": "SHUFFLE_ROWS", + "Parameters": { + "groupByColumns": "[\"state\"]", + "sourceColumns": "[\"zip\"]" + } + } + } +] \ No newline at end of file diff --git a/docs/Use-Cases.drawio b/docs/Use-Cases.drawio new file mode 100644 index 0000000..9b8cf2c --- /dev/null +++ b/docs/Use-Cases.drawio @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Use-Cases.jpg b/docs/Use-Cases.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03a25af323f5aa5f39432e70dc6ab1b31af45cad GIT binary patch literal 37699 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%0Abjw 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 literal 0 HcmV?d00001 diff --git a/docs/architecture-2.jpg b/docs/architecture-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27bf62e32993974a1a7c445b21800d5c3bb096fa GIT binary patch literal 166180 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

7RX&d2X_I%;dDJU7_B z7~C0@nSfvf$}cA|F2+w`;BpFpeYF$Yq<`w~9wGyp7}s~oce_D;@Mt(w%G*;z_cyt*qnVl4ID{0zdc>MvPagy8fL5f9R zVtI4%xs7{Pabc$+y6@p2Qm*Lwm}q-(v&v({nU!hDO41wYcePtbkqT<+8hSZX3lsC) z7tv=M5^C4;WKAx+?_G?ep|l=VGy@_Cc-x{l1P5G;bbJoP9+YMjXW6V!x|}6pt1Y?J z!E?IIUoD`&@$Fk~lWXiJ`bv{C&?85$s%2vA&XLZ}8iObnjUfYE%ZJ(@RG+8nFX}J8 zXZn!(^B^gLVKc4aiN~v-WJAC|%#17qw;0o4T*B71!Fb@8h@|h{6sHgU`~YUqYkruH zCvyATJNJB!>zYOja1O?8$G8L2Id6(QveWxSP*r>G8F}y6$3@9PM2PmI5M~+Yi1q4h z&l{rQmN{Z_0g%EIR!+f;l%Jlu4&)jfhMQ#31;zqx<(1mQ+@u^@Ia8G^mrp}KI zxt*QztZeg1HVr8f;A47b95I39h1(XpjNNkOc^8cfl0?nd`PbIFIJ`3Ele@C!8|}4g z^3bQL25$QfE=r0878qSMAv%n$75l;NQuUaWbJ`6a_-e6EDL&xE=g|qs$gyK!A3LD~ z^riw?SNlBlvO+FDsc6<>y_5*U3!n&?PJ}1{aCCuztXki@sNTsWOP9S#Z!XlP`CRT> zPD~Yg7=KZzL;#crKTql;4B(Pm0o*foAgo4W?;HC3s+}az6|$$t)~GS7+0O7uX7u&- zc_=m{BAjGC+oJ0Brh)hR_8VN6nA?S{w&(&S&VUd+?m2uJbGW=}Li_3-v+GeoF?RCi zx6MRSBG!)cw3Vam`=%4RcY_AndBJg12x%R#a%$?}1hdtY>0WRAl-bFZBQeaJkyZq| zyPR6u?aZoqMvwBX5A(b`qcFZ!V*g~^U-a{~l9SRX+I0W~{Wu}n$kU?Bce(D&@@2`v ztBA2H2R=OuM#L^i{3Db+|T{7cE5cBlAcZ;F8kby3wyI z4#m{`*v!GNjGJ@Y#@w2Sc=9ov#^WdG%%`Jls$3uWB;1lD2afie1GBDXbuQ%E@0G%4 zTK!6;zQ`;bpl@u%aJJVAy`dq(-d6)gbh+I#5lB9WF|l^>_6VFdlxWbhuK}*A{Pw|< zVt5o?w9J!rOxg(@=^R-&jqsM`G&ysE!Gh))xW^~+ zyN*8Y%69dEaMmUIxE940)mwg1l5CxK8IQShT-+D5whJtj38GpXOkj8O4A3Ymgzyr2 zJR!P-<(h1MtyZ~}3<??NkrdOQ-j7=0r^-cbNFv15xnn>xs~i7| zhQ26LVUvf%>P=5_Z+}3=uB;cF?UTv&_u)NyDd0MF4J0?u&7yNp;RPjIg`VnCtQc(e3R}q<^A){_Qct)HC%l4Jy6iDyXYRjp%bhb8|{- zrE{pL8Ri{aX;8AIA#^R{ol6;I*t^)~uJX3%qeLG*55xoRF4Yv64M4rjo?(MrARAG~ zZY^P$5a%gsbwFUS4D3(3c3G=dH6u0rI*i=rj#YJWpCPxsQ|?qXy`nj_WJYn{J0RKO zJ)QTWNTF}wY8V@Cw8jri1VonsJfoOdWIM6KI@MKBs_VVbwV~PJC`vpAwJ%M=kV0oa znRYc=ba?9Oh0k>~@;C3*Dt$ejG0mI1+Xq&*CAIT8CdtLo9Q4lNz_Du>4;VdM>)Vch zM9U%QiOeIgx=5@EW5%aQT%D!CLH*XVXRQojmyLo@FCjJruD#3}k{=P@*P zlycPM>Frw5?hsf?3Qiq&j+L>Yif9*M7?RvE|?7S*{L%C^Fm) zX4BAsTVg_dib$|-C}Ve|r@xk@M@ey?&%+zl;?w{k3k%^qbH3v_5}h^RXlP9!pmsBT z{3yNIkI)M`wX6ZIAG9BX-|~+)C^d*@W}b6$=XJ5146f6-@rf~*jUG}HwHhf zkcBpHgL(B^KDQ$>27&X|J!lT+-32Ht!fK7mfJMVZ3I~ciIf;a9-MV3UTN6(niKQCe zetT>811Zj3_`5p^x=g7TAKnpzWc@aSRhz>^A=F6oWCi$5gBi9Uxh`?ImNSZ9Z7o2_ zyR75hNEznQJM1O87ZS&mrkHS76%K8Hn~MMbx>a?Z#xXof9RsJ}q2IjaktChwa|q6* zOr_ebcK)en^-bxUQj|{H&zPkN;cKnomzY~Pmcbv*pAgIYv;bcSXo4NX_jR~cfs3`p z)16T94bGZRO*Mr1q-Cayv*!g5=UYMhN)bIkSkVdGQN>-=Ke-Er)xtS%z!626ay;FWH5`bv(@T~Z&qxdLR)wTFOU2rfz|cZ{mgEzqEB6mji%s`RSvu+Dz3Addv-@NQ^*{hpW3hglm+ytZB*#>;A;{rF$q1LO%vZ z!EFjDH%RMv+YnvigWPvPae!!L_RU6Of|Ni>D3hWwufC9n_We^}G2Q}`;#p-ujQ-N! zPXqspG!gVqnn%<8GqNfF#`=bFI=ucqr6zCwl>by>wYY& z>)t^U`^nG(#`;}lKBk?rOa0o@9bc8qp%Xur$KeU4$O1=KvCf}H~<#p2ZEy3ic;Wb@2Y?S;0Vo?fTT&G}yf z{FL!09NU{~rN3wtBB;GGG0?rpP(x2oPiKY+>+8?1y~M`6x#ReAmPK{j_FV6)p)o%` zh-CGToH(&^xx9b>;g+l@$yvGXD`o5r1WTTU#L_uw_4dAfYBF!(CvZ}a8}dx-cq`J? z7;Dgi1V}B*5brrfB?~3c&tblu^xyqI z2L^ipa5@V}1D&`!z~pP_GSqX76YSsvc<(k`6M*4JtYBx@h*jcH1^|BbQQ_}nu>O8c zTjToCC+N>_XaHOOLj}5%FMg=ig4_}c3Z%A<<{pZ;F97iSVX7e`-uoy5K!@KDM7@R3 z|Dq98ibk&S1K_oM^Dx!Mf1!^4pT`dUC(YCToQ{7^$Nx{KZ=U zmH0nd+5OM=0$)P(Vzw=zYx%!JMi+~bnyt*gj!tZB@CZdGT^vASGe7y}54<7Tf8l5P z&$)F)lq{}}88W>n^|>}oF9<}v?hWJxKU4u$%=I$(4EZs#=MCKO7tKds6$(uT7UjQ4CwXMrk}s#09DG0v&+`zCsD;d5r-Ly2GuR-O7J?v#@pS8+OzbMzHf zatT1BTV-(s-2LDd=C|LExh~$SR{QxqqS?cAA2Ys^-rE5MymnGW-ZN$LYoXfO@FpP8 zhLIJJ-vCZtIwPPus#icghuL!j><*wnDh=R(DJj}1i$|tFUnnmNL%Im2G#|#r9R&W( zRe1)b{Npvrr29aHrxKA+* zAjD~NYVO|5q=o|nn0&dSFg3Q`AOXutb}RakE3m`UrNCUV|3%X`P+W}4ME=cH$aXGjKWqm`uja4) zHi$?Ss|3Aw#O$zH03~3cQ<<52`!-A(AQS)bGJuILbCiWy0_I9@IU(}P^P zh@zg3{zW6Bk)MwSY#ILMs=s+H`JXjPQURW|A&kNXqT2n#@9tUXpRexESNGQ|^UqcH zKfCI7Q9u!k5iUcFXwA%FBHmGZ^U6Ei|9F4xYt3z=4Nekl7Qo(0 zQn+!J0qrz1kTv>jKQ&sk>#3FgBzkQcu~~*K*gvox2AbD&?m|-c5BvYQ?c&B~5rbn z&LGzr4r?)rNI(NTc@~)fAOsu%hoi)0roI5kO{knx zQt(AIkdyy0Ej1@SZy4_jZ<}XH^_S&f+bZNl|E4qu{i9iW+vk-(YXX1!SpTdE{IPEJ zhw1!hIp;r0asT(CZr;6}Uo>ggNC4&g-cLY#CZ=>5L z0M#asCwEgN2$(31&iBismxS3uYqty&oD=;j^Tdckemoe7EA3l)n9SxZ}=zEwxt(T{k@8Sz9+|%TYt-`NxWnLa;vz zbpL;-t#V|N1zj4Qqjq4icKn0(WR2WH(xO#OZM+{elj$^_Px+!q5<6{%aL&&x$+9>8 zTX4FM=|0Lgw$q}ezLykJjIatO$duh0_S+`#yg$eS{l7Ane@C_UI8bsSiCIk$oUcQ7 zFWVrOMWzr}wWSb%cya4>qnYrmOTTDJ!Y2T1jr1xJJKUdhCyMTh{so5jc^MBxX=qf0 zbJ9ojLyGn`pIoT}byG|zU*ot-Z(MT!hG2t>j>4UZY*z_&F+2S?+HZa@HRdnj%+r;)CyBs^l(P%}ABw_K%u5mQhm~v6pRkc*-3$%J& ztBosQC)$Jd_Jb+%VD(1J^4B0`6ksA^hxLxb5LCN=EAdIfH?-orZ1j>6@%QV ze0+Ga()nPp8@i>|jU2<~9@;QS$XSo9L7)XQtJn`Yh8fv(b#j>f`;i6>6m~Y)1uMiL zAi_b>&&MP~xCsb=bbjhYs%25OX%u5qjF9Mltar_ehZ-x<1BA3|@y?v4?e!l#oK!D~ zy-S}LYqecVOVh9O%qYULTl8nn)X;~Op&4x8Zgo+3{LrDM`-vEuyPuXHz^Zcc&epuB zgPxzH{Y(#SLspB%PIRlvgaM3Ombq5098oJ?gBR1m3gP#L82_qJ1Mj|=7s zsTQ=3r$ydKN^jsxGwf7rx3ZSnHb{3nFs(CPQ58c)QO=T$;NGOfyrMV=7r~EgY!ECU zdu8>ZZbkwwYFyBKZdqSvQTi{#7B_p-*T;BqURhIAV%*Nx;#1C4a)TI(5nvFa2k^6_ zJ5BwZi|uwogzr5k3*Jrfub)i|7`4DQJx{ma*PMIDkhbw$!6EuepZn~p0VpV`Mf1L7RL7X&eeEqtM_J4rh17N0X`sx#x8ZSbD3b|fvFi7Fqj z`K&#-xIdU9{l#-f_Sm<0qiFhv1GgA($)1FWxo(Q2I?{~56%U6(pF*7SyDrub7x{<< zoqMv-d7iNW;WPm5I3;;S+gEOxc$fdh?E>-L^JVPWcL#mr0;-;~Ef|K-KNu zO51aZ8;S`i=jl<*)FXC_WRe5EfE174Pt@RiEpLN*`sU+v+k*)py7r88YE8a3UKgOT z?Tq4BW0$v6qO|~tF!c5p_jp#Pralfd)HrI36MO3u`CG(@V?LK1VnGyYtF2|Fs1jh5 zi>7zg$~urOR{Yyv>^zy&k^Gq__ z3yxqvk#o81S>nF_@_<_*=Ri@PvMzT*9#LLJ#eBcSNJ;{`enY=7MC1|5-*ccr>;sH> z#_cdOdS_i1HjJrMkjM`D*-*7@?p@W}%hctTD127E%SIh=;#g1e5@B%iK2{x$YSX800o|7*V8+eG@cHQ1UmMX zJb!uPe#{NegTVy?`#1 zA;&exFnft6QSN9#zj06^z^ndp#k$*=V@c1l@B-T1Xfa(IrFL2Xk zsS$)BT$(7Ii!+X)vJ-X6Fl(wBWe&j_tqqjY;2~Y?l`8=yEt{|pfN$4a-K^a+tM`8$cZx{@*eS00j;q5x!~LuqM`m8t)@7#q zP4Jh5VZ92XZU>l=%rw+SQS(R&#oxZdE*1LK;B1%~qb#J8EfFP$gDpQ?8*r38H zj3s%0(bSCx276g3lP`4nymCBpU(7v8%9?Al6CZ50aAh6Ph!638oPxflHlbH3UaQh7 z5fQhyn*6)3RU^@}%ZQ0o1o`+283-ZWRvi2qB)gA2JgL8QQalT4`SsyQQJm?XzbFf= zLc>6geFf>AP$Y*Lq@FB)7uE6ltzynvuw6xdJ`)VP_qt|I$GBVo3>S zoaJZxXU^wmXXgroMPniQPyjqpO3GhRi1NI@g(uG4T+^vfc`M$a(jxUFE1vDyP|0qx z`za&&SBNPifPA4q?%Bb(ALLfTD&I^IKy8b^Bahn0hdgNiqqL+=007cfM8#l!e|+>Jk9SGQL(b9lni(S1uie*pHhN|pWNh}a0~)U@Q9&%0eo96(^p&9oYz}mz=m%5ZiITc(g_(M7u|Lz@IF>3cyWmquV6hjiYk726xqY*8@eQz00#MLE74b#LAWFi^>rB0MPj~ z2|%tenBph`CwNeM18F|;0JLgLV<1dU&}4kWR>N0gaaVoy0o3REBF5O)OgawM^7A)P>J3iLEP6WP;du2h`cljRW?VNVs*uaYu6kw%px(nU=fro~d91G4*>^bX=u!encb ze1lQnyxGdgBzJRL38<%HT8!fEHoSH`A#f^g8K|C0pni8fhY|TsT|h&3I5?) zT|icMl_-kjk_d#$$R-?6{mwS=E%t65ZYKnEK8{X?dlNR>0Nn&1uZ%13s~^8f*POc5 z!aSK#JJ=^!@0yeASSkQBWR)>Qe2X$`dVOFSxs}U)z%9$$rpsx%-Ud*;5Rs zCWCIB{3dIN-BAB-|Bg@33Im_#cr|(u-K-#DD)a7mGu-|r*Ac`JI zP(voD)attzJoJP0q}y$GsK-lzp*zy|^#(1^Yh#ZyITxE0c!@0o;$QU19sn#0#QE=kRONqs73r^e&A8YHC&>oa^{GJVQH2KX8T$Pa9$@@vBMQL43)!njF;mfVB ze!{X-nM`N5{K8aUMP!IJ5eL@B^WAe82$_)-=J)+LwV7jI>Ugx*T%>uF_%JULERZkS z{rHlzE4WSXem;Cmu?kCfUax=sI>h{MDoPeF|5pxi4wiPLYK{Y$#v(FO{3-oM8? zGMB)|d4*HzRk)gS)?r#3vI-S1sUhK#d#*Wf?x5MYZ294&rm&|2e>F4vpuEpp310m9 z$nh8JYCTIu!iV&2s0asKW_y`I3m6N8lQ1mXbpKv%)q$GE57}gyFYxumH#QiDe1kv&aX~2n|_cM1S!rtNTM& zWl4eNGz{6*#{ng&bLMW}LX>(9mUMN)7c{08YDgkS>;sUe8APqBW-y6XGMyZT7A z=xmEThi0F@`lH9=CyVYE-XRq70jQdCf&dgtiXlb>JCYj#hINZ~qd&aXFZtg3_=VZi zK$P%dttJ*$o`WLa-9aIxt#Q9EON)#use zgG|4n(oWK)OKnjSsY9r>o!4&$M8ph5__<`{AMF?P6E@&^rNxK=wBS)*_fzk3Y3IS} z{tF(?Ia=}rk%f)~PLH%b|L1fAjvN!Ds=W|kc;RR|WZNu(T2*30xjOuaq!6rts{Jx_ z_hzeO3cpnxyJv?C_i}yRd2(Pl(&_UfcNE_wX}gmxsF_3^QVZG3825b3(iE?kP(JIc znRqQ(#)amUg$C3K!|%p(0@aRWgWqoYjsTBWYd#vOh~s@Jg}XO6(G$SepiM*D%Eu(u zLc3kQsBI+M+FR|!bU!|!q%n0qchiCAVM2kIZRosAfO-7FryXTpXv{VVgs+Vz`oSDH zUlL_n+HdDvxzVA(uR7v&_q^~Eav?)OiOj0q+231NJemuxM0)v_^ln98A(_)Y{pyN)D%7VnIy10m!^;PF$K; zRafKBhpOGf+K3bm*rJl4&I1QBGk|}>+J!>ExRFa?Pd#X{ee)@X5DuuYbx*5MN63K` zQ7+D^qukG1=)OItXPz?)N-vwftk(D>9pKppbGgzUu)E%?sD6KAsN_`fW~tc)(dB$^ zo6yC+TvjU254hb?`9v$?0=g;sdVCvk`fjVIn)c#B;EA&~+~-(Url3((3nS8qv+!Fa zG+a)2NG1}(b-z~dfgDHbIU&8Dp_8)AQGPR7hYO%cJL7J~eN^QHF^yP6q09%G(*Q<xHe$?- zDCk#mwrh3xs7ajS ztAUg~tY2yahlVOW&}qOq!4mn-l9{StNdmYll!>xZ1KFfG)l$j6i^yPUO)|vD*aM97 z6HmGai(=LuN8!agPd>I^&$Q{k{_fDdzQrRJV$$n$jceR7pbnJPnP1-nk8L=|rOX^oZ*7;SmwH(8?kf0T#SjrpbCCU||K z3J80Go_4i=Mo2UTVpj$7jr=9^~z+WH@KLrX3*nqS=-gP<*GjfW8 zV{zTX0TWQlXF~NhQwYifBJ>@y&P->j3VAKw4=N>IvI+zsfX|%GRZ*UPHr(}yH~fO6L;vlfzB{F z%6?rsFju(2+9Nai(EZZV^FwZ&^BSSt6)ATYAws)j0_omw8G%-tk4 zg-&P1buZ)j+VX5WrCGOTTnqCIB@G+(3(JFVFGXH#YjRK0GZ(2PLgdA_gDTwewph{pK_)EX}Qw(D~svNI}^RoAG-hC#s*tvKidC4 z@*?+LGJpYYuLOAS1-0X5kXhyPu7+~72tCw;yyd0=Et9rPsT&bKPc-yG(W64PibGhTE z*AT=2u+HL!3UzGSj>1t+Z2yKlUHw@?!-5aW6ns#eU|flx_$A^kQGTM2i8)T?Jk?+I zQcI8L^5wzil66@M3kE0@^#-n%1HPa^Uj=N3zg&?a8N6Adq$q5HZPF7Y&>IV_QRLu$?$1ZzScVnTy2rz; zwaB;w7)=aCIE1s!7lMHCvHh| zIN1IViZ<+D33v?4Qw?1AoWCImn{lfjo9kd!rkMeL1(17QTe5PZsLG%~3!K?L0xS~d zy9ub$^6V>!KlvNYETz6y1JuoWK%U~;jT2Fo5i=vMiYrEmp0&15KYA89y?CHPBfs)+ z#5K0l9PGJ3Km97>C?Kwqn@QHcW||XxeQ5e;xVBj9_{PcRR(s?_R}OyhkHva!EpV1( zqQEH>J5YNR+B86fr$sx%Ms+3DV6K=5Yf$)3Pu)NkNwu*Bipho&Q6EaL@ji?7F$k=A z^146n(1FtPVvzOECq-;ly+FxKmSjBZEob)-LmozdlMMK%?Y@GF!6?r`qg!yy|A=b- z-LJ?ITmMCA2F3fLN#>C)o|Jj~iq#WeJ+r*aE?!c*`_ddOKEw_=>#uHH8JeicQF9#g z3GntARig7uRX0vD^nE`26|RWNWUrb8AG~TNZo6l3 zUGiKTA9gtFU*u9Kuq1H0**t7z`;NaemDfDka*-t7I#BSL>`eh?p zrsmQflCV7Aq_rp;1w@$eba%0*ZjG9+zpr|9lF64lkNAxn^po~4TI`$n&?R=?I1q7k zkoTjM)P7f2r>7r{IRyKT7FNVyoSDWplp6Q}u-C^IL^Hc){y6mG%(TOm%PBv@jbOF6 zO48-yjS8oRk69h&ricF3f*{G(`H^C7uwd^Y1(w(I(lW+{bo(FdF)o9ZV%%6lBMo>B9MIOca z1+}QE8dOv+3xBczDlUSnz)^IxMT}^#`F>Q>-ecX{=^Vs_6~QtYI3c;kXyd;j1x28C1Qg7O$hF&lbW8tnJO?gf>x2H;nT_i6&dEs6BZAs6hDb z(1M^ez{)jvs@-HKEwc_{nPbKb9WkbbKs*+>g4s3;A2j^OCI54uiv+(bz)AJegob2y z;7%lgWTw{vB7gSnEu9Vmet7}g?H$HCFbBKQK-rF?V%N+<@n#y9XTDdRiLkwV_(|dm zawPE`9%Em@|E=_(5*Xz3 zkZF~aEsIgKC_{1N1{kF?Y$W>&Js)TfAi|J8oyfgY7eN>>GF`(3;1Iuv-C| zi^C`-U{LJ^BSvFZaEw383;b*2TlNjF|JI8dnQ54%_m~+&Dkp2Wq6D!Ju0vxV(S+Ax zoEbhqVNrN%aC0MFEmk&is4+wOF1VY-FI@p7U*D^~bN8in2IJ%=6BK6x7c`h{ZEJa2 zqei_2uW4HwEP>ABKG2<4P@0Y2aS-&XiRlD&%O)^DBZyUi|AzQmi-PYsXq2H?P)iGB z%gO^|)Qi@v{^od#YC1fIj9r_0@BXnN`ctj1s>iles;c;d`$h=$p<&(s*5~todpKgs z^O)-y>+Dn~MMsN4l9MKw$JUD1vqjf={Fw?{xGrfmW&!T*8I@m~F--#~2jGf)lH}ke ze5nL0R!Az}lTm8yzh`;*X(EGnO$yB#sZG@A($!DG<=-w`$|-IanW&+@eEt4Y9yw6I z^EgMC1Z4DI1qVUP_YXiNwU?Gl)h;3uW}>ZXDZZT_Bs`TjTYUY!>Z|AHI|i0IU(cGf zOKWnJTwAS8x_Ynh{C$C8?mvUzoa0DmDmpOp433+If>0NUY5T(1*Ny+T zxd8#U`#%Q)FNmQBCkuEYh+vq5|G4V!^dt-e1vc;DJfQJhx<8_Pg}4ehnQsRhc=fAq zSH$SS>Bi{32llH3!N7E&neyE9uufKN?JX_lL4~uI#&&KcE$tl|IwA>(PlGnFF_yMX z+LdpR(Z-ciKTV&vjMbh#+EkaT@jPApxt>o^gR4xki`~tCS7iM=7fZ+f^UllPqaxEP z4i+Y#f%0@|48z}$24hxEDVPd_c{R5=NbRtpim`~$-Jt4M0*!C;|GoAuX#d&UPe-5! z?zM<>CQih#%bAq1rFYa&#f9`n?1Kv^>yd{p6 zQ=#;iTXKPwR0SK#DvR_RvK!O_=IVQD*Vl8i-3OmE-EQlcG-tFIen&G zs(50DhYkTSwCMU)V5+#C%qO~etrC7xPIZQyjy*p=heGif*;?# z=eF@4kWUb{xk{aX=0ow!QW@Mb zNVP2M#nnK zF~#$Y5^w$XkC^-|6W6n1@_iga_2S}RPOAy5U~8f6`idZVMiAqXEQ$>4apW7(I~!gy zr;t(9Uj(;voRP_1qqt_BSwMD0qO#%luR-K2IPuSI7QwU~M_U>O>j8B5>VVf7TxBbfT|GWlTv{HQ!a$M=#oz-{RQfLdtt4j0nT?bmg98R{X4ZvU@uP)4^BD zP_3utG-;j#ofMXD*as+_GapzyxmVX`S(Am|-RCY4`1DKgB;v(@$3EPN>u42D~UUpgxQ^FvN+no2n_>L%uLpCTE{`Z1tGk zs1x$@)$%-3vma|5-sdjvvk;hoe7oIqNYsK$e35-DuAsy#T~|6StZd~$h;}GS{q{9L zrS91C=9kCh7WUj{Gf3M}T!8bihQ~1NhONiSGszzm<`%{@?X#D{4>vT7;DbEwk>PqF z{&KcP*@}M49^dVBle>rTTeL_Drn@2*#}z2R?+FuNoC+@emaeOi=P6fj*IkDdH_eW2 zw&i@f!S&;uWH73tZN3fqhQNY)1PZg58o7SIXW#f4M zkcX>g|rm{0n)ASkriE@`pM0FVV@YFZ-ho;+k4;`2&?=klTBEl2LyK4`8?NEqfc zeCyTZlUC&DwVO5`G--!=VCJXso=e#8xVyVg#ED1OLb>}Xz6mWVRGv-=OaeowxU=+I zXerveY_8cS?k4ZeG=oq34IVq=WMR&OYUDTZ`K)2I>^Q^zwU@Ni2*aCfHSu$DRDCz8 zLZrsc^m*EwqsOFZP?-Y8t8#L)ESf-j27G^w9)ec$^>#?(WeA$N??J$N)yCn(Y*#F)XY6i* z?f29(k4&DKAH0`!t&C>`PLU zyWSglyeV?Q*Le1sJnH+rAlP8!R~`J3ZMV`8ZHzj>tYY{Tp)f!=rMHV9%s4`gGy>v$ zyx%_fu&}d~SXJ@Ioa|Vi^H-0Kn4kpaQ^2vPU0wdpH0V4d>Gf zFaCCeOZjR&zOXQ7SiqxHQt(h&U5d8)km7y*VSzDSF!M1G2RMJQ@UU7<(+>89biA&Q zw}L~jbP=vBd=Spd*bB-~j5CXI8(?&ArgZ!oWLK#{IJ1Uy&tdGl2V6?v+cK7;|HigeIQVYuoT9pXlhS`?DUjON- z^Ava5L*P99yvJ12atBPXyS)9)N12ucW0eoxrDZRbC%0Zdcym7}-9nO+V}VS>;5WMr z^eCV$Jlz0+;nS1T^CKP7#0oR9m%T5jC|{G9u^Pe`*(7QoQR&;MOw!0&fPvIyxZM}@ z1g6e7{^x2qPmea2Txdq5(-ymD8FBTv)ZR~}B}@H*)l4td(TnfT$Xl?}RO49a00JIH zi;QlYKL`+sa0w*6#}PK}Ufnw0qKZ;_pm)mODS((eIXL?1#Eze~Xkc2)C!ZTHg?{4J zk5H9*TBVq8v<7*|id{n@jKjcvTBrxbr#cVch1c7zGG=8mOU~L4W!SQ;Vn3?3H)gW! zjeyy&3U#67DVvc$&khM8t8dhe)FkTb4P+1Wr}7|goSu_Qy70E2U2f{D`MMDO0y4vz zSs(+52qZGhiOHM*rZcFiaH(M7gZwX*8#i$uWcv;7if-@WaS|6uyRC7s@#__2QF4nJ zwSq)glcZ*ik*q(Elw5)EcUPN&io)JV9mO2<=)F^Kx*9^f(Zl@gf)D24NaeaCFlTRH zV!wLJ_ud=R_^`*evZ>m*^u2%JiHZXq+dZ#zz?znkiH~?V#;hgVgFjd-Dh+z zC)4$d*C+2F3ezvYN$iW0zaDk+L|4$jjzB3F*uO^7d| zz#=wUtcb}69E^oSbE;}zghUJnE&)ObNV7&f$#De`-c^sn~=%hxIe4fzvGsJ;s z1who&RM+k z7yI|N9DnJPYJBA8-G`}&>ys;!yOWZ$$5To3L{XE)#7NnKgc2p*NBhrk+&4r^q70|d zN+@&YBi30Un53BGyBUayL@Q5fa{0bWI+WDxUZkmX(6PL;UBl-rcYR-`^8@n_jEfU2 z71n34fUyiNXkGwH5-lu4R)do6dS{7)I@Xid=Uwv4%X~Bz<@}C5Pjo&KydhJ!w#W^k z|0#I1Xf43v;e0FsuXG##6@pMwx#kY>)?f>CdU7C8C)akO=hqz}=Q{J-{D_$n>Z&7P zMO7o(tk2RgMo~(yJq7LKPZx}34rPy7&NI7-ZbNde4V>LMy**i}h zZ{D#Icnq)GOuUCyXFQ;ab=faB0^xqI5q8^;Ca>r3tEYSzuT$^#jo_!J=FL0^xgGhe zk8`bC1gL9OWR|LF;${ShKjA{Yn&u)X`zoqFt?}&;hYha`!b6QHeeoDv8;>_KIZwRX zuE%eX!_4|ch(mKQ-?YlJ20{t(P%6`f+EAmpceScr+8P&Xd zh!eD~heo0gAyfjihNPIybWKBVx%b#L;PF^U)mtU#$4<#)g;ofdi#$d0*ipq|4Io}Y z(x)Z@Oe$DMkzpoD_~DKtb&WaY;(^ENdj@H5qdz{0y&8A{F{bBP3Z7>-=!T%jHYbr< zS6u!*3seLdF-7Q@p_2H98)!;$ZVL3vbp=D};;*yKw zuR<)?x2`6vJ5a*fa!&#!ZyClT25NSz!z=f_tZ9^9Pt0q)%IM(Tr|))7HQsro_jKP{ zx_nRsI(x+MFHN?^YSwosYx%Fa@dI0MA5R7gc)$K7BEYphx5as5@sC0vo+@O@P0RW~ zeCJU38cE zqg+nJnHpbVgv~x`3hVP2yrX`N6E7QiNSVuuyR_jTYObt^Aq+LOci`*=!#+4kUw{aU ztthB`PYoQY6I~oU5_f#5#+H{d+c&Kw%YSo`eKZ1g$)h5%C5(P+RTtLndypXq*idk` zz_WKq3H-;^F7;ctXYlf=UV@orqw-BD(n~f8J~M}CoM(E5x_$9a0ofkBc?Tw%#WB%( z1mrN?7~`*1O?Hw0P%*7Znbn&eh>sOL9S`d(?sp?MZ9sNA;uvkf7~K~cZ^um4^QIav zR73}QL%VY&TCtZVk2KjEHce*x zaB+4+k*^+whyQF;SzrH2eb9Q>Vh2obeZ<+E4)W$kq#jZs#6W9VX+%Q`G@7BZ;1Jdg zI)O$P@m=ripms~;=6QO)KimIS{EM;IfhCOwY|btI$HSb{0XE&a2bnc3XUl<$Byh^c zqGaCz<~=aZpZ&kgd^DJeq9Sv<9kBla-&)FoxypjcwYBUVR zc%3aUZ|?SYmW~8hRKMJLP3Y`^r16L3kElGw!K4OKgh6qBs{)Wscg1uFQcG?yv5X@W z!fh%cp-hwRU^LHnvg7>#Uz_u~=9qe6i=Wf^U`2o3w<2WtA)Gf(2pLV&rn>iF9yv-* z1YiZfP1Wlvys78$P}S4HxZV7;ed?O6yw#VuJtaDuY$skHfWXY(X=s|BX#1RR%<-h} z%ej?vs}JG(yXmf8Xih*VA7uzu*0$|N85hguJ$JPeUmMXfbnwhyi~Fhl_tG~k4;m?( zco+IbyiU8ZwH$4rwn`9zE#$*^7pTDzzkH*!Hlm3RK4)`aH&2;JDR%a%v{0&zHQo1K zKGb>q1~?xbJSv9tU1*FX6FpO~MihP8|uaxW_P@=6PN-l1NoXqwjA}R zE~(p2c|~x^fv0cF8Tu`NryC=};G&Q(*5#5qN#;>^sS2^BK9;`sk2<^7oWka~B^_Bh z4GZ2#c{$8EK5o;C;RPjj%)x4ikR+v@!Bk4={33nBLet|D8-ZJ7p zIX~P7x1w#Ajrg5W9CWA%_tINfJ2 zB%E%Ds;2PJUPhC3;uyA+*)A6UI1368+8^N&bBfoIZ&bXIg?U-xdGb=%4DJQU-Ff*E ziCZ54rR&@PC6d77PPR z?hTHI^@Bbj21N8A64NEEF^mRNtXq_!N1GeUEf?RNr}=r`k$KtKq`vWnNLx=Cfe4nU z(hh^coSz|zoe09?R?kf`Toz{ShoWZTKKe_=y)##H(rLsH%-7Y{Ri#uV3{www zY4l8f$`)yLzFFceGc`ik3j)G$B+O$sDl`$r`Id3E)wKSh=K3|0#T=qw?@{v?4Q%yk zC(y30cS0>VEB=7yK>QyB=0|r$kJnkE-Ye(wzL5DQmwn}FAHiD2?Jot-JzyPm1wBsK z20=&P-`GJw;LyK<-vlCIu9D};@FZxBT2BsK$m=qx7zOpZEN4Ic?Nm>;?9d5$cbljD zJmsN_*p^iyb~=oO_zhvE4T=1Qtk;GQf*$ZTvFKr(6iS1B8l^gJpoFaJ*uFWorchh2 zy&Gz@t$sOJz2o7f3JJ<5u7QB>&$=OD-XhfAPTcIpc9t5D_XZ_NEqc^qNtx=l`>8%y zc%Edxe?5PkgWJ)tK1;mNI?&=Wr_@P)5*uCmuccb0A%hdBn-sNn^F1rDgRSyjikB%H z&+pi0r|{PA<@?mnfoXmyeKAwcvg4(@B1FhBm|8%72|`@J`f5r0`){ko6(sAbH2BzBfntZG+F5V;C!ytlxg0>EhngC`H1Ev~{BpkEuZwl%= z#4cayr2XZ@TAz?hY*g+Y~8(g-|n)`S?RT|DbSDRI%Y>57lYg04^}Q}bN(`Y{%C-K zOS8tNw}Kn=nBfkD#ph~NwTuURP`CGRp)k7;zo6p{u4;3l*p1Uyi3Z(5~wI+=wx?D z6y|2Xp0=Owt{9L=)c$e#v&6jE=c_46w}RR~pH}%k{@4DIV+ctNyalBqnITKTIgFdt z$D#t%W{qoqxcWWN?7OsWn{uF6x^EgfufFlEQO_ptuSl4ifo2-!lRpPONLtsMHcJ9` z>l>oZH_FFZ9sAbV;3_Bh-3Ej@#^CN`4>YSiW*q}O{gliFI-!RU^I~=GXR&LOH)5Tw zeMxu)yud5=+VEzOcF;-K0^8Cc?*MGWdzWl@{R`^zpZyj=e89}#fd8ldfPnuE4%sXH z({4fu%dlaH9u4XfX{?+$mj8O^{=c?ckNvGz8~gA7+$Zkuf;s-Lb+~{j+Af$pn(t!` zv?@_KJH&le0^{9k>1LBJn=8BnmNE^$OfEc-I{Nw6o&1gaMsuA5Ft>0WqE=L72@1i? zVksa?NaKPYKYIO$*~|dPmhGaum3mL-?;-5#ru#FFl^$BS<4@!4sfcab?11m@Br(O> zC2+}>YDc4f^j zLvLc4vXL21q$9nr;++L}F9v$8D^ID*ayHoK>;2;XQFRQz=UL4zVXcCwCX5UtN3YA5 ztm3tnvfay(?P4fkT-)X|eLmk(;S!ZbzV>gK-52;q0vdv%?v8)^aja zvm5Sj(}u0V(FhE!p|vqkL3C;cghQ}~$b`!93d~IF7&8%?wj6?iW#A-fj9LFA!~~A4 z^&7I13qqgmij7G3cGKf_5G)Nit9X-T1^U4yfIRDWAXz8P3zh4Ni4@Y-xoF$1d;0_ziIZfjk<_0%mQsVi1Zj7mh98Jo~k04I_2? zcu}4@_Qn_7LN@e=D~2Dp0Twcg5FbG%1=z=X6pV4hbV>sPbVN1B0r1dP3FkM2+2^U+KdZabo3iUus1I8pX0h?5B>9dt@5nP1>TSJ;< zK6$Pl@dq2asw&;@LTuJRK-iDXj-f}nC%}A9joM3AEemv4ji;)hG&QI5LL^qy)(~Q~ z*mdracdE;GT&6eER%@t^fh5c$43wcmxBr3)17N|#S)2f}*>o%*5j|#yc)Q_EuFCc| z^P~m12A{fnEXcCu$EiMgZX9H%lU4@s>8y)W+^++{Z&nH}OkO8VS&nYb=oEfnK7Vw} zcKm_Wd&`)9wkkGR&d)8U76kCUq@Wy<+FsV!HcP0Q5>!p3uOCFfc~!pD>tu-a9YNG* zQH3uQOEY64zGkb8_|dE;ex=0~%zrWVsXA$>e)`;}&}04LitkHqvh5fVstD^v!+T7nwnZzH{Y($+$NPVArGvfU+Bxu!Zm3B8{z%T)OGUBC=OKNyL3A^^4TF{A+ zU_hxuG{6(j;2=a( z2Y!M0=`dOxB_okxnU6rL0E3z~)>H3e*S)X1aL0;zbSXPuLcI>t>#)42%_S4I*@Kyx z?ZF8eNTCEMNwHx?ogrY@>?k(*OROQ)D#_kkR56L@{jsb2wR7hH{{g}6OvD-X1_+zvc?>Y{e7jL&t@Uas|%DGP|`7plM$7BM-&aevn_^+g$18cmjQ^ zX~DU40^I`>`za8KW0R+-#b_b~;^BL`hwD<8*M&T$M!X=sA>Lr2Sht`@m{V{O)yCGK zy9+M%hcL<7&E)s2QCLlGL7qZI-uHvw@S^d=p_+boPpN^;hQ_S0LgSE^t|!GjK(Q2? zF98;uUzah{_YaZ~sU!)|g7Yu_# zr76a5Xs%n2__$>G7lmX@ZOs{7jbc+BPCe%G_5Mto?6Ym1PJ+NZ(!9+;f>NGO?GG&Q zOb@!DdFch-6nS0y*sbFyuDra6 zB<8wjE>$ZVG3WN>Ritx|(~r^f(o0`@oh#Y>y+Q0EFRKrJu*P=}>yv?a(|AC76L6|H zd(Ji0^OJv%ONyVzRYTE1!S|AdB!mGEhc9 zan^7VBoTV4_J7?$TYqNT;_)H-oT^OYlh zvr#l`G0NW3|8Pq0){^#RtKf*TG&en)@7}^})QXO9cG`z}YEm5f_%{N7z#O}|?Y_Lg zJQYS$A~{64GTSx%SV4q1clCyH(4%)}1_Pnh&~~tlFP1?X&pqB$-6*X`r8SbWp_MJk zU#C|b&7VIDN$IU~`kJ%S{M;{E3uKo431z{P?BCQm!INwW^SD(K(=ouT!g)%B#UL~5Z!h$y+7Js*S|#&sc5e#wz5zOk3HMjEvo5F9>hqEJ zAOK0e8Axd_8VBnk7Rv~DQOg?UiyA|{>g~+>#f5Me z5N^&%*{0btU!z68+nIOb_MwN>FM{j(V#>kKljP3y42e=}!B(O>Z7Rcw4SoJd^oi3} z77$5-6wVEsgcUEM+B}f~J~i2NCW|Zk&3TL=(fA)rlLum|4G~D!3mk*u&_f{bX=^5HIX!XN%NF9yTuPo3eZ|cKhv}dPP{%S(DTEEI36#RMTl7k5bX* zhlG)mlW+8Hi|#w7?DOnu^3@;8ubv!DQnHakrFgxtBrxR9q~ks=iAJm#ACO zn+$$6QW!&eYcpw=1h z*jS=f6B?@e@=jP_ZFOBjQ(Ih;|CTmMI5&-EbtZp-0a?9NlXgo%Biqbm z2wT5mAp~vr1>COmT9sB?_fdMmP)dvu-ZIXRT`+AH|EZ#{X(D5o)Z^+g`fC)UzW7?E zTh#W#VFP~cshN2wARM5EH|@sOKxu?rJ@foqV3lJA=RUg_9y_qpig^*d`B{4hk$l5( z8xiOY7O=iPFZT1*RZi)b+-GbgZfz|N^1@`x%+EnZr_sFy;*{h@h-0O^Vw1jJmrh+g^`lUkb+;;qb;s)5TVaWhjm-E~C8Vhp^$nIHM0V(n zK#7o7S97k=O~NJkjOtux3IH_YLMd87p=v^>-~aM4|B*Y{>cp#E92AN&P@A{F?; zTSvDXZhXpQt$8*iex4>8E7n4gipi~P=gH=YSWdJm-fOk6o$?$Jm-PU@Y2Q7mJUU|K zdQj%^1)=*p&|ve7GP67sx31c{5(u0Vqx*iAmL0xVW4=bH_WptHKmP3V7NfH{$gh6j z&}ze!9~qv2piX-(|3)T1bX$JA0cd0+!gZzwW8T+NL zWBp`9v$?_dXyUvA@~*AO-2~$EG`W2@+NI8eEipaxIJ|m&YF%KVjK^?9?HS~J^U15Q5Lul>F2L{|!+%Gqtpo^n%@&n-AVN<)}0k{>dJs1@zKUD!X4-|Zv; zK}0KNMh~|J#zx<))fC-d&IdoMKf7&=do!_myFM1Po1 zLWenfLBJ%)@cs>v`hs#BN9%)>=Oi%2$nLV%rb&GLFqtV`Rjrak*{L@!12h7tBy zpi3GA()eHq48pp}zaeo1jx_^ulqv8MzwyDlVg~VGRna7BPF#7}HhuE!X?ep-ZTLfd zgP*T$q|qGy6uli!F`u#IJi#@NxbHwg7VXV2q?*T}=0*DkF5}1$0-#a*FHm>RzdDs~j>ORG9|_$4w}ky(-#D2yd~j+(VTwzK4#kCFa{VlHKY-c9Ki>XDe5$cI8(p zPAE4pxFZOlN9@ddu!$@oYH;jNs4#HWZ0wh5#UAre)quI6Pinc<7mG{}mw5s5w+=o` zWbm<~g2KP$IE~s_lL;?RBR18*ero~#-sHEhJeXnpnlH8PKTi2&^ z)Cd20f!bG5gLB2jRxwNgv)BEBL@7-Bb|~M6@@#)^5}*Fh9Y>@}!Is3{ryHpo{#4IEia4EW2SXc-`yB-XY01kT>7iG&e@<5*Lai(Pj92m>f?bbji3 z(?jJ^rQ%YRFPjZN?7v=s$XDIpQz?}Z-C_mqi%FPzwa(303hETq_;CwgArfIXMjHQB z!daDSyJQulCicmDO1VVvY2@)Q76;zoZXSCj(7hQsuMJevwa|lj!dh$@L!9axgK~cj zR%$)*`WqRAwVz|(^%k3HzIV{4_^KWoAEFO_$!B}1Z54o4VnBdS3djs&Vq4&pZ9{Aq zK?uEXu2&gE1z&eGRcS3P&q}O8fS`|(rX#M`wUx9_!K;KVIJer#xEXVDMJJ4#p-!>s zgz~TGs587MPl}!}JpK08j3%n?hF_O5TKe%TZU2RXB*cT!G`I@9iG;h{s)jl}QHt>* z%$#CCU(F2=efM4@t=ebDhlR-1j=c&$dPqey#OlhW!*BYwQyLO@v*=5p*oA+4?m?;{ zh#e6isU?(aBKiV&HuruP9rjZ3JvuY!C)KWY@S4qlr|^TDr(^;}GU=Fajwc%_!5u?V zfN^J5bR5`!jIk4l)#Bkh!R{YQ6E(oR9PYqryjLw?fAiq}tCa0pa2bqO1IZv;A%$W? zmd+sZVN57wz!aVoJfS&LCjHqs`>y5O9&x9T3mb`2k?K$V@GT`oTxW8OD2!shnKaMe zO6e)u_o2iQIk;ByUHOfDb6TVO58ji)TXF|Mvb_4cU<^Ov*E?`CX$1xs1P-rYt25Rz z@;zG*kNf8DJ#B*WVKMAb6O=y!F@INJw)HGP2fSu*qJZaQX)?MlCLLGIg$R&(`` zq`D^70o@_zKkB z1+jHt$m9RNsC)0Ars8(rHwaP$M5zKosG`zTngS*w(nJJNx)7xoAu26MNKm9V0R;sG z=|vFfozRP@Nbis$C`d^F}Z1+9!0PO@up=y>%!^a@TC|m{p@BN)weyE`aZS>6SPS~pI=CN zklk20B1Wl9MzGRP(e&x(n*kS+PRpFK%I3=GYT*YI>Gj3eL)wth{?qts$dbx~uMwXi z7=N#yl(Zg1!&0|02eq$pWJ6__SM}C->*02hDA!Tsmg}XAc-@AiDH+eaOH3!t`>)EyHDwI z5W_H=v}+Sg37@k*_KuS|au0@9PUsGWTuT`cTf9>yZz1&ZBun@gG?K`-u122fU*bpW zQ-tAysQhkjaI-?BoOqGvjddQYE2uYDHW*cMH-B|5d3m7adBf#TwyZlwS0>THLU^qbdPE&j#1`FTe<8{Tieo?7#b(n3&AM9u|;X^pKX7-0%uUk*1q zu&t%lEVRo$jGE=chCEh_s-&!_m$<0dJ=lJcVpO8eLJ6h|C3lKJW8kjvBS6CqpT}52 z$nNi&?e}hR{74OMWBHw2!=sfOLs5Wv&f4rI}MmkpJ z_1yWS0M7!Gyj0E89l8%?nwM=|A94;Q=W^lq!5WKD9YR*Xuz8@anf`+Y?jp#7WhlzB$XsTd2( zt+B3d*QAj`UcC+}DaP=9`uwAeqZh{a73ZlMk7ZL|N?bfh&ghYFAi=8c2b#h~x-0Gb z3t)5YzNwvCR@NiV`q1<{9KndRZV_u!F{oJ4)p}is}9(}}{nK1SV$({ye-+~~U7k=CoJZFMa!JE%0E|j>NGES5mKV0?a9^AImNb*e zk$r3-R_UF}b#t&wGyBzJ!mswC_IJky;mqgtU)#n{>nq)?W|cX>#ir=$D)6hae+f{S z>iUYS$&RHVPz^nqzvEi(7S8yLdet}BCv3SPeLQ~$ExFNP#Ojcw%2V#&bX3kZC{XPL zWlkS3w%|}uNVfFp$FS1*$T^^0Bf|*LAaG~3>0cSaOe~v})IOX)-`beIq9+}9{#oMa zhhuu%_~WlrSpYXYgPZyUXdvsG0SkBp7g}$kUG1j`$E_L)TKiY|BmIq*l^G?;Usk@t za%cD4A;E!ru&cD>o=o;q+6~OKRt`JnjJJLI3j%@_P`f_i`ZZu^uUxieqK*Th?NwNx z;&deu^-XTUO>k8m$bFqF7-OZZS>HBD9HDs!eyQd_| zB}lHiyy=2&+%6&ktn?x`%EcKxcgi!wt0k_z zh25~!J@}2Zg*=QI!13v7RD!p!lGtQRi8q|9V9KK^oB7Lc8JE*@W4$$V5Ey>qE1a1Vj%gzIjH6}(>`TMOWed!vC2E5IS zrb>F~Z{4@grr1$axm+SZv*!c+Z~~JBAQ3l|(Go-?2U=`=7qxlso~~(GXPJpdd9Su~ z(!}W6ib0&M-SnnkiPrJR;Zc|dBN@g6^b@d8VM4QdVT5>i^tORkbRDnQYDM5h70)Q$ zX%*WGDh$`fa*1|lEl77EaFHzblVDw0;Y(<~k<EpMi^p;k zC6X5(av$w`&Y1QIp&E>npkTXAauxsULqfi6%h?q{4H< z6shUe6kIPD<%BQp1{Y71qP;J?qg}p^qFG85t$6zA^qd|m@#K3`=sD->qgi?!5_$u> zIkBJvAVSxlB5MiM5D=oMVnRb~ARtpesX%VeOE)bwIB;J?>6P+SA0 z0u{uJ0yQ1r4IvNpW-{|~j@IdX7U)UO>??)kX;|E`5qV!-#jWlaTq6aMczaMhBFv-$ z)17nRS8})}qUK>I){P(@cA57&A-4(Y+uodHu4&ZNm?H7=3+h>kCHnzEmP?i72C6)n zuNxbaY1Fge(A$2JvQj~a52;WWqMRsD%^ktN2|8bQ089e{x&2gj#UYCM;lfRwxV&Z za}#Fj?@tVm+bb?t0uIpHe#Q|jy&2p)F=dkf<^O-Q1lH_#cs+>UBj`F5zUzPwkt54& zTqA~UQcIw}`s38 z#}=IIR%;e!7S+@Fj{EPW-z@Cj)Om#C$$UM%%}=;> z3R>%0#}?05Di>MY+CwTmESj=Dy*8kC?EJi7IA22h36l!+EufI5&HN-X%giNg9rS4z zO1nKVcC9H?G102L!b0EOPgc6e`&LU?%8QFJ*0;B~>1W31Tr_J`KUHV!*8MK9+K0TbQtU;1gY0hZ z5xCm?VrqtD8yJ#uO_=GC#!ZwTGR#fB09W%E$EebekZpQYXKzvsu>8YiCJM;`60Y9K zhf9N5bJjl}S~M18PqMh<-2b~sxd=g8Kscg&s4Tz(0^$H_j$oZZG8e{n@sSiAvaXEC zjZ3HYUo~x-BpOyFRty*|Hd==PmNP?Vy?-m~Q-jD(`K08h?Xb+>77l+P5$Vm5*(qX! zB5AshUS!&S^y`eNsqQ~^YPt4~rU~192<#-qC>d=8#;&9>a(|g0UbubR0FR6m&P;ma zrCV)&Xx`31v2Q)8|A9O^6|TnfEprVl_xWb9-TMvUFcHkiY(tXn4J?V_{&A>BEc zR&*4wbVVVxk2o`Q$#?A2HnmKV_wD8e!l4-K!QyXHe0p&rTbU~~3(EUg_Qxf*^XkYu zlNQ1Rm+kJ&`@M*O1kN7^QNi7n)A}7byc!$AVbK0;wN`RvR3&Dq5OCVE*->|}`z>-w z#x~t3N%LNwZh~6sT=heiJ%7qpFYI77;6PNJRiFk{18>TFFwhBN_7YLEuoOo%>sERD z%=Ac{X~B-KZO!E;IvV2j@6fruVU83q7zhXi`wYOz`@jh`&!g1o_emVUoK&)d<{#!mi$yK=V>5cR3fQTYz{a~3{fek>l}V}WbB?o zy8bCo7oQ*{q1cnv85SnaqDh1;uq_OCGe1PXPtl5O4M5)@^05K=U1x7QfJtEzt)@Ep zzCn<55sx^FTj3AFIpuyG2V2^UDqv-RjDpv0ZHQF<2o_jSc)}E#^G+A`H{R}O5KbAA zIcs?D_zUs5QqjRmHi}5+1@uK4-wbn_3O`D}ic#ei?(a2w57wk3CKqRt+^c*pgGJ2t zrDt8ToUl7AAD8*oBBR%T;eE{GFbY6Tu*OPp3D9!6o=na43Zj^L$iJyKyc%+MXy8~Z z_XSJ%Q=2c~uKqgwY0zZ-B7YktIU!Ir+S8_MqJBkBIMyjs-yM8cI5o>t5|iD&nk zlvX>AbT#%f-xG9K7__$W0r zbN>}M;$Q;1XhhTRe-M-{72gdd$0?W9I``NzYBq9vFo;(xv9Q~FSwWN<-7)5Ix&XPM z8!CprM45~T40(X1h+9Nkj`J^*JoC~yTVL27Fia>jlGC{`jDBzl3VOHdaypV(P~WMW zjGQ}DuH!Gz4>;m(-k@m~2j6>8xcZAq63fB+j@v+^o!(+FaYeX!)H8G)!3#Nkc>f1&iq}=^GEknLw`WbJr?ubLo zuUZ&^sX)FXK2Ap|&?hP?D(Y_#dd;Q|L)f~!{`*(8v?Ihg+duzT?l#69vzl*6P#O%5byn#;zuXd;Tj2EgjgtosNXy;dgzJ zz1mJ~lwy2PRpla(Zvf_*B0v0?+E3_P*|@jqe2&{rL3hCSBT3P;_@e#_&kTb19nZ1y8}fgL};qB~k20Z}QIeuz!=KRDAI|G6muvU#I% zD>zVGyiGwc=WOQx-E9jR`Tv^v|6%C=e-~{%@qdig63RTJ$2JA63hwd5Tds>-eaP|q z=B-+HcsScvj(PQ4);rySY{8oAlBb?)pZQ@Gr3bxX`EMTLeeALu$3&dOQ*VX(uZeab+6 zv8xJ%SPHl>uT>JJ7`H`fy?BK3 zYoRSYzS7&wN4~{8NX@U|fq|@v&q{I&PS-W(xf-7tqX}~qRY@vduOi{1vN`lL+e-9& zFTR%V;`&zA616or-*fUDXPgRA zD)DJ{Se^AtK^8ZrVr7Vz**4AhUtHo=1@j*uVWK4LoHI!u_)Ibn&prUyffjaDOXoWm z^{Um10}dDKmh&9dD|O4?2@ju@wYuPSoM0rhD*~=V-zTJtW38Zr54#pXTV%4|=C zn$}AhniuPwo~=8MbO@s$cs`rd?Tw5H51 zGBQk4FEEGIPMV;LrCq15PZBq7bDxeDy)GP{F$aTtGR~>&e+)t zZSVyt2R_S{qI5MN*`G|K;%n;3@82mgPmdGRj1rYRJjS0nNGW^fs-5RZ=x@n{>>nPZ z?pH&3>T(j=r2t3bMUl?6dfDzNn*%H=IA!|cl~Zi;k7LyCb{kKhl^~Ve1ap`-PEo!a zFy6Gw1086|NxBfR^6=`8MvF;Eky9`7czw-4bL!P<-E$RLSHwJnYS14w>y0Ind&6|P zo?!+TCCClE$f*6{uEK>R~b?n;PO4AEdPou%TAi7q2)j+VM+&JIq;`hT%o2 zk&NQnd6}co!?ogAk=tl3P~(B+;B9r0;`-$p6JK(4{kx$#^~mbI3b8A$U5^`(FgHlp zJDL<4ye}vb_}|f>zOC40v(LNZxqq$OvNe4q%^@_)Dg-GNH-QUCnhr~f4aXlmCgl&B zW&S|!aU&w3V)rlyJQrCeTam;Cm&?iaWBAe5yc%Rv`h36a)sG+YlvHIue)l{kVP;MP z!^g&TZ;}e87MEj?ctMF~8sh znWq4K>M$5un7c5u1T-KRlb3hgT z-V!GYiuZ^HN8=hg_n&7jreVP>TF23)>tg}&VHbPS_C)xPC_KcDmqF}8IuszRjbz-0 z8BK42IqP&Du!47gMp%;DbC5;7yid%E6sQ3Yzqt5XEze6DdzdeB()-+4#q}yr0EHAU z(BrF&8Oy+N22={HX>pN2M1^+UGPP9j`?cprV)Kr3!Y3&+;a%tR%tlY$OAQUhl)gi~ z!!Kw0fX|i>sPBcXKwxBFne!j}_M?yRYUyQ@n@jR=k6MEe5H;zu_i$ zbgMqbv2Ff>MrF$YF7Ozp6mau;l7yCZ+$jyy@3kkEZnSzX-jFXSz`f!dloc9g6{2c^ zyOjMc1K>?1S^+R1k2wH^<$yM)!qk#!>f+*(xrpM50pBZ~WKF^|;iFQ6Ke(iy?B#68 z;;~{rSZ=Ian~uR()ve7|IEA%SB=fm7wR%YYMO|U*y9XLwd1-Ojco$G zi<*p7(9f9|3{wC+h&WLol<1!C^WHNvBPyV8f_3QIvE`Z^7uD1?x=||$lGCgw1vLdz zJYN9O$iGm^?=^;HVd z-6+t$M5rZbJv5JPMs`=2chzUu`^DlfsECf;e!1CHH~xKWR%=H<{+3P|4?Eru%-l8a z7>?i1p!3s+(cQ+aqVHx<^`xF)o5_8RZx2mMOnKYi`97TE=AGhkj{Vr|w`2TK7$j#+ z=ZIoXCLckW5)QXfB095Akq=VyDQZ_n?*;sHw4ObcxpG(fiBCg=m!-oweU7I`zLj*c zR$?jK)aJ&Kb_E{n>rE1rn|}ST5)QQ@_r2V&+PQM#!o5vPP4*Ku=aU~m z!p$%9LP8H0(!$V6)r>bJMjn1N@Ra&cn70JStueVo=kb7{m1jn}H~Z7Y2|Q5mRJ*Gr zHoP}ooly-ZoCO+k!Pv*8Zp0C^c01+8o6q1p@rg;c^W(WS=9+Gn&C3j@bf^ z*^bB5tqoRig2V@2&$o^vhCvNm+eZVq=uUy+)5_mc7YF?U21cQ^SIaD=6`z#0CVo0| z4&+__r4oUCm%n79f3b=DW#r*i>Dk2_W`7_L;mk}okdXIxoQn0?B%Kxi2f~kvH>5bV z750Xep|GDd`G=aG@@g)8Dt!~#ZY3R;THt0}nF{}BAgRW04R9>+KB%~X0$X(x!JN^Q zhX)ar{}$fDAx-*ODL#XO-)shuZf>Qh^z)t74dd>ip(fD#1L?LSGlugik-GQ?lQn z@h_x;h4vH($Y~H~E?Eq+P#4HK9q}|for>r}RhU3om; zkr`CJbyJRQ?Krso2$(>gnEj$-+3OsX(xCekT^!MLA81!%yvC?`J8>{RsK=}Fg=Am_ zH8{VMiD6~qVa*y1266%?kD@ygu33ELDDuv!HAyucsy7)Ap0(I^g;R7@4*!5Qezc?uau-) zQ%P6HcVE4-cW4QDq7)|^?JsoqSdWD8HCGjnId(P|Zshz}$o(r+rK~mvQH$*w=AeKM{?GX^%0XqwD<$LZ2*e9h>;&goG zT8dL5yfVXR4!u4XX3yxGUf^+nu*sGR1rswi4uZ{A@DkooBzJx9s?P+++k{Xm4sT3W zGH=ns7zeUVi1sN_T{&)c1q30QonHSgKob6`-Q({|ceM7;IBPb67owznX zc(Ev>#Jp^-5w(1mGm|u6a4y8nCW@84{T%?O!O`wxI2U9B*s9pAg&)J5=_JO}Xd0QI zd$njyvwC)BPH9N?QfaNU-iojJnJ0#aA&Lvkaf}k+02-pH2r{xWC(3&E6PQUs*W7T) zH+V#rkFfWe^zRlMR@wP>Md5@^n(}g=xstzAGI@+zLuq?oLviTgnX}srxlaLep2eIgNh)0uc=-^ASI1#ABT+uK&C|<4exy&a6m|UE0v> zr@>afU#_o-JtKd@cdT#l<&|(3xk#x=kqvJAuqYr-RM-%wELlg^R&sh{xpCF^>a5Pp z3zVEpL09?WJ`Jd}Hb1>UTkvG%{ad5V4j7PgdMe4Ju5_jZFcsqK`!;G~bGfmOX218L z;-{RrzAoa-+5Ce)2-h#s=BNokA(m{84^C)TTn#$wZG#d05ZYK#C*Q) zj0#$c%zQ^vLWL6e`px6vT!aZaTzbqmpruh4@t~wWL$iiIxz658?VaArRjwf80^fqv z@qpD3vv0%&8@}E2?O@+R zThWZ^NJ>s0p9ozR444)u_}NXEt~c|yp4wWjsnyCbSt)!|rZ3(ya75zE^$9<5R5OM9#1fQmkHMu zp}uBRz*Ny@jOf-Iq^0O)7Zmqxa(kmLoVaUt)l=gl^hR}it)4_e{aU;C$5FOt>DOe> z(R;-9p(I>98sS1veexSon+y)LtZ6e#Ddu`(N)_uG+)WkbydQ_YEfr+l^~gR~Nh`Ka zc)~TTw+z~nz|X>t5eqwmQoLz;s$CInPvL1l(_Gc?#-KpN65-$&7Ik6Cn_o7(#QAba zV&XEye=SFtu0zvfj=&BA%H==dJO$*a76KwpQgZT_**P=okL%q9t3#@@s_5~6%emjA zuCx?N9T5BuVeL{k1^e(S;A12WHO1tIvjI09%(RS0D++0WpK9WjSt?BnKbM3CeV#kv zm@j<`&O-Sd4CR4)<#3|K7#Y8?;wS;aC*eow6{HRQ?nheDyR=t9-u4rpK_FpB@uJH) zHkIkUOO9pihOif8MAwqUD!hKU2OIw=>eVvJ+!2Hb70)>BeQn4Jy!UMg`uzCE7Nej` z#Z?-VZ}4iP{wkXK?6jA*SzAVrIV;+tk+K75w@%5*nG{%@(3gr)ihW1(Qiad)TDna=Bx|s#j-te zLlq>^36Mm>VG6+jP+LL=J)F@NvfLXD`hj~_;8z>2^(t7Kmwe738_d>~UlZ!Dm;iY|$>>JT z*btvXkG3BKB8(Z?_n6`acr*J&1i$xIh24_3c6^MeTjQR*yXTcS+fDapweeM&@5CU~ zf)_8B{CGrUN3QKr8QNBE>>Q+Cpp-`QYQu{JfCSj*fcW}J?(f~w` zLGU0TH#G2-5zuOC4Jn5mtrG|Oc5YHu@(G`z-Lt-*3cNEXdIyuvxi_k~oO`Oou)0hT zd0S~l{{9rBLswwbG38N1SI8W6dDsDf2H@~HQPA3{#{r3lhl!@czWLtGhjW#`omsk^eW#|yB;WclKKwoI zl;11yxR6(jD%@W)vm@Fb3(`IhsaKer00xymAG!TvFbn0Iow^Gz^k`O{7WT;(dj*v< zY?y%V&Iagn1pL<>@n603#W z`ak7`U%-iD=4M|rIRI!WUFqxY^e4OIIPG0* zBwPS3Pm`yg0u;dVKCA(#^6)6WZBXI$-Ri@WEz>qTlLnNN(ezx(yr|Sm?MEb;_Lq$4 zc44|1phrHj)CCp%J-dYUT%4a?O?3kM&+REipYU_G8ecztybF0Fs%Sf%c*R7N66V5^k9s_8N*Zm){kY`iHaoOw4K7pm{W8}D!J@!p~b^eUju%y zVm(&t3GF69=fyW|A$Bo)4!P$o+i(15JKVp_oS6+)$^EThEk(cmfe2x=(NF+dJm-xS zMmyXk#31d`=N*S)-A@gLJWz2BYZ5(Oz?1MxNHT-+6F=ENp4}m_Cz5!zX`Pu5TNb?U zUz2;nBB>Rtxtx+Ad%8nQHpu|3SmA^D8@eOdZ8MHJzc9TDu9cxz$S=OUUzNx^dLJ|W z!Q{fx^5uqI>3Tnj!+akX<|`Q(t42m^KG=fG)mrJy zLw4REtVsJw6aqih{RT{nAb{R4Uh^A4elhx24dQ;L~$(@dvpIQm-M@DC}lSMjl zGwh&5oGFx@=+mlpr+aEBAPYnXEm4hwWSO1ahz zxsf*_7CMglD%J}F=V4D>L%;rQw;S~>xPV9j4QxD?pQ-5p6s_J%-E#Pw@7Xup?W>5-f%x?pYrB8~94u%@QZ(H6)v%sZH_aS~ z$0HNb9<;Nd2whW0jV%SbmilH5`m)tIo*31UcR4r0R=8bWZaiUAN({gJfpz)rC2>h* z5Z8a!i^1oJ08nf#I$p29fTzKQq z=_JXhZa?S^#)&D^){tw^CqXaPymvzEU18jJ>DTW*)9PN&;yt8xjzQ1bLl}IZpyP!L z2QiPn`XqLc^L%crbJnz9Xft1Mj#!s!V?1)gLaXo7m$&Q|m^+XB9j9Vo3UnCxeH|g^ zFc6QZ{V?9@rr4RUCl{`LQ3SC^v*?exLnWgu=wr0bE| zAY1OU`WCM;5bvS$8r`u<)makVYu>lu0Ou~D3A08~t!GI{9su6gtPtHALf8-ube?TY zHFeuHY}rb$#Y~aS#5|K8u~KD z#m-NP-+#F_NmC_Qq61=vv0OyxQzEDcMkU+_p<-TxHb~js5Gt~)V8rGs1x;=WGKMvV zkcce>-KkmxO23P##;5?5i{$44B~L43fiCiv-c6EDc3f`A*<2-fR4Y|)&m%$NV#-J0*`Lpt&|(Z`oU)u z9H-urza&$Pj9SLuzjvvLl6#SyrCWYfSaYuK_gRRLTe{xH4PRq}-c2C!_qXmn-Ug*IuINd}qU*yRjC0vse)T*X_hY{*q5pJ+^k#%U&hz zX!hos^OgI3ZFVtiq@1hUNM7bRuBIVD7lq@|Ri$tnjnggmW?T+_Y)w5^>V5^;xcQcU zpxpM(Hy*K_t-0^aYfHpo#$&fXkgucdmw^rPPRHT_K#L}|1gKrE8F_yXjm5ttrcVkb znPq%vnUA`!rS*EP;snw38Mo_KjvqFlQRyaLt5f`^nJTd%Nqy-s0&y}`+$Yvw$ME{s z6*(RP*TCW>Cq7-V?Y~v@1aOxO#rrn&IUb^!Qje~sZr(@kw-Ip$f8>Bp{QLxpiI4jxU?*X{=G zl|^$b&z~oee1lD@o5AnEmpQWkJrZ<~IaJ1Eh_v%d-r&qUJF$M`;s-xVo_8l%k4f(= zhQsXwk+b6Zpu31XWCO@kWV;`tkhdv3%}#GstnG@%6D(rIMmtmP*Oc7p$FOr=U<&Wq zfQ^K@D>>pgUo#(YUI|$8B_KFZmZU2YhNBzG%I`ViR8Hu_^102t`hvIa`LmEt(KW?9#_QcT zr{I2Dv%TqGj`*5C&g7T9-RC+guW~c_dDS;k?DQoT2KsLz~*QVW7eF=v)8mP4zSqPNiIq8S~NI7u;tkjkWIQbNP%-XiMw zvH0g~k`*<_r+y$eguo5N5QYVCB;h0g#jE7v9wfhKvrxWS@l0U_(X@Ct>|k5-!r-9+ zoJUfK%X7p2^S9qZQIIfc@)WfU6j;uzH8hKKBcNWHoB73;e+xMDN}F6c%h(fgwNi=i zTe+X}-n6BFVy~r5Suy|3V z3v9RqQ?bBU`E3A8Kql=DA6ZL}8@*W3I-N6gGb%8#tLCko+w&vmE_|~gw!gg-Pt_Rl zqnnBk;lVM9O4WElcF!*{s)akP_%`Pqo;wi}RF_N|Hz^%7GWX%?g4c&QI!redtSHuR z${gn%KffL1$>Td*QGdcARyk=RHbX|S-UND; z%&*?m2cKbWan#RG%*Edx%ouWJy&$gEw^_GH*8{$ju>_l^<*mUKj^6#C__yAnu>N4B zwULF8(iMdns-K#l`OA>I?M{#U5wEc|$Zpl>MLAFiNhb1!c2n)6O0i%iBJP!Td-#CK zshTK_7t%Z(FRW5+FlTp;XJ6{i0a&|)VQP$5Aiez9DpX(>!9uqnOJ%h?0APe9`kh$Jlr}UuY9##-A}YsYQ#rMwwP8oopo_ON zQtA*f`_QP}EpDK{Gn4HRHKSrRXk>b+JGi)`;gIeRAFz_@i^=5 z-33|8?K4(RCJK;4ffz|p0Vhz9)Fg0~2>~%DDy@2XSvXdo%&3U+C9lZ}jqJAKnGogU zRF6+Rk`FSq4r?6{B;3;EnA&&EsV#mCcmE6)=C(iu{8L1@hXBHq*{8il03Wn|IsRyd&iyFk@ct!%9!N%qO)l67sW z*Xv!|uf}%M)I(gA;LxcDeD-D^;%F;^g2Xq((S6?1uh)^k#0nP{0a=$Kee;^)R8`kr3D24#tw3%no~_hmyEuLF}~vPH?kamWDSNwg73*?vs(YMxC) zl#ZgTlBav7@l0IHT?^Mx2qymqA~Dlwqm6=d1XwA>w&eDAZ-Gj%J;zqLI}|;sUc$Ya zxfma`{ATpr#_~Xl(Wz&pT^k*ugRmM3oVq-|%QqW}?|)2}l5PfqN#e?yVczRK68UL8 z?cQFppNBV_5{yCJ^WmdAth;M*dkWywx|f6mFCP)6rra#4CYGz2SV?EePjO(!27ELk@@1_T;RZtPY+ewC?>G57V!7wHw`SB z%}DxrquEAbsPWky?)wLx34VM1?SN3m(vPAyPbY?-fELKSNA7R(nB7jgqw4XV>*eL7 zk-Vs5x8xq=t<~Q-UD5&JyGk4Vueo^of3>GWKFx+KL5H|l(~`^~H{b2BG7rTU()C-x z`HHMcZlC(hpw#TsqcYjEK5DwvPJ<6|u5a9SM7%HSizIt3rR?xna4f-20F`(oC&+#7 z#PI~uRvIWG$w_b3Lm%)YHE@_dz3u3ia;qRz_KxAUR>1AdU2i&;QLsBat91p~qbYc$ zKU#?;BI9_ql4PSTGQXHgC|mj{26{Oy$Opqc>fOU~|3FX!9FM5U;AXiN**hGC(F8t& z9r%>?G5QVFZob3)#5qTYt93M1$JG^)R8OY^pW@aAgwBLq7}N#7KIb^P9Gnc2#F>)h z;%Ll;pK$(GzW4Ut89|>*>97ysiPyWYH>V*t-+$^eQOQe;FfM<5E20w;$0!A2NselS z2C&}ar(c=8QTTz}_bR09dW_-kxG%!5UikMBMOYWZbxtdipMhs?06HE+yRs#K@8$HX zWLT3|nd6*hF?ql1fQAmQ_L>>4xkXJ>t~pMVSEl-)B_G=X#dT0Sy~QZgy-Tx06;p!& z<#(PK=tyk^huK8oM4*mlOXk3J$J_dcnz;&Ie0<#c)WG_p@mszTFr_6C>x)f*i8InL zG61PJNbJ_q(e~p&AC`5&k?nq7FfVx6hP03G;6gzRqpzbjwAW50Bw>@|sMO?i6to3orNtS(ZrKnv9-|SK1*D54gX5dY=sf&nY$(ZR8ek3 zj8di?fHVO5DJ}LR0^JzpulVM%T{G=Zg}p5wWl$;wO-jKb6l8GB{m5~iJ0%cW(7&^U z`J%9QxAwsDhH9e=K91`r{vqQ2Gh6oGW#NA&Z9*zDuQ0ysjkdH zZS7p;o-hf%31vPXE&;RN(w4ja30{2QpZ@0iGx@*Y>DZ(y2`K6V$@mKY4yO|_p9@h> zzJw~i;NV^U63JzGfk*9yY%XNV0UWXoM4tsHrI?{D4>AX}G~Em~W~S^*SUNWFb~Ur` z$yu7N&AC%>kESGDMa6JVOOp?L@<}4%=r|5CF zZ8}`5I@$7I&=PqFJl34SkKhCsrYP{qnJ{HTFpt1;qB%kt#dsv^4=0J%@4sh*Uio+_ zNR{pp63wMjrgFhCLnJsG&q@KiN`e}=*$k3XnnHA4qT%?eq}W%tXSJEv6QH3QU11}E z5AizqWuxOB-i)gM%ia68Nh=V|5CAb1InnQ7X8M;}ChsAgb=9@FO6LbgKCE%t+>6Nc8%Tb^m{^|NqP1 zMJ?stjssRVQq3VO^HT#$;#i+bHsGL0CxT}Y9s?H$Oqg5MKK%S=(9r##v?ipz7WcvC zD$C^s+tPtO{d3_Db84pcgEvBHH+<@sEMu{6R~ZzbWCscDj-or^>X{Kr;3J!n-S@ zumn(Is6sFV5%dD@C3>_GT{nlS_y>{))`ZU!!MT`cKmNwof+4XX@OAJ>Z2oaSQoC>+;7VPI`hIqby zEOeAOcI0EnGd&@2YWhFDpMO0sNU$U~BO2}oZplwnQLvF1NxM1kOxGpPnvkOA=A_ws zoBVpbJ);KVr#dZTz}<4^f%f{x>+O0^%D$nDeL>$Rq=~{MjlKDm!97 z_bXmE2S?vCH%#>^;*v{<$dR|Q!$m(3_hh}zQn1$v1?#NvF#T zYg(U6^3uj&_Bpw1f$Z(;l?NHwu!GYe9?+^U@kA*+rq$^E!eH6Y=H8JLVfvP@Pp04I z4>HtzEatj#JQYO2wf$rCf(5}t=l%mRV$@^30QWt~HTXfZBk+DPuZQ_(sJy-47(osK zS{{g;A(0ke(GZ@q0kauTe@UG8glR?1=nhttPrRt8lS>4FC8Abt7o24wkaM;W%#)GZ z!x3~30z5`ivKE{*(FpbRH)?nMx?q2`x`&ysIP#qQ@%}CHC0U^{3l1*QaO4s%=9H~o zAy{U=&mg`twN~YMRQOseUZCM%zJTIAIUfGT^QC!!4|$F6Ag((`SGlm4aKYKr!PxYq zh`HzMO;^oM-HL*3!+C+vLA4ef%4L9fr~O7!N|Dba_M8&CA0nE+GXu1Qt4t+ce10pl z=o8>3b4rFSW(IN^wkq^>Vb}MP=)fH*inUz63Kxrx7J;h_KD1#k_<5^822% zMl#j8R)nkJm+&^h1`vPPQ8FT;l_jqiOmyl6PX!u4?o&bg zWczV@)N%T`I&ysm-%~M#r#}YW4y^W_IzF&UwaB)fd}y24sAQtMV>8yVcqrL!OxG}z zJ-hjRtoNj~i1VcM121np`x7qB&7^aXgMyKy!a#Kkj!#Rq9_w>&>Gyye6uwR;F&_jM z3hXV`z1C`8Xe;B+%30q#iZ^XAOEo)o7zjgJDk6e=UBH<#SoUoJgvrcpq^7VM)yR4)2 zi;g$V8(LH4zigC@$sQ@#zI9xZ*xUq?8XPKl8lG$h_?$xPf}g3Y?}ZJLkD6}-GWCw) z@?I!vror!gZvZ$clko%2hT#S5hfK!M7x2D(*Xq9dEm@3_8pbL}4yJ=5F7ua!I5HGf zKRVv;fvANmR*s=%364;wNnNV~&&eQrP+c5m6owsaeK5bsNa493mS<(Ly=Q#2zx7So z{WFTWdEw`W;L%XNfUUy_0M^|t=2#z5*1GNFMVYx=pP~9N_}bVdfgykC<)KpxOB%y~ z10LClW%JxGj;2HL7r70_Gzj#b{w3)ju+!m_|P z0RGJ(3lMa)!Fsu6%q>0CZoPKMA>>Xv>XW7JL!)DE9*J>hIphy7we*5}M>)VQgfsv< zqYlL_}4Zqn7s*6{>8WRwcjbZldN z@y6@Oph&^$h_8|ycb(Da{_W5YRC)o(LgVEF#g zOX+r(SQfTFamY=Hyr;7OqC_n3DvXaJ0-oWGue(ROuUF+LcTpJxNFv}KDl68Ap_~(!TOV#4W@gJ3Um8mu|Nnj;*F z1DU(t4%6+PV&?3Zj|ZI7FEo}zl%=XUQ7A*C?~h@uD7#-c!Q1g~?AMRgzLA{Iz)h%% zFGnpd3LgG80Per9zJD0T$3l~(D^Zd`vt9+Nff&cx8cGqjX{H>mJJgt>Ecq?+$>(GD ztvRg>&s}cOjyf&)m1CmzhQU8VQvU{}{!8WwXaK=uXLKxJya0)I87(}SPm?UviYul> zrZ!0}*#7j+DvX!%GhS4;k(G9&{u27#`ya0)v=;={T1LHwjLi3#!@M^QnP4_E3&kR1 zxRItcZ3Z+=JFL^_8xfo9bJ<#?OvV+^K#r0 z&Eb_{g>(AXus7D4hO=v?TLE3$T9S$m)H(o8R87vQ!6h({qOPt@4%twIT93|sMvux@ zw`!fej_!+i#*9C^bq^UEiX)(Syig8C`ryPG>;WAWZT_ef-Vnt}{#j zqrLBrYpQFr4T4|+qx2rCC{>W6v?xdu5d@_Q5drBSDi9NXx0Rg2*MQ}o* zPDQeQF!4H-x}tqr(vPNy?1r($c6u{X?vMoRr)6$;W9_&FIU}_Q)?<%iI?l~2==i(b zinuLyLqvH>xm}2@evZltXq+iF(2=8+hntZ;6@bJSFnJ0t>n1j%m&U!|P5kHdsmkLS&0ys##Ef-T=y zR9Qm%qvAQ)l9@kpYCKlC4X0iK9;VTB82K5+2V_}f;+WdkBm4!qDm0bxW4!8|g8YWz zQ)LNUw~C(K))rwH`FvV9^?Tuk;==^UA>>C)_0>eOiVdwRMRglHF42-OJO-HPlvES* z)=G#{UAf6V{HEB0wfDpvc}6?6_~s#{KTVk0RaI7M-;nfXVp}lW@H@N1fQ?yw%aA6w zV{JTb@+|jE#9#4?OeU5TuZV6Od|Pq>gXP~d+af2--G z-U-P-g;pN@sUil)RROG3Y;eX>SJLR}ycNHv1Sz#F&6s{9FHRv2<5$%|wJ&Bfvk`K@WM(a~>N$goRk1Mloc@*X;cwLEnXMN$Uef+DWY+a~ zhv*@_aS<)!yHbGf4n2?pSxVBu83$*U1GY*UD(xdlPp?sINd27U4)+9R`=#Gj9yWCu zwYIVcVY#av6i)e$mosBU-Zw!ITHk;KasJQvq3IhvSQMwu4EPxQiCN|iyi!UED$gL- z(!keKV{vmW+ePq-w95~M#Gqc-HrTTF0qTTn3NHYN2z~oWt{hgX{AQr2fWE2jb>tmU zq=5~mhlPYrf;E{CmjTld`qqcWp~(K}&z6SNRC{lyc{H#&!q#?KVyZo&~DN zfb4>?t2>Z+7HEgU%=&cEI54+0X<9tND)~gg*jOGu`eUsC zLOD4iG3DIeD7f@R05biuN%+y_(-S(i&)o)3dom3VI5>Ki<~D6#krVa7HJ+V8Kx9a@A9U@iIN za@;`uNCKnFx!Wo%oiWItud{M+3r+mU-U&^T8o{{*%C>^wnkKJvlaQPGy|)1LdzAU^ zXMa~8dIQvbMK>$|E#{vHe4fl3fLNXhoTN5=8n!3DvZl^ezpm|K~{M>{MgM^t;ThKKT-nO0cG77KmG@}7UqR^QO? zT616iWgC)7^6UHs5nWma6d88`i0I@sKvU=YZxGQd)Ew*=LIk+v0CJqpG=Z|;AS_h? z!fFB_tgh!MY!wthuq0qR765{!&@}%mc?>{$Jdgl_brrLL1`sSK2!1sdK(L+-(Qa&c z1F814fG{h7V2wfm0E-Pxd9yT0N!6pMIs67-`2t=+j{pEGQ3^PS+%MgKP1|{*rqW?$ zx6kEZQanNb5mdNuSO-_7ZUu&r5-HtCuME8bhWb-707s9|7 z*8_r)o=nH&A)v?!)&YoN^k~$XaH)Trf@GevvcK_N1$Qg<_ zHBR#o0y5MSt@uj$2aIq(O0&Ln+@Q!!qG~PxsXxlk`RqjaRKhuQLE%3o(*8zh_?1v= zNzKAIkK5pmmE<5SNycx{Pn#s+Mz#-DbrK&{&XsQvRX#umZMwc3I~_sZldXSRorsb_l>rbX^y*d_x_0n0_^`iNB4t-h6qI_4{ZvW+eLs8+Pnt&rS zNtaREMiBfMZ0mw1(6vwRfyRMU+x;v43)%w+SUkxord6o&?e?%2vCj>sV^1I6Mpf`5 zb(-wg`heO9nM*O2DjLyf9f4sx1Q_i)|E~37NnUchSr9HL{`nqg zEe#0CKIH(>+wcAeovy(wJw^bQV6A3*pgQ+|j-megHU8Aw4O;$!P}bG03*g+ ziE-LjpB>)$zqi*e__EWb1Nqn%VGjZ!;05#e@2+)@C#*+CTeC$%5B*|-#T+Pp3=#m9 zNd9k!>VFMtn{25spz|fD;fq5UV)P>&K;GgLWYZk8lC?ZZ)-CerDg20;wTapT)u49G zG+)1tkTg#DmudD-zoC0y|64@q|5~}fA)?>E_Rn;Re>eOdRt6Y%#_kADo`A008~`ZK zG1h%4ZO<}Wv46Uy4lr&;0oMTm;L6QD=+79*EjLNZ6=8hV>j)`@;J@}l{@C8wyWboC zT7&<;@c;iIy|HhfG+tX_2QJcU6#bb9Mfa(SI z3~Iod^aPMYUZ?fzQO+_^ZvcQPNfQvd`DKuL|I8lf<|cFps8K>EB{l{1`$FgZu132N z5$9i^N&_+}Q~=>~py_pEe%~elgX~Y6mj4rUNWF)K1faq);5=h)@^?jc`O_LTy?z~Y z2eO>uya$4!HZ1o*@f+*EE3_i&cQt}tyQX&*y#EsZO~>4S3Hu{qe>(a#MemP<{gJRg z681;Jesr`SqW(+RzP~2yuL=8W!v31Dzb5<-UK7w7q*}5n%ARtHkY5QXB0@V=!-&Yp z!m&qb>(-l|1sf%~C|MUuc9TlS(9NhKQKfD1e|Kn5XNvgn5yO2);c1R!oze(^*UaE! zi4SOFq-#@+?lewR%mV;JVd1)3LsrVKoU^$&KdMW+DZiuV==!R#_M8c$`&Z2G!4&ir z5_k_JrZzoEjZD*{#kbp)=vZmqVLykJj}qk`=#>T5roROs$w>vQXZKN<|K3p8P$5Nv zWc->U-0LgDrSq+l#fEHSl5SR8`)%G}&9U~?39&oztQw;Y@7m%l%fu2hG;|~QQ@f*a z2)~nw+17cuRbR)dQAgkus!09#>pxGGqTl=W7m?9_h+g#{Jahk^fBs$ku3gZlNrYbm z9VY<{bPO%N+pfgUj4%Tn9jF1&lmP$XR!Cm zXv9sCmuKV7SYxW=AHn3Zc>NhRJdh*8DH+r_FC{>De6AI$p%Sk`bRY@OJc}W?71EiebUp zLy6UR0uo526eM|04fI#6uau~Ee4os#H&8z_svBOr=?G69w_qVR{Q&vGyp2blaf!Zt z6)^HI;!u|op`Rj02zxBeso{>@9=Yf1Dl0Q_rn6Qo3R!7>+ReSDdTm9M8IL{%*RkQz zWh;`h)tQigUoGjnwENN4cGpU-YKB^Yl)%`^>Ft3qTo6jUT?syrm_q1jgM_CUKlM!) zQ07wu4j%$X+Z8^j7m7Xilsg+!d^QfC{%nSxc3QIw&}n|mPr`O-TvG+F$N zNcMBwa&N27WySgam2Wo2$SCSPfPVs>KJyQZBl^FhXtF=X{{#Rya%ERRij^uBsT?9E5t_0M(_`)??CS{A`Z-BnAD{_XJW(CG@ld-)cN?=HhQlO zkK`Di-`#P!uq{$=(TaM_FA)_A3By+|A>?xysqAbT4y_ry_{_)ZD)t06oG*}?_N6e? zmQPFCfqcb5$kz7EPV(g49nkz0l>hmzA({%1Pj!~tX<)X$V(VN)r!m0PuLx0-)Z?TB z`6P=s8kG9m3^bS#`f*QCjw1ovyKT6M)-MVi!ol+4_E)BFh(MqkiL|%Srz=4zP`qTUnYgsT>5eLQvE?pU5_v4L;>N3l;g3U!#x_>c z-pmwRQsi{1_Ts~>o@6A24VuaDq^`kqO_yoxk<@^k`UxB)!n!gxToH8;^Tb28gm->- z>vfupkffCUwtRV!<&6Aezfhr!j7r?Zd6EwojFj0dAdsIpaj+)y9MwLrsp}K2PNUft7-oKn|-D(_`#w46{*?hoMd{ zE-MsWZZhT$G$$|#s)xEHCDF7AKz~ejhCY_DA{K&$1K!~jKYOd%cPY(07 zvKq#NR{FPeFQP4}$&Njp#>}z1z)@%4`8Vwi&NItBe706+hiw-9R2?DC73CGC?^*RzKW&WROUl(x_;d>wpd6YGYnq;_2d0)Ajx9UD> zI57e#dwxylHY$VepB2gfGv7>me>v6UkL>vS_xjr=8+tq&aS)gwpfmEo1hGz48MQDX zpVCGWNOjvSBSxW^8HTFhqk$}!uknNP$Ii--r|K9b8E(zhm`h}YMNz9E-m5w@0B7LV zBvr-6;voKLdom!mo}Op;wyWG=fYVc!k`X*)5?wMLOHKz8^_t!5hJvAucV8F`O54pz&sMK9wr;qu>PAM&@tDAnl&A>}eZ>dVD!Z6bWDW;9J zp?z2_{NyuFqp^Mwec9RM4Zd@)8P=VdyDLuZ=&4`t;y*uc8;b44;@m33??pbf)#0(N zIj=S1{??<~`_^rWbB2E*%onG+~#?16~crQzVKJ&J(c2dhOzp?cuO7(f{E>tXPJhgY?TLj;j07-_boJ zz?;f|Ac0j~k7{t9LWAM=zK>7Z!p*cKvqI?1+j&w`-xU|ri6uUkylHIA5vP6F0<6oO zYWCD#qn6d+)cCT1Ts_9FGn*3lRq+uYwn~MImosU4+;T~w-JUZ^q;7lxr(lZnEZy-W zYt^=RROxZVlO+u5XC^~B(W9uJ5fAZDPLf<(2Hk*YUxT67yKY58MPvSFiVAJR*iJFW z9NCLJOeKM+mBf=sDCzn`YCRBm6coB^U+Fwlc(+6LM9C^!-i;!eS2HrDvj98WTO-f| z*?&!kBZzX~SGxOu_JjH>6xsb5;cuG}{>XNs-&vy8IF6Fd;NMZ-Re(TaaA4!7=@8)Z zr=E^-L-WOw0l`*40i?+$)cR*u<2Tgn27ul=&2?}OWCtWWEgm96^e|shE;2{2;0Nmq zvAGDH-YFZkqsz4nesz@<+9zzrF2fCyVi8WnKk zv1>n~lZ?swlfK`GPGW*tL&-Wafz=^^ilbblAW#2H@5)0*y()w@ftZ4udA(@I-w(U|m7?b6 zDLtJA)4V6Bo2Hj=Mb=O)0|Yw8#Ib~V&!0-oqMXt|jsp&EXNS7V3}L3U&OR?{HsvG~ z*scNN)=N^XLIKynp{{u`D#yU3==##=pn_-BmS?r!q#2PGajMjevi7HB^}pwL=-ywZ z6919CasOU_ZU!h*H^d1@v}*DXAS}>dWFCCx>z{4A%z*nUL90icDEOajApWW^5y-@odko%W=MmRFw%+d`m71l#7~RQ zyq&ADWLX#9pGfY(Ch>(q>o=3e`RM`9BZP6A%l1j)GbawlaZHd+;_h~Ge1ppo6GxMcrpJ)o;lf!0K-8&xI7KAn!3WZ($mNBQ$%~&HIXctR4rWF;;2|qi{GHK@VQnHAfDgM#a_1q>z(Umk!pcjvj-X9AzRp;7_78gMg_pd{;NA$Qss_Z|tqu zX2>xn%+sd>dVYgd*3m=a+<2T%U7LXvqtpsD9KKQ`k{@ZiLsvUz zD5!V9vBhQ0?O>lhkk4<$a{GxYlUvnlL~sEG75J^7L~SglVhC`8z)gntQN-|_u@7FE zwSNu!lIeZ-y&Cto`nMjM1_JD?AEK--oTq2v7L=pD!`zVrEv2YJqL8ACs^&S)k7Uy| z8s2BB@)h3Ty%c{Sq4>22ho;m{n9rJdvh^4Bx3WAH$t`iAq|byQjB>d8igFwibX!8Cm|k_w!> z+*yxCf=Ip7lbrB+Z04;#59H7fBVxCKUqDsjrA#q1wU_m$rUYx%?_e$=<>6n*)19qQ zK8kkVRLs(L>#kry@kRF0h9dFzl?e~@MzlTeTsU^<+=4p}9So`Z5w3X}(BNU!lvsW! zo)Hb8f>n`$#deDf21E&i>FiO9!{$j! zyzxGMrj5C2e%ljui#breQ|MRHz{(7y-A{0mc&1{bpuC8S1@OyI4p&v*<$(9>(WTxp zrv4EV3U(GFmeacPM-(2pr;vik&J-4WL9>SY43HO1F(@Ft(d%!Ev+nfvouj_FW;vq2 z66xU=YIOiFs2=nr>cq>~cH_4T=*U@WaRoSh@(4nTz&BML*`VT~1Uc@qdo|ZLv^JUB zvGLI6B0;H6imy-XM)rG=T&9#k{> z7QT6<1?eF8c^c{A@XLffVh7ThS%q#!GgBCbkWjcqo1ft6z6a$pl?^6i+(uvW%Th(l zZaX-KT7jn213;lI49wCaY*kY775ZU+kW$$#oUxvw#E9ap?33d>bHmX1p3!Vw5|ond3iW z%vDj%!kU`7Vi8WYQwjqY-}|r^^&e#8?JIzIIdYv&4aqg1#2o%-zrW9 zPx2tHIZwxiO&+lxvL488uwc45B*0LmTz`Ylx3c(5@)b(ytgtD_es^^i+nBd86!@6u zor2cSuJxD!aS-zQ2*rYAL59+XQT*ygWF6c5Fs2wqDx){^(oj`jx2J!e^>Bc{U-Fm2 zDUQs8IBo?^#wd#%2Z~vkxDyK&>eadUOtoPW_>lfu%$vLdxBcFYM(IP!NGrE z`cR8G6N3u0Awh?|{!$^tm05*(I!sHWUH!37{L6c%=%+A8EUgw{_-lJ0UY-2r013yE@K@Zp`N2OrYXr;hLKw)py;@$(BG7n!Pz8fy3S?>O_ zALe^Zs&x;mz8J_jdCq<=A-Y48*!*SXe!mj`*Nb<9WJRs6AHUmt=EK6>KtMQ75$_+4 z6oIEq7XXXD@601JQvze6JE4Qm`{>e0o3~(g5&WK1V33oq&pc@53*yt+s3iI-mWQEX z(_m&d=rN49jMpB>peplPx*0*K{-*lne(sWrqzv{6T_66pjwWE1&0}}nw4P4V93Y$?Lk=DNHCEGzkCqkRVwIEmi$2avht@DZuG)7C{BO& zx+epR!kD6)(*>uwZ#e1>{It;y8r&Y_9LZQjOg)JI&Q@m+#Tn# zz@3NHwWlXT1l6dCv@-}N-6~RmJsu~cJZhT8mDY24$ho4*DdBeUgrU))jouGDLYpDt z_nNx6+EK@lBY<%w?SyN2&y*a`g7V<*r7{}>cI&2`g2n!lt=KGw+MCYI}Mw4f%d{ zI>4-ZB5*w%%8c?l|5L`P;ofdlrl&?zHsgy2Ek$asXT8$+gkK&$LU~)ed4ZLAonx67 z=*-?>nVd)-BysISU_-Ddgwqx{LrzhsMx0mG$Y!opCuAzK z0CTG#R#4H8n~UyZJwY9d{A&(l8}93>7eHb`UCW05B;fu_Q}C~RAbWo~32;B$_~Sy0 z|FA95$tZVN?86_;kOQQ^Yxum7==sJO$nixQZ~yFo6I`*|Fk1VQ$UIS>X3yZxGhXrJ z!;dF0GmB4|m4F+&bM(>a6-_~Efv36mL3qZjm4O(syvLn+`bvDo`_VTVwI69~Cf7q3 zuq7a0$YD7eba-Srhxkm8D_Xjt$io*5*7#<=p>^XE5cNlFrM`BlDjE}Wx-~q*m_-U8 zUqkl5I1y&hU?e|j+ithL#KfDi+V?&k+h>WZ3yNjl}~N2p2wq! zPK0CCGnl95X)Fy$uA0Wp0^bH-nL^jP2Ytl`>UJiMhv+~_AlA9um8OG+_#7O%4IBil zZ4ie+%EhT*K7IT^L8wSST1LHIAw}6j&Z|B<`h*pK*Hf{(2DkK?7D{u-_OwqAEy86R zq#hC2uEbiD(fY_bh5iBdv=3Ii+n;Wjm4CE!l<-sW@T@Ph%Xx#aCV6L*+_5zI0fe@@ z_iIvVAfM;Sg);ex&qW|lR{jH)pN?4w)Gor(OB^*$UuKP%{ zh~jasV&i^#<;Of=0s;a}d3)UJgi6yssaixKya||QV9|&cO0$(CUT+#iudUsI)MFAq zZy?lXzIHNvmPUY+PSO9Yp9VZX>TO^ETGR>SYOtSv-iMIE$(R|tuPV(4_k9^0yb&W6 zSXJMm65i<%tezu(O%NBbsD6TcnZiJdo)&QL%wQ%ArWCpe^7DLKqY#c*$sT4om*SxN zmTu$|$b+Bl2`nzHjj?~4v0#SfzHz!K-r_0LW!21rpf0a9M5kCN>u8bY@|;afUjKuw zavXdnVjDHG4oEb5kEjp>38Cd=HGRZIrm)IbmT`EdY5#+QRIiHVfC+8x%@YT=zc;gX zaeXQ*m~WPO4F1{^0;@1?0mpE05m86fo$PUjujIAITD*nXbffOR=N^-KF7bGZ<2?U{ zRQDdp9UUR4jKBJ1D)sp@8}ZaxjuQ;yK^WshA97Q#*n zn2H(tL>5caY%CrbZx-4_!5u7I^*$Mxd@KOggHq1Ps_K-dvdnM`FMc zQPBV~kb({(dS)^#gCUZ3SWVDwvePwp6cCuu|PxkB4Fv(I4N%{%`5 zx0`&P;FOE-YSYH65@@)g_Pe^!p&9WuJEyI5JJ(%xf00jJh25TlM`dcUpj`Wp8Q+B< z`GQBNg-~YzXlvpoa77`wW}=~HeF#zdakq^eRi)>LqJ@)PUYY9{6}Bml2^26)^*3?R z`X@n1#btjxK9beUBWPRNtE6%}BX74)wlcka%5iHqcC=NWtmz<~DaT2@S+1Ftuy$CK z^?IxWDe<)n5TUHDRTeBv4&S{Qd9<&G{{*9NjXe5;_{3Nuk9XVvDo9fapV^5HLGc5l z5zRil45^T7_hxb1Y|pi<2=#;*Mlc3ruT~Kzp zrYbD7iJKIg<7Ow8U>>YySxUE=e{=qvV`qvKVf)&PjMi5(JtO@@FOvORH$eyiP-PX6 zyELF!A70|+DG|3r0L~*->3NH8t5=&#)7@b362VBTYZD}<*4r_|-cy|`GkDX1TvAz{ znZe^t}^dwknlROPwr6*sb1P&_2@*w2;8hF5uE96+mToM=4qK2?A_rUW{ z?_cffFltbE4^cRmA=-H`mSGo6VXJd3phjwPsISQc^1#%6)&R$&>Lf|^3K zFT3bxIiyN5*YZgI6SA(`!}eV8G*xwEW1!M?WX^m^24--g&8_5-(|#?`p1!3kY@`^fhE?tO}k>{RGcs?L2e-mrMc7ixjSsV%JGG-bQ3?2oQt}MQuPCP#a+*(S&f)H?_Bld>QmjDED3b=UWc{1CmOKCi==kxw3Jmn zf&}r!*ZE*W4tz#D?u~Jo5^=}ltz#crEYKyX8!IZDrgP(#$nCq=@6l}+tdMc*BRL(W zE)!CuJg#6b3J131whulg!L8T+(Y;VK; zDd@8yK#-tGj^v4Zq^DiqD6ez4v2v0a$(P748DB~c;J90SUQ55Li{wvy@U4l<1y$J@ zLQx@u;W;LQG>&Ce+Li>f222y4-O&{HI%~O61cGX$7k? zWHVCC2k=TN!XVd`kaXU-%5~&!%zEa!m(!RUV6LDWy!GOnLgiVfuS z%wjHEx~iJ_5SZ{{Fb5xiV+EOFTSms`I+B!PvYm<-cSG_gD~4f~SHweIszYtZ*v~_J z)3aLyyLCW9AZL1>**@5EIReOvrBihA?d+F} zSQ&26U7!cl_`q(D*i9JR*$qW67t%MWoZ)}@pldw>4r0A9yY*X%bB>&0%F`7Jy5k-a zeW~<73dl?70mD|ip$_QeukBJ^GX;6pw;^n4FgYXh9 zDVprPa)s>Y-dl&G-hY;De)H1vV$k+=XNi-I$$!87*FJOn|Ngl6 zd&baz;@W+-@qfV<`akhr{*XEKpPx&=PZ<6GU>yHtWy?RZKkeV^|IVC2zfbb||6m;N OpRoT=pI_+rhW{5o-o3B@ literal 0 HcmV?d00001 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..7820c73 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,53 @@ +# +# 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. +# + +# Note: Currently testing and supported with code coverage sonarqube +# collection for python lambda (python pytest, python unittest) and javascript jest +# and CDK TypeScript + +# Refer to https://docs.sonarqube.org/latest/project-administration/narrowing-the-focus/ +# for details on sources and exclusions. Note also .gitignore +# TODO: customize sonar.tests if needed. Currently source/tests and source are not mutually exclusive +sonar.sources=source/ + +# Focusing sonarqube analysis on non test code first and reducing noise from analysis of test code. Projects +# can extend analysis to test code at a later stage. +# - The deployment/*-assets/** directory for this solution includes glue source code which is already scanned +# as part of the source scan. Therefore, excluding them from rescan under deployment (avoids false calculation +# for duplicate, unit test coverage) +sonar.exclusions= \ + source/**/*.html, \ + source/**/*test*, \ + source/**/build/**/*, \ + source/**/setup.py, \ + source/**/test/**/*, \ + source/**/tests/**/*, \ + source/cdk_solution_helper_py/**/*, \ + source/infrastructure/deploy.py + + +sonar.sourceEncoding=UTF-8 + +## Python Specific Properties* +# coverage +# https://docs.sonarqube.org/pages/viewpage.action?pageId=4784149 +# Comma-separated list of ant pattern describing paths to coverage reports, relative to projects +# root. Leave unset to use the default ("coverage-reports/*coverage-*.xml"). +sonar.python.coverage.reportPaths=source/tests/coverage-reports/*.coverage.xml + +# Uncomment to enable debugging by default +#sonar.verbose=true +#sonar.log.level=DEBUG + +# Disable if needed +#sonar.scm.disabled=true diff --git a/source/.coveragerc b/source/.coveragerc new file mode 100644 index 0000000..65df14b --- /dev/null +++ b/source/.coveragerc @@ -0,0 +1,15 @@ +[run] +omit = + infrastructure/setup.py + infrastructure/deploy.py + infrastructure/cdk.out/* + tests/* + cdk_solution_helper_py/helpers_common/* +source = + infrastructure + aws_lambda + +[report] +fail_under = 0.0 +exclude_lines = + setuptools diff --git a/source/aws_lambda/automatic_brew_job_launch/__init__.py b/source/aws_lambda/automatic_brew_job_launch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/aws_lambda/automatic_brew_job_launch/lambda_function.py b/source/aws_lambda/automatic_brew_job_launch/lambda_function.py new file mode 100644 index 0000000..2fd1db9 --- /dev/null +++ b/source/aws_lambda/automatic_brew_job_launch/lambda_function.py @@ -0,0 +1,147 @@ +# ###################################################################################################################### +# 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 json +import os +from botocore.exceptions import ClientError +from datetime import datetime, timedelta, timezone +from aws_solutions.core.helpers import get_service_client, get_service_resource +from aws_lambda_powertools import Logger + +logger = Logger(utc=True, service="sfmc-lambda-standalone") + +TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%SZ" +WAITING_TIME_IN_SECONDS = 60 +EXPECTED_FINISH_TIME_DELTA = 0 + +STATE_MACHINE_ARN = "STATE_MACHINE_ARN" +DDB_TABLE_NAME = "DDB_TABLE_NAME" +AUTOMATIC_DATABREW_JOB_LAUNCH = "AUTOMATIC_DATABREW_JOB_LAUNCH" + + +def verify_env_setup(): + if not (os.environ.get(DDB_TABLE_NAME) and os.environ.get(STATE_MACHINE_ARN)): + err_msg = f"The lambda requires {DDB_TABLE_NAME} and {STATE_MACHINE_ARN} environment variables to be configured." \ + f" One or more of these environment variables have not been configured" + logger.error(err_msg) + raise ValueError(err_msg) + + +def format_timestamp(timestamp): + """ + Timestamps must conform to RFC3339 profile ISO 8601 for stepfunctions choice. + """ + return timestamp.strftime(TIMESTAMP_FORMAT) + + +def put_timestamp(dynamodb_table, watching_key, timestamp_str): + try: + dynamodb_table.put_item( + Item={ + 'watching_key': watching_key, + 'timestamp_str': timestamp_str, + } + ) + logger.info(f"Update the latest S3 object create event time {timestamp_str} in the {dynamodb_table}") + except ClientError as error: + logger.error(error) + raise error + + +def extract_record_info(record): + bucket_name = record['s3']['bucket']['arn'] + event_time = record['eventTime'] + return bucket_name, event_time + + +def invoke_state_machine(watching_key, utc_timestamp_in_str, delayed_sec, time_delta): + stepfunctions_client = get_service_client("stepfunctions") + + expected_upload_finish_time = datetime.strptime(utc_timestamp_in_str, TIMESTAMP_FORMAT) + timedelta( + seconds=time_delta) + expected_upload_finish_time_str = format_timestamp(expected_upload_finish_time) + + state_machine_input = { + "watching_key": watching_key, + "waiting_time_in_seconds": delayed_sec, + "expected_upload_finish_time_str": expected_upload_finish_time_str + } + state_machine_input_str = json.dumps(state_machine_input) + + state_machine_arn = os.environ[STATE_MACHINE_ARN] + + logger.info(f'Invoking automatic brew job launch workflow {state_machine_arn} with input {state_machine_input_str}') + + return stepfunctions_client.start_execution( + stateMachineArn=state_machine_arn, + input=state_machine_input_str + ) + + +def get_timestamp(dynamodb_table, watching_key): + response = dynamodb_table.get_item( + Key={'watching_key': watching_key}, + ) + return response.get('Item', "") + + +def has_newer_timestamp(ts, current_timestamp_in_dynamodb): + logger.info( + f"Compare current event timestamp {ts} and timestamp in dynamodb {current_timestamp_in_dynamodb}") + return ts > current_timestamp_in_dynamodb + + +def get_watching_key(event): + unique_watching_keys = set() + for record in event['Records']: + bucket_name, event_time = extract_record_info(record) + logger.debug(f'Processing new file {record["s3"]["object"]} upload to {bucket_name} at {event_time}') + unique_watching_keys.add(bucket_name) + + watching_key = ';'.join(sorted(unique_watching_keys)) + return watching_key + + +def event_handler(event, _): + verify_env_setup() + + if os.environ[AUTOMATIC_DATABREW_JOB_LAUNCH] == "OFF": + logger.info("Automatic Databrew job launch is off") + else: + try: + watching_key = get_watching_key(event) + + ts = datetime.now(timezone.utc) + ts_in_str = format_timestamp(ts) + + dynamodb_client = get_service_resource("dynamodb") + dynamodb_table = dynamodb_client.Table(os.environ[DDB_TABLE_NAME]) + + item_in_dynamodb = get_timestamp(dynamodb_table, watching_key) + + if not item_in_dynamodb or has_newer_timestamp(ts_in_str, item_in_dynamodb["timestamp_str"]): + put_timestamp(dynamodb_table, watching_key, ts_in_str) + logger.info( + f'Lambda finished processing object create notification for {watching_key} at event time [{ts_in_str}]') + + response = invoke_state_machine(watching_key, ts_in_str, WAITING_TIME_IN_SECONDS, + EXPECTED_FINISH_TIME_DELTA) + else: + logger.info("Not invoking state machine due to not newer event timestamp") + return + + except Exception as err: + logger.error(err) + raise err + + return {"automatic_brew_job_launch_execution": response["executionArn"]} diff --git a/source/aws_lambda/brew_run_job/__init__.py b/source/aws_lambda/brew_run_job/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/brew_run_job/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/brew_run_job/lambda_function.py b/source/aws_lambda/brew_run_job/lambda_function.py new file mode 100644 index 0000000..82f5dd4 --- /dev/null +++ b/source/aws_lambda/brew_run_job/lambda_function.py @@ -0,0 +1,66 @@ +# ###################################################################################################################### +# 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 os +import time +import shared.stepfunctions as stepfunctions + +from datetime import datetime, timedelta +from aws_lambda_powertools import Logger +from aws_solutions.core.helpers import get_service_client, get_service_resource + +logger = Logger(utc=True) + +DDB_TABLE_NAME = "DDB_TABLE_NAME" +TTL_EXPIRY_PERIOD = 7 + +def verify_env_setup(): + if not (os.environ.get(DDB_TABLE_NAME)): + err_msg = f"The lambda requires {DDB_TABLE_NAME} environment variable to be configured. One or more of these environment varialbes have not been configured" + logger.error(err_msg) + return False + return True + +def handler(event, _): + + task_token = event["task_token"] + exp_time = int((timedelta(days=TTL_EXPIRY_PERIOD) + datetime.fromtimestamp(int(time.time()))).timestamp()) + if not verify_env_setup(): + stepfunctions.send_task_failure("", task_token) + raise ValueError("Cannot Verify the environment Variables for the lambda") + + stepfunctions.send_heart_beat(task_token) + + try: + + data_brew_client = get_service_client("databrew") + # dynamo db client + ddb_client = get_service_resource("dynamodb") + ddb_table = ddb_client.Table(os.getenv("DDB_TABLE_NAME")) + job_name = event["brew_job_name"] + response = data_brew_client.start_job_run(Name=job_name) + job_id = response["RunId"] + ddb_table.put_item(Item={"job_id": job_id, + "task_token": task_token, + "exp_timestamp": exp_time}) + + logger.info(f"Task token of the following databrew job '{job_name}' with job id '{job_id}'" + f" is updated in the following {DDB_TABLE_NAME}") + + except Exception as err: + logger.error(err) + stepfunctions.send_task_failure("", task_token) + raise err + stepfunctions.send_heart_beat(task_token) + + return {"brew_job_run_id": response["RunId"]} diff --git a/source/aws_lambda/connectors/__init__.py b/source/aws_lambda/connectors/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/connectors/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/connectors/salesforce/__init__.py b/source/aws_lambda/connectors/salesforce/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/connectors/salesforce/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/connectors/salesforce/connector_profile.py b/source/aws_lambda/connectors/salesforce/connector_profile.py new file mode 100644 index 0000000..6dca7ac --- /dev/null +++ b/source/aws_lambda/connectors/salesforce/connector_profile.py @@ -0,0 +1,100 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +""" +This module is the Lambda responsible for updating a Salesforce Marketing Cloud connection +""" +import json +import os +from aws_lambda_powertools import Logger +from aws_solutions.core.helpers import get_service_client + +from shared.connectors.salesforce.connector import SalesforceConnectorProfile + +CONNECTOR_SECRET_ARN = os.environ["CONNECTOR_SECRET_ARN"] + +logger = Logger(utc=True, service="sfmc-lambda-standalone") + + +class ConnectorProfileFunctionException(Exception): + """ + This is a concrete subclass for exceptions in this module + """ + + def __init__(self, *args: object) -> None: + super().__init__(*args) + + +def get_connector_profile(): + # get the secret from the supplied arn + secretsmanager_client = get_service_client("secretsmanager") + connection = json.loads( + secretsmanager_client.get_secret_value(SecretId=CONNECTOR_SECRET_ARN)[ + "SecretString" + ] + ) + profile = SalesforceConnectorProfile( + connection["profile_name"], + connection["client_id"], + connection["client_secret"], + connection["token_endpoint"], + connection["instance_url"], + ) + return profile + + +def create_event_handler(event, _): + """ + This function is the entry point for Lambda function execution + """ + try: + logger.info(json.dumps(event, default=str)) + # get the connection configuration + profile = get_connector_profile() + return profile.create() + + except Exception as error: + # log it and continue bubbling + logger.error(error) + raise error + + +def update_event_handler(event, _): + """ + This function is the entry point for Lambda function execution + """ + try: + logger.info(json.dumps(event, default=str)) + # get the connection configuration + profile = get_connector_profile() + return profile.update() + + except Exception as error: + # log it and continue bubbling + logger.error(error) + raise error + + +def delete_event_handler(event, _): + """ + This function is the entry point for Lambda function execution + """ + try: + logger.info(json.dumps(event, default=str)) + # get the connection configuration + profile = get_connector_profile() + return profile.delete() + + except Exception as error: + # log it and continue bubbling + logger.error(error) + raise error diff --git a/source/aws_lambda/custom_resource/__init__.py b/source/aws_lambda/custom_resource/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/custom_resource/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/custom_resource/salesforce/__init__.py b/source/aws_lambda/custom_resource/salesforce/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/custom_resource/salesforce/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/custom_resource/salesforce/connector_profile.py b/source/aws_lambda/custom_resource/salesforce/connector_profile.py new file mode 100644 index 0000000..a34dcfc --- /dev/null +++ b/source/aws_lambda/custom_resource/salesforce/connector_profile.py @@ -0,0 +1,84 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +""" +This module is a custom resource Lambda responsible for +working with a Salesforce Marketing Cloud connection in Appflow +""" + +from aws_lambda_powertools import Logger +from crhelper import CfnResource +from shared.connectors.salesforce.connector import SalesforceConnectorProfile + +logger = Logger(utc=True, service="sfmc-lambda-custom-resource") +helper = CfnResource(log_level="ERROR", boto_level="ERROR") + + +def connector_profile_from_event(event): + """ + Helper to remove duplicate code + """ + properties = event["ResourceProperties"] + connector = SalesforceConnectorProfile( + properties["profile_name"], + properties["client_id"], + properties["client_secret"], + properties["token_endpoint"], + properties["instance_url"], + ) + return (connector, properties["profile_name"]) + + +@helper.create +def custom_resource_create(event, _): + """ + This function handles the create message from cloudformation + """ + # use parameters passed from the custom resource + connector, profile_name = connector_profile_from_event(event) + connector.create() + # return the profile's name as the physical id + return profile_name + + +@helper.update +def custom_resource_update(event, _): + """ + This function handles the update message from cloudformation + """ + # use parameters passed from the custom resource + connector, profile_name = connector_profile_from_event(event) + connector.update() + # return the profile's name as the physical id + return profile_name + + +@helper.delete +def custom_resource_delete(event, _): + """ + This function handles the delete message from cloudformation + """ + # fail silently on delete + try: + # physical id is the connector profile name + profile_name = event["PhysicalResourceId"] + connector = SalesforceConnectorProfile(profile_name) + connector.delete() + except Exception as error: + logger.error(error) + + +def event_handler(event, context): + """ + This is the Lambda custom resource entry point. + """ + helper(event, context) diff --git a/source/aws_lambda/custom_resource/salesforce/requirements.txt b/source/aws_lambda/custom_resource/salesforce/requirements.txt new file mode 100644 index 0000000..7ede011 --- /dev/null +++ b/source/aws_lambda/custom_resource/salesforce/requirements.txt @@ -0,0 +1,3 @@ +crhelper +requests>=2.28.1 +boto3 diff --git a/source/aws_lambda/custom_resource/string_manipulation/__init__.py b/source/aws_lambda/custom_resource/string_manipulation/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/custom_resource/string_manipulation/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/custom_resource/string_manipulation/requirements.txt b/source/aws_lambda/custom_resource/string_manipulation/requirements.txt new file mode 100644 index 0000000..832072f --- /dev/null +++ b/source/aws_lambda/custom_resource/string_manipulation/requirements.txt @@ -0,0 +1 @@ +crhelper diff --git a/source/aws_lambda/custom_resource/string_manipulation/string_manip.py b/source/aws_lambda/custom_resource/string_manipulation/string_manip.py new file mode 100644 index 0000000..568c62e --- /dev/null +++ b/source/aws_lambda/custom_resource/string_manipulation/string_manip.py @@ -0,0 +1,44 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +""" +This module is a custom lambda for string manipulation +""" + +from aws_lambda_powertools import Logger +from crhelper import CfnResource + + +logger = Logger(utc=True, service='transform-custom-lambda') +helper = CfnResource(log_level="ERROR", boto_level="ERROR") + + +def event_handler(event, context): + """ + This is the Lambda custom resource entry point. + """ + logger.info(event) + helper(event, context) + + +@helper.create +@helper.update +def on_create_or_update(event, _) -> None: + resource_properties = event["ResourceProperties"] + input_string: str = resource_properties["input_string"] + logger.info(f"Input string: {input_string}") + helper.Data.update( + { + "output_string": input_string.lower() + } + ) + \ No newline at end of file diff --git a/source/aws_lambda/custom_resource/transform/__init__.py b/source/aws_lambda/custom_resource/transform/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/custom_resource/transform/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/custom_resource/transform/empty-file.txt b/source/aws_lambda/custom_resource/transform/empty-file.txt new file mode 100644 index 0000000..e69de29 diff --git a/source/aws_lambda/custom_resource/transform/recipe_from_s3.py b/source/aws_lambda/custom_resource/transform/recipe_from_s3.py new file mode 100644 index 0000000..45b77d4 --- /dev/null +++ b/source/aws_lambda/custom_resource/transform/recipe_from_s3.py @@ -0,0 +1,137 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +""" +This module is a custom resource Lambda for Transforms responsible for +creating/updating/deleting DataBrew recipe and uploading sample file on create. +""" + +import json +import re +from typing import Union +from aws_lambda_powertools import Logger +from crhelper import CfnResource +from aws_solutions.core.helpers import get_service_client + +logger = Logger(utc=True, service='transform-custom-lambda') +helper = CfnResource(log_level="ERROR", boto_level="ERROR") + + +def event_handler(event, context): + """ + This is the Lambda custom resource entry point. + """ + logger.info(event) + helper(event, context) + + +@helper.create +@helper.update +def on_create_or_update(event, _) -> None: + """ + This function handles the create and update of databrew recipe + """ + resource_properties = event["ResourceProperties"] + + recipe_s3_location: str = resource_properties["recipe_s3_location"] + file_content = recipe_file_content(recipe_s3_location) + + databrew = get_service_client("databrew") + request_type = event["RequestType"] + if request_type == "Create": + upload_sample_file_object(resource_properties) + + response: dict[str, str] = databrew.create_recipe( + Name=resource_properties["recipe_name"], + Steps=json.loads(file_content) + ) + logger.info(f'Created recipe: {response["Name"]}') + + elif request_type == "Update": + response: dict[str, str] = databrew.update_recipe( + Name=resource_properties["recipe_name"], + Steps=json.loads(file_content) + ) + logger.info(f'Updated recipe: {response["Name"]}') + + +@helper.delete +def on_delete(event, _) -> None: + """ + This function handles the delete + """ + logger.info(f"Resource marked for deletion: {event['PhysicalResourceId']}") + resource_properties = event["ResourceProperties"] + recipe_name = resource_properties["recipe_name"] + databrew = get_service_client("databrew") + + response = databrew.list_recipe_versions( + Name=recipe_name, + MaxResults=100 + ) + recipe_versions: list[str] = [recipe_item["RecipeVersion"] for recipe_item in response["Recipes"]] + + # delete any previous recipe versions + for version in recipe_versions: + databrew.delete_recipe_version( + Name=recipe_name, + RecipeVersion=version, + ) + + # delete the latest version + response: dict[str, str] = databrew.delete_recipe_version( + Name=recipe_name, + RecipeVersion="LATEST_WORKING", + ) + logger.info(f'Deleted recipe: {response["Name"]}') + + +def upload_sample_file_object(resource_properties) -> None: + s3 = get_service_client("s3") + inbound_bucket_prefix: str = resource_properties["inbound_bucket_prefix"] + object_name = f"{inbound_bucket_prefix}empty-file-object" + inbound_bucket_name: str = resource_properties["inbound_bucket_name"] + + s3.upload_file( + "empty-file.txt", + inbound_bucket_name, + object_name, + ) + logger.info("Uploaded sample data file") + + +def recipe_file_content(recipe_s3_location: str): + if recipe_s3_location: + (recipe_bucket, recipe_key) = get_bucket_key_from_location(recipe_s3_location) + logger.info(f"Recipe bucket: {recipe_bucket}, Recipe key: {recipe_key}") + s3 = get_service_client("s3") + s3_obj = s3.get_object( + Bucket=recipe_bucket, + Key=recipe_key, + ) + logger.info("Recipe file contents read, creating recipe") + return s3_obj["Body"].read().decode("utf-8") + else: + logger.info("Recipe file not provided, creating empty recipe") + return "[]" + + +def get_bucket_key_from_location(recipe_file_location: str) -> tuple[str, Union[str, None]]: + pattern: str = r"^(.*?)\/(.*)$" + # matches (abc)/(def/1-2-3/pqr) + + if match := re.match(pattern, recipe_file_location): + groups: tuple[str] = match.groups() + return groups[0], groups[1] + + raise ValueError("Invalid recipe file location format") + diff --git a/source/aws_lambda/custom_resource/transform/requirements.txt b/source/aws_lambda/custom_resource/transform/requirements.txt new file mode 100644 index 0000000..6b5b1a1 --- /dev/null +++ b/source/aws_lambda/custom_resource/transform/requirements.txt @@ -0,0 +1,2 @@ +crhelper +boto3 diff --git a/source/aws_lambda/setup.py b/source/aws_lambda/setup.py new file mode 100644 index 0000000..37fed74 --- /dev/null +++ b/source/aws_lambda/setup.py @@ -0,0 +1,40 @@ +# ##################################################################################################################### +# 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 setuptools + + +setuptools.setup( + name="aws_lambda", + version="0.2.0", + description="Data Connectors for AWS Clean Rooms - Lambda Functions", + author="AWS Solutions Builders", + packages=setuptools.find_packages(exclude=("shared",)), + package_data={"": ["*.json", "*.yaml"]}, + include_package_data=True, + python_requires=">=3.7", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + "Typing :: Typed", + ], +) diff --git a/source/aws_lambda/shared/util/requirements.txt b/source/aws_lambda/shared/util/requirements.txt new file mode 100644 index 0000000..5e77405 --- /dev/null +++ b/source/aws_lambda/shared/util/requirements.txt @@ -0,0 +1 @@ +requests==2.28.1 \ No newline at end of file diff --git a/source/aws_lambda/shared/util/setup.py b/source/aws_lambda/shared/util/setup.py new file mode 100644 index 0000000..9f217e3 --- /dev/null +++ b/source/aws_lambda/shared/util/setup.py @@ -0,0 +1,38 @@ +# ###################################################################################################################### +# 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 setuptools + + +setuptools.setup( + name="shared", + version="0.0.0", + description="Data Connectors for AWS Clean Rooms - Shared Libraries for Lambda", + author="AWS Solutions Builders", + packages=setuptools.find_packages(exclude=("build",)), + include_package_data=True, + python_requires=">=3.7", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + "Typing :: Typed", + ], +) diff --git a/source/aws_lambda/shared/util/shared/__init__.py b/source/aws_lambda/shared/util/shared/__init__.py new file mode 100644 index 0000000..871f097 --- /dev/null +++ b/source/aws_lambda/shared/util/shared/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/shared/util/shared/connectors/__init__.py b/source/aws_lambda/shared/util/shared/connectors/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/shared/util/shared/connectors/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/shared/util/shared/connectors/salesforce/__init__.py b/source/aws_lambda/shared/util/shared/connectors/salesforce/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/aws_lambda/shared/util/shared/connectors/salesforce/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/aws_lambda/shared/util/shared/connectors/salesforce/connector.py b/source/aws_lambda/shared/util/shared/connectors/salesforce/connector.py new file mode 100644 index 0000000..fa2ce78 --- /dev/null +++ b/source/aws_lambda/shared/util/shared/connectors/salesforce/connector.py @@ -0,0 +1,128 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +""" +This module contains helper functions for working with AppFlow connector profiles +""" + +from aws_solutions.core.helpers import get_service_client + +from shared.connectors.salesforce.token import AccessToken + + +class SalesforceConnectorProfile: + """ + This class encapsulates the CUD functions for AppFlow connector profiles + """ + + def __init__( + self, + profile_name, + client_id=None, + client_secret=None, + token_endpoint=None, + instance_url=None, + ): + # only delete function can be used if profile_name is provided alone + self.profile_name = profile_name + self.connector_type = "CustomConnector" + self.connector_mode = "Public" + self.client_id = client_id + self.client_secret = client_secret + self.token_endpoint = token_endpoint + self.grant_type = "CLIENT_CREDENTIALS" + self.instance_url = instance_url + if client_id and client_secret and token_endpoint: + self.access_token = AccessToken( + self.client_id, self.client_secret, self.token_endpoint + ) + + def create(self): + """ + This function is responsible for creating a new AppFlow connector profile + """ + # retrieve a fresh access token + token_data = self.access_token.retrieve_token() + # create a new connection profile with the access token + appflow_client = get_service_client("appflow") + response = appflow_client.create_connector_profile( + connectorProfileName=self.profile_name, + connectorLabel="SalesforceMarketingCloud", + connectionMode=self.connector_mode, + connectorType="CustomConnector", + connectorProfileConfig={ + "connectorProfileProperties": { + "CustomConnector": { + "profileProperties": {"instanceUrl": self.instance_url}, + "oAuth2Properties": { + "tokenUrl": self.token_endpoint, + "oAuth2GrantType": self.grant_type, + }, + } + }, + "connectorProfileCredentials": { + "CustomConnector": { + "authenticationType": "OAUTH2", + "oauth2": { + "clientId": self.client_id, + "clientSecret": self.client_secret, + "accessToken": token_data["access_token"], + }, + } + }, + }, + ) + return response["connectorProfileArn"] + + def update(self): + """ + This function is used to update the token used with a connector profile + """ + # retrieve a fresh access token + token_data = self.access_token.retrieve_token() + # create an existing connection profile with the access token + appflow_client = get_service_client("appflow") + response = appflow_client.update_connector_profile( + connectorProfileName=self.profile_name, + connectionMode=self.connector_mode, + connectorProfileConfig={ + "connectorProfileProperties": { + "CustomConnector": { + "profileProperties": {"instanceUrl": self.instance_url}, + "oAuth2Properties": { + "tokenUrl": self.token_endpoint, + "oAuth2GrantType": self.grant_type, + }, + } + }, + "connectorProfileCredentials": { + "CustomConnector": { + "authenticationType": "OAUTH2", + "oauth2": { + "clientId": self.client_id, + "clientSecret": self.client_secret, + "accessToken": token_data["access_token"], + }, + } + }, + }, + ) + return response["connectorProfileArn"] + + def delete(self): + """ + This function is responsible for removing an existing connector profile + """ + appflow_client = get_service_client("appflow") + appflow_client.delete_connector_profile( + connectorProfileName=self.profile_name, forceDelete=False + ) diff --git a/source/aws_lambda/shared/util/shared/connectors/salesforce/token.py b/source/aws_lambda/shared/util/shared/connectors/salesforce/token.py new file mode 100644 index 0000000..c63f221 --- /dev/null +++ b/source/aws_lambda/shared/util/shared/connectors/salesforce/token.py @@ -0,0 +1,61 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +""" +This module contains helper functions for obtaining +an access token directly from SFMC +""" +import requests + + +class AccessTokenException(Exception): + """ + This class is a subclass of Exception for access token errors + """ + + def __init__(self, *args: object) -> None: + super().__init__(*args) + + +class AccessToken: + """ + This class encapsulates the HTTP layer + for retrieving an access token + """ + + def __init__(self, client_id, client_secret, token_endpoint) -> None: + self.client_id = client_id + self.client_secret = client_secret + self.token_endpoint = token_endpoint + self.grant_type = "client_credentials" + self.body = { + "grant_type": self.grant_type, + "client_id": self.client_id, + "client_secret": self.client_secret, + } + + def retrieve_token(self): + """ + This function is responsible for retrieving a + token over HTTP from an OIDC provider + """ + response = requests.post(self.token_endpoint, json=self.body) + if response.ok: + json = response.json() + return { + "access_token": json["access_token"], + "expires_in": json["expires_in"], + } + else: + raise AccessTokenException( + f"{response.status_code} status returned from {self.token_endpoint}" + ) diff --git a/source/aws_lambda/shared/util/shared/requirements.txt b/source/aws_lambda/shared/util/shared/requirements.txt new file mode 100644 index 0000000..5e77405 --- /dev/null +++ b/source/aws_lambda/shared/util/shared/requirements.txt @@ -0,0 +1 @@ +requests==2.28.1 \ No newline at end of file diff --git a/source/aws_lambda/shared/util/shared/secrets_manager/__init__.py b/source/aws_lambda/shared/util/shared/secrets_manager/__init__.py new file mode 100644 index 0000000..4411b3d --- /dev/null +++ b/source/aws_lambda/shared/util/shared/secrets_manager/__init__.py @@ -0,0 +1,28 @@ +# ###################################################################################################################### +# 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 botocore +from aws_lambda_powertools import Logger +from aws_solutions.core.helpers import get_service_client +from botocore.exceptions import ClientError + +logger = Logger(utc=True) + + +def get_secret_value(secret_name): + service_client = get_service_client("secretsmanager") + try: + service_client.get_secret_value(SecretId=secret_name) + except ClientError as error: + logger.error(f"Error occured when trying secret: {secret_name}. Following error occured: {str(error)}") + raise error diff --git a/source/aws_lambda/shared/util/shared/stepfunctions/__init__.py b/source/aws_lambda/shared/util/shared/stepfunctions/__init__.py new file mode 100644 index 0000000..2032fdb --- /dev/null +++ b/source/aws_lambda/shared/util/shared/stepfunctions/__init__.py @@ -0,0 +1,62 @@ +# ###################################################################################################################### +# 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 botocore +from aws_lambda_powertools import Logger +from aws_solutions.core.helpers import get_service_client +from botocore.exceptions import ClientError + +logger = Logger(utc=True) + + +def send_task_success(output: str, task_token: str, service_client: botocore.client.BaseClient = None): + """Method to send success status with output to stepfunction state""" + if not service_client: + service_client = get_service_client("stepfunctions") + try: + logger.debug("Sending successful output") + service_client.send_task_success(output=output, taskToken=task_token) + except ClientError as error: + logger.error( + f"Error ocurred when sending task success status for output: {output} and taskToken: {task_token}. Following error occured: {str(error)}. Sending task failure" + ) + send_task_failure(error, task_token, service_client=service_client) + raise error + + +def send_task_failure(error: Exception, task_token: str, service_client: botocore.client.BaseClient = None): + """Method to send failure status with error information to stepfunction state""" + if not service_client: + service_client = get_service_client("stepfunctions") + try: + logger.debug("Sending failure output") + service_client.send_task_failure( + cause=getattr(error, "message", str(error)), error=str(error), taskToken=task_token + ) + except ClientError as cli_error: + logger.error( + f"Failure to send error status to stepfunction for taskToken {task_token}. Following error occured {str(cli_error)}" + ) + raise cli_error + + +def send_heart_beat(task_token: str, service_client: botocore.client.BaseClient = None): + if not service_client: + service_client = get_service_client("stepfunctions") + try: + logger.debug("Sending heartbeat") + service_client.send_task_heartbeat(taskToken=task_token) + except ClientError as cli_error: + logger.error( + f"Error occured when sending heart beat for task {task_token}. Following error occured: {str(cli_error)}. Will not send failure notice" + ) diff --git a/source/aws_lambda/step_function_call_back/__init__.py b/source/aws_lambda/step_function_call_back/__init__.py new file mode 100644 index 0000000..973f1dc --- /dev/null +++ b/source/aws_lambda/step_function_call_back/__init__.py @@ -0,0 +1,14 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### + + diff --git a/source/aws_lambda/step_function_call_back/lambda_function.py b/source/aws_lambda/step_function_call_back/lambda_function.py new file mode 100644 index 0000000..17f6f41 --- /dev/null +++ b/source/aws_lambda/step_function_call_back/lambda_function.py @@ -0,0 +1,53 @@ +# ###################################################################################################################### +# 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 os +import sys +import json + +import shared.stepfunctions as stepfunctions + +from aws_lambda_powertools import Logger +from boto3.dynamodb.conditions import Key +from aws_solutions.core.helpers import get_service_resource + +logger = Logger(utc=True) +DDB_TABLE_NAME = "DDB_TABLE_NAME" + + +def verify_env_setup(): + if not (os.environ.get(DDB_TABLE_NAME)): + err_msg = f"The lambda requires {DDB_TABLE_NAME} environment variable to be configured. One or more of these environment varialbes have not been configured" + logger.error(err_msg) + raise ValueError(err_msg) + +def handler(event, _): + verify_env_setup() + + try: + job_id = event['detail']['jobRunId'] + logger.info(f"Querying the dynamodb table {DDB_TABLE_NAME} to retrieve the token for the following job {job_id}") + + ddb_client = get_service_resource('dynamodb') + ddb_table = ddb_client.Table(os.environ["DDB_TABLE_NAME"]) + response = ddb_table.query(KeyConditionExpression=Key("job_id").\ + eq(job_id)) + task_token = response["Items"][0]["task_token"] + except Exception as err: + logger.error(f"The following error were found while querying database to " + f"retrieve the task_token ==>> {err}") + raise err + + logger.info("The Token is found and retreived. Communicating with step function to continue...") + stepfunctions.send_task_success(json.dumps({"status": "Success", + "job_run_id": job_id}), task_token) diff --git a/source/cdk_solution_helper_py/CHANGELOG.md b/source/cdk_solution_helper_py/CHANGELOG.md new file mode 100644 index 0000000..6f2d2c2 --- /dev/null +++ b/source/cdk_solution_helper_py/CHANGELOG.md @@ -0,0 +1,14 @@ +# Change Log +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). + +## [2.0.0] - 2022-01-31 +### Changed +- support for CDK 2.x added, support for CDK 1.x removed + +## [1.0.0] - 2021-09-23 +### Added +- initial release + diff --git a/source/cdk_solution_helper_py/README.md b/source/cdk_solution_helper_py/README.md new file mode 100644 index 0000000..6384ae9 --- /dev/null +++ b/source/cdk_solution_helper_py/README.md @@ -0,0 +1,122 @@ +## Prerequisites + +- [Python 3.9](https://www.python.org/downloads/) +- [AWS CDK](https://aws.amazon.com/cdk/) version 2.45.0 or higher + +## Build the solution for deployment +You can use the AWS CDK to deploy the solution directly or use the CloudFormation template described in downstream sections. +[Bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) configurations. + +```shell script +# Installing the Python dependencies and setting up CDK + +cd + +# Create python virtualenv and activate it +.venv python -m venv .venv +source .venv/bin/activate + +# Install required python packages +pip install -r source/requirements-dev.txt + +# change into the infrastructure directory +cd source/infrastructure + +# set environment variables required by the solution - use your own bucket name here +export BUCKET_NAME="placeholder" + +# bootstrap CDK (required once - deploys a CDK bootstrap CloudFormation stack for assets) +cdk bootstrap --cloudformation-execution-policies arn:aws:iam::aws:policy/ + +# deploy with CDK +cdk deploy +``` + +At this point, the stack will be built and deployed using CDK - the template will take on default CloudFormation +parameter values. To modify the stack parameters, you can use the `--parameters` flag in CDK deploy - for example: + +```shell script +cdk deploy --parameters [...] +``` + +## Package the solution for release + +It is highly recommended to use CDK to deploy this solution (see step #1 above). While CDK is used to develop the +solution, to package the solution for release as a CloudFormation template use the `build-s3-cdk-dist` script: + +``` +cd /deployment + +export DIST_OUTPUT_BUCKET=my-bucket-name +export SOLUTION_NAME=my-solution-name +export VERSION=my-version + +build-s3-cdk-dist deploy \ + --source-bucket-name $DIST_OUTPUT_BUCKET \ + --solution-name $SOLUTION_NAME \ + --version-code $VERSION \ + --cdk-app-path ../source/infrastructure/deploy.py \ + --cdk-app-entrypoint app:build_app --sync +``` + +> **Note**: `build-s3-cdk-dist` will use your current configured `AWS_REGION` and `AWS_PROFILE`. To set your defaults +install the [AWS Command Line Interface](https://aws.amazon.com/cli/) and run `aws configure`. +Additionally, the ```--region``` flag can be passed to the build-s3-cdk-dist script to specify the AWS region. + +#### Parameter Details: + +- `$DIST_OUTPUT_BUCKET` - This is the global name of the distribution. For the bucket name, the AWS Region is added to +the global name (example: 'my-bucket-name-us-east-1') to create a regional bucket. The lambda artifact should be +uploaded to the regional buckets for the CloudFormation template to pick it up for deployment. +- `$SOLUTION_NAME` - The name of This solution (example: your-solution-name) +- `$VERSION` - The version number of the change + +> **Notes**: The `build_s3_cdk_dist` script expects the bucket name as one of its parameters, and this value should +not include the region suffix. See below on how to create the buckets expected by this solution: +> +> The `SOLUTION_NAME`, and `VERSION` variables might also be defined in the `cdk.json` file. + +## Upload deployment assets to your Amazon S3 buckets + +Create the CloudFormation bucket defined above, as well as a regional bucket in the region you wish to deploy. The +CloudFormation template is configured to pull the Lambda deployment packages from Amazon S3 bucket in the region the +template is being launched in. Create a bucket in the desired region with the region name appended to the name of the +bucket. eg: for us-east-1 create a bucket named: ```my-bucket-us-east-1```. + +For example: + +```bash +aws s3 mb s3://my-bucket-name --region us-east-1 +aws s3 mb s3://my-bucket-name-us-east-1 --region us-east-1 +``` + +Copy the built S3 assets to your S3 buckets: + +``` +use the --sync option of build-s3-cdk-dist to upload the global and regional assets +``` + +> **Notes**: Choose your desired region by changing region in the above example from us-east-1 to your desired region +of the S3 buckets. + +## Launch the CloudFormation template + +* Get the link of `your-solution-name.template` uploaded to your Amazon S3 bucket. +* Deploy the solution to your account by launching a new AWS CloudFormation stack using the link of the +`your-solution-name.template`. + +*** + +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. \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py new file mode 100644 index 0000000..6956347 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py @@ -0,0 +1,41 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from pathlib import Path + +from aws_solutions.cdk.context import SolutionContext +from aws_solutions.cdk.stack import SolutionStack +from aws_solutions.cdk.synthesizers import SolutionStackSubstitions + + +class CDKSolution: + """ + A CDKSolution stores helper utilities for building AWS Solutions using the AWS CDK in Python + + :type cdk_json_path: Path + :param cdk_json_path: The full path to the cdk.json context for your application + :type qualifier: str + :param qualifier: A string that is added to all resources in the CDK bootstrap stack. The default value has no significance. + """ + + def __init__(self, cdk_json_path: Path, qualifier="hnb659fds"): + self.qualifier = qualifier + self.context = SolutionContext(cdk_json_path=cdk_json_path) + self.synthesizer = SolutionStackSubstitions(qualifier=self.qualifier) + + def reset(self) -> None: + """ + Get a new synthesizer for this CDKSolution - useful for testing + :return: None + """ + self.synthesizer = SolutionStackSubstitions(qualifier=self.qualifier, generate_bootstrap_version_rule=False) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aspects.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aspects.py new file mode 100644 index 0000000..29f0fec --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aspects.py @@ -0,0 +1,29 @@ +# ##################################################################################################################### +# 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 jsii +from aws_cdk import CfnCondition, IAspect +from constructs import IConstruct + + +@jsii.implements(IAspect) +class ConditionalResources: + """Mark any CDK construct as conditional (this is useful to apply to stacks and L2+ constructs)""" + + def __init__(self, condition: CfnCondition): + self.condition = condition + + def visit(self, node: IConstruct): + if "is_cfn_element" in dir(node) and node.is_cfn_element(node): + node.cfn_options.condition = self.condition + elif "is_cfn_element" in dir(node.node.default_child): + node.node.default_child.cfn_options.condition = self.condition diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/__init__.py new file mode 100644 index 0000000..2895559 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/__init__.py @@ -0,0 +1,16 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_solutions.cdk.aws_lambda.cfn_custom_resources.resource_hash.hash import ( + ResourceHash, +) 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 new file mode 100644 index 0000000..e6b74e2 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/hash.py @@ -0,0 +1,84 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### +from pathlib import Path + +from aws_cdk import ( + CfnResource, + Stack, +) +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.python.function import SolutionsPythonFunction +from aws_solutions.cdk.cfn_nag import add_cfn_nag_suppressions, CfnNagSuppression + + +class ResourceHash(Construct): + """Used to create unique resource names based on the hash of the stack ID""" + + def __init__( + self, + scope: Construct, + construct_id: str, + purpose: str, + max_length: int, + ): + super().__init__(scope, construct_id) + + uuid = "ResourceHashFunction-b8785f53-1531-4bfb-a119-26aa638d7b19" + stack = Stack.of(self) + self._resource_name_function = stack.node.try_find_child(uuid) + + if not self._resource_name_function: + self._resource_name_function = SolutionsPythonFunction( + stack, + uuid, + entrypoint=Path(__file__).parent + / "src" + / "custom_resources" + / "hash.py", + function="handler", + ) + add_cfn_nag_suppressions( + resource=self._resource_name_function.node.default_child, + suppressions=[ + CfnNagSuppression( + "W89", "This AWS Lambda Function is not deployed to a VPC" + ), + CfnNagSuppression( + "W92", + "This AWS Lambda Function does not require reserved concurrency", + ), + ], + ) + + properties = { + "ServiceToken": self._resource_name_function.function_arn, + "Purpose": purpose, + "MaxLength": max_length, + } + + self.logical_name = f"{construct_id}HashResource" + self.resource_name_resource = CfnResource( + self, + self.logical_name, + type="Custom::ResourceHash", + properties=properties, + ) + + @property + def resource_name(self): + return self.resource_name_resource.get_att("Name") + + @property + def resource_id(self): + return self.resource_name_resource.get_att("Id") diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/hash.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/hash.py new file mode 100644 index 0000000..4e45d7e --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/hash.py @@ -0,0 +1,89 @@ +# ##################################################################################################################### +# 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 hashlib import md5 +from os import getenv + +from crhelper import CfnResource + +logger = logging.getLogger(__name__) +helper = CfnResource(log_level=getenv("LOG_LEVEL", "WARNING")) + + +class StackId: + def __init__(self, event): + self.stack_id = event.get("StackId") + self.partition = self.get_arn_component(1) + self.service = self.get_arn_component(2) + self.region = self.get_arn_component(3) + self.account = self.get_arn_component(4) + self.stack_name = self.get_arn_component(5).split("/")[1] + + def get_arn_component(self, idx: int) -> str: + return self.stack_id.split(":")[idx] + + @property + def hash(self): + # NOSONAR - safe to hash, not for cryptographic purposes + digest = md5() # nosec + digest.update(bytes(f"{self.stack_id.rsplit('/', 1)[0]}", "ascii")) + return digest.hexdigest().upper() + + +def get_property(event, property_name, property_default=None): + resource_prop = event.get("ResourceProperties", {}).get( + property_name, property_default + ) + if not resource_prop: + raise ValueError(f"missing required property {property_name}") + return resource_prop + + +@helper.create +def generate_hash(event, _): + """ + Generate a resource name containing a hash of the stack ID (without unique ID) and resource purpose. + This is useful when you need to create named IAM roles + + :param event: The CloudFormation custom resource event + :return: None + """ + stack_id = StackId(event) + purpose = get_property(event, "Purpose") + max_length = int(get_property(event, "MaxLength", 64)) + + name = f"{purpose}-{stack_id.hash[:8]}" + + if len(name) > max_length: + raise ValueError( + f"the derived resource name {name} is too long ({len(name)} / {max_length}) - please use a shorter Purpose" + ) + + logger.info(f"the derived resource name is {name}") + helper.Data["Name"] = name + helper.Data["Id"] = stack_id.hash + + +@helper.update +@helper.delete +def no_op(_, __): + pass # pragma: no cover + + +def handler(event, _): + """ + Handler entrypoint - see generate_hash for implementation details + :param event: The CloudFormation custom resource event + :return: PhysicalResourceId + """ + helper(event, _) # pragma: no cover diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/requirements.txt b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/requirements.txt new file mode 100644 index 0000000..76fcf16 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_hash/src/custom_resources/requirements.txt @@ -0,0 +1 @@ +crhelper==2.0.6 \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/__init__.py new file mode 100644 index 0000000..0667198 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/__init__.py @@ -0,0 +1,16 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_solutions.cdk.aws_lambda.cfn_custom_resources.resource_name.name import ( + ResourceName, +) 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 new file mode 100644 index 0000000..f6f4a65 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/name.py @@ -0,0 +1,90 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### +from pathlib import Path +from typing import Optional + +from aws_cdk import ( + CfnResource, + Aws, + Stack, +) +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.python.function import SolutionsPythonFunction +from aws_solutions.cdk.cfn_nag import add_cfn_nag_suppressions, CfnNagSuppression + + +class ResourceName(Construct): + """Used to create unique resource names of the format {stack_name}-{purpose}-{id}""" + + def __init__( + self, + scope: Construct, + construct_id: str, + purpose: str, + max_length: int, + resource_id: Optional[str] = None, + ): + super().__init__(scope, construct_id) + + uuid = "ResourceNameFunction-d45b185a-fe34-44ab-a375-17f89597d9ec" + stack = Stack.of(self) + self._resource_name_function = stack.node.try_find_child(uuid) + + if not self._resource_name_function: + self._resource_name_function = SolutionsPythonFunction( + stack, + uuid, + entrypoint=Path(__file__).parent + / "src" + / "custom_resources" + / "name.py", + function="handler", + ) + add_cfn_nag_suppressions( + resource=self._resource_name_function.node.default_child, + suppressions=[ + CfnNagSuppression( + "W89", "This AWS Lambda Function is not deployed to a VPC" + ), + CfnNagSuppression( + "W92", + "This AWS Lambda Function does not require reserved concurrency", + ), + ], + ) + + properties = { + "ServiceToken": self._resource_name_function.function_arn, + "Purpose": purpose, + "StackName": Aws.STACK_NAME, + "MaxLength": max_length, + } + if resource_id: + properties["Id"] = resource_id + + self.logical_name = f"{construct_id}NameResource" + self.resource_name_resource = CfnResource( + self, + self.logical_name, + type="Custom::ResourceName", + properties=properties, + ) + + @property + def resource_name(self): + return self.resource_name_resource.get_att("Name") + + @property + def resource_id(self): + return self.resource_name_resource.get_att("Id") diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/name.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/name.py new file mode 100644 index 0000000..2c76ef1 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/name.py @@ -0,0 +1,74 @@ +# ##################################################################################################################### +# 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 os import getenv +from uuid import uuid4 as uuid + +from crhelper import CfnResource + +logger = logging.getLogger(__name__) +helper = CfnResource(log_level=getenv("LOG_LEVEL", "WARNING")) + + +def get_property(event, property_name, property_default=None): + resource_prop = event.get("ResourceProperties", {}).get( + property_name, property_default + ) + if not resource_prop: + raise ValueError(f"missing required property {property_name}") + return resource_prop + + +@helper.create +def generate_name(event, _): + """ + Generate a resource name containing the stack name and the resource purpose. This is useful + when you need to associate policies that refer to a resource by name (and thus need + a predictable resource name). This is commonly used when associating policies with buckets + or other resources that might introduce a circular resource dependency + + :param event: The CloudFormation custom resource event + :return: None + """ + resource_id = get_property(event, "Id", uuid().hex[0:12]) + stack_name = get_property(event, "StackName") + purpose = get_property(event, "Purpose") + max_length = int(get_property(event, "MaxLength")) + + name = f"{stack_name}-{purpose}-{resource_id}".lower() + if len(name) > max_length: + logger.warning("cannot use stack name in bucket name - trying default") + name = f"{purpose}-{resource_id}".lower() + if len(name) > max_length: + raise ValueError( + f"the derived resource name {name} is too long ({len(name)} / {max_length}) - please use a shorter purpose or stack name" + ) + + logger.info(f"the derived resource name is {name}") + helper.Data["Name"] = name + helper.Data["Id"] = resource_id + + +@helper.update +@helper.delete +def no_op(_, __): + pass # pragma: no cover + + +def handler(event, _): + """ + Handler entrypoint - see generate_name for implementation details + :param event: The CloudFormation custom resource event + :return: PhysicalResourceId + """ + helper(event, _) # pragma: no cover diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/requirements.txt b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/requirements.txt new file mode 100644 index 0000000..76fcf16 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/resource_name/src/custom_resources/requirements.txt @@ -0,0 +1 @@ +crhelper==2.0.6 \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/__init__.py new file mode 100644 index 0000000..f9dba6b --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/__init__.py @@ -0,0 +1,16 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_solutions.cdk.aws_lambda.cfn_custom_resources.solutions_metrics.metrics import ( + Metrics, +) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/metrics.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/metrics.py new file mode 100644 index 0000000..fdd2b36 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/metrics.py @@ -0,0 +1,102 @@ +# ##################################################################################################################### +# 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 aws_cdk.aws_lambda as lambda_ + +from pathlib import Path +from typing import Dict + +from aws_cdk import ( + CfnResource, + Fn, + CfnCondition, + Aws, +) +from constructs import Construct + +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 + +class Metrics(Construct): + """Used to track anonymous solution deployment metrics.""" + + def __init__( + self, + scope: Construct, + construct_id: str, + metrics: Dict[str, str], + ): + super().__init__(scope, construct_id) + + if not isinstance(metrics, dict): + raise ValueError("metrics must be a dictionary") + + self._metrics_function = SolutionsPythonFunction( + self, + "MetricsFunction", + entrypoint=Path(__file__).parent + / "src" + / "custom_resources" + / "metrics.py", + function="handler", + runtime=lambda_.Runtime.PYTHON_3_9, + ) + add_cfn_nag_suppressions( + resource=self._metrics_function.node.default_child, + suppressions=[ + CfnNagSuppression( + "W89", "This AWS Lambda Function is not deployed to a VPC" + ), + CfnNagSuppression( + "W92", + "This AWS Lambda Function does not require reserved concurrency", + ), + ], + ) + + self._send_anonymous_usage_data = CfnCondition( + self, + "SendAnonymousUsageData", + expression=Fn.condition_equals( + Fn.find_in_map("Solution", "Data", "SendAnonymousUsageData"), "Yes" + ), + ) + self._send_anonymous_usage_data.override_logical_id("SendAnonymousUsageData") + + properties = { + "ServiceToken": self._metrics_function.function_arn, + "Solution": self.node.try_get_context("SOLUTION_NAME"), + "Version": self.node.try_get_context("VERSION"), + "Region": Aws.REGION, + **metrics, + } + self.solution_metrics = CfnResource( + self, + "SolutionMetricsAnonymousData", + type="Custom::AnonymousData", + properties=properties, + ) + self.solution_metrics.override_logical_id("SolutionMetricsAnonymousData") + self.solution_metrics.cfn_options.condition = self._send_anonymous_usage_data + + NagSuppressions.add_resource_suppressions( + self._metrics_function.role, + [ + { + "id": 'AwsSolutions-IAM5', + "reason": '* Resources will be suppred by cdk nag and it has to be not suppressed', + "appliesTo": ['Resource::arn::logs:::log-group:/aws/lambda/*'] + }, + ], + ) \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/metrics.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/metrics.py new file mode 100644 index 0000000..0696aa1 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/metrics.py @@ -0,0 +1,81 @@ +# ##################################################################################################################### +# 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 +import uuid +from datetime import datetime +from os import getenv + +import requests +from crhelper import CfnResource + +logger = logging.getLogger(__name__) +helper = CfnResource(log_level=getenv("LOG_LEVEL", "WARNING")) +METRICS_ENDPOINT = "https://metrics.awssolutionsbuilder.com/generic" + + +def _sanitize_data(event): + resource_properties = event["ResourceProperties"] + # Remove ServiceToken (lambda arn) to avoid sending AccountId + resource_properties.pop("ServiceToken", None) + resource_properties.pop("Resource", None) + + # Solution ID and unique ID are sent separately + resource_properties.pop("Solution", None) + resource_properties.pop("UUID", None) + + # Add some useful fields related to stack change + resource_properties["CFTemplate"] = ( + event["RequestType"] + "d" + ) # Created, Updated, or Deleted + + return resource_properties + + +@helper.create +@helper.update +@helper.delete +def send_metrics(event, _): + resource_properties = event["ResourceProperties"] + random_id = event.get("PhysicalResourceId", str(uuid.uuid4())) + helper.Data["UUID"] = random_id + + try: + headers = {"Content-Type": "application/json"} + payload = { + "Solution": resource_properties["Solution"], + "UUID": random_id, + "TimeStamp": datetime.utcnow().isoformat(), + "Data": _sanitize_data(event), + } + + logger.info(f"Sending payload: {payload}") + response = requests.post(METRICS_ENDPOINT, json=payload, headers=headers) + logger.info( + f"Response from metrics endpoint: {response.status_code} {response.reason}" + ) + if "stackTrace" in response.text: + logger.exception("Error submitting usage data: %s" % response.text) + # raise when there is an HTTP error (non success code) + response.raise_for_status() + except requests.exceptions.RequestException as exc: + logger.exception(f"Could not send usage data: {exc}") + except Exception as exc: + logger.exception(f"Unknown error when trying to send usage data: {exc}") + + return random_id + + +def handler(event, context): + helper(event, context) # pragma: no cover 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 new file mode 100644 index 0000000..3242c1c --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/cfn_custom_resources/solutions_metrics/src/custom_resources/requirements.txt @@ -0,0 +1,2 @@ +requests==2.28.1 +crhelper==2.0.6 \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment.py new file mode 100644 index 0000000..a14d9ee --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment.py @@ -0,0 +1,48 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### + +from dataclasses import dataclass, field + +from aws_cdk import Aws +from aws_cdk.aws_lambda import IFunction + +from aws_solutions.cdk.aws_lambda.environment_variable import EnvironmentVariable + + +@dataclass +class Environment: + """ + Tracks environment variables common to AWS Lambda functions deployed by this solution + """ + + scope: IFunction + solution_name: EnvironmentVariable = field(init=False, repr=False) + solution_id: EnvironmentVariable = field(init=False, repr=False) + solution_version: EnvironmentVariable = field(init=False, repr=False) + log_level: EnvironmentVariable = field(init=False, repr=False) + powertools_service_name: EnvironmentVariable = field(init=False, repr=False) + + def __post_init__(self): + cloudwatch_namespace_id = f"data_connectors_solution_{Aws.STACK_NAME}" + cloudwatch_service_id_default = f"Workflow" + + self.solution_name = EnvironmentVariable(self.scope, "SOLUTION_NAME") + self.solution_id = EnvironmentVariable(self.scope, "SOLUTION_ID") + self.solution_version = EnvironmentVariable(self.scope, "SOLUTION_VERSION") + self.log_level = EnvironmentVariable(self.scope, "LOG_LEVEL", "INFO") + self.powertools_service_name = EnvironmentVariable( + self.scope, "POWERTOOLS_SERVICE_NAME", cloudwatch_service_id_default + ) + self.powertools_metrics_namespace = EnvironmentVariable( + self.scope, "POWERTOOLS_METRICS_NAMESPACE", cloudwatch_namespace_id + ) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment_variable.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment_variable.py new file mode 100644 index 0000000..9a360d5 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/environment_variable.py @@ -0,0 +1,31 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### + +from dataclasses import dataclass, field + +from aws_cdk.aws_lambda import IFunction + + +@dataclass +class EnvironmentVariable: + scope: IFunction + name: str + value: str = field(default="") + + def __post_init__(self): + if not self.value: + self.value = self.scope.node.try_get_context(self.name) + self.scope.add_environment(self.name, self.value) + + def __str__(self): + return self.value diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### 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 new file mode 100644 index 0000000..b501947 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/bundling.py @@ -0,0 +1,117 @@ +# ##################################################################################################################### +# 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 +import shutil +import subprocess +from pathlib import Path +from typing import Union, Dict, Optional + +import jsii +from aws_cdk import ILocalBundling, BundlingOptions + +from aws_solutions.cdk.helpers import copytree + +logger = logging.getLogger("cdk-helper") + + +class UnsupportedBuildEnvironment(Exception): + pass + + +@jsii.implements(ILocalBundling) +class SolutionsJavaBundling: + """This interface allows AWS Solutions to package lambda functions for Java without the use of Docker""" + + def __init__( + self, + to_bundle: Path, + gradle_task: str, + distribution_path: Path, + gradle_test: Optional[str] = None, + ): + self.to_bundle = to_bundle + self.gradle_task = gradle_task + self.gradle_test = gradle_test + self.distribution_path = distribution_path + + def try_bundle(self, output_dir: str, options: BundlingOptions) -> bool: + source = Path(self.to_bundle).absolute() + + is_gradle_build = (source / "gradlew").exists() + if not is_gradle_build: + raise UnsupportedBuildEnvironment("please use a gradle project") + + # Run Tests + if self.gradle_test: + self._invoke_local_command( + name="gradle", + command=["./gradlew", self.gradle_test], + cwd=source, + ) + + # Run Build + self._invoke_local_command( + name="gradle", + command=["./gradlew", self.gradle_task], + cwd=source, + ) + + # if the distribution path is a path - it should only contain one jar or zip + if self.distribution_path.is_dir(): + children = [child for child in self.distribution_path.iterdir()] + if len(children) != 1: + raise ValueError( + "if the distribution path is a path it should only contain one jar or zip file" + ) + if children[0].suffix not in (".jar", ".zip"): + raise ValueError( + "the distribution path does not include a single .jar or .zip file" + ) + copytree(self.distribution_path, output_dir) + elif self.distribution_path.is_file(): + suffix = self.distribution_path.suffix + if suffix not in (".jar", ".zip"): + raise ValueError("the distribution file is not a .zip or .jar file") + shutil.copy(self.distribution_path, output_dir) + + return True + + def _invoke_local_command( + self, + name, + command, + env: Union[Dict, None] = None, + cwd: Union[str, Path, None] = None, + return_stdout: bool = False, + ): + + cwd = Path(cwd) + rv = "" + + with subprocess.Popen( + command, + shell=False, + stdout=subprocess.PIPE, + universal_newlines=True, + cwd=cwd, + env=env, + ) as p: + for line in p.stdout: + logger.info("%s %s: %s" % (self.to_bundle.name, name, line.rstrip())) + if return_stdout: + rv += line + + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, p.args) + + return rv.strip() diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/function.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/function.py new file mode 100644 index 0000000..7e79d3c --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/java/function.py @@ -0,0 +1,117 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### +from pathlib import Path +from typing import Optional + +import aws_cdk.aws_iam as iam +from aws_cdk import ( + BundlingOptions, + BundlingOutput, + Aws, + DockerImage, +) +from aws_cdk.aws_lambda import Function, Runtime, RuntimeFamily, Code +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.java.bundling import SolutionsJavaBundling + + +class SolutionsJavaFunction(Function): + """This is similar to aws-cdk/aws-lambda-python, however it handles local building of Java Lambda Functions""" + + def __init__( + self, # NOSONAR + scope: Construct, + construct_id: str, + project_path: Path, + distribution_path: str, + gradle_task: str, + gradle_test: Optional[str] = None, + **kwargs, + ): + self.scope = scope + self.construct_id = construct_id + self.project_path = project_path + self.gradle_task = gradle_task + self.gradle_test = gradle_test + + if not project_path.is_dir(): + raise ValueError( + f"project_path {project_path} must be a directory, not a file" + ) + + # create default least privileged role for this function unless a role is passed + if not kwargs.get("role"): + kwargs["role"] = self._create_role() + + # Java 11 is the default runtime (Lambda supports 8/ 11) + if not kwargs.get("runtime"): + kwargs["runtime"] = Runtime.JAVA_11 + + if kwargs["runtime"].family != RuntimeFamily.JAVA: + raise ValueError( + f"SolutionsJavaFunction must use a Java runtime ({kwargs['runtime']} was provided)" + ) + + # This Construct will handle the creation of the 'code' parameter + if kwargs.get("code"): + raise ValueError( + f"SolutionsJavaFunction expects a Path `project_path` (python file) and `function` (function in the entrypoint for AWS Lambda to invoke)" + ) + + bundling = SolutionsJavaBundling( + to_bundle=project_path, + gradle_task=gradle_task, + gradle_test=gradle_test, + distribution_path=distribution_path, + ) + + kwargs["code"] = Code.from_asset( + path=str(project_path), + bundling=BundlingOptions( + image=DockerImage.from_registry("scratch"), # NOT USED + command=["NOT-USED"], + entrypoint=["NOT-USED"], + local=bundling, + output_type=BundlingOutput.ARCHIVED, + ), + ) + super().__init__(scope, construct_id, **kwargs) + + def _create_role(self) -> iam.Role: + """ + Build a role that allows an AWS Lambda Function to log to CloudWatch + :param name: The name of the role. The final name will be "{name}-Role" + :return: aws_cdk.aws_iam.Role + """ + return iam.Role( + self.scope, + f"{self.construct_id}-Role", + assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), + inline_policies={ + "LambdaFunctionServiceRolePolicy": iam.PolicyDocument( + statements=[ + iam.PolicyStatement( + actions=[ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + resources=[ + f"arn:{Aws.PARTITION}:logs:{Aws.REGION}:{Aws.ACCOUNT_ID}:log-group:/aws/lambda/*" + ], + ) + ] + ) + }, + ) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/__init__.py new file mode 100644 index 0000000..8b086e5 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/__init__.py @@ -0,0 +1,16 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### + +from aws_solutions.cdk.aws_lambda.layers.aws_lambda_powertools.layer import ( + PowertoolsLayer, +) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/layer.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/layer.py new file mode 100644 index 0000000..f5ad9d0 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/layer.py @@ -0,0 +1,34 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### + +from pathlib import Path + +from aws_cdk import Stack +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.python.layer import SolutionsPythonLayerVersion + + +class PowertoolsLayer(SolutionsPythonLayerVersion): + def __init__(self, scope: Construct, construct_id: str, **kwargs): + requirements_path: Path = Path(__file__).absolute().parent / "requirements" + super().__init__(scope, construct_id, requirements_path, **kwargs) + + @staticmethod + def get_or_create(scope: Construct, **kwargs): + stack = Stack.of(scope) + construct_id = "PowertoolsLayer-8E932F0F-197D-4026-A354-23D184C2A624" + exists = stack.node.try_find_child(construct_id) + if exists: + return exists + return PowertoolsLayer(stack, construct_id, **kwargs) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt new file mode 100644 index 0000000..b36c5ee --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt @@ -0,0 +1 @@ +aws-lambda-powertools>=1.24.0 \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### 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 new file mode 100644 index 0000000..4118911 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/bundling.py @@ -0,0 +1,224 @@ +# ##################################################################################################################### +# 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 importlib.util +import logging +import os +import platform +import subprocess +from pathlib import Path +from typing import Dict, Union + +import jsii +from aws_cdk import ILocalBundling, BundlingOptions +from aws_cdk.aws_lambda import Runtime + +from aws_solutions.cdk.helpers import copytree + +DEFAULT_RUNTIME = Runtime.PYTHON_3_7 +BUNDLER_DEPENDENCIES_CACHE = "/var/dependencies" +REQUIREMENTS_TXT_FILE = "requirements.txt" +REQUIREMENTS_PIPENV_FILE = "Pipfile" +REQUIREMENTS_POETRY_FILE = "pyproject.toml" + + +logger = logging.getLogger("cdk-helper") + + +class SolutionsPythonBundlingException(Exception): + pass + + +@jsii.implements(ILocalBundling) +class SolutionsPythonBundling: + """This interface allows AWS Solutions to package lambda functions without the use of Docker""" + + def __init__(self, to_bundle, libraries, install_path=""): + self.to_bundle = to_bundle + self.libraries = libraries + self.install_path = install_path + + @property + def platform_supports_bundling(self): + os_platform = platform.system() + os_platform_can_bundle = os_platform in ["Darwin", "Linux", "Windows"] + 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: + if not self.platform_supports_bundling: + raise SolutionsPythonBundlingException( + "this platform does not support bundling" + ) + + source = Path(self.to_bundle).absolute() + + # copy source + copytree(source, output_dir) + + # copy libraries + for lib in self.libraries: + lib_source = Path(lib).absolute() + lib_dest = Path(output_dir).joinpath(lib.name) + copytree(lib_source, lib_dest) + + try: + self._local_bundle_with_poetry(output_dir) + self._local_bundle_with_pipenv(output_dir) + self._local_bundle_with_pip(output_dir) + except subprocess.CalledProcessError as cpe: + raise SolutionsPythonBundlingException( + f"local bundling was tried but failed: {cpe}" + ) + + return True + + def _invoke_local_command( + self, + name, + command, + save_stdout: Path = None, + env: Union[Dict, None] = None, + cwd: Union[str, Path, None] = None, + ): + if save_stdout and save_stdout.exists(): + raise SolutionsPythonBundlingException( + f"{save_stdout} already exists - abandoning" + ) + + if save_stdout: + save_file = open(save_stdout, "w") + else: + save_file = None + + cwd = Path(cwd) + + with subprocess.Popen( + command, + shell=False, + stdout=subprocess.PIPE, + universal_newlines=True, + cwd=cwd, + env=env, + ) as p: + for line in p.stdout: + logger.info("%s %s: %s" % (self.to_bundle.name, name, line.rstrip())) + if save_file: + save_file.write(line) + + if save_file: + save_file.close() + + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, p.args) + + def validate_requirements_file(self, output_dir): + requirements_file = Path(output_dir) / REQUIREMENTS_TXT_FILE + with open(requirements_file, "r") as requirements: + for requirement in requirements: + if requirement.lstrip().startswith("-e"): + raise SolutionsPythonBundlingException( + "ensure no requirements are flagged as editable. if editable requirements are required, break down your requirements into requirements.txt and requirements-dev.txt" + ) + + def _source_file_exists(self, name, output_dir): + source_file = Path(output_dir) / name + exists = source_file.exists() + logger.info("%s file %s found" % (name, "was" if exists else "was not")) + return exists + + def _required_package_exists(self, package): + if not importlib.util.find_spec(package): + missing_package = ( + f"required package {package} was not installed - please install it" + ) + logger.warning(missing_package) + raise SolutionsPythonBundlingException(missing_package) + return True + + def _local_bundle_with_pip(self, output_dir): + if not self._source_file_exists(REQUIREMENTS_TXT_FILE, output_dir): + logger.info("no pip bundling to perform") + return + + self._required_package_exists("pip") + self.validate_requirements_file(output_dir) + + requirements_build_path = Path(output_dir).joinpath(self.install_path) + command = [ + "pip", + "install", + "-t", + str(requirements_build_path), + "-r", + str(Path(output_dir) / REQUIREMENTS_TXT_FILE), + ] + self._invoke_local_command("pip", command, cwd=self.to_bundle) + + def _local_bundle_with_pipenv(self, output_dir): + if not self._source_file_exists(REQUIREMENTS_PIPENV_FILE, output_dir): + return # no Pipenv file found - do not bundle with Pipenv + + if self._source_file_exists(REQUIREMENTS_TXT_FILE, output_dir): + logger.error( + "both a Pipenv and requirements.txt file were found - use one or the other" + ) + raise SolutionsPythonBundlingException( + "confusing Python package bundling - use one of requirements.txt (pip), pipenv (Pipenv) or pyproject.toml (poetry)" + ) + + self._required_package_exists("pipenv") + + command = ["pipenv", "--bare", "lock", "--no-header", "-r"] + env = os.environ.copy() + env.update( + { + "PIPENV_VERBOSITY": "-1", + "PIPENV_CLEAR": "true", + } + ) + self._invoke_local_command( + "pipenv", + command, + save_stdout=Path(output_dir) / REQUIREMENTS_TXT_FILE, + env=env, + cwd=output_dir, + ) + + def _local_bundle_with_poetry(self, output_dir): + if not self._source_file_exists(REQUIREMENTS_POETRY_FILE, output_dir): + return # no pyproject.toml file found - do not bundle with poetry + + if self._source_file_exists(REQUIREMENTS_TXT_FILE, output_dir): + logger.error( + "both a pyproject.toml and requirements.txt file were found - use one or the other" + ) + raise SolutionsPythonBundlingException( + "confusing Python package bundling - use one of requirements.txt (pip), pipenv (Pipenv) or pyproject.toml (poetry)" + ) + + self._required_package_exists("poetry") + + command = [ + "poetry", + "export", + "--with-credentials", + "--format", + REQUIREMENTS_TXT_FILE, + "--output", + str(Path(output_dir) / REQUIREMENTS_TXT_FILE), + ] + self._invoke_local_command("poetry", command, cwd=output_dir) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/function.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/function.py new file mode 100644 index 0000000..d9c06e4 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/function.py @@ -0,0 +1,191 @@ +# ##################################################################################################################### +# 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 hashlib +import os +from pathlib import Path +from typing import List, Union + +import aws_cdk.aws_iam as iam +from aws_cdk import ( + AssetHashType, + BundlingOptions, + DockerImage, + Aws, +) +from aws_cdk.aws_lambda import Function, Runtime, RuntimeFamily, Code +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.python.bundling import SolutionsPythonBundling + +DEFAULT_RUNTIME = Runtime.PYTHON_3_7 +DEPENDENCY_EXCLUDES = ["*.pyc"] + + +class DirectoryHash: + # fmt: off + # NOSONAR - safe to hash; side-effect of collision is to create new bundle + _hash = hashlib.sha1() # nosec + # fmt: on + + @classmethod + def hash(cls, *directories: Path): + # NOSONAR - safe to hash; see above + DirectoryHash._hash = hashlib.sha1() # nosec + if isinstance(directories, Path): + directories = [directories] + for directory in sorted(directories): + DirectoryHash._hash_dir(str(directory.absolute())) + return DirectoryHash._hash.hexdigest() + + @classmethod + def _hash_dir(cls, directory: Path): + for path, dirs, files in os.walk(directory): + for file in sorted(files): + DirectoryHash._hash_file(Path(path) / file) + for directory in sorted(dirs): + DirectoryHash._hash_dir(str((Path(path) / directory).absolute())) + break + + @classmethod + def _hash_file(cls, file: Path): + with file.open("rb") as f: + while True: + block = f.read(2 ** 10) + if not block: + break + DirectoryHash._hash.update(block) + + +class SolutionsPythonFunction(Function): + """This is similar to aws-cdk/aws-lambda-python, however it handles local bundling""" + + def __init__( + self, # NOSONAR (python:S107) - allow large number of method parameters + scope: Construct, + construct_id: str, + entrypoint: Path, + function: str, + libraries: Union[List[Path], Path, None] = None, + **kwargs, + ): + self.scope = scope + self.construct_id = construct_id + self.source_path = entrypoint.parent + + # validate source path + if not self.source_path.is_dir(): + raise ValueError( + f"entrypoint {entrypoint} must not be a directory, but rather a .py file" + ) + + # validate libraries + self.libraries = libraries or [] + self.libraries = ( + self.libraries if isinstance(self.libraries, list) else [self.libraries] + ) + for lib in self.libraries: + if lib.is_file(): + raise ValueError( + f"library {lib} must not be a file, but rather a directory" + ) + + # create default least privileged role for this function unless a role is passed + if not kwargs.get("role"): + kwargs["role"] = self._create_role() + + # python 3.7 is selected to support custom resources and inline code + if not kwargs.get("runtime"): + kwargs["runtime"] = DEFAULT_RUNTIME + + # validate that the user is using a python runtime for AWS Lambda + if kwargs["runtime"].family != RuntimeFamily.PYTHON: + raise ValueError( + f"SolutionsPythonFunction must use a Python runtime ({kwargs['runtime']} was provided)" + ) + + # build the handler based on the entrypoint Path and function name + if kwargs.get("handler"): + raise ValueError( + f"SolutionsPythonFunction expects a Path `entrypoint` (python file) and `function` (function in the entrypoint for AWS Lambda to invoke)" + ) + else: + kwargs["handler"] = f"{entrypoint.stem}.{function}" + + # build the code based on the entrypoint Path + if kwargs.get("code"): + raise ValueError( + f"SolutionsPythonFunction expects a Path `entrypoint` (python file) and `function` (function in the entrypoint for AWS Lambda to invoke)" + ) + + bundling = SolutionsPythonBundling( + self.source_path, + self.libraries, + ) + + kwargs["code"] = self._get_code(bundling, runtime=kwargs["runtime"]) + + # initialize the parent Function + super().__init__(scope, construct_id, **kwargs) + + def _get_code(self, bundling: SolutionsPythonBundling, runtime: Runtime) -> Code: + # try to create the code locally - if this fails, try using Docker + code_parameters = { + "path": str(self.source_path), + "asset_hash_type": AssetHashType.CUSTOM, + "asset_hash": DirectoryHash.hash(self.source_path, *self.libraries), + "exclude": DEPENDENCY_EXCLUDES, + } + + # to enable docker only bundling, use image=self._get_bundling_docker_image(bundling, runtime=runtime) + code = Code.from_asset( + bundling=BundlingOptions( + image=DockerImage.from_registry( + "scratch" + ), # NOT USED - FOR NOW ALL BUNDLING IS LOCAL + command=["NOT-USED"], + entrypoint=["NOT-USED"], + local=bundling, + ), + **code_parameters, + ) + + return code + + def _create_role(self) -> iam.Role: + """ + Build a role that allows an AWS Lambda Function to log to CloudWatch + :param name: The name of the role. The final name will be "{name}-Role" + :return: aws_cdk.aws_iam.Role + """ + return iam.Role( + self.scope, + f"{self.construct_id}-Role", + assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"), + inline_policies={ + "LambdaFunctionServiceRolePolicy": iam.PolicyDocument( + statements=[ + iam.PolicyStatement( + actions=[ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + resources=[ + f"arn:{Aws.PARTITION}:logs:{Aws.REGION}:{Aws.ACCOUNT_ID}:log-group:/aws/lambda/*" + ], + ) + ] + ) + }, + ) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/layer.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/layer.py new file mode 100644 index 0000000..e7f8add --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/python/layer.py @@ -0,0 +1,85 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from pathlib import Path +from typing import Union, List +from uuid import uuid4 + +from aws_cdk import BundlingOptions, DockerImage, AssetHashType +from aws_cdk.aws_lambda import LayerVersion, Code +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.python.function import SolutionsPythonBundling + +DEPENDENCY_EXCLUDES = ["*.pyc"] + + +class SolutionsPythonLayerVersion(LayerVersion): + """Handle local packaging of layer versions""" + + def __init__( + self, + scope: Construct, + construct_id: str, + requirements_path: Path, + libraries: Union[List[Path], None] = None, + **kwargs, + ): # NOSONAR + self.scope = scope + self.construct_id = construct_id + self.requirements_path = requirements_path + + # validate requirements path + if not self.requirements_path.is_dir(): + raise ValueError( + f"requirements_path {self.requirements_path} must not be a file, but rather a directory containing Python requirements in a requirements.txt file, pipenv format or poetry format" + ) + + libraries = [] if not libraries else libraries + for lib in libraries: + if lib.is_file(): + raise ValueError( + f"library {lib} must not be a file, but rather a directory" + ) + + bundling = SolutionsPythonBundling( + self.requirements_path, libraries=libraries, install_path="python" + ) + + kwargs["code"] = self._get_code(bundling) + + # initialize the LayerVersion + super().__init__(scope, construct_id, **kwargs) + + def _get_code(self, bundling: SolutionsPythonBundling) -> Code: + # create the layer version locally + code_parameters = { + "path": str(self.requirements_path), + "asset_hash_type": AssetHashType.CUSTOM, + "asset_hash": uuid4().hex, + "exclude": DEPENDENCY_EXCLUDES, + } + + code = Code.from_asset( + bundling=BundlingOptions( + image=DockerImage.from_registry( + "scratch" + ), # NEVER USED - FOR NOW ALL BUNDLING IS LOCAL + command=["not_used"], + entrypoint=["not_used"], + local=bundling, + ), + **code_parameters, + ) + + return code diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/cfn_nag.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/cfn_nag.py new file mode 100644 index 0000000..8440e31 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/cfn_nag.py @@ -0,0 +1,59 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from dataclasses import dataclass +from typing import List + +import jsii +from aws_cdk import CfnResource, IAspect +from constructs import IConstruct + + +@dataclass +class CfnNagSuppression: + rule_id: str + reason: str + + +def add_cfn_nag_suppressions( + resource: CfnResource, suppressions: List[CfnNagSuppression] +): + resource.add_metadata( + "cfn_nag", + { + "rules_to_suppress": [ + {"id": suppression.rule_id, "reason": suppression.reason} + for suppression in suppressions + ] + }, + ) + + +@jsii.implements(IAspect) +class CfnNagSuppressAll: + """Suppress certain cfn_nag warnings that can be ignored by this solution""" + + def __init__(self, suppress: List[CfnNagSuppression], resource_type: str): + self.suppressions = suppress + self.resource_type = resource_type + + def visit(self, node: IConstruct): + if "is_cfn_element" in dir(node) and node.is_cfn_element(node): + if getattr(node, "cfn_resource_type", None) == self.resource_type: + add_cfn_nag_suppressions(node, self.suppressions) + + elif "is_cfn_element" in dir(node.node.default_child) and ( + getattr(node.node.default_child, "cfn_resource_type", None) + == self.resource_type + ): + add_cfn_nag_suppressions(node.node.default_child, self.suppressions) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/context.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/context.py new file mode 100644 index 0000000..066ad41 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/context.py @@ -0,0 +1,84 @@ +# ##################################################################################################################### +# 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 json +import logging +from functools import wraps +from os import environ +from pathlib import Path +from typing import Union + +ARGUMENT_ERROR = "functions decorated with `with_cdk_context` can only accept one dictionary argument - the additional context overrides to use" + +logger = logging.getLogger("cdk-helper") + + +class SolutionContext: + def __init__(self, cdk_json_path: Union[None, Path] = None): + self.cdk_json_path = cdk_json_path + self.context = self._load_cdk_context() + + def requires( # NOSONAR - higher cognitive complexity allowed + self, context_var_name, context_var_value=None + ): + context = self.context + + def cdk_context_decorator(f): + @wraps(f) + def wrapper(*args): + # validate function arguments + if len(args) > 1: + raise ValueError(ARGUMENT_ERROR) + if len(args) == 1 and not isinstance(args[0], dict): + raise TypeError(ARGUMENT_ERROR) + + if len(args) == 0: + args = (context,) + + # override the CDK context as required + if len(args) == 1: + context.update(args[0]) + + env_context_var = environ.get(context_var_name) + if env_context_var: + context[context_var_name] = env_context_var + elif context_var_name and context_var_value: + context[context_var_name] = context_var_value + + if not context.get(context_var_name): + raise ValueError( + f"Missing cdk.json context variable or environment variable for {context_var_name}." + ) + + args = (context,) + + return f(*args) + + return wrapper + + return cdk_context_decorator + + def _load_cdk_context(self): + """Load context from cdk.json""" + if not self.cdk_json_path: + return {} + + try: + with open(self.cdk_json_path, "r") as f: + config = json.loads(f.read()) + except FileNotFoundError: + logger.warning(f"{self.cdk_json_path} not found, using empty context!") + return {} + context = config.get("context", {}) + return context diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/__init__.py new file mode 100644 index 0000000..bae5553 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/__init__.py @@ -0,0 +1,14 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_solutions.cdk.helpers.copytree import copytree, ignore_globs diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/copytree.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/copytree.py new file mode 100644 index 0000000..25987e5 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/copytree.py @@ -0,0 +1,57 @@ +# ##################################################################################################################### +# 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 os +import shutil +from pathlib import Path + + +def ignore_globs(*globs): + """Function that can be used as copytree() ignore parameter. + + Patterns is a sequence of glob-style patterns + that are used to exclude files""" + + def _ignore_globs(path, names): + ignored_names = [] + paths = [Path(os.path.join(path, name)).resolve() for name in names] + for pattern in globs: + for i, p in enumerate(paths): + if p.match(pattern): + ignored_names.append(names[i]) + return set(ignored_names) + + return _ignore_globs + + +def copytree(src, dst, symlinks=False, ignore=None): + if ignore: + ignore.extend([ignored[:-2] for ignored in ignore if ignored.endswith("/*")]) + else: + ignore = [] + + if not os.path.exists(dst): + os.makedirs(dst) + + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + + # ignore full directories upfront + if any(Path(s).match(ignored) for ignored in ignore): + continue + + if os.path.isdir(s): + shutil.copytree(s, d, symlinks, ignore=ignore_globs(*ignore)) + else: + shutil.copy2(s, d) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/loader.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/loader.py new file mode 100644 index 0000000..7faea22 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/loader.py @@ -0,0 +1,94 @@ +# ##################################################################################################################### +# 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 importlib +import json +import logging +from functools import wraps +from pathlib import Path + +logger = logging.getLogger("cdk-helper") + + +class CDKLoaderException(Exception): + pass + + +def log_error(error): + logger.error(error) + raise CDKLoaderException(error) + + +def _cdk_json_present(func): + @wraps(func) + def cdk_json_present(cdk_app_path: Path, cdk_app_name): + app_path = cdk_app_path.parent + cdk_json_dict = {} + if not Path(app_path / "cdk.json").exists(): + log_error(f"please ensure a cdk.json is present at {app_path}") + + try: + cdk_json_dict = json.loads(Path(app_path / "cdk.json").read_text()) + except ValueError as exc: + log_error(f"failed to parse cdk.json: {exc}") + + cdk_app = cdk_json_dict.get("app") + if not cdk_app: + log_error(f"failed to find `app` in cdk.json") + + if "python3" not in cdk_app: + log_error( + f"this helper only supports python3 CDK apps at this time - yours was declared as {cdk_app}" + ) + + return func(cdk_app_path, cdk_app_name) + + return cdk_json_present + + +@_cdk_json_present +def load_cdk_app(cdk_app_path, cdk_app_name): + """ + Load a CDK app from a folder path (dynamically) + :param cdk_app_path: The full path of the CDK app to load + :param cdk_app_name: The module path (starting from cdk_app_path) to find the function returning synth() + :return: + """ + + try: + (cdk_app_name, cdk_app_entrypoint) = cdk_app_name.split(":") + except ValueError: + log_error("please provide your `cdk_app_name` as path.to.cdk:function_name") + + if not cdk_app_path.exists(): + log_error(f"could not find `{cdk_app_name}` (please use a full path)") + + spec = importlib.util.spec_from_file_location(cdk_app_name, cdk_app_path) + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) + except Exception as exc: + log_error(f"could not load `{cdk_app_entrypoint}` in `{cdk_app_name}`: {exc}") + + try: + cdk_function = getattr(module, cdk_app_entrypoint) + except AttributeError as exc: + log_error( + f"could not find CDK entrypoint `{cdk_app_entrypoint}` in `{cdk_app_name}`" + ) + + logger.info(f"loaded AWS CDK app from {cdk_app_path}") + logger.info( + f"loaded AWS CDK app at {cdk_app_name}, entrypoint is {cdk_app_entrypoint}" + ) + return cdk_function diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/logger.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/logger.py new file mode 100644 index 0000000..5351235 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/helpers/logger.py @@ -0,0 +1,35 @@ +# ##################################################################################################################### +# 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 + + +class Logger: + """Set up a logger fo this package""" + + @classmethod + def get_logger(cls, name: str) -> logging.Logger: + """ + Gets the current logger for this package + :param name: the name of the logger + :return: the logger + """ + logger = logging.getLogger(name) + if not len(logger.handlers): + logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + formatter = logging.Formatter("[%(levelname)s]\t%(name)s\t%(message)s") + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.propagate = False + return logger diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/interfaces.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/interfaces.py new file mode 100644 index 0000000..c27737a --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/interfaces.py @@ -0,0 +1,115 @@ +# ##################################################################################################################### +# 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 dataclasses import dataclass +from typing import Union, List + +import jsii +from aws_cdk import ( + ITemplateOptions, + Stack, + NestedStack, + CfnParameter, +) + +logger = logging.getLogger("cdk-helper") + + +@dataclass +class _TemplateParameter: + """Stores information about a CloudFormation parameter, its label (description) and group""" + + name: str + label: str + group: str + + +class TemplateOptionsException(Exception): + pass + + +@jsii.implements(ITemplateOptions) +class TemplateOptions: + """Helper class for setting up template CloudFormation parameter groups, labels and solutions metadata""" + + _metadata = {} + + def __init__( + self, + stack: Union[Stack, NestedStack], + construct_id: str, + description: str, + filename: str, + ): + self.stack = stack + self.filename = filename + self._parameters: List[_TemplateParameter] = [] + self.stack.template_options.description = description + self.stack.template_options.metadata = self.metadata + + self._metadata = self._get_metadata() + + if not filename.endswith(".template"): + raise TemplateOptionsException("template filenames must end with .template") + + # if this stack is a nested stack, record its CDK ID in the parent stack's resource to it + if getattr(stack, "nested_stack_resource"): + stack.nested_stack_resource.add_metadata( + "aws:solutions:templateid", construct_id + ) + stack.nested_stack_resource.add_metadata( + "aws:solutions:templatename", filename + ) + + @property + def metadata(self) -> dict: + return self._metadata + + def _get_metadata(self) -> dict: + pgs = set() + parameter_groups = [ + p.group + for p in self._parameters + if p.group not in pgs and not pgs.add(p.group) + ] + metadata = { + "AWS::CloudFormation::Interface": { + "ParameterGroups": [ + { + "Label": {"default": parameter_group}, + "Parameters": [ + parameter.name + for parameter in self._parameters + if parameter.group == parameter_group + ], + } + for parameter_group in parameter_groups + ], + "ParameterLabels": { + parameter.name: {"default": parameter.label} + for parameter in self._parameters + }, + }, + "aws:solutions:templatename": self.filename, + "aws:solutions:solution_id": self.stack.node.try_get_context("SOLUTION_ID"), + "aws:solutions:solution_version": self.stack.node.try_get_context( + "SOLUTION_VERSION" + ), + } + self.stack.template_options.metadata = metadata + return metadata + + def add_parameter(self, parameter: CfnParameter, label: str, group: str): + self._parameters.append(_TemplateParameter(parameter.logical_id, label, group)) + self._metadata = self._get_metadata() diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/mappings.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/mappings.py new file mode 100644 index 0000000..f07b1f8 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/mappings.py @@ -0,0 +1,55 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_cdk import CfnMapping +from constructs import Construct + + +class Mappings: + def __init__( + self, + parent: Construct, + solution_id: str, + send_anonymous_usage_data: bool = True, + quicksight_template_arn: bool = False, + ): + self.parent = parent + + # Track the solution mapping (ID, version, anonymous usage data) + self.solution_mapping = CfnMapping( + parent, + "Solution", + mapping={ + "Data": { + "ID": solution_id, + "Version": "%%SOLUTION_VERSION%%", + "SendAnonymousUsageData": "Yes" + if send_anonymous_usage_data + else "No", + } + } + ) + + # track the s3 bucket, key prefix and (optional) quicksight template source + general = { + "S3Bucket": "%%BUCKET_NAME%%", + "KeyPrefix": "%%SOLUTION_NAME%%/%%SOLUTION_VERSION%%", + } + if quicksight_template_arn: + general["QuickSightSourceTemplateArn"] = "%%QUICKSIGHT_SOURCE%%" + + self.source_mapping = CfnMapping( + parent, + "SourceCode", + mapping={"General": general} + ) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/__init__.py new file mode 100644 index 0000000..330623f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/__init__.py @@ -0,0 +1,12 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### 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 new file mode 100644 index 0000000..8ceebe4 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/scripts/build_s3_cdk_dist.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python3 + +# ##################################################################################################################### +# 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 os +import re +import shutil +import subprocess +from dataclasses import dataclass, field +from pathlib import Path + +import boto3 +import botocore +import click + +from aws_solutions.cdk.helpers import copytree +from aws_solutions.cdk.helpers.loader import load_cdk_app +from aws_solutions.cdk.helpers.logger import Logger +from aws_solutions.cdk.tools import Cleaner + +logger = Logger.get_logger("cdk-helper") + + +class PathPath(click.Path): + def convert(self, value, param, ctx): + return Path(super().convert(value, param, ctx)) + + +@dataclass +class BuildEnvironment: + source_bucket_name: str = field(default="") + solution_name: str = field(default="") + version_code: str = field(default="") + template_dir: str = field(default_factory=os.getcwd, init=False) + template_dist_dir: str = field(init=False, repr=False) + build_dir: str = field(init=False, repr=False) + build_dist_dir: str = field(init=False, repr=False) + source_dir: str = field(init=False, repr=False) + infrastructure_dir: str = field(init=False, repr=False) + + def __post_init__(self): + self.template_dist_dir = os.path.join(self.template_dir, "global-s3-assets") + self.build_dir = os.path.join(self.template_dir, "build-s3-assets") + self.build_dist_dir = os.path.join(self.template_dir, "regional-s3-assets") + self.source_dir = os.path.normpath( + os.path.join(self.template_dir, os.pardir, "source") + ) + self.infrastructure_dir = os.path.join(self.source_dir, "infrastructure") + self.open_source_dir = os.path.join(self.template_dir, "open-source") + self.github_dir = os.path.normpath( + os.path.join(self.template_dir, os.pardir, ".github") + ) + + logger.debug("build environment template directory: %s" % self.template_dir) + logger.debug( + "build environment template distribution directory: %s" + % self.template_dist_dir + ) + logger.debug("build environment build directory: %s" % self.build_dir) + logger.debug( + "build environment build distribution directory: %s" % self.build_dist_dir + ) + logger.debug("build environment source directory: %s" % self.source_dir) + logger.debug( + "build environment infrastructure directory: %s" % self.infrastructure_dir + ) + logger.debug("open source dir: %s" % self.open_source_dir) + + def clean_for_scan(self): + """Clean up the build environment partially to optimize code scan in next build stage""" + cleaner = Cleaner() + cleaner.cleanup_source(self.source_dir) + return cleaner + + def clean(self): + """Clean up the build environment""" + cleaner = self.clean_for_scan() + cleaner.clean_dirs(self.template_dist_dir, self.build_dir, self.build_dist_dir) + return cleaner + + def clean_for_open_source(self): + """Clean up the build environment for the open source build""" + cleaner = self.clean_for_scan() + cleaner.clean_dirs(self.open_source_dir) + return cleaner + + +class BaseAssetPackager: + """Shared commands across asset packagers""" + + local_asset_path = None + s3_asset_path = None + + def sync(self): + """Sync the assets packaged""" + if not self.local_asset_path: + raise ValueError("missing local asset path for sync") + if not self.s3_asset_path: + raise ValueError("missing s3 asset path for sync") + + self.check_bucket() + try: + with subprocess.Popen( + [ + "aws", + "s3", + "sync", + self.local_asset_path, + self.s3_asset_path, + "--no-progress", + "--acl", + "bucket-owner-full-control", + ], + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) as p: + for line in p.stdout: + logger.info("s3 sync: %s" % line.strip()) + for line in p.stderr: + logger.error("s3 sync: %s" % line.strip()) + except FileNotFoundError: + logger.error("awscli is missing") + raise click.ClickException("--sync requires awscli to be installed") + if p.returncode != 0: + raise click.ClickException("--sync failed") + + def check_bucket(self) -> bool: + """Checks bucket ownership before sync""" + bucket = self.s3_asset_path.split("/")[2] + sts = boto3.client("sts") + account = sts.get_caller_identity()["Account"] + + s3 = boto3.client("s3") + try: + s3.head_bucket(Bucket=bucket, ExpectedBucketOwner=account) + except botocore.exceptions.ClientError as err: + status = err.response["ResponseMetadata"]["HTTPStatusCode"] + error = err.response["Error"]["Code"] + if status == 404: + logger.error("missing bucket: %s" % error) + elif status == 403: + logger.error("access denied - check bucket ownership: %s" % error) + else: + logger.exception("unknown error: %s" % error) + raise + return True + + +class RegionalAssetPackager(BaseAssetPackager): + """Used to package regional assets""" + + def __init__(self, build_env: BuildEnvironment, region="us-east-1"): + self.build_env = build_env + self.local_asset_path = build_env.build_dist_dir + self.s3_asset_path = f"s3://{build_env.source_bucket_name}-{region}/{build_env.solution_name}/{build_env.version_code}" + + def package(self): + logger.info("packaging regional assets") + + +class GlobalAssetPackager(BaseAssetPackager): + """Used to package global assets""" + + def __init__(self, build_env: BuildEnvironment): + self.build_env = build_env + self.local_asset_path = build_env.template_dist_dir + self.s3_asset_path = f"s3://{build_env.source_bucket_name}/{build_env.solution_name}/{build_env.version_code}" + + 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_prerelease_ver = r'0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*' + re_semver = rf"^v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:{re_prerelease_ver})(?:\.(?:{re_prerelease_ver}))*))?(?:\+(?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", + help="The log level to use", + default="INFO", + type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), +) +@click.pass_context +def cli(ctx, log_level): + """This CLI helps to build and package your AWS Solution CDK application as CloudFormation templates""" + ctx.ensure_object(dict) + logger.setLevel(log_level) + + +@cli.command() +@click.pass_context +def clean_for_scan(ctx): + """Use this to partially clean generated build files to optimize code scan in next build stage""" + env = BuildEnvironment() + env.clean_for_scan() + + +@cli.command() +@click.pass_context +@click.option("--ignore", "-i", multiple=True) +@click.option("--solution-name", help="The name of the solution.", required=True) +def source_code_package(ctx, ignore, solution_name): + """Use this to build the source package folder and zip file""" + env = BuildEnvironment() + env.clean_for_open_source() + + # set up some default ignore directories + ignored = [ + "**/cdk.out/*", + "**/__pycache__/*", + "*.pyc", + "*.pyo", + "*.pyd", + "**/.gradle/*", + "**/.idea/*", + "**/.coverage/*", + "**/.pytest_cache/*", + "**/*.egg-info", + "**/__pycache__", + ] + ignored.extend(ignore) + + required_files = [ + "LICENSE.txt", + "NOTICE.txt", + "README.md", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "CHANGELOG.md", + ".gitignore", + ] + + # copy source directory + try: + copytree( + env.source_dir, os.path.join(env.open_source_dir, "source"), ignore=ignored + ) + copytree(env.github_dir, os.path.join(env.open_source_dir, ".github")) + except FileNotFoundError: + raise click.ClickException( + "The solution requires a `source` folder and a `.github` folder" + ) + + # copy all required files + for name in required_files: + try: + shutil.copyfile( + Path(env.source_dir).parent / name, Path(env.open_source_dir) / name + ) + except FileNotFoundError: + raise click.ClickException( + f"The solution is missing the required file {name}" + ) + + # copy the required run-unit-tests.sh + (Path(env.open_source_dir) / "deployment").mkdir() + try: + shutil.copyfile( + Path(env.template_dir) / "run-unit-tests.sh", + Path(env.open_source_dir) / "deployment" / "run-unit-tests.sh", + ) + except FileNotFoundError: + raise click.ClickException( + f"The solution is missing deployment/run-unit-tests.sh" + ) + + shutil.make_archive( + base_name=os.path.join(env.template_dir, solution_name), + format="zip", + root_dir=os.path.join(env.open_source_dir), + logger=logger, + ) + + # finalize by deleting the open-source folder data and copying the zip file over + env.clean_for_open_source() + shutil.move( + os.path.join(env.template_dir, f"{solution_name}.zip"), env.open_source_dir + ) + + +@cli.command() +@click.pass_context +@click.option( + "--source-bucket-name", + help="Configure the bucket name of your target Amazon S3 distribution bucket. A randomized value is recommended. " + "You will also need to create an S3 bucket where the name is -. The solution's " + "CloudFormation template will expect the source code to be located in a bucket matching that name.", + required=True, +) +@click.option("--solution-name", help="The name of the solution.", required=True) +@click.option( + "--version-code", + help="The version of the package.", + required=True, + callback=validate_version_code, +) +@click.option( + "--cdk-app-path", + help="The CDK Python app path", + required=True, + type=PathPath(dir_okay=False), +) +@click.option( + "--cdk-app-entrypoint", + help="The CDK Python app entrypoint", + required=True, +) +@click.option( + "--sync", + help="Use this to sync your assets to the global and regional source-buckets.", + default=False, + is_flag=True, +) +@click.option( + "--region", + help="Use this flag to control which regional bucket to push your assets to", + default="us-east-1", +) +def deploy( + ctx, # NOSONAR (python:S107) - allow large number of method parameters + source_bucket_name, + solution_name, + version_code, + cdk_app_path, + cdk_app_entrypoint, + sync, + region, +): + """Runs the CDK build of the project, uploading assets as required.""" + + # load the cdk app dynamically + cdk = load_cdk_app( + cdk_app_path=cdk_app_path, + cdk_app_name=cdk_app_entrypoint, + ) + + # set up relevant directories and clean the build environment + env = BuildEnvironment( + source_bucket_name=source_bucket_name, + solution_name=solution_name, + version_code=version_code, + ) + + # clean up the build environment from previous builds before running this build + env.clean() + + # run cdk asset packaging + cdk( + { + "BUCKET_NAME": source_bucket_name, + "SOLUTION_NAME": solution_name, + "SOLUTION_VERSION": version_code, + "SOLUTIONS_ASSETS_REGIONAL": env.build_dist_dir, + "SOLUTIONS_ASSETS_GLOBAL": env.template_dist_dir, + } + ) + + # run regional asset packaging + rap = RegionalAssetPackager(env, region=region) + rap.package() + + # run global asset packaging + gap = GlobalAssetPackager(env) + gap.package() + + # sync as required + if sync: + rap.sync() + gap.sync() + + +if __name__ == "__main__": + cli() 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 new file mode 100644 index 0000000..4af2b71 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stack.py @@ -0,0 +1,78 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from __future__ import annotations + +import re + +import jsii +from aws_cdk import Stack, Aspects, IAspect +from constructs import Construct, IConstruct + +from aws_solutions.cdk.aws_lambda.cfn_custom_resources.solutions_metrics import Metrics +from aws_solutions.cdk.interfaces import TemplateOptions +from aws_solutions.cdk.mappings import Mappings + +RE_SOLUTION_ID = re.compile(r"^SO\d+$") +RE_TEMPLATE_FILENAME = re.compile(r"^[a-z]+(?:-[a-z]+)*\.template$") # NOSONAR + + +def validate_re(name, value, regex: re.Pattern): + if regex.match(value): + return value + raise ValueError(f"{name} must match '{regex.pattern}") + + +def validate_solution_id(solution_id: str) -> str: + return validate_re("solution_id", solution_id, RE_SOLUTION_ID) + + +def validate_template_filename(template_filename: str) -> str: + return validate_re("template_filename", template_filename, RE_TEMPLATE_FILENAME) + + +@jsii.implements(IAspect) +class MetricsAspect: + def __init__(self, stack: SolutionStack): + self.stack = stack + + def visit(self, node: IConstruct): + """Called before synthesis, this allows us to set metrics at the end of synthesis""" + if node == self.stack: + self.stack.metrics = Metrics(self.stack, "Metrics", self.stack.metrics) + + +class SolutionStack(Stack): + def __init__( + self, + scope: Construct, + construct_id: str, + description: str, + template_filename, + **kwargs, + ): + super().__init__(scope, construct_id, **kwargs) + + self.metrics = {} + self.solution_id = self.node.try_get_context("SOLUTION_ID") + self.solution_version = self.node.try_get_context("SOLUTION_VERSION") + self.mappings = Mappings(self, solution_id=self.solution_id) + self.solutions_template_filename = validate_template_filename(template_filename) + self.description = description.strip(".") + self.solutions_template_options = TemplateOptions( + self, + construct_id=construct_id, + description=f"({self.solution_id}) - {self.description}. Version {self.solution_version}", + filename=template_filename, + ) + Aspects.of(self).add(MetricsAspect(self)) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/__init__.py new file mode 100644 index 0000000..ef2f9eb --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/__init__.py @@ -0,0 +1,12 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solution_fragment.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solution_fragment.py new file mode 100644 index 0000000..9e98d86 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solution_fragment.py @@ -0,0 +1,82 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### +from typing import List, Dict +from typing import Optional + +from aws_cdk import Duration +from aws_cdk.aws_lambda import CfnFunction +from aws_cdk.aws_stepfunctions import State, INextable, TaskInput, StateMachineFragment +from aws_cdk.aws_stepfunctions_tasks import LambdaInvoke +from constructs import Construct + + +class SolutionFragment(StateMachineFragment): + def __init__( + self, # NOSONAR (python:S107) - allow large number of method parameters + scope: Construct, + id: str, + function: CfnFunction, + payload: Optional[TaskInput] = None, + input_path: Optional[str] = "$", + result_path: Optional[str] = "$", + output_path: Optional[str] = "$", + result_selector: Optional[Dict] = None, + failure_state: Optional[State] = None, + backoff_rate: Optional[int] = 1.05, + interval: Optional[Duration] = Duration.seconds(5), + max_attempts: Optional[int] = 5, + ): + super().__init__(scope, id) + + self.failure_state = failure_state + + self.task = LambdaInvoke( + self, + id, + lambda_function=function, + retry_on_service_exceptions=True, + input_path=input_path, + result_path=result_path, + output_path=output_path, + payload=payload, + payload_response_only=True, + result_selector=result_selector, + ) + self.task.add_retry( + backoff_rate=backoff_rate, + interval=interval, + max_attempts=max_attempts, + errors=["ResourcePending"], + ) + if self.failure_state: + self.task.add_catch( + failure_state, + errors=["ResourceFailed", "ResourceInvalid"], + result_path="$.statesError", + ) + self.task.add_catch( + failure_state, errors=["States.ALL"], result_path="$.statesError" + ) + + @property + def start_state(self) -> State: + return self.task + + @property + def end_states(self) -> List[INextable]: + """ + Get the end states of this chain + :return: The chainable end states of this chain (i.e. not the failure state) + """ + states = [self.task] + return states 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 new file mode 100644 index 0000000..088e680 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/stepfunctions/solutionstep.py @@ -0,0 +1,145 @@ +# ###################################################################################################################### +# 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. # +# ###################################################################################################################### + +from pathlib import Path +from typing import Optional, List + +from aws_cdk import Duration +from aws_cdk.aws_events import EventBus +from aws_cdk.aws_lambda import Tracing, Runtime, RuntimeFamily +from aws_cdk.aws_stepfunctions import IChainable, TaskInput, State +from constructs import Construct + +from aws_solutions.cdk.aws_lambda.environment import Environment +from aws_solutions.cdk.aws_lambda.python.function import SolutionsPythonFunction +from aws_solutions.cdk.cfn_nag import add_cfn_nag_suppressions, CfnNagSuppression +from aws_solutions.cdk.stepfunctions.solution_fragment import SolutionFragment + + +class SolutionStep(Construct): + def __init__( + self, # NOSONAR (python:S107) - allow large number of method parameters + scope: Construct, + id: str, + function: str = "lambda_handler", + entrypoint: Path = None, + input_path: str = "$", + result_path: str = "$", + output_path: str = "$", + payload: Optional[TaskInput] = None, + layers=None, + failure_state: Optional[IChainable] = None, + libraries: Optional[List[Path]] = None, + ): + super().__init__(scope, f"{id} Solution Step") + + self.function = self._CreateLambdaFunction( + self, + f"{self._snake_case(id)}_fn", + layers=layers, + function=function, + entrypoint=entrypoint, + libraries=libraries, + ) + add_cfn_nag_suppressions( + self.function.role.node.try_find_child("DefaultPolicy").node.find_child( + "Resource" + ), + [ + CfnNagSuppression( + "W12", "IAM policy for AWS X-Ray requires an allow on *" + ) + ], + ) + + self._input_path = input_path + self._result_path = result_path + self._output_path = output_path + self._payload = payload + self._failure_state = failure_state + + self._create_resources() + self._set_permissions() + self.environment = self._set_environment() + + def state( + self, # NOSONAR (python:S107) - allow large number of method parameters + scope: Construct, + construct_id, + payload: Optional[TaskInput] = None, + input_path: Optional[str] = None, + result_path: Optional[str] = None, + result_selector: Optional[str] = None, + output_path: Optional[str] = None, + failure_state: Optional[State] = None, + **kwargs, + ): + payload = payload or self._payload + input_path = input_path or self._input_path + result_path = result_path or self._result_path + output_path = output_path or self._output_path + failure_state = failure_state or self._failure_state + + return SolutionFragment( + scope, + construct_id, + function=self.function, + payload=payload, + input_path=input_path, + result_path=result_path, + output_path=output_path, + failure_state=failure_state, + result_selector=result_selector, + **kwargs, + ) + + def _snake_case(self, name) -> str: + return name.replace(" ", "_").lower() + + def _set_permissions(self) -> None: + raise NotImplementedError("please implement _set_permissions") + + def grant_put_events(self, bus: EventBus): + self.function.add_environment("EVENT_BUS_ARN", bus.event_bus_arn) + bus.grant_put_events_to(self.function) + + def _create_resources(self) -> None: + pass # not required + + def _set_environment(self) -> Environment: + return Environment(self.function) + + class _CreateLambdaFunction(SolutionsPythonFunction): + def __init__(self, scope: Construct, construct_id: str, **kwargs): + entrypoint = kwargs.pop("entrypoint", None) + if not entrypoint or not entrypoint.exists(): + raise ValueError("an entrypoint (Path to a .py file) must be provided") + + libraries = kwargs.pop("libraries", None) + 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") + kwargs["layers"] = kwargs.get("layers", []) + kwargs["tracing"] = Tracing.ACTIVE + kwargs["timeout"] = Duration.seconds(15) + kwargs["runtime"] = Runtime("python3.9", RuntimeFamily.PYTHON) + + super().__init__( + scope, + construct_id, + entrypoint, + function, + 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 new file mode 100644 index 0000000..a1c3e7f --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py @@ -0,0 +1,305 @@ +# ##################################################################################################################### +# 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 json +import logging +import os +import re +import shutil +from contextlib import suppress +from dataclasses import field, dataclass +from fileinput import FileInput +from pathlib import Path +from typing import List, Dict + +import jsii +from aws_cdk import IStackSynthesizer, DefaultStackSynthesizer, ISynthesisSession + +logger = logging.getLogger("cdk-helper") + + +@dataclass +class CloudFormationTemplate: + """Encapsulates the transformations that are required on a CDK generated CloudFormation template for AWS Solutions""" + + path: Path + contents: Dict = field(repr=False) + assets: Path = field(repr=False) + stack_name: str = field(repr=False, init=False) + cloud_assembly_path: Path = field(repr=False, init=False) + assets_global: List[Path] = field(repr=False, default_factory=list, init=False) + assets_regional: List[Path] = field(repr=False, default_factory=list, init=False) + global_asset_name: str = field(repr=False, init=False) + + def __post_init__(self): + self.cloud_assembly_path = self.path.parent + self.stack_name = self.path.stem.split(".")[0] + self.assets_global.append(self.path) + try: + self.global_asset_name = self.contents["Metadata"][ + "aws:solutions:templatename" + ] + except KeyError: + logger.warning( + "for nested stack support, you must provide a filename to TemplateOptions for each stack" + ) + + def delete_bootstrap_parameters(self): + """Remove the CDK bootstrap parameters, since this stack will not be bootstrapped""" + with suppress(KeyError): + del self.contents["Parameters"]["BootstrapVersion"] + + with suppress(KeyError): + if len(self.contents["Parameters"]) == 0: + del self.contents["Parameters"] + + with suppress(KeyError): + del self.contents["Rules"]["CheckBootstrapVersion"] + + with suppress(KeyError): + if len(self.contents["Rules"]) == 0: + del self.contents["Rules"] + + def delete_cdk_helpers(self): + """Remove the CDK bucket deployment helpers, since solutions don't have a bootstrap bucket.""" + to_delete = [] + for (resource_name, resource) in self.contents.get("Resources", {}).items(): + if "Custom::CDKBucketDeployment" in resource["Type"]: + to_delete.append(resource_name) + if "CDKBucketDeployment" in resource_name: + to_delete.append(resource_name) + for resource in to_delete: + logger.info(f"deleting resource {resource}") + del self.contents["Resources"][resource] + + def patch_nested(self): + """Patch nested stacks for S3 deployment compatibility""" + template_output_bucket = os.getenv( + "TEMPLATE_OUTPUT_BUCKET", + { + "Fn::FindInMap": [ # NOSONAR (python:S1192) - string for clarity + "SourceCode", + "General", + "S3Bucket", + ] + }, + ) + for (resource_name, resource) in self.contents.get("Resources", {}).items(): + resource_type = resource.get("Type") + if resource_type == "AWS::CloudFormation::Stack": + try: + nested_stack_filename = resource["Metadata"][ + "aws:solutions:templatename" + ] + except KeyError: + raise KeyError("nested stack missing required TemplateOptions") + + # update CloudFormation resource properties for S3Bucket and S3Key + # fmt: off + resource["Properties"]["TemplateURL"] = { + "Fn::Join": [ # NOSONAR (python:S1192) - string for clarity + "", + [ + "https://", + template_output_bucket, + ".s3.", + {"Ref": "AWS::URLSuffix"}, + "/", + { + "Fn::FindInMap": ["SourceCode", "General", "KeyPrefix"] # NOSONAR (python:S1192) - string for clarity + }, + "/", + nested_stack_filename, + ], + ] + } + # fmt: on + + def patch_lambda(self): + """Patch the lambda functions for S3 deployment compatibility""" + for (resource_name, resource) in self.contents.get("Resources", {}).items(): + resource_type = resource.get("Type") + if resource_type in ["AWS::Lambda::Function", "AWS::Lambda::LayerVersion"]: + logger.info(f"{resource_name} ({resource_type}) patching") + + # the key for S3Key for AWS::Lambda:LayerVersion is under "Content". + # the key for S3Key FOR AWS::Lambda::Function is under "Code" + content_key = ( + "Content" + if resource_type == "AWS::Lambda::LayerVersion" + else "Code" + ) + try: + resource_id = resource["Properties"][content_key]["S3Key"].split( + "." + )[0] + except KeyError: + logger.warning( + "found resource without an S3Key (this typically occurs when using inline code or during test)" + ) + continue + + asset = self.assets["files"][resource_id] + asset_source_path = self.path.parent.joinpath(asset["source"]["path"]) + asset_packaging = asset["source"]["packaging"] + + # CDK does not zip assets prior to deployment - we do it here if a zip asset is detected + if asset_packaging == "zip": + # create archive if necessary + logger.info(f"{resource_name} packaging into .zip file") + archive = shutil.make_archive( + base_name=asset_source_path, + format="zip", + root_dir=str(asset_source_path), + ) + elif asset_packaging == "file": + archive = self.cloud_assembly_path.joinpath(asset["source"]["path"]) + else: + raise ValueError( + f"Unsupported asset packaging format: {asset_packaging}" + ) + + # rename archive to match the resource name it was generated for + archive_name = f"{resource_name}.zip" + archive_path = self.cloud_assembly_path.joinpath(archive_name) + shutil.move(src=archive, dst=archive_path) + + # update CloudFormation resource properties for S3Bucket and S3Key + # fmt: off + resource["Properties"][content_key]["S3Bucket"] = { + "Fn::Join": [ # NOSONAR (python:S1192) - string for clarity + "-", + [ + { + "Fn::FindInMap": ["SourceCode", "General", "S3Bucket"] # NOSONAR (python:S1192) - string for clarity + }, + {"Ref": "AWS::Region"}, + ], + ] + } + resource["Properties"][content_key]["S3Key"] = { + "Fn::Join": [ # NOSONAR (python:S1192) - string for clarity + "/", + [ + { + "Fn::FindInMap": ["SourceCode", "General", "KeyPrefix"] # NOSONAR (python:S1192) - string for clarity + }, + archive_name, + ], + ] + } + # fmt: on + + # add resource to the list of regional assets + self.assets_regional.append(archive_path) + + def _build_asset_path(self, asset_path): + asset_output_path = self.cloud_assembly_path.joinpath(asset_path) + asset_output_path.mkdir(parents=True, exist_ok=True) + return asset_output_path + + def save(self, asset_path_global: Path = None, asset_path_regional: Path = None): + """Save the template (will save to the asset paths if specified)""" + self.path.write_text(json.dumps(self.contents, indent=2)) + + # global solutions assets - default folder location is "global-s3-assets" + if asset_path_global: + asset_path = self._build_asset_path(asset_path_global) + for asset in self.assets_global: + shutil.copy( + str(asset), + str(asset_path.joinpath(self.global_asset_name)), + ) + + # regional solutions assets - default folder location is "regional-s3-assets" + if asset_path_regional: + asset_path = self._build_asset_path(asset_path_regional) + for asset in self.assets_regional: + shutil.copy(str(asset), str(asset_path)) + + +@jsii.implements(IStackSynthesizer) +class SolutionStackSubstitions(DefaultStackSynthesizer): + """Used to handle AWS Solutions template substitutions and sanitization""" + + substitutions = None + substitution_re = re.compile("%%[a-zA-Z-_][a-zA-Z-_]+%%") + + def _template_names(self, session: ISynthesisSession) -> List[Path]: + assembly_output_path = Path(session.assembly.outdir) + templates = [assembly_output_path.joinpath(self._bound_stack.template_file)] + + # add this stack's children to the outputs to process (todo: this only works for singly-nested stacks) + for child in self._bound_stack.node.children: + if child_template := getattr(child, "template_file", None): + templates.append(assembly_output_path.joinpath(child_template)) + return templates + + def _templates(self, session: ISynthesisSession) -> (Path, Dict): + assembly_output_path = Path(session.assembly.outdir) + + assets = {} + with suppress(StopIteration): + assets = json.loads(next(assembly_output_path.glob(f"{self._bound_stack.stack_name}*.assets.json")).read_text()) + + for path in self._template_names(session): + yield CloudFormationTemplate(path, json.loads(path.read_text()), assets) + + def synthesize(self, session: ISynthesisSession): + # when called with `cdk deploy` this outputs to cdk.out + # when called from python directly, this outputs to a temporary directory + result = DefaultStackSynthesizer.synthesize(self, session) + + asset_path_regional = self._bound_stack.node.try_get_context("SOLUTIONS_ASSETS_REGIONAL") + asset_path_global = self._bound_stack.node.try_get_context("SOLUTIONS_ASSETS_GLOBAL") + + logger.info( + f"solutions parameter substitution in {session.assembly.outdir} started" + ) + for template in self._template_names(session): + logger.info(f"substutiting parameters in {str(template)}") + with FileInput(template, inplace=True) as template_lines: + for line in template_lines: + # handle all template subsitutions in the line + for match in SolutionStackSubstitions.substitution_re.findall(line): + placeholder = match.replace("%", "") + if replacement := self._bound_stack.node.try_get_context(placeholder): + line = line.replace(match, replacement) + else: + raise ValueError( + f"Please provide a parameter substitution for {placeholder} via environment variable or CDK context" + ) + + # print the (now substituted) line in the context of template_lines + print(line, end="") + logger.info(f"substituting parameters in {str(template)} completed") + logger.info("solutions parameter substitution completed") + + # do not perform solution resource/ template cleanup if asset paths not passed + if not asset_path_global or not asset_path_regional: + return + + logger.info( + f"solutions template customization in {session.assembly.outdir} started" + ) + for template in self._templates(session): + template.patch_lambda() + template.patch_nested() + template.delete_bootstrap_parameters() + template.delete_cdk_helpers() + template.save( + asset_path_global=asset_path_global, + asset_path_regional=asset_path_regional, + ) + logger.info("solutions template customization completed") + + return result diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/__init__.py new file mode 100644 index 0000000..fa96974 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/__init__.py @@ -0,0 +1,14 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_solutions.cdk.tools.cleaner import Cleaner diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/cleaner.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/cleaner.py new file mode 100644 index 0000000..7b0e764 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/tools/cleaner.py @@ -0,0 +1,77 @@ +# ##################################################################################################################### +# 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 +import os +import shutil +from dataclasses import dataclass +from pathlib import Path + +logger = logging.getLogger("cdk-helper") + + +@dataclass +class Cleanable: + """Encapsulates something that can be cleaned by the cleaner""" + + name: str + file_type: str + pattern: str + + def __post_init__(self): + if self.file_type not in ("d", "f"): + raise ValueError("only directories and files are allowed ('d' or 'f')") + + def delete(self, source_dir): + source_path = Path(source_dir) + + for path in source_path.rglob(self.pattern): + if "aws_solutions" not in str( + path.name + ): # prevent the module from being unlinked in a dev environment + if self.file_type == "d" and path.is_dir(): + logger.info(f"deleting {self.name} directory {path}") + shutil.rmtree(path, ignore_errors=True) + if self.file_type == "f" and path.is_file(): + logger.info(f"deleting {self.name} file {path}") + try: + path.unlink() + except FileNotFoundError: + pass + + +class Cleaner: + """Encapsulates functions that help clean up the build environment.""" + + TO_CLEAN = [ + Cleanable("Python bytecode", "f", "*.py[cod]"), + Cleanable("Python Coverage databases", "f", ".coverage"), + Cleanable("CDK Cloud Assemblies", "d", "cdk.out"), + Cleanable("Python egg", "d", "*.egg-info"), + Cleanable("Python bytecode cache", "d", "__pycache__"), + Cleanable("Python test cache", "d", ".pytest_cache"), + ] + + @staticmethod + def clean_dirs(*args): + """Recursively remove each of its arguments, then recreate the directory""" + for dir_to_remove in args: + logger.info("cleaning %s" % dir_to_remove) + shutil.rmtree(dir_to_remove, ignore_errors=True) + os.makedirs(dir_to_remove) + + @staticmethod + def cleanup_source(source_dir): + """Cleans up all items found in TO_CLEAN""" + for item in Cleaner.TO_CLEAN: + item.delete(source_dir) diff --git a/source/cdk_solution_helper_py/helpers_cdk/setup.py b/source/cdk_solution_helper_py/helpers_cdk/setup.py new file mode 100644 index 0000000..bbe2ccb --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_cdk/setup.py @@ -0,0 +1,78 @@ +# ##################################################################################################################### +# 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 re +from pathlib import Path + +import setuptools + +VERSION_RE = re.compile(r"\#\# \[(?P.*)\]", re.MULTILINE) # NOSONAR + + +def get_version(): + """ + Detect the solution version from the changelog. Latest version on top. + """ + changelog = open(Path(__file__).resolve().parent.parent / "CHANGELOG.md").read() + versions = VERSION_RE.findall(changelog) + if not len(versions): + raise ValueError("use the standard semver format in your CHANGELOG.md") + build_version = versions[0] + print(f"Build Version: {build_version}") + return build_version + + +setuptools.setup( + name="aws-solutions-cdk", + version=get_version(), + description="Tools to make AWS Solutions deployments with CDK + Python more manageable", + long_description=open("../README.md").read(), + author="Amazon Web Services", + url="https://aws.amazon.com/solutions/implementations", + license="Apache License 2.0", + packages=setuptools.find_namespace_packages(), + package_data={ + "": [ + "requirements.txt", + "Dockerfile", + "__aws_solutions_bundling_version__", + ] + }, + install_requires=[ + "pip>=21.3", + "aws_cdk_lib>=2.7.0", + "Click>=7.1.2", + "boto3>=1.17.52", + "requests>=2.28.1", + "crhelper>=2.0.6", + ], + entry_points=""" + [console_scripts] + build-s3-cdk-dist=aws_solutions.cdk.scripts.build_s3_cdk_dist:cli + """, + python_requires=">=3.7", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + "Typing :: Typed", + ], + zip_safe=False, +) diff --git a/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/__init__.py b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/__init__.py new file mode 100644 index 0000000..a608fb6 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/__init__.py @@ -0,0 +1,24 @@ +# ##################################################################################################################### +# 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. # +# ##################################################################################################################### + +from aws_solutions.core.config import Config + +config = Config() + +from aws_solutions.core.helpers import ( + get_aws_region, + get_aws_partition, + get_service_client, + get_service_resource, + get_aws_account, +) 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 new file mode 100644 index 0000000..ea84a01 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/config.py @@ -0,0 +1,75 @@ +# ##################################################################################################################### +# 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 os +import re +from typing import Dict + +import botocore.config + +from aws_solutions.core.logging import get_logger + +logger = get_logger(__name__) + + +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: + def __init__(self, env_var, default: str = "", regex: re.Pattern = None): + self._env_var = env_var + self._regex = regex + self._value = default + + def _get_value_or_default(self) -> str: + if self._value: + return self._value + return os.environ.get(self._env_var) + + def __get__(self, instance, owner) -> str: + value = str(self._get_value_or_default()) + if self._regex and not self._regex.match(value): + raise ValueError( + f"`{value}` received, but environment variable {self._env_var} (or default) must be set and match the pattern {self._regex.pattern}" + ) + return value + + def __set__(self, instance, value) -> None: + self._value = value + + +class Config: + """Stores information about the current solution""" + + id = SolutionConfigEnv("SOLUTION_ID", regex=SOLUTION_ID_RE) + version = SolutionConfigEnv("SOLUTION_VERSION", regex=SOLUTION_VERSION_RE) + _botocore_config = None + + @property + def botocore_config(self) -> botocore.config.Config: + if not self._botocore_config: + self._botocore_config = botocore.config.Config( + **self._botocore_config_defaults + ) + return self._botocore_config + + @botocore_config.setter + def botocore_config(self, other_config: botocore.config.Config): + self._botocore_config = self.botocore_config.merge(other_config) + + @property + def _botocore_config_defaults(self) -> Dict: + return {"user_agent_extra": f"AwsSolution/{self.id}/{self.version}"} diff --git a/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/helpers.py b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/helpers.py new file mode 100644 index 0000000..09272d0 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/helpers.py @@ -0,0 +1,90 @@ +# ##################################################################################################################### +# 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 os +import boto3 +import aws_solutions.core.config + +_helpers_service_clients = {} +_helpers_service_resources = {} +_session = None + +class EnvironmentVariableError(Exception): + pass + +def get_aws_region(): + """ + Get the caller's AWS region from the environment variable AWS_REGION + :return: the AWS region name (e.g. us-east-1) + """ + if region := os.environ.get("AWS_REGION"): + return region + else: + raise EnvironmentVariableError("Missing AWS_REGION environment variable.") + + +def get_aws_partition(): + """ + Get the caller's AWS partion by driving it from AWS region + :return: partition name for the current AWS region (e.g. aws) + """ + region_name = get_aws_region() + china_region_name_prefix = "cn" + us_gov_cloud_region_name_prefix = "us-gov" + aws_us_gov_cloud_regions_partition = "aws-us-gov" + if region_name.startswith(china_region_name_prefix): + return "aws-cn" + elif region_name.startswith(us_gov_cloud_region_name_prefix): + return aws_us_gov_cloud_regions_partition + else: + return "aws" + + +def get_session(): + global _session + if not _session: + _session = boto3.session.Session() + return _session + + +def get_service_client(service_name): + global _helpers_service_clients + config = aws_solutions.core.config.botocore_config + session = get_session() + + if service_name not in _helpers_service_clients: + _helpers_service_clients[service_name] = session.client( + service_name, config=config, region_name=get_aws_region() + ) + return _helpers_service_clients[service_name] + + +def get_service_resource(service_name): + global _helpers_service_resources + config = aws_solutions.core.config.botocore_config + session = get_session() + + if service_name not in _helpers_service_resources: + _helpers_service_resources[service_name] = session.resource( + service_name, config=config, region_name=get_aws_region() + ) + return _helpers_service_resources[service_name] + + +def get_aws_account() -> str: + """ + Get the caller's AWS account ID from STS + :return: the AWS account ID of the caller + """ + sts = get_service_client("sts") + return sts.get_caller_identity().get("Account") diff --git a/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/logging.py b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/logging.py new file mode 100644 index 0000000..269ce90 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_common/aws_solutions/core/logging.py @@ -0,0 +1,58 @@ +# ##################################################################################################################### +# 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 +import os + +DEFAULT_LEVEL = "WARNING" + + +def get_level(): + """ + Get the logging level from the LOG_LEVEL environment variable if it is valid. Otherwise set to WARNING + :return: The logging level to use + """ + valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + requested_level = os.environ.get("LOG_LEVEL", DEFAULT_LEVEL) + + if requested_level and requested_level in valid_levels: + return requested_level + + return DEFAULT_LEVEL + + +def get_logger(name): + """ + Get a configured logger. Compatible with both the AWS Lambda runtime (root logger) and local execution + :param name: The name of the logger (most often __name__ of the calling module) + :return: The logger to use + """ + logger = None + + # first case: running as a lambda function or in pytest with conftest + # second case: running a single test or locally under test + if len(logging.getLogger().handlers) > 0: + logger = logging.getLogger() + logger.setLevel(get_level()) + + # overrides + logging.getLogger("boto3").setLevel(logging.WARNING) + logging.getLogger("botocore").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) + else: + # fmt: off + logging.basicConfig(level=get_level()) # NOSONAR - log level is user-specified; logs to stdout for AWS Lambda + # fmt: on + logger = logging.getLogger(name) + + return logger diff --git a/source/cdk_solution_helper_py/helpers_common/setup.py b/source/cdk_solution_helper_py/helpers_common/setup.py new file mode 100644 index 0000000..6bc3f13 --- /dev/null +++ b/source/cdk_solution_helper_py/helpers_common/setup.py @@ -0,0 +1,63 @@ +# ##################################################################################################################### +# 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 re +from pathlib import Path + +import setuptools + +VERSION_RE = re.compile(r"\#\# \[(?P.*)\]", re.MULTILINE) # NOSONAR + + +def get_version(): + """ + Detect the solution version from the changelog. Latest version on top. + """ + changelog = open(Path(__file__).resolve().parent.parent / "CHANGELOG.md").read() + versions = VERSION_RE.findall(changelog) + if not len(versions): + raise ValueError("use the standard semver format in your CHANGELOG.md") + build_version = versions[0] + print(f"Build Version: {build_version}") + return build_version + + +setuptools.setup( + name="aws-solutions-python", + version=get_version(), + description="Tools to make AWS Solutions deployments with CDK + Python more manageable", + long_description=open("../README.md").read(), + author="Amazon Web Services", + url="https://aws.amazon.com/solutions/implementations", + license="Apache License 2.0", + packages=setuptools.find_namespace_packages(exclude=("build",)), + install_requires=[ + "boto3>=1.17.52", + "pip>=22.3", + ], + python_requires=">=3.7", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + "Typing :: Typed", + ], + zip_safe=False, +) diff --git a/source/cdk_solution_helper_py/requirements-dev.txt b/source/cdk_solution_helper_py/requirements-dev.txt new file mode 100644 index 0000000..585cc06 --- /dev/null +++ b/source/cdk_solution_helper_py/requirements-dev.txt @@ -0,0 +1,17 @@ +aws_cdk_lib>=2.45.0 +black +boto3>=1.17.49 +requests==2.28.1 +crhelper>=2.0.6 +Click +moto +pipenv +cdk-nag==2.18.* +poetry +pytest +pytest-cov>=2.11.1 +pytest-mock>=3.5.1 +tox +tox-pyenv +-e helpers_cdk +-e helpers_common \ No newline at end of file diff --git a/source/images/solution-architecture.jpg b/source/images/solution-architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7634f22bb2da94ea734ad0aea7693a3a25d4e9df GIT binary patch literal 324249 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