├── .coveragerc ├── .gitallowed ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── workflow.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── Makefile.CI ├── Makefile.CodeQuality ├── Makefile.Project ├── Makefile.Puppet ├── Makefile.Test ├── NOTICE ├── README.md ├── docs ├── .gitignore ├── Makefile ├── logo.png ├── make.bat ├── requirements.txt ├── source │ ├── conf.py │ ├── index.rst │ └── puppet │ │ ├── bootstrapping_spokes.rst │ │ ├── caching.rst │ │ ├── contributing.rst │ │ ├── designing_your_manifest.rst │ │ ├── execution_modes.rst │ │ ├── faq.rst │ │ ├── getting_up_and_running.rst │ │ ├── intrinsic_functions.rst │ │ ├── kitchen_sink.yaml │ │ ├── kitchen_sink_example.rst │ │ ├── notifications.md │ │ ├── project_assurance.rst │ │ ├── puppet-getting-started-what-am-i-going-to-install-pipeline.png │ │ ├── puppet-getting-started-what-am-i-going-to-install.png │ │ ├── puppet-getting-started-what-am-i-going-to-install.xml │ │ ├── sharing_a_portfolio.rst │ │ ├── splitting.rst │ │ ├── using_the_cli.rst │ │ ├── using_the_sdk.rst │ │ ├── what_is_this.md │ │ ├── whatisthis.png │ │ └── whatisthis.xml ├── tips.md └── uml │ ├── launch.png │ ├── launch.puml │ ├── share.png │ └── share.puml ├── nose2.cfg ├── poetry.lock ├── pyproject.toml ├── servicecatalog_puppet ├── __init__.py ├── asset_helpers.py ├── asset_helpers_unit_test.py ├── aws.py ├── cli.py ├── commands │ ├── __init__.py │ ├── bootstrap.py │ ├── graph.py │ ├── management.py │ ├── manifest.py │ ├── misc.py │ ├── orgs.py │ ├── show_codebuilds.py │ ├── show_pipelines.py │ ├── spoke_management.py │ ├── task_reference.py │ ├── task_reference_helpers │ │ ├── __init__.py │ │ ├── complete_generator.py │ │ ├── generators │ │ │ ├── __init__.py │ │ │ ├── boto3_parameter_handler.py │ │ │ ├── boto3_parameter_handler_test.py │ │ │ ├── c7n_aws_lambdas.py │ │ │ ├── generator.py │ │ │ ├── imported_portfolios.py │ │ │ ├── imported_portfolios_test.py │ │ │ ├── launches.py │ │ │ ├── organizational_units.py │ │ │ ├── portfolios.py │ │ │ ├── s3_parameter_handler.py │ │ │ ├── service_control_policies.py │ │ │ ├── spoke_local_portfolios.py │ │ │ ├── spoke_local_portfolios_test.py │ │ │ ├── ssm_outputs_handler.py │ │ │ ├── ssm_parameter_handler.py │ │ │ ├── ssm_parameter_handler_test.py │ │ │ ├── stacks.py │ │ │ ├── tag_policies.py │ │ │ └── workspaces.py │ │ └── hub_generator.py │ └── version.py ├── config.py ├── config_unit_test.py ├── constants.py ├── constants_unit_test.py ├── environmental_variables.py ├── example-config-small.yaml ├── macros.py ├── manifest_utils.py ├── manifest_utils_for_expand_unit_test.py ├── manifest_utils_for_explode_unit_test.py ├── manifest_utils_unit_test.py ├── manifests │ ├── manifest-quickstart.yaml │ └── manifest-simple.yaml ├── print_utils.py ├── print_utils_unit_test.py ├── remote_config.py ├── remote_config_unit_test.py ├── schema.yaml ├── sdk.py ├── serialisation_utils.py ├── servicecatalog-puppet-initialiser.template.yaml ├── servicecatalog-puppet-org-master.template.yaml ├── servicecatalog-puppet-regional.template.yaml ├── servicecatalog-puppet-scp-master.template.yaml ├── servicecatalog-puppet-spoke.template.yaml ├── task_reference_constants.py ├── template_builder │ ├── __init__.py │ └── hub │ │ ├── __init__.py │ │ ├── bootstrap.py │ │ ├── bootstrap_region.py │ │ ├── bootstrap_region_unit_test.py │ │ └── bootstrap_unit_test.py ├── templates │ ├── launch_role_constraints.template.yaml.j2 │ ├── policies.template.yaml.j2 │ ├── product.template.yaml.j2 │ ├── static-html-page.html │ └── update_resource_constraints.template.yaml.j2 ├── utils.py ├── viz_template.py ├── waluigi │ ├── __init__.py │ ├── cache_download_client.py │ ├── constants.py │ ├── dag_utils.py │ ├── event_recorder.py │ ├── locks │ │ ├── __init__.py │ │ ├── external.py │ │ └── external_unit_test.py │ ├── processes │ │ ├── __init__.py │ │ └── runner.py │ ├── runner_factory.py │ ├── shared_tasks │ │ ├── __init__.py │ │ ├── task_processing_time.py │ │ ├── task_topological_generations_with_scheduler.py │ │ ├── task_topological_generations_without_scheduler.py │ │ ├── task_topological_generations_without_scheduler_unit_test.py │ │ ├── task_trace.py │ │ └── workers │ │ │ ├── __init__.py │ │ │ └── worker_requiring_scheduler.py │ ├── task_mixins │ │ ├── __init__.py │ │ ├── io_mixin.py │ │ ├── io_mixin_test.py │ │ └── task_executor_mixin.py │ └── threads │ │ ├── __init__.py │ │ └── runner.py └── workflow │ ├── __init__.py │ ├── apps │ ├── __init__.py │ ├── provision_app_task.py │ └── provision_app_task_test.py │ ├── assertions │ ├── __init__.py │ ├── do_assert_task.py │ └── do_assert_task_test.py │ ├── c7n │ ├── __init__.py │ ├── create_custodian_role_task.py │ ├── deploy_c7n_policies.py │ ├── forward_events_for_account_tasks.py │ ├── forward_events_for_region_tasks.py │ └── prepare_c7n_hub_account_task.py │ ├── codebuild_runs │ ├── __init__.py │ ├── do_execute_code_build_run_task.py │ └── do_execute_code_build_run_task_test.py │ ├── dependencies │ ├── __init__.py │ ├── resources_factory.py │ ├── task_factory.py │ └── tasks.py │ ├── general │ ├── __init__.py │ ├── boto3_task.py │ └── terminate_cloudformation_stack_task.py │ ├── generate │ ├── __init__.py │ ├── generate_policies_task.py │ └── generate_policies_task_test.py │ ├── lambda_invocations │ ├── __init__.py │ ├── do_invoke_lambda_task.py │ └── do_invoke_lambda_task_test.py │ ├── launch │ ├── __init__.py │ ├── do_terminate_product_task.py │ ├── do_terminate_product_task_test.py │ ├── provision_product_dry_run_task.py │ ├── provision_product_task.py │ ├── provision_product_task_test.py │ ├── provisioning_artifact_parameters_task.py │ ├── provisioning_artifact_parameters_task_test.py │ ├── run_deploy_in_spoke_task.py │ ├── run_deploy_in_spoke_task_test.py │ ├── terminate_product_dry_run_task.py │ └── terminate_product_dry_run_task_test.py │ ├── management.py │ ├── management_test.py │ ├── manifest │ ├── __init__.py │ ├── generate_manifest_with_ids_task.py │ ├── generate_manifest_with_ids_task_test.py │ └── manifest_mixin.py │ ├── organizational_units │ ├── __init__.py │ └── get_or_create_organizational_unit_task.py │ ├── portfolio │ ├── __init__.py │ ├── accessors │ │ ├── __init__.py │ │ ├── get_all_products_and_their_versions_task.py │ │ └── get_all_products_and_their_versions_task_test.py │ ├── associations │ │ ├── __init__.py │ │ ├── association_utils.py │ │ ├── create_associations_for_spoke_local_portfolio_task.py │ │ ├── create_associations_for_spoke_local_portfolio_task_test.py │ │ ├── terminate_associations_for_spoke_local_portfolio_task.py │ │ └── terminate_associations_for_spoke_local_portfolio_task_test.py │ ├── constraints_management │ │ ├── __init__.py │ │ ├── create_launch_role_constraints_for_spoke_local_portfolio_task.py │ │ ├── create_launch_role_constraints_for_spoke_local_portfolio_task_test.py │ │ ├── create_resource_update_constraints_for_spoke_local_portfolio_task.py │ │ ├── create_resource_update_constraints_for_spoke_local_portfolio_task_test.py │ │ ├── terminate_launch_role_constraints_for_spoke_local_portfolio_task.py │ │ ├── terminate_launch_role_constraints_for_spoke_local_portfolio_task_test.py │ │ ├── terminate_resource_update_constraints_for_spoke_local_portfolio_task.py │ │ └── terminate_resource_update_constraints_for_spoke_local_portfolio_task_test.py │ ├── portfolio_management │ │ ├── __init__.py │ │ ├── copy_into_spoke_local_portfolio_task.py │ │ ├── copy_into_spoke_local_portfolio_task_test.py │ │ ├── create_associations_task.py │ │ ├── create_associations_task_test.py │ │ ├── create_spoke_local_portfolio_task.py │ │ ├── create_spoke_local_portfolio_task_test.py │ │ ├── disassociate_products_from_portfolio_task.py │ │ ├── disassociate_products_from_portfolio_task_test.py │ │ ├── get_portfolio_task.py │ │ ├── get_portfolio_task_test.py │ │ ├── import_into_spoke_local_portfolio_task.py │ │ ├── import_into_spoke_local_portfolio_task_test.py │ │ ├── terminate_associations_task.py │ │ ├── terminate_associations_task_test.py │ │ ├── terminate_spoke_local_portfolio_task.py │ │ └── terminate_spoke_local_portfolio_task_test.py │ └── sharing_management │ │ ├── __init__.py │ │ ├── describe_portfolio_shares_task.py │ │ ├── share_and_accept_portfolio_task.py │ │ ├── share_and_accept_portfolio_task_test.py │ │ ├── share_portfolio_via_orgs_task.py │ │ └── share_portfolio_via_orgs_task_test.py │ ├── runner.py │ ├── runner_test.py │ ├── s3 │ └── get_s3_object_task.py │ ├── service_control_policies │ ├── __init__.py │ ├── do_execute_service_control_policies_task.py │ ├── do_execute_service_control_policies_task_test.py │ ├── do_terminate_service_control_policies_task.py │ ├── do_terminate_service_control_policies_task_test.py │ ├── get_or_create_policy_task.py │ └── get_or_create_policy_task_test.py │ ├── simulate_policies │ ├── __init__.py │ ├── do_execute_simulate_policy_task.py │ └── do_execute_simulate_policy_task_test.py │ ├── ssm │ ├── __init__.py │ ├── get_ssm_parameter_task.py │ ├── get_ssm_parameter_task_test.py │ ├── ssm_outputs_task.py │ └── ssm_outputs_task_test.py │ ├── stack │ ├── __init__.py │ ├── get_cloud_formation_template_from_s3.py │ ├── get_cloud_formation_template_from_s3_test.py │ ├── prepare_account_for_stack_task.py │ ├── provision_stack_dry_run_task.py │ ├── provision_stack_dry_run_task_test.py │ ├── provision_stack_task.py │ ├── provision_stack_task_test.py │ ├── terminate_stack_task.py │ └── terminate_stack_task_test.py │ ├── tag_policies │ ├── __init__.py │ ├── do_execute_tag_policies_task.py │ ├── do_execute_tag_policies_task_test.py │ └── get_or_create_policy_task.py │ ├── task_mixins │ ├── __init__.py │ ├── client_mixin.py │ └── env_var_mixin.py │ ├── tasks.py │ ├── tasks_unit_tests_helper.py │ ├── workflow_utils.py │ └── workspaces │ ├── __init__.py │ ├── prepare_account_for_workspace_task.py │ ├── prepare_account_for_workspace_task_test.py │ ├── provision_dry_run_workspace_task.py │ ├── provision_dry_run_workspace_task_test.py │ ├── provision_workspace_task.py │ ├── provision_workspace_task_test.py │ ├── terminate_dry_run_workspace_task.py │ ├── terminate_dry_run_workspace_task_test.py │ ├── terminate_workspace_task.py │ └── terminate_workspace_task_test.py ├── setup.py ├── test_writer-v2.py ├── test_writer.py └── tests ├── .gitignore ├── continuous-runner ├── .gitignore ├── README.md ├── has_deployed_to.js ├── package-lock.json ├── package.json ├── serverless.yml └── setup_deploy_to.js └── features ├── management ├── bootstrapping-a-puppet-account.feature └── bootstrapping-a-spoke-account.feature └── steps ├── __init__.py ├── constants.py ├── test_givens.py ├── test_thens.py └── test_whens.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = servicecatalog_puppet -------------------------------------------------------------------------------- /.gitallowed: -------------------------------------------------------------------------------- 1 | 432100098765 2 | 012345678910 3 | 009876543210 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.{cmd,[cC][mM][dD]} text eol=crlf 3 | *.{bat,[bB][aA][tT]} text eol=crlf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Attachments** 24 | Please attach a copy of your manifest, expanded-manifest, logs and build artefacts where appropriate. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: CodeCov 2 | on: [push, pull_request] 3 | jobs: 4 | ci: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | python-version: [3.11] 9 | poetry-version: [1.4.2] 10 | os: [ubuntu-20.04] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-python@v2 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Run image 18 | uses: abatilo/actions-poetry@v2.0.0 19 | with: 20 | poetry-version: ${{ matrix.poetry-version }} 21 | # https://github.com/python-poetry/poetry/issues/4511 22 | #- name: setuptools 23 | # run: poetry run pip install 'setuptools==65.5.1' 24 | - name: Install 25 | run: make install 26 | - name: run tests 27 | run: make unit-tests 28 | - name: Upload Coverage to Codecov 29 | uses: codecov/codecov-action@v1 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | output.svg 3 | .idea 4 | *.iml 5 | /ServiceCatalogPuppet/ 6 | aws_service_catalog_puppet.egg-info/ 7 | dist 8 | __pycache__ 9 | /output 10 | .envrc 11 | /ignored 12 | .coverage 13 | /venv 14 | /results 15 | venv-testing 16 | /data 17 | .venv 18 | output/ 19 | results/ 20 | log-* 21 | testing.sh 22 | config.yaml 23 | README.rst 24 | reports 25 | *.log 26 | exploded_results/ 27 | parameters.yaml 28 | htmlcov/ 29 | coverage.xml 30 | .DS_Store 31 | ignored.html 32 | *.html 33 | state1.json 34 | .fleet 35 | GetSSMParamTask.zip 36 | manifest-expanded.yaml 37 | manifest-task-reference.json 38 | resources.json 39 | tasks/ 40 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: make-black 5 | name: make-black 6 | entry: make black 7 | language: system 8 | types: [ python ] 9 | require_serial: true 10 | - id: make-unit-tests 11 | name: make-unit-tests 12 | entry: make unit-tests 13 | language: system 14 | types: [ python ] 15 | require_serial: true 16 | - id: make-prepare-for-testing 17 | name: make-prepare-for-testing 18 | entry: make prepare-for-testing 19 | language: system 20 | types: [ python ] 21 | require_serial: true 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help install pre-build build bump-patch bump-minor bump-major version bootstrap bootstrap-branch expand deploy clean deploy-spoke black pycodestyle 2 | .DEFAULT_GOAL := help 3 | 4 | WS=ignored/testing/$(ENV_NUMBER) 5 | FACTORY_VENV=${WS}/factory 6 | PUPPET_VENV=${WS}/puppet 7 | 8 | include Makefile.CI 9 | include Makefile.Project 10 | include Makefile.Puppet 11 | include Makefile.CodeQuality 12 | include Makefile.Test 13 | 14 | help: help-prefix help-targets 15 | 16 | help-prefix: 17 | @echo Usage: 18 | @echo ' make ' 19 | @echo ' make = ' 20 | @echo '' 21 | @echo Available targets 22 | 23 | HELP_TARGET_MAX_CHAR_NUM = 25 24 | 25 | help-targets: 26 | @awk '/^[a-zA-Z\-\_0-9]+:/ \ 27 | { \ 28 | helpMessage = match(lastLine, /^## (.*)/); \ 29 | if (helpMessage) { \ 30 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 31 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 32 | helpGroup = match(helpMessage, /^@([^ ]*)/); \ 33 | if (helpGroup) { \ 34 | helpGroup = substr(helpMessage, RSTART + 1, index(helpMessage, " ")-2); \ 35 | helpMessage = substr(helpMessage, index(helpMessage, " ")+1); \ 36 | } \ 37 | printf "[ %s| %-$(HELP_TARGET_MAX_CHAR_NUM)s %s\n", \ 38 | helpGroup, helpCommand, helpMessage; \ 39 | } \ 40 | } \ 41 | { lastLine = $$0 }' \ 42 | $(MAKEFILE_LIST) \ 43 | | sort -t'|' -sk1,1 \ 44 | | awk -F '|' ' \ 45 | { \ 46 | cat = $$1; \ 47 | if (cat != lastCat || lastCat == "") { \ 48 | if ( cat == "0" ) { \ 49 | print "Targets:" \ 50 | } else { \ 51 | gsub("_", " ", cat); \ 52 | printf "%s ] \n", cat; \ 53 | } \ 54 | } \ 55 | print " " $$2 \ 56 | } \ 57 | { lastCat = $$1 }' 58 | -------------------------------------------------------------------------------- /Makefile.CI: -------------------------------------------------------------------------------- 1 | .PHONY: install pre-build build 2 | 3 | ## @CI_actions Installs the checked out version of the code to your poetry managed venv 4 | install: 5 | poetry env use 3.11 6 | poetry install 7 | 8 | ## @CI_actions Runs code quality checks 9 | pre-build: unit-tests 10 | rm setup.py || echo "There was no setup.py" 11 | poetry show --no-dev | awk '{print "poetry add "$$1"=="$$2}' | sort | sh 12 | 13 | ## @CI_actions Builds the project into an sdist 14 | build: 15 | poetry build -f sdist 16 | -------------------------------------------------------------------------------- /Makefile.CodeQuality: -------------------------------------------------------------------------------- 1 | .PHONY: black pycodestyle importanize 2 | 3 | ## @Code_quality Runs black on the checked out code 4 | black: 5 | poetry run black servicecatalog_puppet 6 | 7 | ## @Code_quality Runs importanize on the checked out code 8 | importanize: 9 | poetry run importanize servicecatalog_puppet 10 | 11 | ## @Code_quality Runs pycodestyle on the the checked out code 12 | pycodestyle: 13 | poetry run pycodestyle --statistics -qq servicecatalog_puppet 14 | 15 | pre-commit: importanize black unit-tests prepare-for-testing 16 | -------------------------------------------------------------------------------- /Makefile.Project: -------------------------------------------------------------------------------- 1 | .PHONY: bump-patch bump-minor bump-major clean prepare-for-testing 2 | 3 | ## @Project_setup Increment patch number 4 | bump-patch: 5 | poetry version patch 6 | 7 | ## @Project_setup Increment minor number 8 | bump-minor: 9 | poetry version minor 10 | 11 | ## @Project_setup Increment major number 12 | bump-major: 13 | poetry version major 14 | 15 | ## @Project_setup Cleans up after a build 16 | clean: 17 | rm -rf data results output config.yaml 18 | rm -rf dist aws-service-catalog-puppet-* aws_service_catalog_puppet.egg-info/ reports 19 | rm -rf ignored/src/ServiceCatalogPuppet/tasks 20 | 21 | ## @Project_setup Generates a setup.py so you can test bootstrapped branches in AWS Codecommit 22 | prepare-for-testing: build 23 | tar -zxvf dist/aws_service_catalog_puppet-$$(poetry version -s).tar.gz -C dist aws_service_catalog_puppet-$$(poetry version -s)/setup.py 24 | cp dist/aws_service_catalog_puppet-$$(poetry version -s)/setup.py setup.py 25 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Service Catalog Puppet Framework 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-service-catalog-puppet 2 | 3 | ![logo](./docs/logo.png) 4 | 5 | ## Badges 6 | 7 | [![codecov](https://codecov.io/gh/awslabs/aws-service-catalog-puppet/branch/master/graph/badge.svg?token=e8M7mdsmy0)](https://codecov.io/gh/awslabs/aws-service-catalog-puppet) 8 | 9 | 10 | ## What is it? 11 | This is a python3 framework that makes it easier to share multi region AWS Service Catalog portfolios and makes it 12 | possible to provision products into accounts declaratively using a metadata based rules engine. 13 | 14 | With this framework you define your accounts in a YAML file. You give each account a set of tags, a default region and 15 | a set of enabled regions. 16 | 17 | Once you have done this you can define portfolios should be shared with each set of accounts using the tags and you 18 | can specify which regions the shares occur in. 19 | 20 | In addition to this, you can also define products that should be provisioned into accounts using the same tag based 21 | approach. The framework will assume role into the target account and provision the product on your behalf. 22 | 23 | 24 | ## Getting started 25 | 26 | You can read the [installation how to](https://service-catalog-tools-workshop.com/30-how-tos/10-installation/30-service-catalog-puppet.html) 27 | or you can read through the [every day use](https://service-catalog-tools-workshop.com/30-how-tos/50-every-day-use.html) 28 | guides. 29 | 30 | You can read the [documentation](https://aws-service-catalog-puppet.readthedocs.io/en/latest/) to understand the inner 31 | workings. 32 | 33 | 34 | ## Going further 35 | 36 | The framework is one of a pair. The other is [aws-service-catalog-factory](https://github.com/awslabs/aws-service-catalog-factory). 37 | With Service Catalog Factory you can create pipelines that deploy multi region portfolios very easily. 38 | 39 | ## License 40 | 41 | This library is licensed under the Apache 2.0 License. 42 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/docs/logo.png -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==2.0.0 2 | recommonmark==0.5.0 3 | setuptools==70.0.0 4 | aws-service-catalog-puppet 5 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../..')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'aws-service-catalog-puppet' 21 | copyright = '2019, Eamonn Faherty' 22 | author = 'Eamonn Faherty' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | 31 | master_doc = 'index' 32 | 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.napoleon', 36 | 'sphinx.ext.coverage', 37 | 'sphinx.ext.viewcode', 38 | 'recommonmark', 39 | 'sphinx.ext.autosectionlabel', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # List of patterns, relative to source directory, that match files and 46 | # directories to ignore when looking for source files. 47 | # This pattern also affects html_static_path and html_extra_path. 48 | exclude_patterns = [] 49 | 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | # The theme to use for HTML and HTML Help pages. See the documentation for 54 | # a list of builtin themes. 55 | # 56 | html_theme = 'classic' 57 | 58 | # Add any paths that contain custom static files (such as style sheets) here, 59 | # relative to this directory. They are copied after the builtin static files, 60 | # so a file named "default.css" will overwrite the builtin "default.css". 61 | html_static_path = ['_static'] 62 | 63 | 64 | source_suffix = { 65 | '.rst': 'restructuredtext', 66 | '.txt': 'markdown', 67 | '.md': 'markdown', 68 | } 69 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to aws-service-catalog-puppets's documentation! 2 | ======================================================= 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | :glob: 7 | 8 | puppet/what_is_this 9 | puppet/getting_up_and_running 10 | puppet/designing_your_manifest 11 | puppet/bootstrapping_spokes 12 | puppet/sharing_a_portfolio 13 | puppet/execution_modes 14 | puppet/notifications 15 | puppet/caching 16 | puppet/intrinsic_functions 17 | puppet/splitting 18 | puppet/upgrading 19 | puppet/faq 20 | puppet/kitchen_sink_example 21 | 22 | puppet/using_the_cli 23 | puppet/using_the_sdk 24 | puppet/project_assurance 25 | puppet/contributing 26 | -------------------------------------------------------------------------------- /docs/source/puppet/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | General Advice 5 | -------------- 6 | 7 | If you are planning on building a feature please raise a github issue describing the requirement to ensure someone else 8 | is not already building the same capability. 9 | 10 | When you raise the github issue please explain how you are going to architect the capability. There are some pointers 11 | here on design principals but it would be good to get some visibility on what you are planning sooner rather than later. 12 | 13 | 14 | Building locally 15 | ---------------- 16 | 17 | You can build and run this framework locally. There is a Makefile to make this easier for you. In order to run the 18 | Make targets you will need to have python poetry and python 3.11 installed. We recommend using pipx to install poetry. 19 | 20 | Running ``make help`` will display a description of the targets along with a description of what they do. 21 | -------------------------------------------------------------------------------- /docs/source/puppet/getting_up_and_running.rst: -------------------------------------------------------------------------------- 1 | Getting up and running 2 | ====================== 3 | 4 | ServiceCatalog-Puppet runs in your AWS Account. In order for you to install it into your account you can use the 5 | aws-service-catalog-puppet cli. This is distributed via [PyPi](https://pypi.org/project/aws-service-catalog-puppet/) 6 | 7 | 8 | What am I going to install? 9 | --------------------------- 10 | 11 | Once you have completed the install you will have the following pipeline in your account: 12 | 13 | 14 | .. image:: ./puppet-getting-started-what-am-i-going-to-install-pipeline.png 15 | 16 | 17 | using the following services: 18 | 19 | .. image:: ./puppet-getting-started-what-am-i-going-to-install.png 20 | 21 | 22 | 23 | Before you install 24 | ------------------ 25 | 26 | You should consider which account will be the home for your puppet. This account will contain the AWS CodePipelines 27 | and will need to be accessible to any accounts you would like to share with. If you are using ServiceCatalog-Factory, 28 | we recommend you install both tools into the same account. 29 | 30 | 31 | Installing 32 | ------------------- 33 | 34 | see https://service-catalog-tools-workshop.com/installation.html 35 | -------------------------------------------------------------------------------- /docs/source/puppet/intrinsic_functions.rst: -------------------------------------------------------------------------------- 1 | Intrinsic functions 2 | =================== 3 | 4 | ------------------------------------- 5 | What are intrinsic functions 6 | ------------------------------------- 7 | 8 | .. note:: 9 | 10 | The first intrinsic function was added in version 0.115.0 11 | 12 | Intrinsic functions allow you to specify a token in the manifest.yaml file and have it replaced with a value generated 13 | by the tools at runtime. The aim of them is to make your manifest.yaml file more portable across environments and to 14 | make your life managing the manifest.yaml file easier. 15 | 16 | 17 | PuppetAccountId 18 | --------------- 19 | 20 | .. note:: 21 | 22 | This was added in version 0.115.0 23 | 24 | You can use the token ${AWS::PuppetAccountId} anywhere in your manifest files. When you do so it will be replaced 25 | with the puppet account id you are using. This replacement occurs in the expand phase and the deploy phase is unaware 26 | of the notation. When using the PuppetAccountId intrinsic function we recommend ensuring it is string by surrounding 27 | it in double quotes - this avoids issues where the account id may be passed as a number later on. 28 | 29 | Here is an example usage of it in the parameters section: 30 | 31 | .. code-block:: yaml 32 | 33 | schema: puppet-2019-04-01 34 | parameters: 35 | PuppetAccountId1: 36 | default: "${AWS::PuppetAccountId}" 37 | OrganizationAccountAccessRole: 38 | default: OrganizationAccountAccessRole 39 | 40 | And here is an example in a stack: 41 | 42 | .. code-block:: yaml 43 | 44 | stacks: 45 | networking-stack: 46 | name: networking-stack 47 | version: v1 48 | parameters: 49 | PuppetAccountId: 50 | default: "${AWS::PuppetAccountId}" 51 | deploy_to: 52 | tags: 53 | - regions: default_region 54 | tag: role:puppethub 55 | -------------------------------------------------------------------------------- /docs/source/puppet/notifications.md: -------------------------------------------------------------------------------- 1 | Notifications 2 | ============= 3 | 4 | You can listen to the AWS CloudFormation stack events from your product provisioning. 5 | 6 | This is the recommended way of discovering provisioning errors. 7 | 8 | When you bootstraped your account you will have created an AWS SQS Queue: 9 | ```servicecatalog-puppet-cloudformation-events``` in your default region. 10 | 11 | You will also have SNS Topics in each region configured to push events to this queue: 12 | ```servicecatalog-puppet-cloudformation-regional-events``` 13 | 14 | Please note this will only receive notifications for products provisioned using 15 | ServiceCatalog-Puppet - any self service vending from AWS Service Catalog will not 16 | publish to this queue. 17 | 18 | You should handle consuming the queue and have your own error handling code. -------------------------------------------------------------------------------- /docs/source/puppet/project_assurance.rst: -------------------------------------------------------------------------------- 1 | Project Assurance 2 | ================= 3 | 4 | Assurance 5 | --------- 6 | 7 | This project has been through an assurance process to ensure the project is: 8 | 9 | - valuable to AWS customers 10 | - properly licenced 11 | 12 | 13 | The same process ensures that there are mechanisms to ensure maintainers are: 14 | 15 | - likely able to acceptably support it with regards to being responsive to github issues and pull requests 16 | 17 | 18 | And finally, at the time of publishing: 19 | 20 | - any 3rd party components actually contained in the repo are checked to ensure they are correctly licensed and that we are correctly complying with the open source licenses that apply to those 3rd party components. 21 | 22 | 23 | Project Management 24 | ------------------ 25 | 26 | Quality Assurance 27 | ~~~~~~~~~~~~~~~~~ 28 | 29 | CICD Process 30 | ^^^^^^^^^^^^ 31 | 32 | Unit tests are run on every commit. If unit tests fail a release of the project cannot occur. 33 | 34 | The project dependencies are scanned on each commit for known vulnerabilities. If an issue is discovered a release of the project cannot occur. 35 | 36 | 37 | Review Process 38 | ^^^^^^^^^^^^^^ 39 | 40 | There are regular reviews of the source code where static analysis results and unit test coverage are assessed. 41 | 42 | 43 | Raising a feature request 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | Product feature requests drive the majority of changes to this project. If you would like to raise a feature request 47 | please raise a Github issue. 48 | 49 | 50 | Backwards compatibility 51 | ~~~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | All changes to date have been fully backwards compatible. Effort will be made to ensure this where possible. 54 | 55 | 56 | Design consultation 57 | ~~~~~~~~~~~~~~~~~~~ 58 | 59 | When there is a significant addition or change to the internal implementation we consult a limited number of users. 60 | Users are asked to access the potential impact so that we can understand the impact and the potential value of the 61 | change. If you would like to register as such a user please raise a Github issue. 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/source/puppet/puppet-getting-started-what-am-i-going-to-install-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/docs/source/puppet/puppet-getting-started-what-am-i-going-to-install-pipeline.png -------------------------------------------------------------------------------- /docs/source/puppet/puppet-getting-started-what-am-i-going-to-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/docs/source/puppet/puppet-getting-started-what-am-i-going-to-install.png -------------------------------------------------------------------------------- /docs/source/puppet/puppet-getting-started-what-am-i-going-to-install.xml: -------------------------------------------------------------------------------- 1 | 7VrRbqM4FP2aPE4FGELy2KTtzkpdqVJGO7tPlQMOeMdgZJxJsl+/12AIYKdNp8mk7U4btfiCjX3uOfb1dUZonm1/E7hI/+AxYSPPibcjdDPyPHfquPBPWXa1JXTD2pAIGuuH9oYF/Zdoo6OtaxqTsveg5JxJWvSNEc9zEsmeDQvBN/3HVpz131rghBiGRYSZaf1KY5nW1kng7O2fCU3S5s2uo+9kuHlYG8oUx3zTMaHbEZoLzmV9lW3nhCnwGlzqencH7rYdEySXx1TIHt1rurotUhz6j86fn799yb98cid1M98xW+sRz9YUMKq7LHcNDtD7Ql2uM3YncAaXs01KJVkUOFL2DbgfbKnMGJRcuCyl4N/InDMuqibQajWdIgR39CuJkGR7cDBuCxFwi/CMSLGDR3SFT+OxhlXzyvODurzZeylonkm7HproB7FmRtI2vgcPLjR+L8DSGxtYrmDsnkNwlKp/OV4yAtA6giSU52fFuIHBHYeqSgMAcOQ0+KPpAP/AxN8LQwv+wbnwdw3473AkedXt6yjiaxjaEPKC01xWHQlm8IExzZ1RAHfmqnTlBQPDsBz2Da5ZUm30DcNy2De4w+bdwfvdYQc7BqPUa94ZvN/pdBA+aMbXktEcCNVMpoouicAxBV40NMt5Tvo0tLF0xXOpp3PXa8oaeNVqQ/Zsm6iV4wpvSv8qEXxdVK/8HSZ0691HuHyMGF/Hj5jJJzWwoowNOq1IT2F2v2Y0Ue1Lrl6HdYmRVdUijITmyX1VukGO7n3nFfB7p5g5i3GZklgP6RS6mvh9XTXrYEdWrfR6smqMJ5cVMmS1IOI7BV97zhxLzHgCV63UBgKzMuoQmgeYZvGw+hl4+KBbGp/f4yVhD7ykktbconHMbKRYcil51uFFBH0i4mWcdwacd/fEUqQ6pIBC8HgdyZr+s8KqgwiirIhnGZWnIp3bJ93YJN3ExjnvXJwzV9JfnLs85wpaEAXsqUK4cPq2WBe+jHWOPVL+xb7zsG9ZoX0a6gXBG6NeMwF3uGcQC5qBXe8g9GogLSUWgDyWxCSJU/3YaaXvnGQVcSY9UJE3vTI3BciCKzobrOaewIQ1hg2/LnIhU57wHLPbvbUL90HhkDy+VukG1UhB8tqilTF5EnqAV+z+0sqtCn+rAkCnizfb7s2bXS/aVL3vOazkaxGRI6hmeNZzgEMJkU/V9ewUEIRhSb/3e2Lzpq76oLZdTywFyB9sEuue6WoDUrT9eAVPglPwBLYnedxOwvtn7rnaZVT8+YdIudO0wGvJ++w64FKLp573snesS/1XuvR1AvUM4GFvl6sVd+SNq/3dUsBVUu3L6kEPHbOH/dAq1YEYi0jD71vW5XamHMygavGNzj+Dhv5ABp45fzZK6c6f/rnmz5Zu71YG/pEy6KRkLyED35ABjLa0y6AASFecUV5+YCmM0VAKviWYmPxUMUzfuRia6PV5Mbx2mX8dzmYsnJCcCBXbeg6jpYoc+EqBUG8darKVsMcp1ehSrCw5qVLtAKfnLFXFSBBowdyvfRzNBM0JRKOZ5qytoxjXtcTfQXguV5rHS+9dQ8fGVV54UQ1Z4qqK/2C7/rqAv7bUhqGnDywWNEYDsfimWByLWMb+uXxm5p8GPpvzmDw0STh1mC2q+Y78z1znoSPmOcf7mfOcuX9c2Dcrb+0cPRz34yzbObrrWMKs852jI3P9752jP0/1d3WQPumfvXgTy0F6YDtId8/mgIss0m0erC3oPJjfGg5kwqrSAxEUhq+S1vX5skqIDlJyle2OKjR0xTZppxPvYOncP2HcgI5OsU0uGTc03fzhPLTKel40Cx325YSQ5QD9NCloKO6/vVVnIvffgUO3/wE= -------------------------------------------------------------------------------- /docs/source/puppet/using_the_sdk.rst: -------------------------------------------------------------------------------- 1 | Using the SDK 2 | ============= 3 | 4 | .. note:: 5 | 6 | This was added in 0.18.0 7 | 8 | Service Catalog Puppet includes a published SDK. You can make use of the python functions available: 9 | 10 | .. code-block:: python 11 | 12 | from servicecatalog_puppet import sdk 13 | 14 | 15 | The functions available are: 16 | 17 | Functions 18 | --------- 19 | 20 | .. automodule:: servicecatalog_puppet.sdk 21 | :members: 22 | -------------------------------------------------------------------------------- /docs/source/puppet/what_is_this.md: -------------------------------------------------------------------------------- 1 | What is this? 2 | ============= 3 | 4 | Service Catalog Puppet is a framework that enables you to provision service 5 | catalog products into accounts that need to have them. You declare your service 6 | catalog products and the accounts you want them to be available in via a 7 | configuration file. Service Catalog Puppet then walks through this configuration 8 | file and determines which products need to be made available in which accounts. 9 | You can use tags or account numbers to indicate which products should be 10 | available in which accounts. For example if using tags for both accounts and 11 | products, products tagged dev will be made available in accounts tagged dev. 12 | 13 | The framework works through your lists, dedupes and spots collisions and 14 | then provisions the products into your AWS accounts for you. It handles Service 15 | Portfolio sharing, accepting Portfolio shares and can provision products cross 16 | account and cross region. 17 | 18 | 19 | ### High level architecture diagram 20 | 21 | ![What is this](./whatisthis.png) 22 | 23 | You use an AWS CodeBuild project in a central _puppet_ account that provisions AWS 24 | Service Catalog Products into _spoke_ accounts on your behalf. The framework 25 | takes care of cross account sharing and cross region product replication for 26 | you. 27 | -------------------------------------------------------------------------------- /docs/source/puppet/whatisthis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/docs/source/puppet/whatisthis.png -------------------------------------------------------------------------------- /docs/source/puppet/whatisthis.xml: -------------------------------------------------------------------------------- 1 | 2 | 7VrLbts6EP0abwNKsmVr6ThJWyAFgnpRdFUwEi3xltIIFBXb/fqSFqkX5SRA5Dg3cLKIOeLznDOjGcYTb5XuvnCcJ98hImziomg38W4mrutM0UL+UZZ9ZZl7qDLEnEa6U2NY079EG023kkak6HQUAEzQvGsMIctIKDo2zDlsu902wLqr5jgmlmEdYmZbf9JIJJV1MUON/SuhcWJWdpB+kmLTWRuKBEewbZm824m34gCi+pTuVoQp8Awu1bi7I0/rjXGSidcM8KoBT5iV+mx6X2JvDiu3mKuP/IDkdYSLhKjRjmwUgsMfsgIG/NDZQ2ge3PjyyYYyZuwZZESZIBOaS2eq292hi5u5tGNG40zaQnkGIh9eJyJlekG1+gMW0p4dLC5SM9mn1kA8ES7IrmXSKHwhkBLB97KLeYqCaoiWpGs0um0IdgPNWtIi1zXcYi2quJ67wV1+0NAP0+BaNPwgMYXMIgNKwWgmETfCRvL4MccRJQ2YGu8WbNuECrLOcaim2Uqn7NHhmrZeSc1qeE93sfLiK7wtplcxhzI/LPlNOtfg09+82vqAOhbzwAmOqENRRaWLLTX7AvKWFhjZKPEV8gg0i+8PrRsP2Sqql+jodAyBuD2BuAMCmQ4IxPFHEMjUEsjX8lEalmEIpTxTXyfyUKIrgS4ZxiVtHizv6xOT0ihSywyKSgogiw6wo9P4pTO3YQ+cIbd8O+izS3DU6vO7JPgzW/qzU8VG32JhnUtc1ayfXfx93D0bdwedSPzzi/iPkGCSpndR/+Ki/vqNG9jAn0z+A3InkSwCdBO4SCCGDLPbxtqDoMXAf0SIvVY4LgVIUzPDPahU5/k8pYCSh3ofGhWBeUzqHKOyqS0+Cy0nDAv61K1jhnA6DF1yjvetDjnQTBStmR+UocWYqeOMqzheG/MX+ztT1OOo2kHDWH2U15Ho/a9InH0KEk11PxaJgRUBlz/X0rCCiDzQnKh6yKJ5sE7qvVbk751a8lj9ZL3CNoefXuxsT1S/AVEreN7jR8IeoKCCQieI9qPrIwgB6QuvuZcqOXNOo3GnqZpkyztW1+UcojIUVVF3nQ9Wd6EEPDeAjxLh3X6En9nJ/WKopHJGiPDOxw0OjjsQHdDY0eHVQNm3E7+W3+8t9Iak2c5Cerp7xAUNryIIy5SYNOZtanK7YvLtdMEfEtMYaZoz/bhiQraYgrNpCT0TzVeQptROaC+x/DSxPKzgHueaZnrGSF6r/gM638x2Pvd8kdy+0aq8b034Ew1VVbnCAjOIL074Dk5YVKj/DjXm41TNQf/OyH4Nni6nWnxcT/QHcqr52TzRvtW8eOIn98TgPYsb+/pW51kMyugOeIrFq/+/eBHX23ItBfmmhnwUbfkuOpu23KM5/EVbn1Fbp8wgZLP5skt1Odh8Zci7/Qc= -------------------------------------------------------------------------------- /docs/tips.md: -------------------------------------------------------------------------------- 1 | ## export all env vars from a codebuild build id in your local 2 | aws codebuild batch-get-builds --ids servicecatalog-puppet-deploy-in-spoke:0568a289-ebb6-4189-9bc0-5d6a4ff4879d | jq -r '.builds[0].environment.environmentVariables[]| "export \(.name)=\"\(.value)\""' 3 | 4 | -------------------------------------------------------------------------------- /docs/uml/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/docs/uml/launch.png -------------------------------------------------------------------------------- /docs/uml/launch.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | alt GettingIdsForAPIs 4 | SCT -> ServiceCatalog: list_accepted_portfolio_shares() 5 | SCT -> ServiceCatalog: list_portfolios() 6 | SCT -> ServiceCatalog: search_products_as_admin() 7 | SCT -> ServiceCatalog: list_provisioning_artifacts() 8 | SCT -> ServiceCatalog: list_launch_paths() 9 | end 10 | 11 | alt CheckingForPreviousProvision 12 | SCT -> ServiceCatalog: search_provisioned_products() 13 | SCT -> ServiceCatalog: describe_provisioned_product() 14 | end 15 | 16 | alt IfPreviousProvisioningNeedsTerminating 17 | SCT -> ServiceCatalog: terminate_provisioned_product() 18 | SCT -> ServiceCatalog: describe_record() 19 | end 20 | 21 | alt CalculateParametersForProvisioning 22 | SCT -> CloudFormation: get_template_summary() 23 | SCT -> CloudFormation: describe_stacks() 24 | end 25 | 26 | alt IfUsingProductPlans 27 | SCT -> ServiceCatalog: list_provisioned_product_plans() 28 | SCT -> ServiceCatalog: delete_provisioned_product_plan() 29 | SCT -> ServiceCatalog: create_provisioned_product_plan() 30 | SCT -> ServiceCatalog: describe_provisioned_product_plan() 31 | SCT -> ServiceCatalog: execute_provisioned_product_plan() 32 | SCT -> ServiceCatalog: describe_provisioned_product() 33 | else NotUsingProductPlans 34 | alt IsAnUpdate 35 | SCT -> ServiceCatalog: update_provisioned_product() 36 | else 37 | SCT -> ServiceCatalog: provision_product() 38 | end 39 | end 40 | @enduml -------------------------------------------------------------------------------- /docs/uml/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/docs/uml/share.png -------------------------------------------------------------------------------- /docs/uml/share.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | alt GettingIdsForAPIs 4 | SCT -> ServiceCatalog: list_accepted_portfolio_shares() 5 | SCT -> ServiceCatalog: list_portfolios() 6 | end 7 | 8 | alt CreateSpokePortfolio 9 | SCT -> ServiceCatalog: create_portfolio 10 | SCT -> CloudFormation: create stack using AWS::ServiceCatalog::PortfolioPrincipalAssociation resources 11 | end 12 | 13 | alt AddProducts 14 | alt IfUsingImports 15 | alt InHub 16 | SCT -> ServiceCatalog: search_products_as_admin 17 | SCT -> ServiceCatalog: list_provisioning_artifacts 18 | end 19 | alt InSpoke 20 | SCT -> ServiceCatalog: search_products_as_admin 21 | SCT -> ServiceCatalog: list_provisioning_artifacts 22 | SCT -> ServiceCatalog: associate_product_with_portfolio 23 | end 24 | 25 | else IfUsingCopy 26 | alt InHub 27 | SCT -> ServiceCatalog: search_products_as_admin 28 | SCT -> ServiceCatalog: list_provisioning_artifacts 29 | end 30 | 31 | alt InSpoke 32 | SCT -> ServiceCatalog: search_products_as_admin 33 | SCT -> ServiceCatalog: list_provisioning_artifacts 34 | SCT -> ServiceCatalog: describe_copy_product_status 35 | SCT -> ServiceCatalog: associate_product_with_portfolio 36 | SCT -> ServiceCatalog: update_provisioning_artifact 37 | end 38 | end 39 | end 40 | 41 | alt AddLaunchRoleConstraints 42 | SCT -> ServiceCatalog: search_products_as_admin 43 | SCT -> CloudFormation: create stack using AWS::ServiceCatalog::LaunchRoleConstraint resources 44 | end 45 | 46 | 47 | @enduml -------------------------------------------------------------------------------- /nose2.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | start-dir = servicecatalog_puppet 3 | test-file-pattern = *_test.py 4 | plugins = nose2.plugins.junitxml 5 | 6 | [junit-xml] 7 | always-on = True 8 | keep_restricted = False 9 | path = reports/junit/junit.xml 10 | test_fullname = True 11 | 12 | [coverage] 13 | always-on = True 14 | coverage = servicecatalog_puppet 15 | coverage-report = term html 16 | coverage-config = .coveragerc 17 | 18 | [parameters] 19 | always-on = True -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | [tool.poetry] 5 | name = "aws-service-catalog-puppet" 6 | version = "0.255.0" 7 | description = "Making it easier to deploy ServiceCatalog products" 8 | classifiers = ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Natural Language :: English"] 9 | homepage = "https://service-catalog-tools-workshop.com/" 10 | readme = "README.md" 11 | repository = "https://github.com/awslabs/aws-service-catalog-puppet-framework" 12 | authors = ["Eamonn Faherty "] 13 | packages = [ 14 | { include = "servicecatalog_puppet", from = "." }, 15 | ] 16 | include = ["servicecatalog_puppet"] 17 | 18 | [tool.poetry.scripts] 19 | servicecatalog-puppet = "servicecatalog_puppet.cli:cli" 20 | 21 | [tool.poetry.urls] 22 | issues = "https://github.com/awslabs/aws-service-catalog-puppet-framework/issues" 23 | 24 | [tool.poetry.dev-dependencies] 25 | codecov = "^2.1.7" 26 | behave = "^1.2.6" 27 | pylint = "^2.5.3" 28 | black = "24.10.0" 29 | pycodestyle = "^2.6.0" 30 | nose2 = "^0.10.0" 31 | 32 | [tool.poetry.group.dev.dependencies] 33 | viztracer = "^0.15.4" 34 | importanize = "^0.7.0" 35 | codecov = "^2.1.13" 36 | 37 | [tool.dephell.main] 38 | versioning = "semver" 39 | from = {format = "poetrylock", path = "poetry.lock"} 40 | envs = ["main"] 41 | to = {format = "poetry", path = "pyproject.toml"} 42 | 43 | [tool.poetry.dependencies] 44 | python = "^3.11" 45 | requests = "2.32.3" 46 | pyyaml = "6.0.1" 47 | jinja2 = "3.1.4" 48 | click = "8.1.7" 49 | boto3 = "1.35.88" 50 | better-boto = "0.42.0" 51 | terminaltables = "==3.1.0" 52 | luigi = "3.6.0" 53 | cfn-flip = "==1.2.3" 54 | networkx = "2.6.3" 55 | troposphere = "4.8.3" 56 | awacs = "2.0.2" 57 | jmespath = "1.0.1" 58 | deepdiff = "^5.3.0" 59 | deepmerge = "^0.2.1" 60 | yamale = "^3.0.8" 61 | colorama = "^0.4.5" 62 | orjson = "^3.8.0" 63 | MarkupSafe = "2.0.1" 64 | 65 | [tool.poetry.build] 66 | generate-setup-file = true 67 | -------------------------------------------------------------------------------- /servicecatalog_puppet/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/asset_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import os 5 | 6 | 7 | def resolve_from_site_packages(what): 8 | return os.path.sep.join([os.path.dirname(os.path.abspath(__file__)), what]) 9 | 10 | 11 | def read_from_site_packages(what): 12 | return open(resolve_from_site_packages(what), "r").read() 13 | -------------------------------------------------------------------------------- /servicecatalog_puppet/asset_helpers_unit_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import os 4 | from unittest import mock as mocker 5 | from unittest.mock import call 6 | 7 | 8 | def test_resolve_from_site_packages(): 9 | # setup 10 | from servicecatalog_puppet import asset_helpers as sut 11 | 12 | expected_result = os.path.sep.join( 13 | [os.path.dirname(os.path.abspath(__file__)), "foo"] 14 | ) 15 | 16 | # exercise 17 | actual_result = sut.resolve_from_site_packages("foo") 18 | 19 | # verify 20 | assert actual_result == expected_result 21 | 22 | 23 | @mocker.patch("builtins.open", new_callable=mocker.MagicMock()) 24 | def test_read_from_site_packages(mocked_open): 25 | # setup 26 | from servicecatalog_puppet import asset_helpers as sut 27 | 28 | expected_param = os.path.sep.join( 29 | [os.path.dirname(os.path.abspath(__file__)), "foo"] 30 | ) 31 | 32 | # exercise 33 | sut.read_from_site_packages("foo") 34 | 35 | # verify 36 | assert mocked_open.call_count == 1 37 | assert mocked_open.call_args == call(expected_param, "r") 38 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/graph.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from servicecatalog_puppet import serialisation_utils 4 | 5 | 6 | colors = dict() 7 | 8 | default_color = "#88000022" 9 | 10 | 11 | def generate_node(task_reference, task): 12 | reference = task_reference.replace("-", "_") 13 | section_name = task.get("section_name") 14 | account_id = task.get("account_id") 15 | region = task.get("region") 16 | return dict( 17 | color=colors.get(section_name, default_color), 18 | label=f""" 19 | 20 | 21 | 22 | 23 |
{task_reference}
section_name {section_name}
account_id {account_id}
region {region}
""", 24 | ) 25 | 26 | 27 | def escape(input): 28 | return input.replace("-", "_").replace("/", "__") 29 | 30 | 31 | def generate_edge(task_reference, dependency_by_reference): 32 | return f"{escape(task_reference)} -> {escape(dependency_by_reference)}" 33 | 34 | 35 | def graph(content_file_path): 36 | content = open(content_file_path.name, "r").read() 37 | reference = serialisation_utils.load(content) 38 | all_tasks = reference.get("all_tasks") 39 | 40 | nodes = dict() 41 | edges = list() 42 | 43 | for task_reference, task in all_tasks.items(): 44 | nodes[task_reference] = generate_node(task_reference, task) 45 | for dependency_by_reference in task.get("dependencies_by_reference", []): 46 | edges.append(generate_edge(task_reference, dependency_by_reference)) 47 | 48 | nodes_as_a_graph = "" 49 | for task_reference, node in nodes.items(): 50 | reference = escape(task_reference) 51 | nodes_as_a_graph += f"""{reference} [ 52 | color="{node.get('color')}" 53 | label=<{node.get('label')}> 54 | shape=plain 55 | ] 56 | """ 57 | 58 | for e in edges: 59 | nodes_as_a_graph += e + "\n" 60 | 61 | return f"""digraph workflow {{ 62 | {nodes_as_a_graph} 63 | }}""" 64 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/generators/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/generators/organizational_units.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import copy 4 | import os 5 | 6 | from servicecatalog_puppet import constants 7 | 8 | 9 | def handle_organizational_units( 10 | all_tasks, 11 | all_tasks_task_reference, 12 | item_name, 13 | puppet_account_id, 14 | section_name, 15 | task_reference, 16 | task_to_add, 17 | ): 18 | if not task_to_add.get("parent_ou_id"): 19 | # parent may not exist as it wasn't specified 20 | parent_path = os.path.dirname(task_to_add.get("path")) 21 | if parent_path != task_to_add.get("path"): 22 | parent_task_reference = f"{constants.ORGANIZATIONAL_UNITS}_{parent_path.replace('/', '%2F')}_{task_to_add.get('account_id')}_{task_to_add.get('region')}" 23 | if not all_tasks.get(parent_task_reference): 24 | parent_task_to_add = copy.deepcopy(task_to_add) 25 | parent_task_to_add["task_reference"] = parent_task_reference 26 | parent_task_to_add["path"] = parent_path 27 | if parent_path == "/": 28 | parent_task_to_add["name"] = parent_path 29 | else: 30 | parent_task_to_add["name"] = os.path.basename(parent_path) 31 | all_tasks[parent_task_reference] = parent_task_to_add 32 | handle_organizational_units( 33 | all_tasks, 34 | all_tasks_task_reference, 35 | item_name, 36 | puppet_account_id, 37 | section_name, 38 | parent_task_reference, 39 | parent_task_to_add, 40 | ) 41 | task_to_add["parent_ou_task_ref"] = parent_task_reference 42 | task_to_add["dependencies_by_reference"].append(parent_task_reference) 43 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/generators/portfolios.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from servicecatalog_puppet import constants, task_reference_constants 4 | 5 | 6 | def get_or_create_describe_portfolio_shares_task_ref( 7 | all_tasks, puppet_account_id, sharing_type, portfolio_task_ref, task_to_add 8 | ): 9 | describe_portfolio_shares_task_ref = ( 10 | f"{constants.DESCRIBE_PORTFOLIO_SHARES}-{sharing_type}-{portfolio_task_ref}" 11 | ) 12 | if not all_tasks.get(describe_portfolio_shares_task_ref): 13 | all_tasks[describe_portfolio_shares_task_ref] = { 14 | "section_name": constants.DESCRIBE_PORTFOLIO_SHARES, 15 | "task_reference": describe_portfolio_shares_task_ref, 16 | "account_id": puppet_account_id, 17 | "region": task_to_add.get("region"), 18 | "type": sharing_type, 19 | "portfolio_task_reference": portfolio_task_ref, 20 | "dependencies_by_reference": [portfolio_task_ref], 21 | task_reference_constants.MANIFEST_SECTION_NAMES: dict(), 22 | task_reference_constants.MANIFEST_ITEM_NAMES: dict(), 23 | task_reference_constants.MANIFEST_ACCOUNT_IDS: dict(), 24 | } 25 | task = all_tasks[describe_portfolio_shares_task_ref] 26 | task[task_reference_constants.MANIFEST_SECTION_NAMES].update( 27 | task_to_add.get(task_reference_constants.MANIFEST_SECTION_NAMES) 28 | ) 29 | task[task_reference_constants.MANIFEST_ITEM_NAMES].update( 30 | task_to_add.get(task_reference_constants.MANIFEST_ITEM_NAMES) 31 | ) 32 | task[task_reference_constants.MANIFEST_ACCOUNT_IDS].update( 33 | task_to_add.get(task_reference_constants.MANIFEST_ACCOUNT_IDS) 34 | ) 35 | 36 | return describe_portfolio_shares_task_ref 37 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/generators/service_control_policies.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from servicecatalog_puppet import constants, task_reference_constants 4 | 5 | 6 | def handle_service_control_policies( 7 | all_tasks, 8 | all_tasks_task_reference, 9 | item_name, 10 | puppet_account_id, 11 | section_name, 12 | task_reference, 13 | task_to_add, 14 | ): 15 | get_or_create_policy_ref = ( 16 | f"{constants.GET_OR_CREATE_SERVICE_CONTROL_POLICIES_POLICY}-{item_name}" 17 | ) 18 | if not all_tasks.get(get_or_create_policy_ref): 19 | all_tasks[get_or_create_policy_ref] = { 20 | "task_reference": get_or_create_policy_ref, 21 | "execution": "hub", 22 | "account_id": puppet_account_id, 23 | "region": task_to_add.get("region"), 24 | "policy_name": task_to_add.get("service_control_policy_name"), 25 | "policy_description": task_to_add.get("description"), 26 | "policy_content": task_to_add.get("content"), 27 | "dependencies_by_reference": list(), 28 | task_reference_constants.MANIFEST_SECTION_NAMES: dict(), 29 | task_reference_constants.MANIFEST_ITEM_NAMES: dict(), 30 | task_reference_constants.MANIFEST_ACCOUNT_IDS: dict(), 31 | "section_name": constants.GET_OR_CREATE_SERVICE_CONTROL_POLICIES_POLICY, 32 | } 33 | all_tasks[all_tasks_task_reference][ 34 | "get_or_create_policy_ref" 35 | ] = get_or_create_policy_ref 36 | all_tasks[all_tasks_task_reference]["dependencies_by_reference"].append( 37 | get_or_create_policy_ref 38 | ) 39 | all_tasks[get_or_create_policy_ref][ 40 | task_reference_constants.MANIFEST_SECTION_NAMES 41 | ].update(task_to_add.get(task_reference_constants.MANIFEST_SECTION_NAMES)) 42 | all_tasks[get_or_create_policy_ref][ 43 | task_reference_constants.MANIFEST_ITEM_NAMES 44 | ].update(task_to_add.get(task_reference_constants.MANIFEST_ITEM_NAMES)) 45 | all_tasks[get_or_create_policy_ref][ 46 | task_reference_constants.MANIFEST_ACCOUNT_IDS 47 | ].update(task_to_add.get(task_reference_constants.MANIFEST_ACCOUNT_IDS)) 48 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/generators/tag_policies.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from servicecatalog_puppet import constants, task_reference_constants 4 | 5 | 6 | def handle_tag_policies( 7 | all_tasks, 8 | all_tasks_task_reference, 9 | item_name, 10 | puppet_account_id, 11 | section_name, 12 | task_reference, 13 | task_to_add, 14 | ): 15 | get_or_create_policy_ref = ( 16 | f"{constants.GET_OR_CREATE_TAG_POLICIES_POLICY}-{item_name}" 17 | ) 18 | if not all_tasks.get(get_or_create_policy_ref): 19 | all_tasks[get_or_create_policy_ref] = { 20 | "task_reference": get_or_create_policy_ref, 21 | "execution": "hub", 22 | "account_id": puppet_account_id, 23 | "region": task_to_add.get("region"), 24 | "policy_name": task_to_add.get("tag_policy_name"), 25 | "policy_description": task_to_add.get("description"), 26 | "policy_content": task_to_add.get("content"), 27 | "dependencies_by_reference": list(), 28 | task_reference_constants.MANIFEST_SECTION_NAMES: dict(), 29 | task_reference_constants.MANIFEST_ITEM_NAMES: dict(), 30 | task_reference_constants.MANIFEST_ACCOUNT_IDS: dict(), 31 | "section_name": constants.GET_OR_CREATE_TAG_POLICIES_POLICY, 32 | } 33 | all_tasks[all_tasks_task_reference][ 34 | "get_or_create_policy_ref" 35 | ] = get_or_create_policy_ref 36 | all_tasks[all_tasks_task_reference]["dependencies_by_reference"].append( 37 | get_or_create_policy_ref 38 | ) 39 | all_tasks[get_or_create_policy_ref][ 40 | task_reference_constants.MANIFEST_SECTION_NAMES 41 | ].update(task_to_add.get(task_reference_constants.MANIFEST_SECTION_NAMES)) 42 | all_tasks[get_or_create_policy_ref][ 43 | task_reference_constants.MANIFEST_ITEM_NAMES 44 | ].update(task_to_add.get(task_reference_constants.MANIFEST_ITEM_NAMES)) 45 | all_tasks[get_or_create_policy_ref][ 46 | task_reference_constants.MANIFEST_ACCOUNT_IDS 47 | ].update(task_to_add.get(task_reference_constants.MANIFEST_ACCOUNT_IDS)) 48 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/task_reference_helpers/generators/workspaces.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from servicecatalog_puppet import constants, task_reference_constants 4 | 5 | 6 | def handle_workspaces( 7 | all_tasks, 8 | all_tasks_task_reference, 9 | item_name, 10 | puppet_account_id, 11 | section_name, 12 | task_reference, 13 | task_to_add, 14 | ): 15 | workspace_account_preparation_ref = ( 16 | f"{constants.WORKSPACE_ACCOUNT_PREPARATION}-{task_to_add.get('account_id')}" 17 | ) 18 | if all_tasks.get(workspace_account_preparation_ref) is None: 19 | all_tasks[workspace_account_preparation_ref] = { 20 | "puppet_account_id": puppet_account_id, 21 | "task_reference": workspace_account_preparation_ref, 22 | "dependencies_by_reference": [constants.CREATE_POLICIES], 23 | "account_id": task_to_add.get("account_id"), 24 | "section_name": constants.WORKSPACE_ACCOUNT_PREPARATION, 25 | task_reference_constants.MANIFEST_SECTION_NAMES: dict(), 26 | task_reference_constants.MANIFEST_ITEM_NAMES: dict(), 27 | task_reference_constants.MANIFEST_ACCOUNT_IDS: dict(), 28 | } 29 | all_tasks[workspace_account_preparation_ref][ 30 | task_reference_constants.MANIFEST_SECTION_NAMES 31 | ].update(task_to_add.get(task_reference_constants.MANIFEST_SECTION_NAMES)) 32 | all_tasks[workspace_account_preparation_ref][ 33 | task_reference_constants.MANIFEST_ITEM_NAMES 34 | ].update(task_to_add.get(task_reference_constants.MANIFEST_ITEM_NAMES)) 35 | all_tasks[workspace_account_preparation_ref][ 36 | task_reference_constants.MANIFEST_ACCOUNT_IDS 37 | ].update(task_to_add.get(task_reference_constants.MANIFEST_ACCOUNT_IDS)) 38 | if workspace_account_preparation_ref not in all_tasks[all_tasks_task_reference].get( 39 | "dependencies_by_reference" 40 | ): 41 | all_tasks[all_tasks_task_reference]["dependencies_by_reference"].append( 42 | workspace_account_preparation_ref 43 | ) 44 | -------------------------------------------------------------------------------- /servicecatalog_puppet/commands/version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import click 5 | import pkg_resources 6 | from betterboto import client as betterboto_client 7 | 8 | 9 | def version(): 10 | click.echo( 11 | "cli version: {}".format( 12 | pkg_resources.require("aws-service-catalog-puppet")[0].version 13 | ) 14 | ) 15 | with betterboto_client.ClientContextManager("ssm") as ssm: 16 | response = ssm.get_parameter(Name="service-catalog-puppet-regional-version") 17 | click.echo( 18 | "regional stack version: {} for region: {}".format( 19 | response.get("Parameter").get("Value"), 20 | response.get("Parameter").get("ARN").split(":")[3], 21 | ) 22 | ) 23 | response = ssm.get_parameter(Name="service-catalog-puppet-version") 24 | click.echo( 25 | "stack version: {}".format( 26 | response.get("Parameter").get("Value"), 27 | ) 28 | ) 29 | -------------------------------------------------------------------------------- /servicecatalog_puppet/environmental_variables.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | EXECUTOR_ACCOUNT_ID = "SCT_EXECUTOR_ACCOUNT_ID" 5 | DRIFT_TOKEN = "SCT_DRIFT_TOKEN" 6 | RUN_TOKEN = "SCT_RUN_TOKEN" 7 | EXECUTION_MODE = "SCT_EXECUTION_MODE" 8 | PUPPET_ACCOUNT_ID = "SCT_PUPPET_ACCOUNT_ID" 9 | SINGLE_ACCOUNT_ID = "SCT_SINGLE_ACCOUNT_ID" 10 | NUM_WORKERS = "SCT_NUM_WORKERS" 11 | SHOULD_DELETE_ROLLBACK_COMPLETE_STACKS = "SCT_SHOULD_DELETE_ROLLBACK_COMPLETE_STACKS" 12 | SHOULD_USE_PRODUCT_PLANS = "SCT_SHOULD_USE_PRODUCT_PLANS" 13 | INITIALISER_STACK_TAGS = "SCT_INITIALISER_STACK_TAGS" 14 | VERSION = "SCT_VERSION" 15 | HOME_REGION = "SCT_HOME_REGION" 16 | REGIONS = "SCT_REGIONS" 17 | SHOULD_USE_SNS = "SCT_SHOULD_USE_SNS" 18 | SHOULD_FORWARD_EVENTS_TO_EVENTBRIDGE = "SCT_SHOULD_FORWARD_EVENTS_TO_EVENTBRIDGE" 19 | SHOULD_FORWARD_FAILURES_TO_OPSCENTER = "SCT_SHOULD_FORWARD_FAILURES_TO_OPSCENTER" 20 | OUTPUT_CACHE_STARTING_POINT = "SCT_OUTPUT_CACHE_STARTING_POINT" 21 | IS_CACHING_ENABLED = "SCT_IS_CACHING_ENABLED" 22 | GLOBAL_SHARING_MODE = "SCT_GLOBAL_SHARING_MODE" 23 | GLOBAL_SHARE_TAG_OPTIONS = "SCT_GLOBAL_SHARE_TAG_OPTIONS" 24 | ON_COMPLETE_URL = "SCT_ON_COMPLETE_URL" 25 | SPOKE_EXECUTION_MODE_DEPLOY_ENV = "SCT_SPOKE_EXECUTION_MODE_DEPLOY_ENV" 26 | SCHEDULER_THREADS_OR_PROCESSES = "SCT_SCHEDULER_THREADS_OR_PROCESSES" 27 | SCHEDULER_ALGORITHM = "SCT_SCHEDULER_ALGORITHM" 28 | SPOKE_SCHEDULER_THREADS_OR_PROCESSES = "SCT_SPOKE_SCHEDULER_THREADS_OR_PROCESSES" 29 | SPOKE_SCHEDULER_ALGORITHM = "SCT_SPOKE_SCHEDULER_ALGORITHM" 30 | GLOBAL_SHARE_PRINCIPALS = "SCT_GLOBAL_SHARE_PRINCIPALS" 31 | AWS_STS_REGIONAL_ENDPOINTS = "AWS_STS_REGIONAL_ENDPOINTS" 32 | -------------------------------------------------------------------------------- /servicecatalog_puppet/example-config-small.yaml: -------------------------------------------------------------------------------- 1 | regions: [ 2 | 'eu-west-1', 3 | 'eu-west-2', 4 | 'eu-west-3' 5 | ] 6 | should_collect_cloudformation_events: true 7 | should_forward_events_to_eventbridge: true 8 | should_forward_failures_to_opscenter: true 9 | -------------------------------------------------------------------------------- /servicecatalog_puppet/macros.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | def get_accounts_for_path(client, path): 4 | ou = client.convert_path_to_ou(path) 5 | response = client.list_children_nested(ParentId=ou, ChildType="ACCOUNT") 6 | return ",".join([r.get("Id") for r in response]) 7 | 8 | 9 | macros = {"get_accounts_for_path": get_accounts_for_path} 10 | -------------------------------------------------------------------------------- /servicecatalog_puppet/manifests/manifest-simple.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | schema: puppet-2019-04-01 4 | 5 | accounts: 6 | - account_id: '' 7 | name: '' 8 | default_region: eu-west-1 9 | regions_enabled: 10 | - eu-west-1 11 | - eu-west-2 12 | tags: 13 | - type:prod 14 | - partition:eu 15 | - scope:pci 16 | 17 | launches: 18 | account-iam-for-prod: 19 | portfolio: example-simple-central-it-team-portfolio 20 | product: account-iam 21 | version: v1 22 | parameters: 23 | RoleName: 24 | default: DevAdmin 25 | Path: 26 | default: /human-roles/ 27 | deploy_to: 28 | tags: 29 | - tag: type:prod 30 | regions: default_region 31 | -------------------------------------------------------------------------------- /servicecatalog_puppet/print_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import click 4 | 5 | 6 | def echo(message): 7 | click.secho( 8 | message, 9 | ) 10 | 11 | 12 | def warn(message): 13 | click.secho( 14 | message, 15 | err=True, 16 | fg="yellow", 17 | ) 18 | 19 | 20 | def error(message): 21 | click.secho( 22 | message, 23 | err=True, 24 | fg="red", 25 | ) 26 | -------------------------------------------------------------------------------- /servicecatalog_puppet/print_utils_unit_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from unittest import mock 4 | 5 | 6 | @mock.patch("click.secho") 7 | def test_echo(mocked_click_secho): 8 | # setup 9 | from servicecatalog_puppet import print_utils 10 | 11 | message = "hello world" 12 | 13 | # exercise 14 | print_utils.echo(message) 15 | 16 | # verify 17 | mocked_click_secho.assert_called_once_with(message) 18 | 19 | 20 | @mock.patch("click.secho") 21 | def test_warn(mocked_click_secho): 22 | from servicecatalog_puppet import print_utils 23 | 24 | message = "hello world" 25 | 26 | # exercise 27 | print_utils.warn(message) 28 | 29 | # verify 30 | mocked_click_secho.assert_called_once_with( 31 | message, 32 | err=True, 33 | fg="yellow", 34 | ) 35 | 36 | 37 | @mock.patch("click.secho") 38 | def test_error(mocked_click_secho): 39 | from servicecatalog_puppet import print_utils 40 | 41 | message = "hello world" 42 | 43 | # exercise 44 | print_utils.error(message) 45 | 46 | # verify 47 | mocked_click_secho.assert_called_once_with( 48 | message, 49 | err=True, 50 | fg="red", 51 | ) 52 | -------------------------------------------------------------------------------- /servicecatalog_puppet/servicecatalog-puppet-org-master.template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | AWSTemplateFormatVersion: "2010-09-09" 4 | Description: | 5 | Bootstrap template used to bootstrap the org master for ServiceCatalog-Puppet use 6 | {"version": "{{ VERSION }}", "framework": "servicecatalog-puppet", "role": "bootstrap-org-master"} 7 | 8 | Parameters: 9 | PuppetAccountId: 10 | Type: String 11 | MinLength: 12 12 | MaxLength: 12 13 | Version: 14 | Type: String 15 | Default: "{{ VERSION }}" 16 | 17 | Resources: 18 | Param: 19 | Type: AWS::SSM::Parameter 20 | Properties: 21 | Name: !Sub "service-catalog-puppet-org-master-version-${PuppetAccountId}" 22 | Type: String 23 | Value: !Ref Version 24 | Tags: 25 | "ServiceCatalogPuppet:Actor": "Framework" 26 | 27 | PuppetOrgRoleForExpands: 28 | Type: AWS::IAM::Role 29 | Properties: 30 | RoleName: !Sub "PuppetOrgRoleForExpands${PuppetAccountId}" 31 | Path: /servicecatalog-puppet/ 32 | Policies: 33 | - PolicyName: "allowExpands" 34 | PolicyDocument: 35 | Version: "2012-10-17" 36 | Statement: 37 | - Effect: "Allow" 38 | Action: 39 | - organizations:ListRoots 40 | - organizations:DescribeAccount 41 | - organizations:ListOrganizationalUnitsForParent 42 | - organizations:ListChildren 43 | - organizations:ListTagsForResource 44 | Resource: "*" 45 | 46 | AssumeRolePolicyDocument: 47 | Version: "2012-10-17" 48 | Statement: 49 | - Effect: "Allow" 50 | Principal: 51 | AWS: !Sub "arn:${AWS::Partition}:iam::${PuppetAccountId}:root" 52 | Action: 53 | - "sts:AssumeRole" 54 | 55 | Outputs: 56 | PuppetOrgRoleForExpandsArn: 57 | Value: !GetAtt PuppetOrgRoleForExpands.Arn 58 | 59 | Version: 60 | Value: !GetAtt Param.Value 61 | -------------------------------------------------------------------------------- /servicecatalog_puppet/servicecatalog-puppet-regional.template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | AWSTemplateFormatVersion: "2010-09-09" 4 | Description: | 5 | Bootstrap template used to bootstrap a region of ServiceCatalog-Puppet master 6 | {"version": "{{ VERSION }}", "framework": "servicecatalog-puppet", "role": "bootstrap-master-region"} 7 | 8 | Parameters: 9 | Version: 10 | Type: String 11 | Default: "{{ VERSION }}" 12 | DefaultRegionValue: 13 | Type: String 14 | 15 | Resources: 16 | DefaultRegionParam: 17 | Type: AWS::SSM::Parameter 18 | Properties: 19 | Name: /servicecatalog-puppet/home-region 20 | Type: String 21 | Value: !Ref DefaultRegionValue 22 | Tags: 23 | "ServiceCatalogPuppet:Actor": "Framework" 24 | 25 | Param: 26 | Type: AWS::SSM::Parameter 27 | Properties: 28 | Name: service-catalog-puppet-regional-version 29 | Type: String 30 | Value: !Ref Version 31 | Tags: 32 | "ServiceCatalogPuppet:Actor": "Framework" 33 | 34 | PipelineArtifactBucket: 35 | Type: AWS::S3::Bucket 36 | Properties: 37 | BucketName: !Sub "sc-puppet-pipeline-artifacts-${AWS::AccountId}-${AWS::Region}" 38 | VersioningConfiguration: 39 | Status: Enabled 40 | BucketEncryption: 41 | ServerSideEncryptionConfiguration: 42 | - ServerSideEncryptionByDefault: 43 | SSEAlgorithm: AES256 44 | PublicAccessBlockConfiguration: 45 | BlockPublicAcls: TRUE 46 | BlockPublicPolicy: TRUE 47 | IgnorePublicAcls: TRUE 48 | RestrictPublicBuckets: TRUE 49 | Tags: 50 | - Key: "ServiceCatalogPuppet:Actor" 51 | Value: "Framework" 52 | 53 | RegionalProductTopic: 54 | Type: AWS::SNS::Topic 55 | Properties: 56 | DisplayName: servicecatalog-puppet-cloudformation-regional-events 57 | TopicName: servicecatalog-puppet-cloudformation-regional-events 58 | Subscription: 59 | - Endpoint: !Sub "arn:${AWS::Partition}:sqs:${DefaultRegionValue}:${AWS::AccountId}:servicecatalog-puppet-cloudformation-events" 60 | Protocol: "sqs" 61 | 62 | Outputs: 63 | Version: 64 | Value: !GetAtt Param.Value 65 | 66 | RegionalProductTopic: 67 | Value: !Ref RegionalProductTopic 68 | -------------------------------------------------------------------------------- /servicecatalog_puppet/servicecatalog-puppet-scp-master.template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | AWSTemplateFormatVersion: "2010-09-09" 4 | Description: | 5 | Bootstrap template used to bootstrap the org master for ServiceCatalog-Puppet use 6 | {"version": "{{ VERSION }}", "framework": "servicecatalog-puppet", "role": "bootstrap-org-master"} 7 | 8 | Parameters: 9 | PuppetAccountId: 10 | Type: String 11 | MinLength: 12 12 | MaxLength: 12 13 | Version: 14 | Type: String 15 | Default: "{{ VERSION }}" 16 | 17 | Resources: 18 | Param: 19 | Type: AWS::SSM::Parameter 20 | Properties: 21 | Name: !Sub "service-catalog-puppet-scp-master-version-${PuppetAccountId}" 22 | Type: String 23 | Value: !Ref Version 24 | Tags: 25 | "ServiceCatalogPuppet:Actor": "Framework" 26 | 27 | PuppetOrgRoleForExpands: 28 | Type: AWS::IAM::Role 29 | Properties: 30 | RoleName: !Sub "PuppetOrgRoleForSCP${PuppetAccountId}" 31 | Path: /servicecatalog-puppet/ 32 | Policies: 33 | - PolicyName: "allowExpands" 34 | PolicyDocument: 35 | Version: "2012-10-17" 36 | Statement: 37 | - Effect: "Allow" 38 | Action: 39 | - organizations:ListRoots 40 | - organizations:DescribeAccount 41 | - organizations:ListOrganizationalUnitsForParent 42 | - organizations:ListChildren 43 | 44 | - organizations:CreatePolicy 45 | - organizations:AttachPolicy 46 | - organizations:DetachPolicy 47 | - organizations:ListPoliciesForTarget 48 | - organizations:ListPolicies 49 | - organizations:DescribePolicy 50 | - organizations:UpdatePolicy 51 | 52 | - organizations:TagResource 53 | 54 | - organizations:DescribeOrganizationalUnit 55 | - organizations:CreateOrganizationalUnit 56 | Resource: "*" 57 | 58 | AssumeRolePolicyDocument: 59 | Version: "2012-10-17" 60 | Statement: 61 | - Effect: "Allow" 62 | Principal: 63 | AWS: !Sub "arn:${AWS::Partition}:iam::${PuppetAccountId}:root" 64 | Action: 65 | - "sts:AssumeRole" 66 | 67 | Outputs: 68 | PuppetOrgRoleForExpandsArn: 69 | Value: !GetAtt PuppetOrgRoleForExpands.Arn 70 | 71 | Version: 72 | Value: !GetAtt Param.Value 73 | -------------------------------------------------------------------------------- /servicecatalog_puppet/task_reference_constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | 5 | MANIFEST_SECTION_NAMES = "manifest_section_names" 6 | MANIFEST_ITEM_NAMES = "manifest_item_names" 7 | MANIFEST_ACCOUNT_IDS = "manifest_account_ids" 8 | -------------------------------------------------------------------------------- /servicecatalog_puppet/template_builder/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/template_builder/hub/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/template_builder/hub/bootstrap_region_unit_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/template_builder/hub/bootstrap_unit_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | 5 | def test_codecommit_provider(): 6 | # setup 7 | from . import bootstrap 8 | 9 | puppet_version = "0.10.0" 10 | all_regions = [ 11 | "us-east-1", 12 | "us-east-2", 13 | ] 14 | repo_name = "TestRepoName" 15 | repo_branch = "mainly" 16 | source = dict( 17 | Provider="CodeCommit", 18 | Configuration=dict(RepositoryName=repo_name, BranchName=repo_branch), 19 | ) 20 | is_caching_enabled = False 21 | is_manual_approvals = False 22 | scm_skip_creation_of_repo = False 23 | should_validate = False 24 | 25 | # exercise 26 | actual_result = bootstrap.get_template( 27 | puppet_version, 28 | all_regions, 29 | source, 30 | is_caching_enabled, 31 | is_manual_approvals, 32 | scm_skip_creation_of_repo, 33 | should_validate, 34 | ) 35 | 36 | source_stage = actual_result.resources.get("Pipeline").Stages[0] 37 | action = source_stage.Actions[0] 38 | 39 | # verify 40 | assert source_stage.Name == "Source" 41 | assert action.ActionTypeId.Provider == "CodeCommit" 42 | assert actual_result.resources.get("CodeRepo").RepositoryName == repo_name 43 | assert action.Configuration.get("BranchName") == repo_branch 44 | -------------------------------------------------------------------------------- /servicecatalog_puppet/templates/launch_role_constraints.template.yaml.j2: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Launch role contraints for {{portfolio.DisplayName}} 3 | 4 | Conditions: 5 | NoOpCondition: !Equals [ true, false] 6 | 7 | Resources: 8 | NoOpResource: 9 | Type: AWS::S3::Bucket 10 | Description: Resource to ensure that template contains a resource even when there are no shares 11 | Condition: NoOpCondition 12 | 13 | {% for launch_constraint in launch_constraints %}{% for role_arn in launch_constraint.roles %}{% for product in launch_constraint.products %} 14 | #{{ product }} 15 | L{{ portfolio_id|replace("-", "") }}B{{ product_name_to_id_dict.get(product)|replace("-", "") }}C{{ role_arn.split(":")[-1].replace('/','').replace("-", "") }}: 16 | Type: AWS::ServiceCatalog::LaunchRoleConstraint 17 | Properties: 18 | PortfolioId: {{ portfolio_id }} 19 | ProductId: {{ product_name_to_id_dict.get(product) }} 20 | RoleArn: !Sub "{{ role_arn }}"{% endfor %}{% endfor %}{% endfor %} 21 | 22 | 23 | -------------------------------------------------------------------------------- /servicecatalog_puppet/templates/product.template.yaml.j2: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | AWSTemplateFormatVersion: "2010-09-09" 4 | Description: deployment of product {{ launch_details.product }} {{ launch_details.version }} 5 | 6 | Resources: 7 | CloudFormationProvisionedProduct: 8 | Type: AWS::ServiceCatalog::CloudFormationProvisionedProduct 9 | Properties: 10 | ProductId: {{ launch_details.product_id }} 11 | ProvisioningArtifactId: {{ launch_details.provisioning_artifact_id }} 12 | ProvisionedProductName: {{ launch_details.launch_name }} 13 | -------------------------------------------------------------------------------- /servicecatalog_puppet/templates/static-html-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{title}} 4 | 49 | 50 | {{content}} 51 | 52 | -------------------------------------------------------------------------------- /servicecatalog_puppet/templates/update_resource_constraints.template.yaml.j2: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: update resource constraints for {{portfolio}} 3 | 4 | Conditions: 5 | NoOpCondition: !Equals [ true, false] 6 | 7 | Resources: 8 | NoOpResource: 9 | Type: AWS::S3::Bucket 10 | Description: Resource to ensure that template contains a resource even when there are no shares 11 | Condition: NoOpCondition 12 | 13 | {% for update_resource_constraint in update_resource_constraints %}{% for product in update_resource_constraint.products %} 14 | #{{ product }} 15 | L{{ portfolio_id|replace("-", "") }}B{{ product_name_to_id_dict.get(product)|replace("-", "") }}C: 16 | Type: AWS::ServiceCatalog::ResourceUpdateConstraint 17 | Properties: 18 | PortfolioId: {{ portfolio_id }} 19 | ProductId: {{ product_name_to_id_dict.get(product) }} 20 | Description: "TagUpdate = {{update_resource_constraint.get("tag_update_on_provisioned_product")}}" 21 | TagUpdateOnProvisionedProduct: {{update_resource_constraint.get("tag_update_on_provisioned_product")}}{% endfor %}{% endfor %} 22 | 23 | 24 | -------------------------------------------------------------------------------- /servicecatalog_puppet/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import jinja2 4 | 5 | from servicecatalog_puppet.asset_helpers import resolve_from_site_packages 6 | 7 | 8 | TEMPLATE_DIR = resolve_from_site_packages("templates") 9 | 10 | 11 | def slugify_for_cloudformation_stack_name(raw) -> str: 12 | return raw.replace("_", "-") 13 | 14 | 15 | ENV = jinja2.Environment( 16 | loader=jinja2.FileSystemLoader(TEMPLATE_DIR), 17 | extensions=["jinja2.ext.do"], 18 | ) 19 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/cache_download_client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import functools 5 | 6 | from betterboto import client as betterboto_client 7 | 8 | from servicecatalog_puppet import config 9 | 10 | 11 | @functools.lru_cache(maxsize=10) 12 | def get_cache_download_client(): 13 | return betterboto_client.CrossAccountClientContextManager( 14 | "s3", 15 | config.get_cache_download_role_arn(config.get_executor_account_id()), 16 | "s3-client", 17 | ) 18 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | COMPLETED = "COMPLETED" 5 | NOT_SET = "NOT_SET" 6 | ERRORED = "ERRORED" 7 | QUEUE_STATUS = "QUEUE_STATUS" 8 | IN_PROGRESS = "IN_PROGRESS" 9 | BLOCKED = "BLOCKED" 10 | RESOURCES_REQUIRED = "resources_required" 11 | 12 | CONTROL_EVENT__COMPLETE = "CONTROL_EVENT__COMPLETE" 13 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/dag_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import logging 5 | 6 | import networkx as nx 7 | 8 | from servicecatalog_puppet import constants 9 | from servicecatalog_puppet.waluigi.constants import ( 10 | COMPLETED, 11 | ERRORED, 12 | NOT_SET, 13 | QUEUE_STATUS, 14 | ) 15 | 16 | 17 | logger = logging.getLogger(constants.PUPPET_SCHEDULER_LOGGER_NAME) 18 | 19 | 20 | def build_the_dag(tasks_to_run: dict): 21 | g = nx.DiGraph() 22 | # print("-- BUILDING THE DAG!!!") 23 | for uid, task in tasks_to_run.items(): 24 | g.add_nodes_from( 25 | [ 26 | (uid, task), 27 | ] 28 | ) 29 | for duid in task.get("dependencies_by_reference", []): 30 | if tasks_to_run.get(duid): 31 | g.add_edge(uid, duid) 32 | else: 33 | logger.debug( 34 | f"{duid} is not in the task reference - this is fine when running in spoke execution mode and when the task was executed within the hub" 35 | ) 36 | 37 | for uid, task in tasks_to_run.items(): 38 | if task.get(QUEUE_STATUS, NOT_SET) == COMPLETED: 39 | try: 40 | g.remove_node(uid) 41 | except nx.exception.NetworkXError as e: 42 | pass 43 | 44 | elif task.get(QUEUE_STATUS, NOT_SET) == ERRORED: 45 | # print( 46 | # f"looking at task {uid} with status {task.get(QUEUE_STATUS, NOT_SET)}" 47 | # ) 48 | for n in nx.ancestors(g, uid): 49 | try: 50 | g.remove_node(n) 51 | except nx.exception.NetworkXError as e: 52 | pass 53 | try: 54 | g.remove_node(uid) 55 | except nx.exception.NetworkXError as e: 56 | pass 57 | 58 | return g 59 | 60 | 61 | def make_readable_in_codebuild_logs(input): 62 | numbers = "zero one two three four five six seven eight nine".split() 63 | numbers.extend("ten eleven twelve thirteen fourteen fifteen sixteen".split()) 64 | numbers.extend("seventeen eighteen nineteen".split()) 65 | numbers.extend( 66 | tens if ones == "zero" else (tens + "-" + ones) 67 | for tens in "twenty thirty forty fifty sixty seventy eighty ninety".split() 68 | for ones in numbers[0:10] 69 | ) 70 | return numbers[input] 71 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/event_recorder.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import json 5 | import os 6 | from datetime import datetime 7 | from pathlib import Path 8 | 9 | from servicecatalog_puppet import constants 10 | 11 | 12 | def record_event(event_type, task, extra_event_data=None): 13 | task_type = task.__class__.__name__ 14 | task_params = task.param_kwargs 15 | current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 16 | 17 | event = { 18 | "event_type": event_type, 19 | "task_type": task_type, 20 | "task_params": task_params, 21 | "params_for_results": task.params_for_results_display(), 22 | "datetime": current_datetime, 23 | "pid": os.getpid(), 24 | } 25 | if extra_event_data is not None: 26 | event.update(extra_event_data) 27 | 28 | with open( 29 | Path(constants.RESULTS_DIRECTORY) 30 | / event_type 31 | / f"{task_type}-{task.task_id}.json", 32 | "w", 33 | ) as f: 34 | f.write( 35 | json.dumps( 36 | event, 37 | default=str, 38 | indent=4, 39 | ) 40 | ) 41 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/locks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/locks/external.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from servicecatalog_puppet import serialisation_utils 5 | from servicecatalog_puppet.waluigi.constants import RESOURCES_REQUIRED 6 | from servicecatalog_puppet.waluigi.dag_utils import logger 7 | 8 | 9 | def are_resources_are_free_for_task(task_parameters: dict, resources_file_path: str): 10 | with open(resources_file_path, "rb") as f: 11 | resources_in_use = serialisation_utils.json_loads(f.read()) 12 | return are_resources_are_free_for_task_dict(task_parameters, resources_in_use) 13 | 14 | 15 | def are_resources_are_free_for_task_dict(task_parameters, resources_in_use): 16 | return ( 17 | all( 18 | resources_in_use.get(r, False) is False 19 | for r in task_parameters.get(RESOURCES_REQUIRED, []) 20 | ), 21 | resources_in_use, 22 | ) 23 | 24 | 25 | def lock_resources_for_task( 26 | task_reference: str, 27 | task_parameters: dict, 28 | resources_in_use: dict, 29 | resources_file_path: str, 30 | ): 31 | # print(f"Worker locking {task_reference}") 32 | for r in task_parameters.get(RESOURCES_REQUIRED, []): 33 | resources_in_use[r] = task_reference 34 | with open(resources_file_path, "wb") as f: 35 | f.write(serialisation_utils.json_dumps(resources_in_use)) 36 | 37 | 38 | def unlock_resources_for_task(task_parameters: dict, resources_file_path: str): 39 | with open(resources_file_path, "rb") as f: 40 | resources_in_use = serialisation_utils.json_loads(f.read()) 41 | for r in task_parameters.get(RESOURCES_REQUIRED, []): 42 | try: 43 | del resources_in_use[r] 44 | except KeyError: 45 | logger.warn( 46 | f"{task_parameters.get('task_reference')} tried to unlock {r} but it wasn't present" 47 | ) 48 | with open(resources_file_path, "wb") as f: 49 | f.write(serialisation_utils.json_dumps(resources_in_use)) 50 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/processes/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/runner_factory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from servicecatalog_puppet.waluigi.processes import runner as processes_runner 5 | from servicecatalog_puppet.waluigi.threads import runner as threads_runner 6 | 7 | 8 | def get_runner(threads_or_processes: str): 9 | if threads_or_processes == "threads": 10 | return threads_runner 11 | elif threads_or_processes == "processes": 12 | return processes_runner 13 | 14 | raise ValueError(f"threads_or_processes invalid: {threads_or_processes}") 15 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/shared_tasks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/shared_tasks/workers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/task_mixins/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/task_mixins/io_mixin_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import unittest 5 | 6 | from servicecatalog_puppet import constants 7 | 8 | 9 | class IOMixinTest(unittest.TestCase): 10 | def setUp(self) -> None: 11 | from servicecatalog_puppet.waluigi.task_mixins import io_mixin 12 | 13 | self.module = io_mixin 14 | self.sut = self.module.IOMixin() 15 | 16 | def test_should_use_s3_target_if_caching_is_on(self): 17 | # setup 18 | self.sut.cachable_level = constants.CACHE_LEVEL_NO_CACHE 19 | expected_result = False 20 | 21 | # execute 22 | actual_result = self.sut.should_use_s3_target_if_caching_is_on 23 | 24 | # verify 25 | self.assertEqual(expected_result, actual_result) 26 | 27 | def test_should_use_s3_target_if_caching_is_on_when_off(self): 28 | # setup 29 | caching_enabled = [ 30 | constants.CACHE_LEVEL_RUN, 31 | constants.CACHE_LEVEL_PERMANENT, 32 | constants.CACHE_LEVEL_TASK, 33 | constants.CACHE_LEVEL_DEFAULT, 34 | ] 35 | expected_result = True 36 | 37 | # execute 38 | for result in caching_enabled: 39 | self.sut.cachable_level = result 40 | # verify 41 | self.assertEqual( 42 | expected_result, self.sut.should_use_s3_target_if_caching_is_on, result 43 | ) 44 | -------------------------------------------------------------------------------- /servicecatalog_puppet/waluigi/threads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/apps/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/apps/provision_app_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class ProvisionAppTask(tasks.TaskWithParameters): 11 | app_name = luigi.Parameter() 12 | region = luigi.Parameter() 13 | account_id = luigi.Parameter() 14 | 15 | bucket = luigi.Parameter() 16 | key = luigi.Parameter() 17 | version_id = luigi.Parameter() 18 | 19 | ssm_param_inputs = luigi.ListParameter(default=[], significant=False) 20 | 21 | launch_parameters = luigi.DictParameter(default={}, significant=False) 22 | manifest_parameters = luigi.DictParameter(default={}, significant=False) 23 | account_parameters = luigi.DictParameter(default={}, significant=False) 24 | 25 | retry_count = luigi.IntParameter(default=1, significant=False) 26 | worker_timeout = luigi.IntParameter(default=0, significant=False) 27 | ssm_param_outputs = luigi.ListParameter(default=[], significant=False) 28 | requested_priority = luigi.IntParameter(significant=False, default=0) 29 | 30 | execution = luigi.Parameter() 31 | cachable_level = constants.CACHE_LEVEL_RUN 32 | 33 | def params_for_results_display(self): 34 | return { 35 | "task_reference": self.task_reference, 36 | "puppet_account_id": self.puppet_account_id, 37 | "app_name": self.app_name, 38 | "region": self.region, 39 | "account_id": self.account_id, 40 | } 41 | 42 | def run(self): 43 | self.write_empty_output() 44 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/apps/provision_app_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 5 | 6 | 7 | class ProvisionAppTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 8 | app_name = "app_name" 9 | region = "region" 10 | account_id = "account_id" 11 | bucket = "bucket" 12 | key = "key" 13 | version_id = "version_id" 14 | ssm_param_inputs = [] 15 | launch_parameters = {} 16 | manifest_parameters = {} 17 | account_parameters = {} 18 | ssm_param_outputs = [] 19 | execution = "execution" 20 | 21 | def setUp(self) -> None: 22 | from servicecatalog_puppet.workflow.apps import provision_app_task 23 | 24 | self.module = provision_app_task 25 | 26 | self.sut = self.module.ProvisionAppTask( 27 | **self.get_common_args(), 28 | app_name=self.app_name, 29 | region=self.region, 30 | account_id=self.account_id, 31 | bucket=self.bucket, 32 | key=self.key, 33 | version_id=self.version_id, 34 | ssm_param_inputs=self.ssm_param_inputs, 35 | launch_parameters=self.launch_parameters, 36 | manifest_parameters=self.manifest_parameters, 37 | account_parameters=self.account_parameters, 38 | ssm_param_outputs=self.ssm_param_outputs, 39 | execution=self.execution 40 | ) 41 | 42 | self.wire_up_mocks() 43 | 44 | def test_params_for_results_display(self): 45 | # setup 46 | expected_result = { 47 | "task_reference": self.task_reference, 48 | "puppet_account_id": self.puppet_account_id, 49 | "app_name": self.app_name, 50 | "region": self.region, 51 | "account_id": self.account_id, 52 | } 53 | 54 | # exercise 55 | actual_result = self.sut.params_for_results_display() 56 | 57 | # verify 58 | self.assertEqual(expected_result, actual_result) 59 | 60 | def test_run(self): 61 | # setup 62 | # exercise 63 | self.sut.run() 64 | 65 | # verify 66 | self.assert_empty_output() 67 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/assertions/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/assertions/do_assert_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DoAssertTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | assertion_name = "assertion_name" 11 | region = "region" 12 | account_id = "account_id" 13 | execution = "execution" 14 | expected = {} 15 | actual = {} 16 | requested_priority = 1 17 | 18 | def setUp(self) -> None: 19 | from servicecatalog_puppet.workflow.assertions import do_assert_task 20 | 21 | self.module = do_assert_task 22 | 23 | self.sut = self.module.DoAssertTask( 24 | **self.get_common_args(), 25 | assertion_name=self.assertion_name, 26 | region=self.region, 27 | account_id=self.account_id, 28 | execution=self.execution, 29 | expected=self.expected, 30 | actual=self.actual, 31 | requested_priority=self.requested_priority, 32 | ) 33 | 34 | self.wire_up_mocks() 35 | 36 | def test_params_for_results_display(self): 37 | # setup 38 | expected_result = { 39 | "puppet_account_id": self.puppet_account_id, 40 | "assertion_name": self.assertion_name, 41 | "region": self.region, 42 | "account_id": self.account_id, 43 | } 44 | 45 | # exercise 46 | actual_result = self.sut.params_for_results_display() 47 | 48 | # verify 49 | self.assertEqual(expected_result, actual_result) 50 | 51 | @skip 52 | def test_run(self): 53 | # setup 54 | # exercise 55 | actual_result = self.sut.run() 56 | 57 | # verify 58 | raise NotImplementedError() 59 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/c7n/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/codebuild_runs/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/codebuild_runs/do_execute_code_build_run_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DoExecuteCodeBuildRunTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | code_build_run_name = "code_build_run_name" 11 | region = "region" 12 | account_id = "account_id" 13 | project_name = "project_name" 14 | manifest_file_path = "manifest_file_path" 15 | 16 | def setUp(self) -> None: 17 | from servicecatalog_puppet.workflow.codebuild_runs import ( 18 | do_execute_code_build_run_task, 19 | ) 20 | 21 | self.module = do_execute_code_build_run_task 22 | 23 | self.sut = self.module.DoExecuteCodeBuildRunTask( 24 | **self.get_common_args(), 25 | code_build_run_name=self.code_build_run_name, 26 | region=self.region, 27 | account_id=self.account_id, 28 | project_name=self.project_name, 29 | manifest_file_path=self.manifest_file_path, 30 | ) 31 | 32 | self.wire_up_mocks() 33 | 34 | def test_params_for_results_display(self): 35 | # setup 36 | expected_result = { 37 | "puppet_account_id": self.puppet_account_id, 38 | "code_build_run_name": self.code_build_run_name, 39 | "region": self.region, 40 | "account_id": self.account_id, 41 | } 42 | 43 | # exercise 44 | actual_result = self.sut.params_for_results_display() 45 | 46 | # verify 47 | self.assertEqual(expected_result, actual_result) 48 | 49 | @skip 50 | def test_run(self): 51 | # setup 52 | # exercise 53 | actual_result = self.sut.run() 54 | 55 | # verify 56 | raise NotImplementedError() 57 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/general/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/general/boto3_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import json 4 | 5 | import jmespath 6 | import luigi 7 | from deepmerge import always_merger 8 | 9 | import servicecatalog_puppet.manifest_utils 10 | import servicecatalog_puppet.serialisation_utils 11 | from servicecatalog_puppet import constants 12 | from servicecatalog_puppet.workflow.dependencies import tasks 13 | 14 | 15 | remove_punctuation_map = dict((ord(char), None) for char in '\/*?:"<>|\n') 16 | 17 | 18 | def hash(what): 19 | return json.dumps( 20 | servicecatalog_puppet.serialisation_utils.unwrap(what), indent=0 21 | ).translate(remove_punctuation_map) 22 | 23 | 24 | class Boto3Task(tasks.TaskWithReference): 25 | account_id = luigi.Parameter() 26 | region = luigi.Parameter() 27 | 28 | client = luigi.Parameter() 29 | use_paginator = luigi.BoolParameter() 30 | call = luigi.Parameter() 31 | arguments = luigi.DictParameter() 32 | filter = luigi.Parameter() 33 | 34 | cachable_level = constants.CACHE_LEVEL_RUN 35 | 36 | def params_for_results_display(self): 37 | return { 38 | "account_id": self.account_id, 39 | "region": self.region, 40 | "client": self.client, 41 | "use_paginator": self.use_paginator, 42 | "call": self.call, 43 | "arguments": hash(self.arguments), 44 | "filter": hash(self.filter), 45 | } 46 | 47 | def run(self): 48 | with self.spoke_regional_client(self.client) as client: 49 | if self.use_paginator: 50 | paginator = client.get_paginator(self.call) 51 | result = dict() 52 | for page in paginator.paginate(**self.arguments): 53 | always_merger.merge(result, page) 54 | else: 55 | f = getattr(client, self.call) 56 | result = f(**self.arguments) 57 | 58 | actual_result = jmespath.search(self.filter, result) 59 | if isinstance(actual_result, str): 60 | self.write_output(actual_result.strip()) 61 | else: 62 | self.write_output(actual_result) 63 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/general/terminate_cloudformation_stack_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class TerminateCloudFormationStackTask(tasks.TaskWithReference): 11 | stack_name = luigi.Parameter() 12 | 13 | region = luigi.Parameter() 14 | account_id = luigi.Parameter() 15 | 16 | execution = luigi.Parameter() 17 | 18 | cachable_level = constants.CACHE_LEVEL_RUN 19 | 20 | def params_for_results_display(self): 21 | return { 22 | "puppet_account_id": self.puppet_account_id, 23 | "stack_name": self.stack_name, 24 | "account_id": self.account_id, 25 | "region": self.region, 26 | } 27 | 28 | def run(self): 29 | with self.spoke_regional_client("cloudformation") as cloudformation: 30 | cloudformation.ensure_deleted(StackName=self.stack_name) 31 | self.write_empty_output() 32 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/generate/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/generate/generate_policies_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import config, constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class GeneratePolicies(tasks.TaskWithReference): 11 | account_id = luigi.Parameter() 12 | region = luigi.Parameter() 13 | 14 | organizations_to_share_with = luigi.ListParameter() 15 | ous_to_share_with = luigi.ListParameter() 16 | accounts_to_share_with = luigi.ListParameter() 17 | cachable_level = constants.CACHE_LEVEL_NO_CACHE 18 | 19 | def params_for_results_display(self): 20 | return { 21 | "account_id": self.account_id, 22 | "region": self.region, 23 | } 24 | 25 | def run(self): 26 | sharing_policies = dict( 27 | accounts=self.accounts_to_share_with, 28 | ous=self.ous_to_share_with, 29 | organizations=self.organizations_to_share_with, 30 | ) 31 | 32 | if len(sharing_policies.get("accounts", [])) > 50: 33 | self.warning( 34 | "You have specified more than 50 accounts will not create the eventbus policy and spoke execution mode will not work" 35 | ) 36 | template = config.env.get_template("policies.template.yaml.j2").render( 37 | sharing_policies=sharing_policies, 38 | VERSION=constants.VERSION, 39 | HOME_REGION=constants.HOME_REGION, 40 | ) 41 | 42 | with self.spoke_regional_client("cloudformation") as cloudformation: 43 | cloudformation.create_or_update( 44 | ShouldUseChangeSets=False, 45 | StackName="servicecatalog-puppet-policies", 46 | TemplateBody=template, 47 | NotificationARNs=( 48 | [ 49 | f"arn:{config.get_partition()}:sns:{self.region}:{self.puppet_account_id}:servicecatalog-puppet-cloudformation-regional-events" 50 | ] 51 | if self.should_use_sns 52 | else [] 53 | ), 54 | ShouldDeleteRollbackComplete=self.should_delete_rollback_complete_stacks, 55 | Tags=self.initialiser_stack_tags, 56 | ) 57 | 58 | self.write_empty_output() 59 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/generate/generate_policies_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 5 | 6 | 7 | class GeneratePoliciesTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 8 | puppet_account_id = "puppet_account_id" 9 | region = "region" 10 | account_id = "account_id" 11 | organizations_to_share_with = dict(a=1) 12 | ous_to_share_with = dict(b=2) 13 | accounts_to_share_with = dict(c=3) 14 | 15 | def setUp(self) -> None: 16 | from servicecatalog_puppet.workflow.generate import generate_policies_task 17 | 18 | self.module = generate_policies_task 19 | 20 | self.sut = self.module.GeneratePolicies( 21 | **self.get_common_args(), 22 | account_id=self.account_id, 23 | region=self.region, 24 | organizations_to_share_with=self.organizations_to_share_with, 25 | ous_to_share_with=self.ous_to_share_with, 26 | accounts_to_share_with=self.accounts_to_share_with, 27 | ) 28 | 29 | self.wire_up_mocks() 30 | 31 | def test_params_for_results_display(self): 32 | # setup 33 | expected_result = { 34 | "account_id": self.account_id, 35 | "region": self.region, 36 | } 37 | 38 | # exercise 39 | actual_result = self.sut.params_for_results_display() 40 | 41 | # verify 42 | self.assertEqual(expected_result, actual_result) 43 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/lambda_invocations/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/lambda_invocations/do_invoke_lambda_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import json 5 | 6 | import luigi 7 | 8 | from servicecatalog_puppet import config, constants 9 | from servicecatalog_puppet.workflow.dependencies import tasks 10 | 11 | 12 | class DoInvokeLambdaTask(tasks.TaskWithParameters): 13 | lambda_invocation_name = luigi.Parameter() 14 | region = luigi.Parameter() 15 | account_id = luigi.Parameter() 16 | 17 | function_name = luigi.Parameter() 18 | qualifier = luigi.Parameter() 19 | invocation_type = luigi.Parameter() 20 | 21 | manifest_file_path = luigi.Parameter() 22 | 23 | section_name = constants.LAMBDA_INVOCATIONS 24 | cachable_level = constants.CACHE_LEVEL_RUN 25 | 26 | @property 27 | def item_name(self): 28 | return self.lambda_invocation_name 29 | 30 | def params_for_results_display(self): 31 | return { 32 | "puppet_account_id": self.puppet_account_id, 33 | "lambda_invocation_name": self.lambda_invocation_name, 34 | "region": self.region, 35 | "account_id": self.account_id, 36 | } 37 | 38 | def run(self): 39 | home_region = config.get_home_region(self.puppet_account_id) 40 | with self.hub_regional_client( 41 | "lambda", region_name=home_region 42 | ) as lambda_client: 43 | payload = dict( 44 | account_id=self.account_id, 45 | region=self.region, 46 | parameters=self.get_parameter_values(), 47 | ) 48 | response = lambda_client.invoke( 49 | FunctionName=self.function_name, 50 | InvocationType=self.invocation_type, 51 | Payload=json.dumps(payload), 52 | Qualifier=self.qualifier, 53 | ) 54 | success_results = dict(RequestResponse=200, Event=202, DryRun=204) 55 | 56 | if success_results.get(self.invocation_type) != response.get("StatusCode"): 57 | raise Exception( 58 | f"{self.lambda_invocation_name} failed for {self.account_id}, {self.region}" 59 | ) 60 | else: 61 | if response.get("FunctionError"): 62 | error_payload = response.get("Payload").read() 63 | raise Exception(error_payload) 64 | else: 65 | self.write_empty_output() 66 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/lambda_invocations/do_invoke_lambda_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DoInvokeLambdaTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | lambda_invocation_name = "lambda_invocation_name" 11 | region = "region" 12 | account_id = "account_id" 13 | function_name = "function_name" 14 | qualifier = "qualifier" 15 | invocation_type = "invocation_type" 16 | manifest_file_path = "manifest_file_path" 17 | 18 | def setUp(self) -> None: 19 | from servicecatalog_puppet.workflow.lambda_invocations import ( 20 | do_invoke_lambda_task, 21 | ) 22 | 23 | self.module = do_invoke_lambda_task 24 | 25 | self.sut = self.module.DoInvokeLambdaTask( 26 | **self.get_common_args(), 27 | lambda_invocation_name=self.lambda_invocation_name, 28 | region=self.region, 29 | account_id=self.account_id, 30 | function_name=self.function_name, 31 | qualifier=self.qualifier, 32 | invocation_type=self.invocation_type, 33 | manifest_file_path=self.manifest_file_path, 34 | ) 35 | 36 | self.wire_up_mocks() 37 | 38 | def test_params_for_results_display(self): 39 | # setup 40 | expected_result = { 41 | "puppet_account_id": self.puppet_account_id, 42 | "lambda_invocation_name": self.lambda_invocation_name, 43 | "region": self.region, 44 | "account_id": self.account_id, 45 | } 46 | 47 | # exercise 48 | actual_result = self.sut.params_for_results_display() 49 | 50 | # verify 51 | self.assertEqual(expected_result, actual_result) 52 | 53 | @skip 54 | def test_run(self): 55 | # setup 56 | # exercise 57 | actual_result = self.sut.run() 58 | 59 | # verify 60 | raise NotImplementedError() 61 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/launch/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/launch/do_terminate_product_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DoTerminateProductTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | launch_name = "launch_name" 11 | region = "region" 12 | account_id = "account_id" 13 | portfolio = "portfolio" 14 | product = "product" 15 | version = "version" 16 | ssm_param_inputs = [] 17 | launch_parameters = {} 18 | manifest_parameters = {} 19 | account_parameters = {} 20 | ssm_param_outputs = [] 21 | execution = "execution" 22 | 23 | def setUp(self) -> None: 24 | from servicecatalog_puppet.workflow.launch import do_terminate_product_task 25 | 26 | self.module = do_terminate_product_task 27 | 28 | self.sut = self.module.DoTerminateProductTask( 29 | **self.get_common_args(), 30 | launch_name=self.launch_name, 31 | region=self.region, 32 | account_id=self.account_id, 33 | portfolio=self.portfolio, 34 | product=self.product, 35 | version=self.version, 36 | ssm_param_inputs=self.ssm_param_inputs, 37 | launch_parameters=self.launch_parameters, 38 | manifest_parameters=self.manifest_parameters, 39 | account_parameters=self.account_parameters, 40 | ssm_param_outputs=self.ssm_param_outputs, 41 | execution=self.execution, 42 | ) 43 | 44 | self.wire_up_mocks() 45 | 46 | def test_params_for_results_display(self): 47 | # setup 48 | expected_result = { 49 | "puppet_account_id": self.puppet_account_id, 50 | "launch_name": self.launch_name, 51 | "account_id": self.account_id, 52 | "region": self.region, 53 | } 54 | 55 | # exercise 56 | actual_result = self.sut.params_for_results_display() 57 | 58 | # verify 59 | self.assertEqual(expected_result, actual_result) 60 | 61 | @skip 62 | def test_run(self): 63 | # setup 64 | # exercise 65 | actual_result = self.sut.run() 66 | 67 | # verify 68 | raise NotImplementedError() 69 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/launch/provisioning_artifact_parameters_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import time 4 | 5 | import luigi 6 | 7 | from servicecatalog_puppet import constants 8 | from servicecatalog_puppet.workflow.dependencies import tasks 9 | 10 | 11 | class ProvisioningArtifactParametersTask(tasks.TaskWithReference): 12 | portfolio = luigi.Parameter() 13 | product = luigi.Parameter() 14 | version = luigi.Parameter() 15 | region = luigi.Parameter() 16 | cachable_level = constants.CACHE_LEVEL_RUN 17 | 18 | @property 19 | def retry_count(self): 20 | return 5 21 | 22 | def params_for_results_display(self): 23 | return { 24 | "puppet_account_id": self.puppet_account_id, 25 | "portfolio": self.portfolio, 26 | "region": self.region, 27 | "product": self.product, 28 | "version": self.version, 29 | } 30 | 31 | def run(self): 32 | with self.hub_regional_client("servicecatalog") as service_catalog: 33 | provisioning_artifact_parameters = None 34 | retries = 3 35 | while retries > 0: 36 | try: 37 | provisioning_artifact_parameters = ( 38 | service_catalog.describe_provisioning_parameters( 39 | ProductName=self.product, 40 | ProvisioningArtifactName=self.version, 41 | PathName=self.portfolio, 42 | ).get("ProvisioningArtifactParameters", []) 43 | ) 44 | retries = 0 45 | break 46 | except service_catalog.exceptions.ClientError as ex: 47 | if "S3 error: Access Denied" in str(ex): 48 | self.info("Swallowing S3 error: Access Denied") 49 | else: 50 | raise ex 51 | time.sleep(3) 52 | retries -= 1 53 | 54 | self.write_output( 55 | provisioning_artifact_parameters 56 | if isinstance(provisioning_artifact_parameters, list) 57 | else [provisioning_artifact_parameters] 58 | ) 59 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/launch/provisioning_artifact_parameters_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class ProvisioningArtifactParametersTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | portfolio = "portfolio" 13 | product = "product" 14 | version = "version" 15 | region = "region" 16 | 17 | def setUp(self) -> None: 18 | from servicecatalog_puppet.workflow.launch import ( 19 | provisioning_artifact_parameters_task, 20 | ) 21 | 22 | self.module = provisioning_artifact_parameters_task 23 | 24 | self.sut = self.module.ProvisioningArtifactParametersTask( 25 | **self.get_common_args(), 26 | portfolio=self.portfolio, 27 | product=self.product, 28 | version=self.version, 29 | region=self.region, 30 | ) 31 | 32 | self.wire_up_mocks() 33 | 34 | def test_params_for_results_display(self): 35 | # setup 36 | expected_result = { 37 | "puppet_account_id": self.puppet_account_id, 38 | "portfolio": self.portfolio, 39 | "region": self.region, 40 | "product": self.product, 41 | "version": self.version, 42 | } 43 | 44 | # exercise 45 | actual_result = self.sut.params_for_results_display() 46 | 47 | # verify 48 | self.assertEqual(expected_result, actual_result) 49 | 50 | @skip 51 | def test_run(self): 52 | # setup 53 | # exercise 54 | actual_result = self.sut.run() 55 | 56 | # verify 57 | raise NotImplementedError() 58 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/launch/run_deploy_in_spoke_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class RunDeployInSpokeTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | task_reference = "task_reference" 11 | dependencies_by_reference = [] 12 | account_id = "account_id" 13 | generate_manifest_ref = "generate_manifest_ref" 14 | 15 | def setUp(self) -> None: 16 | import os 17 | from servicecatalog_puppet import environmental_variables 18 | 19 | os.environ[environmental_variables.REGIONS] = "[]" 20 | from servicecatalog_puppet.workflow.launch import run_deploy_in_spoke_task 21 | 22 | self.module = run_deploy_in_spoke_task 23 | 24 | self.sut = self.module.RunDeployInSpokeTask( 25 | **self.get_common_args(), 26 | account_id=self.account_id, 27 | generate_manifest_ref=self.generate_manifest_ref, 28 | ) 29 | 30 | self.wire_up_mocks() 31 | 32 | def test_params_for_results_display(self): 33 | # setup 34 | expected_result = { 35 | "task_reference": self.task_reference, 36 | "puppet_account_id": self.puppet_account_id, 37 | "account_id": self.account_id, 38 | } 39 | 40 | # exercise 41 | actual_result = self.sut.params_for_results_display() 42 | 43 | # verify 44 | self.assertEqual(expected_result, actual_result) 45 | 46 | @skip 47 | def test_run(self): 48 | # setup 49 | # exercise 50 | actual_result = self.sut.run() 51 | 52 | # verify 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/launch/terminate_product_dry_run_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateProductDryRunTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | launch_name = "launch_name" 11 | region = "region" 12 | account_id = "account_id" 13 | portfolio = "portfolio" 14 | product = "product" 15 | version = "version" 16 | ssm_param_inputs = [] 17 | launch_parameters = {} 18 | manifest_parameters = {} 19 | account_parameters = {} 20 | ssm_param_outputs = [] 21 | execution = "execution" 22 | 23 | def setUp(self) -> None: 24 | from servicecatalog_puppet.workflow.launch import terminate_product_dry_run_task 25 | 26 | self.module = terminate_product_dry_run_task 27 | 28 | self.sut = self.module.TerminateProductDryRunTask( 29 | **self.get_common_args(), 30 | launch_name=self.launch_name, 31 | region=self.region, 32 | account_id=self.account_id, 33 | portfolio=self.portfolio, 34 | product=self.product, 35 | version=self.version, 36 | ssm_param_inputs=self.ssm_param_inputs, 37 | launch_parameters=self.launch_parameters, 38 | manifest_parameters=self.manifest_parameters, 39 | account_parameters=self.account_parameters, 40 | ssm_param_outputs=self.ssm_param_outputs, 41 | execution=self.execution, 42 | ) 43 | 44 | self.wire_up_mocks() 45 | 46 | @skip 47 | def test_run(self): 48 | # setup 49 | # exercise 50 | actual_result = self.sut.run() 51 | 52 | # verify 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/management.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import config, sdk, serialisation_utils 7 | from servicecatalog_puppet.workflow import tasks 8 | 9 | 10 | class BootstrapSpokeAsTask(tasks.PuppetTask): 11 | puppet_account_id = luigi.Parameter() 12 | account_id = luigi.Parameter() 13 | iam_role_arns = luigi.ListParameter() 14 | role_name = luigi.Parameter() 15 | permission_boundary = luigi.Parameter() 16 | puppet_role_name = luigi.Parameter() 17 | puppet_role_path = luigi.Parameter() 18 | tag = luigi.ListParameter() 19 | 20 | def params_for_results_display(self): 21 | return { 22 | "puppet_account_id": self.puppet_account_id, 23 | "account_id": self.account_id, 24 | } 25 | 26 | def run(self): 27 | partition = config.get_partition() 28 | iam_role_arns_to_use = [iam_role_arn for iam_role_arn in self.iam_role_arns] 29 | iam_role_arns_to_use.append( 30 | f"arn:{partition}:iam::{self.account_id}:role/{self.role_name}" 31 | ) 32 | sdk.bootstrap_spoke_as( 33 | self.puppet_account_id, 34 | iam_role_arns_to_use, 35 | self.permission_boundary, 36 | self.puppet_role_name, 37 | self.puppet_role_path, 38 | self.tag, 39 | ) 40 | self.write_output(self.params_for_results_display()) 41 | 42 | def write_output(self, content): 43 | with self.output().open("w") as f: 44 | content_to_write = serialisation_utils.json_dumps(content).decode("utf-8") 45 | f.write(content_to_write) 46 | 47 | def output(self): 48 | return luigi.LocalTarget( 49 | f"output/{self.__class__.__name__}/{self.puppet_account_id}/{self.account_id}.json", 50 | ) 51 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/management_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class BootstrapSpokeAsTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | puppet_account_id = "puppet_account_id" 11 | account_id = "account_id" 12 | iam_role_arns = [] 13 | role_name = "role_name" 14 | permission_boundary = "permission_boundary" 15 | puppet_role_name = "puppet_role_name" 16 | puppet_role_path = "puppet_role_path" 17 | 18 | def setUp(self) -> None: 19 | from servicecatalog_puppet.workflow import management 20 | 21 | self.module = management 22 | 23 | self.sut = self.module.BootstrapSpokeAsTask( 24 | puppet_account_id=self.puppet_account_id, 25 | account_id=self.account_id, 26 | iam_role_arns=self.iam_role_arns, 27 | role_name=self.role_name, 28 | permission_boundary=self.permission_boundary, 29 | puppet_role_name=self.puppet_role_name, 30 | puppet_role_path=self.puppet_role_path, 31 | tag=[], 32 | ) 33 | 34 | self.wire_up_mocks() 35 | 36 | def test_params_for_results_display(self): 37 | # setup 38 | expected_result = { 39 | "puppet_account_id": self.puppet_account_id, 40 | "account_id": self.account_id, 41 | } 42 | 43 | # exercise 44 | actual_result = self.sut.params_for_results_display() 45 | 46 | # verify 47 | self.assertEqual(expected_result, actual_result) 48 | 49 | @skip 50 | def test_run(self): 51 | # setup 52 | # exercise 53 | actual_result = self.sut.run() 54 | 55 | # verify 56 | raise NotImplementedError() 57 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/manifest/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/manifest/generate_manifest_with_ids_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class GenerateManifestWithIdsTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | puppet_account_id = "puppet_account_id" 11 | task_reference = "task_reference" 12 | manifest_task_reference_file_path = "manifest_task_reference_file_path" 13 | dependencies_by_reference = "dependencies_by_reference" 14 | 15 | def setUp(self) -> None: 16 | from servicecatalog_puppet.workflow.manifest import ( 17 | generate_manifest_with_ids_task, 18 | ) 19 | 20 | self.module = generate_manifest_with_ids_task 21 | 22 | self.sut = self.module.GenerateManifestWithIdsTask(**self.get_common_args()) 23 | 24 | self.wire_up_mocks() 25 | 26 | def test_params_for_results_display(self): 27 | # setup 28 | expected_result = { 29 | "puppet_account_id": self.puppet_account_id, 30 | } 31 | 32 | # exercise 33 | actual_result = self.sut.params_for_results_display() 34 | 35 | # verify 36 | self.assertEqual(expected_result, actual_result) 37 | 38 | @skip 39 | def test_run(self): 40 | # setup 41 | # exercise 42 | actual_result = self.sut.run() 43 | 44 | # verify 45 | raise NotImplementedError() 46 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/manifest/manifest_mixin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from functools import lru_cache 5 | 6 | import luigi 7 | import yaml 8 | 9 | from servicecatalog_puppet import constants, manifest_utils 10 | 11 | 12 | class ManifestMixen(object): 13 | manifest_file_path = luigi.Parameter() 14 | 15 | @property 16 | def status(self): 17 | return ( 18 | self.manifest.get(self.section_name) 19 | .get(self.item_name) 20 | .get("status", constants.PROVISIONED) 21 | ) 22 | 23 | @property 24 | @lru_cache() 25 | def manifest(self): 26 | content = open(self.manifest_file_path, "r").read() 27 | return manifest_utils.Manifest(yaml.safe_load(content)) 28 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/organizational_units/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/accessors/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/accessors/get_all_products_and_their_versions_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class GetAllProductsAndTheirVersionsTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | account_id = "account_id" 13 | region = "region" 14 | portfolio = "portfolio" 15 | portfolio_task_reference = "portfolio_task_reference" 16 | 17 | def setUp(self) -> None: 18 | from servicecatalog_puppet.workflow.portfolio.accessors import ( 19 | get_all_products_and_their_versions_task, 20 | ) 21 | 22 | self.module = get_all_products_and_their_versions_task 23 | 24 | self.sut = self.module.GetAllProductsAndTheirVersionsTask( 25 | **self.get_common_args(), 26 | account_id=self.account_id, 27 | region=self.region, 28 | portfolio=self.portfolio, 29 | portfolio_task_reference=self.portfolio_task_reference, 30 | ) 31 | 32 | self.wire_up_mocks() 33 | 34 | def test_params_for_results_display(self): 35 | # setup 36 | expected_result = { 37 | "task_reference": self.task_reference, 38 | } 39 | 40 | # exercise 41 | actual_result = self.sut.params_for_results_display() 42 | 43 | # verify 44 | self.assertEqual(expected_result, actual_result) 45 | 46 | @skip 47 | def test_run(self): 48 | # setup 49 | # exercise 50 | actual_result = self.sut.run() 51 | 52 | # verify 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/associations/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/associations/association_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from servicecatalog_puppet import utils 4 | 5 | 6 | def generate_stack_name_for_associations_by_item_name(item_name): 7 | return ( 8 | f"associations-v2-for-{utils.slugify_for_cloudformation_stack_name(item_name)}" 9 | ) 10 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/associations/create_associations_for_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class CreateAssociationsForSpokeLocalPortfolioTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | portfolio_task_reference = "portfolio_task_reference" 13 | spoke_local_portfolio_name = "spoke_local_portfolio_name" 14 | account_id = "account_id" 15 | region = "region" 16 | portfolio = "portfolio" 17 | associations = [] 18 | 19 | def setUp(self) -> None: 20 | from servicecatalog_puppet.workflow.portfolio.associations import ( 21 | create_associations_for_spoke_local_portfolio_task, 22 | ) 23 | 24 | self.module = create_associations_for_spoke_local_portfolio_task 25 | 26 | self.sut = self.module.CreateAssociationsForSpokeLocalPortfolioTask( 27 | **self.get_common_args(), 28 | portfolio_task_reference=self.portfolio_task_reference, 29 | spoke_local_portfolio_name=self.spoke_local_portfolio_name, 30 | account_id=self.account_id, 31 | region=self.region, 32 | portfolio=self.portfolio, 33 | associations=self.associations, 34 | ) 35 | 36 | self.wire_up_mocks() 37 | 38 | def test_params_for_results_display(self): 39 | # setup 40 | expected_result = { 41 | "puppet_account_id": self.puppet_account_id, 42 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 43 | "portfolio": self.portfolio, 44 | "region": self.region, 45 | "account_id": self.account_id, 46 | } 47 | 48 | # exercise 49 | actual_result = self.sut.params_for_results_display() 50 | 51 | # verify 52 | self.assertEqual(expected_result, actual_result) 53 | 54 | @skip 55 | def test_run(self): 56 | # setup 57 | # exercise 58 | actual_result = self.sut.run() 59 | 60 | # verify 61 | raise NotImplementedError() 62 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/associations/terminate_associations_for_spoke_local_portfolio_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import luigi 4 | 5 | from servicecatalog_puppet import constants 6 | from servicecatalog_puppet.workflow.dependencies import tasks 7 | from servicecatalog_puppet.workflow.portfolio.associations import association_utils 8 | 9 | 10 | class TerminateAssociationsForSpokeLocalPortfolioTask(tasks.TaskWithReference): 11 | account_id = luigi.Parameter() 12 | region = luigi.Parameter() 13 | portfolio = luigi.Parameter() 14 | spoke_local_portfolio_name = luigi.Parameter() 15 | cachable_level = constants.CACHE_LEVEL_RUN 16 | 17 | def params_for_results_display(self): 18 | return { 19 | "puppet_account_id": self.puppet_account_id, 20 | "portfolio": self.portfolio, 21 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 22 | "region": self.region, 23 | "account_id": self.account_id, 24 | } 25 | 26 | def run(self): 27 | stack_name = ( 28 | association_utils.generate_stack_name_for_associations_by_item_name( 29 | self.spoke_local_portfolio_name 30 | ) 31 | ) 32 | 33 | with self.spoke_regional_client("cloudformation") as cloudformation: 34 | self.info(f"About to delete the stack: {stack_name}") 35 | cloudformation.ensure_deleted(StackName=stack_name) 36 | self.write_empty_output() 37 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/associations/terminate_associations_for_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateAssociationsForSpokeLocalPortfolioTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | account_id = "account_id" 13 | region = "region" 14 | portfolio = "portfolio" 15 | spoke_local_portfolio_name = "spoke_local_portfolio_name" 16 | 17 | def setUp(self) -> None: 18 | from servicecatalog_puppet.workflow.portfolio.associations import ( 19 | terminate_associations_for_spoke_local_portfolio_task, 20 | ) 21 | 22 | self.module = terminate_associations_for_spoke_local_portfolio_task 23 | 24 | self.sut = self.module.TerminateAssociationsForSpokeLocalPortfolioTask( 25 | **self.get_common_args(), 26 | account_id=self.account_id, 27 | region=self.region, 28 | portfolio=self.portfolio, 29 | spoke_local_portfolio_name=self.spoke_local_portfolio_name, 30 | ) 31 | 32 | self.wire_up_mocks() 33 | 34 | def test_params_for_results_display(self): 35 | # setup 36 | expected_result = { 37 | "puppet_account_id": self.puppet_account_id, 38 | "portfolio": self.portfolio, 39 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 40 | "region": self.region, 41 | "account_id": self.account_id, 42 | } 43 | 44 | # exercise 45 | actual_result = self.sut.params_for_results_display() 46 | 47 | # verify 48 | self.assertEqual(expected_result, actual_result) 49 | 50 | @skip 51 | def test_run(self): 52 | # setup 53 | # exercise 54 | actual_result = self.sut.run() 55 | 56 | # verify 57 | raise NotImplementedError() 58 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/constraints_management/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/constraints_management/create_launch_role_constraints_for_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class CreateLaunchRoleConstraintsForSpokeLocalPortfolioTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | portfolio_task_reference = "portfolio_task_reference" 13 | spoke_local_portfolio_name = "spoke_local_portfolio_name" 14 | account_id = "account_id" 15 | region = "region" 16 | portfolio = "portfolio" 17 | launch_constraints = [] 18 | portfolio_get_all_products_and_their_versions_ref = ( 19 | "portfolio_get_all_products_and_their_versions_ref" 20 | ) 21 | 22 | def setUp(self) -> None: 23 | from servicecatalog_puppet.workflow.portfolio.constraints_management import ( 24 | create_launch_role_constraints_for_spoke_local_portfolio_task, 25 | ) 26 | 27 | self.module = create_launch_role_constraints_for_spoke_local_portfolio_task 28 | 29 | self.sut = self.module.CreateLaunchRoleConstraintsForSpokeLocalPortfolioTask( 30 | **self.get_common_args(), 31 | portfolio_task_reference=self.portfolio_task_reference, 32 | spoke_local_portfolio_name=self.spoke_local_portfolio_name, 33 | account_id=self.account_id, 34 | region=self.region, 35 | portfolio=self.portfolio, 36 | launch_constraints=self.launch_constraints, 37 | portfolio_get_all_products_and_their_versions_ref=self.portfolio_get_all_products_and_their_versions_ref, 38 | ) 39 | 40 | self.wire_up_mocks() 41 | 42 | def test_params_for_results_display(self): 43 | # setup 44 | expected_result = { 45 | "puppet_account_id": self.puppet_account_id, 46 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 47 | "portfolio": self.portfolio, 48 | "region": self.region, 49 | "account_id": self.account_id, 50 | } 51 | 52 | # exercise 53 | actual_result = self.sut.params_for_results_display() 54 | 55 | # verify 56 | self.assertEqual(expected_result, actual_result) 57 | 58 | @skip 59 | def test_run(self): 60 | # setup 61 | # exercise 62 | actual_result = self.sut.run() 63 | 64 | # verify 65 | raise NotImplementedError() 66 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/constraints_management/terminate_launch_role_constraints_for_spoke_local_portfolio_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import constants, utils 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class TerminateLaunchRoleConstraintsForSpokeLocalPortfolioTask(tasks.TaskWithReference): 11 | account_id = luigi.Parameter() 12 | region = luigi.Parameter() 13 | portfolio = luigi.Parameter() 14 | spoke_local_portfolio_name = luigi.Parameter() 15 | cachable_level = constants.CACHE_LEVEL_TASK 16 | 17 | @property 18 | def drift_token_parameters(self): 19 | return f"portfolio/{self.portfolio}" 20 | 21 | def params_for_results_display(self): 22 | return { 23 | "puppet_account_id": self.puppet_account_id, 24 | "portfolio": self.portfolio, 25 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 26 | "region": self.region, 27 | "account_id": self.account_id, 28 | } 29 | 30 | def run(self): 31 | stack_name = f"launch-constraints-for-{utils.slugify_for_cloudformation_stack_name(self.spoke_local_portfolio_name)}" 32 | 33 | with self.spoke_regional_client("cloudformation") as cloudformation: 34 | self.info(f"About to delete the stack: {stack_name}") 35 | cloudformation.ensure_deleted(StackName=stack_name) 36 | self.write_empty_output() 37 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/constraints_management/terminate_launch_role_constraints_for_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateLaunchRoleConstraintsForSpokeLocalPortfolioTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | account_id = "account_id" 13 | region = "region" 14 | portfolio = "portfolio" 15 | spoke_local_portfolio_name = "spoke_local_portfolio_name" 16 | 17 | def setUp(self) -> None: 18 | from servicecatalog_puppet.workflow.portfolio.constraints_management import ( 19 | terminate_launch_role_constraints_for_spoke_local_portfolio_task, 20 | ) 21 | 22 | self.module = terminate_launch_role_constraints_for_spoke_local_portfolio_task 23 | 24 | self.sut = self.module.TerminateLaunchRoleConstraintsForSpokeLocalPortfolioTask( 25 | **self.get_common_args(), 26 | account_id=self.account_id, 27 | region=self.region, 28 | portfolio=self.portfolio, 29 | spoke_local_portfolio_name=self.spoke_local_portfolio_name 30 | ) 31 | 32 | self.wire_up_mocks() 33 | 34 | def test_params_for_results_display(self): 35 | # setup 36 | expected_result = { 37 | "puppet_account_id": self.puppet_account_id, 38 | "portfolio": self.portfolio, 39 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 40 | "region": self.region, 41 | "account_id": self.account_id, 42 | } 43 | 44 | # exercise 45 | actual_result = self.sut.params_for_results_display() 46 | 47 | # verify 48 | self.assertEqual(expected_result, actual_result) 49 | 50 | @skip 51 | def test_run(self): 52 | # setup 53 | # exercise 54 | actual_result = self.sut.run() 55 | 56 | # verify 57 | raise NotImplementedError() 58 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/constraints_management/terminate_resource_update_constraints_for_spoke_local_portfolio_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import constants, utils 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class TerminateResourceUpdateConstraintsForSpokeLocalPortfolioTask( 11 | tasks.TaskWithReference 12 | ): 13 | account_id = luigi.Parameter() 14 | region = luigi.Parameter() 15 | portfolio = luigi.Parameter() 16 | spoke_local_portfolio_name = luigi.Parameter() 17 | cachable_level = constants.CACHE_LEVEL_TASK 18 | 19 | @property 20 | def drift_token_parameters(self): 21 | return f"portfolio/{self.portfolio}" 22 | 23 | def params_for_results_display(self): 24 | return { 25 | "puppet_account_id": self.puppet_account_id, 26 | "portfolio": self.portfolio, 27 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 28 | "region": self.region, 29 | "account_id": self.account_id, 30 | } 31 | 32 | def run(self): 33 | stack_name = f"update-resource-constraints-for-{utils.slugify_for_cloudformation_stack_name(self.spoke_local_portfolio_name)}" 34 | 35 | with self.spoke_regional_client("cloudformation") as cloudformation: 36 | self.info(f"About to delete the stack: {stack_name}") 37 | cloudformation.ensure_deleted(StackName=stack_name) 38 | self.write_empty_output() 39 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/constraints_management/terminate_resource_update_constraints_for_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateResourceUpdateConstraintsForSpokeLocalPortfolioTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | account_id = "account_id" 13 | region = "region" 14 | portfolio = "portfolio" 15 | spoke_local_portfolio_name = "spoke_local_portfolio_name" 16 | 17 | def setUp(self) -> None: 18 | from servicecatalog_puppet.workflow.portfolio.constraints_management import ( 19 | terminate_resource_update_constraints_for_spoke_local_portfolio_task, 20 | ) 21 | 22 | self.module = ( 23 | terminate_resource_update_constraints_for_spoke_local_portfolio_task 24 | ) 25 | 26 | self.sut = ( 27 | self.module.TerminateResourceUpdateConstraintsForSpokeLocalPortfolioTask( 28 | **self.get_common_args(), 29 | account_id=self.account_id, 30 | region=self.region, 31 | portfolio=self.portfolio, 32 | spoke_local_portfolio_name=self.spoke_local_portfolio_name 33 | ) 34 | ) 35 | 36 | self.wire_up_mocks() 37 | 38 | def test_params_for_results_display(self): 39 | # setup 40 | expected_result = { 41 | "puppet_account_id": self.puppet_account_id, 42 | "portfolio": self.portfolio, 43 | "spoke_local_portfolio_name": self.spoke_local_portfolio_name, 44 | "region": self.region, 45 | "account_id": self.account_id, 46 | } 47 | 48 | # exercise 49 | actual_result = self.sut.params_for_results_display() 50 | 51 | # verify 52 | self.assertEqual(expected_result, actual_result) 53 | 54 | @skip 55 | def test_run(self): 56 | # setup 57 | # exercise 58 | actual_result = self.sut.run() 59 | 60 | # verify 61 | raise NotImplementedError() 62 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/copy_into_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class CopyIntoSpokeLocalPortfolioTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio_task_reference = "portfolio_task_reference" 13 | portfolio_get_all_products_and_their_versions_ref = ( 14 | "portfolio_get_all_products_and_their_versions_ref" 15 | ) 16 | portfolio_get_all_products_and_their_versions_for_hub_ref = ( 17 | "portfolio_get_all_products_and_their_versions_for_hub_ref" 18 | ) 19 | 20 | def setUp(self) -> None: 21 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 22 | copy_into_spoke_local_portfolio_task, 23 | ) 24 | 25 | self.module = copy_into_spoke_local_portfolio_task 26 | 27 | self.sut = self.module.CopyIntoSpokeLocalPortfolioTask( 28 | **self.get_common_args(), 29 | account_id=self.account_id, 30 | region=self.region, 31 | portfolio_task_reference=self.portfolio_task_reference, 32 | portfolio_get_all_products_and_their_versions_ref=self.portfolio_get_all_products_and_their_versions_ref, 33 | portfolio_get_all_products_and_their_versions_for_hub_ref=self.portfolio_get_all_products_and_their_versions_for_hub_ref, 34 | ) 35 | 36 | self.wire_up_mocks() 37 | 38 | def test_params_for_results_display(self): 39 | # setup 40 | expected_result = { 41 | "task_reference": self.task_reference, 42 | "puppet_account_id": self.puppet_account_id, 43 | "region": self.region, 44 | "account_id": self.account_id, 45 | } 46 | 47 | # exercise 48 | actual_result = self.sut.params_for_results_display() 49 | 50 | # verify 51 | self.assertEqual(expected_result, actual_result) 52 | 53 | @skip 54 | def test_run(self): 55 | # setup 56 | # exercise 57 | actual_result = self.sut.run() 58 | 59 | # verify 60 | raise NotImplementedError() 61 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/create_associations_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import time 4 | 5 | import luigi 6 | 7 | from servicecatalog_puppet import config, constants 8 | from servicecatalog_puppet.workflow.dependencies import tasks 9 | 10 | 11 | class CreateAssociationTask(tasks.TaskWithReference): 12 | account_id = luigi.Parameter() 13 | region = luigi.Parameter() 14 | portfolio = luigi.Parameter() 15 | portfolio_task_reference = luigi.Parameter() 16 | 17 | cachable_level = constants.CACHE_LEVEL_RUN 18 | 19 | def params_for_results_display(self): 20 | return { 21 | "task_reference": self.task_reference, 22 | "puppet_account_id": self.puppet_account_id, 23 | "portfolio": self.portfolio, 24 | "region": self.region, 25 | "account_id": self.account_id, 26 | } 27 | 28 | def run(self): 29 | portfolio_details = self.get_output_from_reference_dependency( 30 | self.portfolio_task_reference 31 | ) 32 | portfolio_id = portfolio_details.get("Id") 33 | 34 | principal_to_associate = config.get_puppet_role_arn(self.account_id) 35 | was_present = self.check_was_present(portfolio_id, principal_to_associate) 36 | self.info(f"Check for association: {was_present}") 37 | if not was_present: 38 | with self.spoke_regional_client("servicecatalog") as servicecatalog: 39 | servicecatalog.associate_principal_with_portfolio( 40 | PortfolioId=portfolio_id, 41 | PrincipalARN=principal_to_associate, 42 | PrincipalType="IAM", 43 | ) 44 | while not was_present: 45 | time.sleep(1) 46 | was_present = self.check_was_present(portfolio_id, principal_to_associate) 47 | self.info(f"Check for association: {was_present}") 48 | 49 | self.write_empty_output() 50 | 51 | def check_was_present(self, portfolio_id, principal_to_associate): 52 | with self.spoke_regional_client("servicecatalog") as servicecatalog: 53 | paginator = servicecatalog.get_paginator("list_principals_for_portfolio") 54 | for page in paginator.paginate(PortfolioId=portfolio_id): 55 | for principal in page.get("Principals", []): 56 | if principal_to_associate == principal.get("PrincipalARN"): 57 | return True 58 | return False 59 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/create_associations_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class CreateAssociationTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio = "portfolio" 13 | portfolio_task_reference = "portfolio_task_reference" 14 | 15 | def setUp(self) -> None: 16 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 17 | create_associations_task, 18 | ) 19 | 20 | self.module = create_associations_task 21 | 22 | self.sut = self.module.CreateAssociationTask( 23 | **self.get_common_args(), 24 | account_id=self.account_id, 25 | region=self.region, 26 | portfolio=self.portfolio, 27 | portfolio_task_reference=self.portfolio_task_reference, 28 | ) 29 | 30 | self.wire_up_mocks() 31 | 32 | def test_params_for_results_display(self): 33 | # setup 34 | expected_result = { 35 | "task_reference": self.task_reference, 36 | "puppet_account_id": self.puppet_account_id, 37 | "portfolio": self.portfolio, 38 | "region": self.region, 39 | "account_id": self.account_id, 40 | } 41 | 42 | # exercise 43 | actual_result = self.sut.params_for_results_display() 44 | 45 | # verify 46 | self.assertEqual(expected_result, actual_result) 47 | 48 | @skip 49 | def test_run(self): 50 | # setup 51 | # exercise 52 | actual_result = self.sut.run() 53 | 54 | # verify 55 | raise NotImplementedError() 56 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/create_spoke_local_portfolio_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import aws, constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class CreateSpokeLocalPortfolioTask(tasks.TaskWithReference): 11 | account_id = luigi.Parameter() 12 | region = luigi.Parameter() 13 | portfolio = luigi.Parameter() 14 | portfolio_task_reference = luigi.Parameter() 15 | cachable_level = constants.CACHE_LEVEL_RUN 16 | 17 | def params_for_results_display(self): 18 | return { 19 | "task_reference": self.task_reference, 20 | "puppet_account_id": self.puppet_account_id, 21 | "portfolio": self.portfolio, 22 | "region": self.region, 23 | "account_id": self.account_id, 24 | } 25 | 26 | def run(self): 27 | with self.spoke_regional_client("servicecatalog") as servicecatalog: 28 | if self.puppet_account_id == self.account_id: 29 | spoke_portfolio = aws.find_portfolio(servicecatalog, self.portfolio) 30 | else: 31 | hub_portfolio_details = self.get_output_from_reference_dependency( 32 | self.portfolio_task_reference 33 | ) 34 | 35 | spoke_portfolio = aws.ensure_portfolio( 36 | servicecatalog, 37 | self.portfolio, 38 | hub_portfolio_details.get("ProviderName"), 39 | hub_portfolio_details.get("Description"), 40 | ) 41 | self.write_output(spoke_portfolio) 42 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/create_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class CreateSpokeLocalPortfolioTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio = "portfolio" 13 | portfolio_task_reference = "portfolio_task_reference" 14 | 15 | def setUp(self) -> None: 16 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 17 | create_spoke_local_portfolio_task, 18 | ) 19 | 20 | self.module = create_spoke_local_portfolio_task 21 | 22 | self.sut = self.module.CreateSpokeLocalPortfolioTask( 23 | **self.get_common_args(), 24 | account_id=self.account_id, 25 | region=self.region, 26 | portfolio=self.portfolio, 27 | portfolio_task_reference=self.portfolio_task_reference, 28 | ) 29 | 30 | self.wire_up_mocks() 31 | 32 | def test_params_for_results_display(self): 33 | # setup 34 | expected_result = { 35 | "task_reference": self.task_reference, 36 | "puppet_account_id": self.puppet_account_id, 37 | "portfolio": self.portfolio, 38 | "region": self.region, 39 | "account_id": self.account_id, 40 | } 41 | 42 | # exercise 43 | actual_result = self.sut.params_for_results_display() 44 | 45 | # verify 46 | self.assertEqual(expected_result, actual_result) 47 | 48 | @skip 49 | def test_run(self): 50 | # setup 51 | # exercise 52 | actual_result = self.sut.run() 53 | 54 | # verify 55 | raise NotImplementedError() 56 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/disassociate_products_from_portfolio_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class DisassociateProductsFromPortfolio(tasks.TaskWithReference): 11 | account_id = luigi.Parameter() 12 | region = luigi.Parameter() 13 | portfolio = luigi.Parameter() 14 | portfolio_task_reference = luigi.Parameter() 15 | cachable_level = constants.CACHE_LEVEL_RUN 16 | 17 | def params_for_results_display(self): 18 | return { 19 | "account_id": self.account_id, 20 | "region": self.region, 21 | "portfolio": self.portfolio, 22 | } 23 | 24 | def run(self): 25 | portfolio = self.get_output_from_reference_dependency( 26 | self.portfolio_task_reference 27 | ) 28 | portfolio_id = portfolio.get("Id") 29 | 30 | if portfolio_id is None: 31 | self.write_empty_output() 32 | return 33 | else: 34 | disassociations = list() 35 | with self.spoke_regional_client("servicecatalog") as servicecatalog: 36 | paginator = servicecatalog.get_paginator("search_products_as_admin") 37 | for page in paginator.paginate(PortfolioId=portfolio_id): 38 | for product_view_details in page.get("ProductViewDetails", []): 39 | product_id = product_view_details.get( 40 | "ProductViewSummary", {} 41 | ).get("ProductId") 42 | servicecatalog.disassociate_product_from_portfolio( 43 | PortfolioId=portfolio_id, 44 | ProductId=product_id, 45 | ) 46 | disassociations.append( 47 | dict(portfolio_id=portfolio_id, product_id=product_id) 48 | ) 49 | 50 | self.write_empty_output() 51 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/disassociate_products_from_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DisassociateProductsFromPortfolioTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio = "portfolio" 13 | portfolio_task_reference = "portfolio_task_reference" 14 | 15 | def setUp(self) -> None: 16 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 17 | disassociate_products_from_portfolio_task, 18 | ) 19 | 20 | self.module = disassociate_products_from_portfolio_task 21 | 22 | self.sut = self.module.DisassociateProductsFromPortfolio( 23 | **self.get_common_args(), 24 | account_id=self.account_id, 25 | region=self.region, 26 | portfolio=self.portfolio, 27 | portfolio_task_reference=self.portfolio_task_reference, 28 | ) 29 | 30 | self.wire_up_mocks() 31 | 32 | def test_params_for_results_display(self): 33 | # setup 34 | expected_result = { 35 | "account_id": self.account_id, 36 | "region": self.region, 37 | "portfolio": self.portfolio, 38 | } 39 | 40 | # exercise 41 | actual_result = self.sut.params_for_results_display() 42 | 43 | # verify 44 | self.assertEqual(expected_result, actual_result) 45 | 46 | @skip 47 | def test_run(self): 48 | # setup 49 | # exercise 50 | actual_result = self.sut.run() 51 | 52 | # verify 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/import_into_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class ImportIntoSpokeLocalPortfolioTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio_task_reference = "portfolio_task_reference" 13 | hub_portfolio_task_reference = "hub_portfolio_task_reference" 14 | portfolio_get_all_products_and_their_versions_ref = ( 15 | "portfolio_get_all_products_and_their_versions_ref" 16 | ) 17 | portfolio_get_all_products_and_their_versions_for_hub_ref = ( 18 | "portfolio_get_all_products_and_their_versions_for_hub_ref" 19 | ) 20 | 21 | def setUp(self) -> None: 22 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 23 | import_into_spoke_local_portfolio_task, 24 | ) 25 | 26 | self.module = import_into_spoke_local_portfolio_task 27 | 28 | self.sut = self.module.ImportIntoSpokeLocalPortfolioTask( 29 | **self.get_common_args(), 30 | account_id=self.account_id, 31 | region=self.region, 32 | portfolio_task_reference=self.portfolio_task_reference, 33 | hub_portfolio_task_reference=self.hub_portfolio_task_reference, 34 | portfolio_get_all_products_and_their_versions_ref=self.portfolio_get_all_products_and_their_versions_ref, 35 | portfolio_get_all_products_and_their_versions_for_hub_ref=self.portfolio_get_all_products_and_their_versions_for_hub_ref, 36 | ) 37 | 38 | self.wire_up_mocks() 39 | 40 | def test_params_for_results_display(self): 41 | # setup 42 | expected_result = { 43 | "task_reference": self.task_reference, 44 | "puppet_account_id": self.puppet_account_id, 45 | "region": self.region, 46 | "account_id": self.account_id, 47 | } 48 | 49 | # exercise 50 | actual_result = self.sut.params_for_results_display() 51 | 52 | # verify 53 | self.assertEqual(expected_result, actual_result) 54 | 55 | @skip 56 | def test_run(self): 57 | # setup 58 | # exercise 59 | actual_result = self.sut.run() 60 | 61 | # verify 62 | raise NotImplementedError() 63 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/terminate_associations_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateAssociationTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio = "portfolio" 13 | portfolio_task_reference = "portfolio_task_reference" 14 | 15 | def setUp(self) -> None: 16 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 17 | terminate_associations_task, 18 | ) 19 | 20 | self.module = terminate_associations_task 21 | 22 | self.sut = self.module.TerminateAssociationTask( 23 | **self.get_common_args(), 24 | account_id=self.account_id, 25 | region=self.region, 26 | portfolio=self.portfolio, 27 | portfolio_task_reference=self.portfolio_task_reference, 28 | ) 29 | 30 | self.wire_up_mocks() 31 | 32 | def test_params_for_results_display(self): 33 | # setup 34 | expected_result = { 35 | "task_reference": self.task_reference, 36 | "puppet_account_id": self.puppet_account_id, 37 | "portfolio": self.portfolio, 38 | "region": self.region, 39 | "account_id": self.account_id, 40 | } 41 | 42 | # exercise 43 | actual_result = self.sut.params_for_results_display() 44 | 45 | # verify 46 | self.assertEqual(expected_result, actual_result) 47 | 48 | @skip 49 | def test_run(self): 50 | # setup 51 | # exercise 52 | actual_result = self.sut.run() 53 | 54 | # verify 55 | raise NotImplementedError() 56 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/terminate_spoke_local_portfolio_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import aws, constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class TerminateSpokeLocalPortfolioTask(tasks.TaskWithReference): 11 | account_id = luigi.Parameter() 12 | region = luigi.Parameter() 13 | portfolio = luigi.Parameter() 14 | cachable_level = constants.CACHE_LEVEL_RUN 15 | 16 | def params_for_results_display(self): 17 | return { 18 | "task_reference": self.task_reference, 19 | "puppet_account_id": self.puppet_account_id, 20 | "portfolio": self.portfolio, 21 | "region": self.region, 22 | "account_id": self.account_id, 23 | } 24 | 25 | def run(self): 26 | with self.spoke_regional_client("servicecatalog") as servicecatalog: 27 | if self.puppet_account_id == self.account_id: 28 | self.write_empty_output() 29 | else: 30 | portfolio = aws.find_portfolio(servicecatalog, self.portfolio) 31 | if portfolio is False: 32 | self.write_empty_output() 33 | else: 34 | servicecatalog.delete_portfolio(Id=portfolio.get("Id")) 35 | self.write_empty_output() 36 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/portfolio_management/terminate_spoke_local_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateSpokeLocalPortfolioTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | region = "region" 12 | portfolio = "portfolio" 13 | 14 | def setUp(self) -> None: 15 | from servicecatalog_puppet.workflow.portfolio.portfolio_management import ( 16 | terminate_spoke_local_portfolio_task, 17 | ) 18 | 19 | self.module = terminate_spoke_local_portfolio_task 20 | 21 | self.sut = self.module.TerminateSpokeLocalPortfolioTask( 22 | **self.get_common_args(), 23 | account_id=self.account_id, 24 | region=self.region, 25 | portfolio=self.portfolio, 26 | ) 27 | 28 | self.wire_up_mocks() 29 | 30 | def test_params_for_results_display(self): 31 | # setup 32 | expected_result = { 33 | "task_reference": self.task_reference, 34 | "puppet_account_id": self.puppet_account_id, 35 | "portfolio": self.portfolio, 36 | "region": self.region, 37 | "account_id": self.account_id, 38 | } 39 | 40 | # exercise 41 | actual_result = self.sut.params_for_results_display() 42 | 43 | # verify 44 | self.assertEqual(expected_result, actual_result) 45 | 46 | @skip 47 | def test_run(self): 48 | # setup 49 | # exercise 50 | actual_result = self.sut.run() 51 | 52 | # verify 53 | raise NotImplementedError() 54 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/sharing_management/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/sharing_management/describe_portfolio_shares_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | from servicecatalog_puppet import constants 7 | from servicecatalog_puppet.workflow.dependencies import tasks 8 | 9 | 10 | class DescribePortfolioSharesTask(tasks.TaskWithReferenceAndCommonParameters): 11 | portfolio_task_reference = luigi.Parameter() 12 | type = luigi.Parameter() 13 | cachable_level = constants.CACHE_LEVEL_RUN 14 | 15 | def params_for_results_display(self): 16 | return { 17 | "task_reference": self.task_reference, 18 | "region": self.region, 19 | "account_id": self.account_id, 20 | "portfolio_task_reference": self.portfolio_task_reference, 21 | "type": self.type, 22 | } 23 | 24 | def run(self): 25 | portfolio_id = self.get_attribute_from_output_from_reference_dependency( 26 | "Id", self.portfolio_task_reference 27 | ) 28 | 29 | with self.spoke_regional_client("servicecatalog") as servicecatalog: 30 | has_more = True 31 | args = dict( 32 | PortfolioId=portfolio_id, 33 | Type=self.type, 34 | ) 35 | shares = dict() 36 | while has_more: 37 | result = servicecatalog.describe_portfolio_shares(**args) 38 | for portfolio_share_detail in result.get("PortfolioShareDetails", []): 39 | shares[portfolio_share_detail.get("PrincipalId")] = ( 40 | portfolio_share_detail 41 | ) 42 | if result.get("NextPageToken"): 43 | has_more = True 44 | args["PageToken"] = result.get("NextPageToken") 45 | else: 46 | has_more = False 47 | self.write_output(shares) 48 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/sharing_management/share_and_accept_portfolio_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class ShareAndAcceptPortfolioForAccountTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | account_id = "account_id" 13 | region = "region" 14 | portfolio = "portfolio" 15 | portfolio_task_reference = "portfolio_task_reference" 16 | share_tag_options = True 17 | share_principals = True 18 | describe_portfolio_shares_task_ref = "describe_portfolio_shares_task_ref" 19 | 20 | def setUp(self) -> None: 21 | from servicecatalog_puppet.workflow.portfolio.sharing_management import ( 22 | share_and_accept_portfolio_task, 23 | ) 24 | 25 | self.module = share_and_accept_portfolio_task 26 | 27 | self.sut = self.module.ShareAndAcceptPortfolioForAccountTask( 28 | **self.get_common_args(), 29 | account_id=self.account_id, 30 | region=self.region, 31 | portfolio=self.portfolio, 32 | portfolio_task_reference=self.portfolio_task_reference, 33 | share_tag_options=self.share_tag_options, 34 | share_principals=self.share_principals, 35 | describe_portfolio_shares_task_ref=self.describe_portfolio_shares_task_ref, 36 | ) 37 | 38 | self.wire_up_mocks() 39 | 40 | def test_params_for_results_display(self): 41 | # setup 42 | expected_result = { 43 | "task_reference": self.task_reference, 44 | "puppet_account_id": self.puppet_account_id, 45 | "portfolio": self.portfolio, 46 | "region": self.region, 47 | "account_id": self.account_id, 48 | "share_tag_options": self.share_tag_options, 49 | "share_principals": self.share_principals, 50 | } 51 | 52 | # exercise 53 | actual_result = self.sut.params_for_results_display() 54 | 55 | # verify 56 | self.assertEqual(expected_result, actual_result) 57 | 58 | @skip 59 | def test_run(self): 60 | # setup 61 | # exercise 62 | actual_result = self.sut.run() 63 | 64 | # verify 65 | raise NotImplementedError() 66 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/portfolio/sharing_management/share_portfolio_via_orgs_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class SharePortfolioViaOrgsTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | region = "region" 11 | portfolio = "portfolio" 12 | ou_to_share_with = "ou_to_share_with" 13 | portfolio_task_reference = "portfolio_task_reference" 14 | share_tag_options = True 15 | share_principals = True 16 | describe_portfolio_shares_task_ref = "describe_portfolio_shares_task_ref" 17 | 18 | def setUp(self) -> None: 19 | from servicecatalog_puppet.workflow.portfolio.sharing_management import ( 20 | share_portfolio_via_orgs_task, 21 | ) 22 | 23 | self.module = share_portfolio_via_orgs_task 24 | 25 | self.sut = self.module.SharePortfolioViaOrgsTask( 26 | **self.get_common_args(), 27 | region=self.region, 28 | portfolio=self.portfolio, 29 | ou_to_share_with=self.ou_to_share_with, 30 | portfolio_task_reference=self.portfolio_task_reference, 31 | share_tag_options=self.share_tag_options, 32 | share_principals=self.share_principals, 33 | describe_portfolio_shares_task_ref=self.describe_portfolio_shares_task_ref, 34 | ) 35 | 36 | self.wire_up_mocks() 37 | 38 | def test_params_for_results_display(self): 39 | # setup 40 | expected_result = { 41 | "puppet_account_id": self.puppet_account_id, 42 | "portfolio": self.portfolio, 43 | "region": self.region, 44 | "ou_to_share_with": self.ou_to_share_with, 45 | "share_tag_options": self.share_tag_options, 46 | "share_principals": self.share_principals, 47 | } 48 | 49 | # exercise 50 | actual_result = self.sut.params_for_results_display() 51 | 52 | # verify 53 | self.assertEqual(expected_result, actual_result) 54 | 55 | @skip 56 | def test_run(self): 57 | # setup 58 | # exercise 59 | actual_result = self.sut.run() 60 | 61 | # verify 62 | raise NotImplementedError() 63 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/runner_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/s3/get_s3_object_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import json 4 | from copy import deepcopy 5 | 6 | import jmespath 7 | import luigi 8 | 9 | from servicecatalog_puppet import constants 10 | from servicecatalog_puppet.workflow.dependencies import tasks 11 | 12 | 13 | class GetS3ObjectTask(tasks.TaskWithReference): 14 | account_id = luigi.Parameter() 15 | key = luigi.Parameter() 16 | region = luigi.Parameter() 17 | cachable_level = constants.CACHE_LEVEL_RUN 18 | 19 | def params_for_results_display(self): 20 | return { 21 | "task_reference": self.task_reference, 22 | "account_id": self.account_id, 23 | "region": self.region, 24 | "key": self.key, 25 | } 26 | 27 | def run(self): 28 | with self.spoke_regional_client("s3") as s3: 29 | object = ( 30 | s3.get_object( 31 | Bucket=f"sc-puppet-parameters-{self.account_id}", Key=self.key 32 | ) 33 | .get("Body") 34 | .read() 35 | ) 36 | self.write_output(object, skip_json_encode=True) 37 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/service_control_policies/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/service_control_policies/do_terminate_service_control_policies_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DoTerminateServiceControlPoliciesTaskTest( 10 | tasks_unit_tests_helper.PuppetTaskUnitTest 11 | ): 12 | service_control_policy_name = "service_control_policy_name" 13 | region = "region" 14 | account_id = "account_id" 15 | ou_name = "ou_name" 16 | content = {} 17 | description = "description" 18 | manifest_file_path = "manifest_file_path" 19 | requested_priority = 1 20 | 21 | def setUp(self) -> None: 22 | from servicecatalog_puppet.workflow.service_control_policies import ( 23 | do_terminate_service_control_policies_task, 24 | ) 25 | 26 | self.module = do_terminate_service_control_policies_task 27 | 28 | self.sut = self.module.DoTerminateServiceControlPoliciesTask( 29 | **self.get_common_args(), 30 | service_control_policy_name=self.service_control_policy_name, 31 | region=self.region, 32 | account_id=self.account_id, 33 | ou_name=self.ou_name, 34 | content=self.content, 35 | description=self.description, 36 | manifest_file_path=self.manifest_file_path, 37 | requested_priority=self.requested_priority, 38 | ) 39 | 40 | self.wire_up_mocks() 41 | 42 | def test_params_for_results_display(self): 43 | # setup 44 | expected_result = { 45 | "puppet_account_id": self.puppet_account_id, 46 | "service_control_policy_name": self.service_control_policy_name, 47 | "region": self.region, 48 | "account_id": self.account_id, 49 | "ou_name": self.ou_name, 50 | } 51 | 52 | # exercise 53 | actual_result = self.sut.params_for_results_display() 54 | 55 | # verify 56 | self.assertEqual(expected_result, actual_result) 57 | 58 | @skip 59 | def test_requires(self): 60 | # setup 61 | # exercise 62 | actual_result = self.sut.requires() 63 | 64 | # verify 65 | raise NotImplementedError() 66 | 67 | @skip 68 | def test_run(self): 69 | # setup 70 | # exercise 71 | actual_result = self.sut.run() 72 | 73 | # verify 74 | raise NotImplementedError() 75 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/simulate_policies/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/ssm/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/stack/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/stack/get_cloud_formation_template_from_s3.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import os 4 | 5 | import botocore 6 | import luigi 7 | 8 | from servicecatalog_puppet import constants 9 | from servicecatalog_puppet.workflow.dependencies import tasks 10 | 11 | 12 | class GetCloudFormationTemplateFromS3(tasks.TaskWithReference): 13 | account_id = luigi.Parameter() 14 | 15 | bucket = luigi.Parameter() 16 | key = luigi.Parameter() 17 | region = luigi.Parameter() 18 | version_id = luigi.Parameter() 19 | cachable_level = constants.CACHE_LEVEL_NO_CACHE 20 | 21 | def params_for_results_display(self): 22 | return { 23 | "task_reference": self.task_reference, 24 | "bucket": self.bucket, 25 | "key": self.key.replace("-${AWS::Region}", f"-{self.region}"), 26 | "region": self.region, 27 | "version_id": self.version_id, 28 | } 29 | 30 | def run(self): 31 | with self.hub_client("s3") as s3: 32 | regional_template = self.key.replace("-${AWS::Region}", f"-{self.region}") 33 | global_template = self.key.replace("-${AWS::Region}", "") 34 | p = dict(Bucket=self.bucket) 35 | if self.version_id != "": 36 | p["VersionId"] = self.version_id 37 | 38 | output = self.output_location_non_cached 39 | self.info(f"Trying regional template: {regional_template} to {output}") 40 | if not os.path.exists(os.path.dirname(output)): 41 | os.makedirs(os.path.dirname(output)) 42 | 43 | try: 44 | s3.download_file(Key=regional_template, Filename=output, **p) 45 | except botocore.exceptions.ClientError as e: 46 | if "404" == str(e.response["Error"]["Code"]): 47 | self.info( 48 | f"Didnt find regional template: {regional_template}, will try global: {global_template}" 49 | ) 50 | s3.download_file(Key=global_template, Filename=output, **p) 51 | else: 52 | raise e 53 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/stack/get_cloud_formation_template_from_s3_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class GetCloudFormationTemplateFromS3Test(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | bucket = "bucket" 12 | key = "key" 13 | region = "region" 14 | version_id = "version_id" 15 | 16 | def setUp(self) -> None: 17 | from servicecatalog_puppet.workflow.stack import ( 18 | get_cloud_formation_template_from_s3, 19 | ) 20 | 21 | self.module = get_cloud_formation_template_from_s3 22 | 23 | self.sut = self.module.GetCloudFormationTemplateFromS3( 24 | **self.get_common_args(), 25 | account_id=self.account_id, 26 | bucket=self.bucket, 27 | key=self.key, 28 | region=self.region, 29 | version_id=self.version_id, 30 | ) 31 | 32 | self.wire_up_mocks() 33 | 34 | def test_params_for_results_display(self): 35 | # setup 36 | expected_result = { 37 | "task_reference": self.task_reference, 38 | "bucket": self.bucket, 39 | "key": self.key.replace("-${AWS::Region}", f"-{self.region}"), 40 | "region": self.region, 41 | "version_id": self.version_id, 42 | } 43 | 44 | # exercise 45 | actual_result = self.sut.params_for_results_display() 46 | 47 | # verify 48 | self.assertEqual(expected_result, actual_result) 49 | 50 | @skip 51 | def test_run(self): 52 | # setup 53 | # exercise 54 | actual_result = self.sut.run() 55 | 56 | # verify 57 | raise NotImplementedError() 58 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/stack/prepare_account_for_stack_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | import troposphere as t 6 | from troposphere import iam 7 | 8 | from servicecatalog_puppet import config, constants 9 | from servicecatalog_puppet.workflow.dependencies import tasks 10 | 11 | 12 | class PrepareAccountForStackTask(tasks.TaskWithReference): 13 | region = luigi.Parameter() 14 | account_id = luigi.Parameter() 15 | 16 | cachable_level = constants.CACHE_LEVEL_PERMANENT 17 | 18 | def params_for_results_display(self): 19 | return { 20 | "region": self.region, 21 | "account_id": self.account_id, 22 | } 23 | 24 | def run(self): 25 | puppet_version = constants.VERSION 26 | description = f"""Bootstrap template used to configure spoke account for stack use 27 | {{"version": "{puppet_version}", "framework": "servicecatalog-puppet", "role": "bootstrap-spoke-stack"}}""" 28 | 29 | template = t.Template(Description=description) 30 | 31 | template.add_resource( 32 | iam.Role( 33 | "PuppetStackRole", 34 | RoleName="PuppetStackRole", 35 | ManagedPolicyArns=[ 36 | t.Sub("arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess") 37 | ], 38 | Path=config.get_puppet_role_path(), 39 | AssumeRolePolicyDocument={ 40 | "Version": "2012-10-17", 41 | "Statement": [ 42 | { 43 | "Action": ["sts:AssumeRole"], 44 | "Effect": "Allow", 45 | "Principal": {"Service": ["cloudformation.amazonaws.com"]}, 46 | } 47 | ], 48 | }, 49 | ) 50 | ) 51 | 52 | with self.spoke_regional_client("cloudformation") as cloudformation: 53 | cloudformation.create_or_update( 54 | StackName=constants.STACK_SPOKE_PREP_STACK_NAME, 55 | TemplateBody=template.to_yaml(), 56 | Capabilities=["CAPABILITY_NAMED_IAM"], 57 | ShouldDeleteRollbackComplete=self.should_delete_rollback_complete_stacks, 58 | Tags=self.initialiser_stack_tags, 59 | ) 60 | 61 | self.write_output(self.params_for_results_display()) 62 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/stack/provision_stack_dry_run_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class ProvisionStackDryRunTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | stack_name = "stack_name" 11 | region = "region" 12 | account_id = "account_id" 13 | bucket = "bucket" 14 | key = "key" 15 | version_id = "version_id" 16 | launch_name = "launch_name" 17 | stack_set_name = "stack_set_name" 18 | get_s3_template_ref = "get_s3_template_ref" 19 | capabilities = [] 20 | ssm_param_inputs = [] 21 | launch_parameters = {} 22 | manifest_parameters = {} 23 | account_parameters = {} 24 | ssm_param_outputs = [] 25 | execution = "execution" 26 | manifest_file_path = "manifest_file_path" 27 | tags = [] 28 | 29 | def setUp(self) -> None: 30 | from servicecatalog_puppet.workflow.stack import provision_stack_dry_run_task 31 | 32 | self.module = provision_stack_dry_run_task 33 | 34 | self.sut = self.module.ProvisionStackDryRunTask( 35 | **self.get_common_args(), 36 | stack_name=self.stack_name, 37 | region=self.region, 38 | account_id=self.account_id, 39 | bucket=self.bucket, 40 | key=self.key, 41 | version_id=self.version_id, 42 | launch_name=self.launch_name, 43 | stack_set_name=self.stack_set_name, 44 | get_s3_template_ref=self.get_s3_template_ref, 45 | capabilities=self.capabilities, 46 | ssm_param_inputs=self.ssm_param_inputs, 47 | launch_parameters=self.launch_parameters, 48 | manifest_parameters=self.manifest_parameters, 49 | account_parameters=self.account_parameters, 50 | ssm_param_outputs=self.ssm_param_outputs, 51 | execution=self.execution, 52 | manifest_file_path=self.manifest_file_path, 53 | tags=self.tags, 54 | ) 55 | 56 | self.wire_up_mocks() 57 | 58 | @skip 59 | def test_run(self): 60 | # setup 61 | # exercise 62 | actual_result = self.sut.run() 63 | 64 | # verify 65 | raise NotImplementedError() 66 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/stack/terminate_stack_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateStackTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | stack_name = "stack_name" 11 | region = "region" 12 | account_id = "account_id" 13 | bucket = "bucket" 14 | key = "key" 15 | version_id = "version_id" 16 | launch_name = "launch_name" 17 | stack_set_name = "stack_set_name" 18 | capabilities = [] 19 | ssm_param_inputs = [] 20 | launch_parameters = {} 21 | manifest_parameters = {} 22 | account_parameters = {} 23 | ssm_param_outputs = [] 24 | execution = "execution" 25 | 26 | def setUp(self) -> None: 27 | from servicecatalog_puppet.workflow.stack import terminate_stack_task 28 | 29 | self.module = terminate_stack_task 30 | 31 | self.sut = self.module.TerminateStackTask( 32 | **self.get_common_args(), 33 | stack_name=self.stack_name, 34 | region=self.region, 35 | account_id=self.account_id, 36 | bucket=self.bucket, 37 | key=self.key, 38 | version_id=self.version_id, 39 | launch_name=self.launch_name, 40 | stack_set_name=self.stack_set_name, 41 | capabilities=self.capabilities, 42 | ssm_param_inputs=self.ssm_param_inputs, 43 | launch_parameters=self.launch_parameters, 44 | manifest_parameters=self.manifest_parameters, 45 | account_parameters=self.account_parameters, 46 | ssm_param_outputs=self.ssm_param_outputs, 47 | execution=self.execution 48 | ) 49 | 50 | self.wire_up_mocks() 51 | 52 | def test_params_for_results_display(self): 53 | # setup 54 | expected_result = { 55 | "puppet_account_id": self.puppet_account_id, 56 | "stack_name": self.stack_name, 57 | "account_id": self.account_id, 58 | "region": self.region, 59 | } 60 | 61 | # exercise 62 | actual_result = self.sut.params_for_results_display() 63 | 64 | # verify 65 | self.assertEqual(expected_result, actual_result) 66 | 67 | @skip 68 | def test_run(self): 69 | # setup 70 | # exercise 71 | actual_result = self.sut.run() 72 | 73 | # verify 74 | raise NotImplementedError() 75 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/tag_policies/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/tag_policies/do_execute_tag_policies_task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import functools 4 | 5 | import luigi 6 | 7 | from servicecatalog_puppet import constants 8 | from servicecatalog_puppet.workflow.dependencies import tasks 9 | 10 | 11 | class DoExecuteTagPoliciesTask(tasks.TaskWithReference): 12 | tag_policy_name = luigi.Parameter() 13 | 14 | get_or_create_policy_ref = luigi.Parameter() 15 | 16 | region = luigi.Parameter() 17 | account_id = luigi.Parameter() 18 | ou_name = luigi.Parameter() 19 | 20 | content = luigi.DictParameter() 21 | description = luigi.Parameter() 22 | 23 | requested_priority = luigi.IntParameter() 24 | manifest_file_path = luigi.Parameter() 25 | cachable_level = constants.CACHE_LEVEL_RUN 26 | 27 | def params_for_results_display(self): 28 | return { 29 | "puppet_account_id": self.puppet_account_id, 30 | "tag_policy_name": self.tag_policy_name, 31 | "region": self.region, 32 | "account_id": self.account_id, 33 | "ou_name": self.ou_name, 34 | } 35 | 36 | @functools.lru_cache(maxsize=32) 37 | def target(self): 38 | with self.organizations_policy_client() as orgs: 39 | if self.account_id != "": 40 | return self.account_id 41 | else: 42 | if str(self.ou_name).startswith("/"): 43 | return orgs.convert_path_to_ou(self.ou_name) 44 | else: 45 | return self.ou_name 46 | 47 | def has_policy_attached(self, orgs): 48 | paginator = orgs.get_paginator("list_policies_for_target") 49 | for page in paginator.paginate(TargetId=self.target(), Filter="TAG_POLICY"): 50 | for policy in page.get("Policies", []): 51 | if policy.get("Name") == self.tag_policy_name: 52 | return True 53 | return False 54 | 55 | def run(self): 56 | with self.organizations_policy_client() as orgs: 57 | self.info("Ensuring attachments for policies") 58 | policy_id = self.get_output_from_reference_dependency( 59 | self.get_or_create_policy_ref 60 | ).get("Id") 61 | if self.has_policy_attached(orgs): 62 | self.write_empty_output() 63 | else: 64 | orgs.attach_policy(PolicyId=policy_id, TargetId=self.target()) 65 | self.write_empty_output() 66 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/tag_policies/do_execute_tag_policies_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class DoExecuteTagPoliciesTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | tag_policy_name = "tag_policy_name" 11 | region = "region" 12 | account_id = "account_id" 13 | ou_name = "ou_name" 14 | content = {} 15 | description = "description" 16 | manifest_file_path = "manifest_file_path" 17 | requested_priority = 1 18 | get_or_create_policy_ref = "get_or_create_policy_ref" 19 | 20 | def setUp(self) -> None: 21 | from servicecatalog_puppet.workflow.tag_policies import ( 22 | do_execute_tag_policies_task, 23 | ) 24 | 25 | self.module = do_execute_tag_policies_task 26 | 27 | self.sut = self.module.DoExecuteTagPoliciesTask( 28 | **self.get_common_args(), 29 | tag_policy_name=self.tag_policy_name, 30 | region=self.region, 31 | account_id=self.account_id, 32 | ou_name=self.ou_name, 33 | content=self.content, 34 | description=self.description, 35 | manifest_file_path=self.manifest_file_path, 36 | requested_priority=self.requested_priority, 37 | get_or_create_policy_ref=self.get_or_create_policy_ref, 38 | ) 39 | 40 | self.wire_up_mocks() 41 | 42 | def test_params_for_results_display(self): 43 | # setup 44 | expected_result = { 45 | "puppet_account_id": self.puppet_account_id, 46 | "tag_policy_name": self.tag_policy_name, 47 | "region": self.region, 48 | "account_id": self.account_id, 49 | "ou_name": self.ou_name, 50 | } 51 | 52 | # exercise 53 | actual_result = self.sut.params_for_results_display() 54 | 55 | # verify 56 | self.assertEqual(expected_result, actual_result) 57 | 58 | @skip 59 | def test_requires(self): 60 | # setup 61 | # exercise 62 | actual_result = self.sut.requires() 63 | 64 | # verify 65 | raise NotImplementedError() 66 | 67 | @skip 68 | def test_run(self): 69 | # setup 70 | # exercise 71 | actual_result = self.sut.run() 72 | 73 | # verify 74 | raise NotImplementedError() 75 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/task_mixins/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/tasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import luigi 5 | 6 | 7 | class PuppetTask( 8 | luigi.Task, 9 | ): 10 | pass 11 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workflow_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import networkx as nx 4 | 5 | 6 | def ensure_no_cyclic_dependencies(name, tasks): 7 | g = nx.DiGraph() 8 | for t_name, t in tasks.items(): 9 | uid = t_name 10 | data = t 11 | g.add_nodes_from( 12 | [ 13 | (uid, data), 14 | ] 15 | ) 16 | for duid in t.get("dependencies_by_reference", []): 17 | g.add_edge(uid, duid) 18 | 19 | is_directed_acyclic_graph = nx.is_directed_acyclic_graph(g) 20 | if not is_directed_acyclic_graph: 21 | print( 22 | f"Generated graph is not a DAG, looking for cycles to help debug (this may take 1 - 2 hours)" 23 | ) 24 | try: 25 | cycle = nx.find_cycle(g) 26 | raise Exception( 27 | f"found cyclic dependency task reference file {name} between: {cycle}" 28 | ) 29 | except nx.exception.NetworkXNoCycle: 30 | pass 31 | raise Exception( 32 | "Generated graph is not a DAG but could not find cycles to help debug." 33 | ) 34 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workspaces/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workspaces/prepare_account_for_workspace_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class PrepareAccountForWorkspaceTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | account_id = "account_id" 11 | 12 | def setUp(self) -> None: 13 | from servicecatalog_puppet.workflow.workspaces import ( 14 | prepare_account_for_workspace_task, 15 | ) 16 | 17 | self.module = prepare_account_for_workspace_task 18 | 19 | self.sut = self.module.PrepareAccountForWorkspaceTask( 20 | **self.get_common_args(), account_id=self.account_id 21 | ) 22 | 23 | self.wire_up_mocks() 24 | 25 | def test_params_for_results_display(self): 26 | # setup 27 | expected_result = { 28 | "account_id": self.account_id, 29 | } 30 | 31 | # exercise 32 | actual_result = self.sut.params_for_results_display() 33 | 34 | # verify 35 | self.assertEqual(expected_result, actual_result) 36 | 37 | @skip 38 | def test_run(self): 39 | # setup 40 | # exercise 41 | actual_result = self.sut.run() 42 | 43 | # verify 44 | raise NotImplementedError() 45 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workspaces/provision_dry_run_workspace_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class ProvisionDryRunWorkspaceTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | workspace_name = "workspace_name" 11 | region = "region" 12 | account_id = "account_id" 13 | bucket = "bucket" 14 | key = "key" 15 | version_id = "version_id" 16 | ssm_param_inputs = [] 17 | launch_parameters = {} 18 | manifest_parameters = {} 19 | account_parameters = {} 20 | ssm_param_outputs = [] 21 | execution = "execution" 22 | 23 | def setUp(self) -> None: 24 | from servicecatalog_puppet.workflow.workspaces import ( 25 | provision_dry_run_workspace_task, 26 | ) 27 | 28 | self.module = provision_dry_run_workspace_task 29 | 30 | self.sut = self.module.ProvisionDryRunWorkspaceTask( 31 | **self.get_common_args(), 32 | workspace_name=self.workspace_name, 33 | region=self.region, 34 | account_id=self.account_id, 35 | bucket=self.bucket, 36 | key=self.key, 37 | version_id=self.version_id, 38 | ssm_param_inputs=self.ssm_param_inputs, 39 | launch_parameters=self.launch_parameters, 40 | manifest_parameters=self.manifest_parameters, 41 | account_parameters=self.account_parameters, 42 | ssm_param_outputs=self.ssm_param_outputs, 43 | execution=self.execution 44 | ) 45 | 46 | self.wire_up_mocks() 47 | 48 | def test_params_for_results_display(self): 49 | # setup 50 | expected_result = { 51 | "puppet_account_id": self.puppet_account_id, 52 | "workspace_name": self.workspace_name, 53 | "region": self.region, 54 | "account_id": self.account_id, 55 | } 56 | 57 | # exercise 58 | actual_result = self.sut.params_for_results_display() 59 | 60 | # verify 61 | self.assertEqual(expected_result, actual_result) 62 | 63 | @skip 64 | def test_run(self): 65 | # setup 66 | # exercise 67 | actual_result = self.sut.run() 68 | 69 | # verify 70 | raise NotImplementedError() 71 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workspaces/provision_workspace_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class ProvisionWorkspaceTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | workspace_name = "workspace_name" 11 | region = "region" 12 | account_id = "account_id" 13 | bucket = "bucket" 14 | key = "key" 15 | version_id = "version_id" 16 | ssm_param_inputs = [] 17 | launch_parameters = {} 18 | manifest_parameters = {} 19 | account_parameters = {} 20 | ssm_param_outputs = [] 21 | execution = "execution" 22 | manifest_file_path = "manifest_file_path" 23 | 24 | def setUp(self) -> None: 25 | from servicecatalog_puppet.workflow.workspaces import provision_workspace_task 26 | 27 | self.module = provision_workspace_task 28 | 29 | self.sut = self.module.ProvisionWorkspaceTask( 30 | **self.get_common_args(), 31 | workspace_name=self.workspace_name, 32 | region=self.region, 33 | account_id=self.account_id, 34 | bucket=self.bucket, 35 | key=self.key, 36 | version_id=self.version_id, 37 | ssm_param_inputs=self.ssm_param_inputs, 38 | launch_parameters=self.launch_parameters, 39 | manifest_parameters=self.manifest_parameters, 40 | account_parameters=self.account_parameters, 41 | ssm_param_outputs=self.ssm_param_outputs, 42 | execution=self.execution, 43 | manifest_file_path=self.manifest_file_path 44 | ) 45 | 46 | self.wire_up_mocks() 47 | 48 | def test_params_for_results_display(self): 49 | # setup 50 | expected_result = { 51 | "puppet_account_id": self.puppet_account_id, 52 | "workspace_name": self.workspace_name, 53 | "region": self.region, 54 | "account_id": self.account_id, 55 | } 56 | 57 | # exercise 58 | actual_result = self.sut.params_for_results_display() 59 | 60 | # verify 61 | self.assertEqual(expected_result, actual_result) 62 | 63 | @skip 64 | def test_run(self): 65 | # setup 66 | # exercise 67 | actual_result = self.sut.run() 68 | 69 | # verify 70 | raise NotImplementedError() 71 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workspaces/terminate_dry_run_workspace_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateDryRunWorkspaceTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | workspace_name = "workspace_name" 11 | region = "region" 12 | account_id = "account_id" 13 | bucket = "bucket" 14 | key = "key" 15 | version_id = "version_id" 16 | ssm_param_inputs = [] 17 | launch_parameters = {} 18 | manifest_parameters = {} 19 | account_parameters = {} 20 | ssm_param_outputs = [] 21 | execution = "execution" 22 | 23 | def setUp(self) -> None: 24 | from servicecatalog_puppet.workflow.workspaces import ( 25 | terminate_dry_run_workspace_task, 26 | ) 27 | 28 | self.module = terminate_dry_run_workspace_task 29 | 30 | self.sut = self.module.TerminateDryRunWorkspaceTask( 31 | **self.get_common_args(), 32 | workspace_name=self.workspace_name, 33 | region=self.region, 34 | account_id=self.account_id, 35 | bucket=self.bucket, 36 | key=self.key, 37 | version_id=self.version_id, 38 | ssm_param_inputs=self.ssm_param_inputs, 39 | launch_parameters=self.launch_parameters, 40 | manifest_parameters=self.manifest_parameters, 41 | account_parameters=self.account_parameters, 42 | ssm_param_outputs=self.ssm_param_outputs, 43 | execution=self.execution 44 | ) 45 | 46 | self.wire_up_mocks() 47 | 48 | def test_params_for_results_display(self): 49 | # setup 50 | expected_result = { 51 | "puppet_account_id": self.puppet_account_id, 52 | "workspace_name": self.workspace_name, 53 | "region": self.region, 54 | "account_id": self.account_id, 55 | } 56 | 57 | # exercise 58 | actual_result = self.sut.params_for_results_display() 59 | 60 | # verify 61 | self.assertEqual(expected_result, actual_result) 62 | 63 | @skip 64 | def test_run(self): 65 | # setup 66 | # exercise 67 | actual_result = self.sut.run() 68 | 69 | # verify 70 | raise NotImplementedError() 71 | -------------------------------------------------------------------------------- /servicecatalog_puppet/workflow/workspaces/terminate_workspace_task_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from unittest import skip 5 | 6 | from servicecatalog_puppet.workflow import tasks_unit_tests_helper 7 | 8 | 9 | class TerminateWorkspaceTaskTest(tasks_unit_tests_helper.PuppetTaskUnitTest): 10 | workspace_name = "workspace_name" 11 | region = "region" 12 | account_id = "account_id" 13 | bucket = "bucket" 14 | key = "key" 15 | version_id = "version_id" 16 | ssm_param_inputs = [] 17 | launch_parameters = {} 18 | manifest_parameters = {} 19 | account_parameters = {} 20 | ssm_param_outputs = [] 21 | execution = "execution" 22 | manifest_file_path = "manifest_file_path" 23 | 24 | def setUp(self) -> None: 25 | from servicecatalog_puppet.workflow.workspaces import terminate_workspace_task 26 | 27 | self.module = terminate_workspace_task 28 | 29 | self.sut = self.module.TerminateWorkspaceTask( 30 | **self.get_common_args(), 31 | workspace_name=self.workspace_name, 32 | region=self.region, 33 | account_id=self.account_id, 34 | bucket=self.bucket, 35 | key=self.key, 36 | version_id=self.version_id, 37 | ssm_param_inputs=self.ssm_param_inputs, 38 | launch_parameters=self.launch_parameters, 39 | manifest_parameters=self.manifest_parameters, 40 | account_parameters=self.account_parameters, 41 | ssm_param_outputs=self.ssm_param_outputs, 42 | execution=self.execution, 43 | manifest_file_path=self.manifest_file_path 44 | ) 45 | 46 | self.wire_up_mocks() 47 | 48 | def test_params_for_results_display(self): 49 | # setup 50 | expected_result = { 51 | "puppet_account_id": self.puppet_account_id, 52 | "workspace_name": self.workspace_name, 53 | "region": self.region, 54 | "account_id": self.account_id, 55 | } 56 | 57 | # exercise 58 | actual_result = self.sut.params_for_results_display() 59 | 60 | # verify 61 | self.assertEqual(expected_result, actual_result) 62 | 63 | @skip 64 | def test_run(self): 65 | # setup 66 | # exercise 67 | actual_result = self.sut.run() 68 | 69 | # verify 70 | raise NotImplementedError() 71 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | htmlcov/ 2 | .coverage 3 | -------------------------------------------------------------------------------- /tests/continuous-runner/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /tests/continuous-runner/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "continuous-runner", 3 | "description": "", 4 | "version": "0.1.0", 5 | "dependencies": { 6 | "js-yaml": "^4.1.0" 7 | }, 8 | "devDependencies": { 9 | "serverless-step-functions": "^2.32.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/features/management/bootstrapping-a-puppet-account.feature: -------------------------------------------------------------------------------- 1 | @behavioural @management @puppet-account 2 | Feature: bootstrapping a puppet account 3 | As an operator 4 | I would like to be able to bootstrap a puppet account 5 | So I can use the framework 6 | 7 | @wip 8 | Scenario: Bootstrap new version 9 | Given a "puppet" account has been bootstrapped with version "x" 10 | When I bootstrap a "puppet" account with version "y" 11 | Then the "puppet" account is bootstrapped with version "y" 12 | 13 | @slow 14 | Scenario Outline: Bootstrap with a permission boundary 15 | Given a "puppet" account has not been bootstrapped 16 | And the config has been set 17 | When I bootstrap a "puppet" account with version "x" and set an IAM Boundary for "" 18 | Then the "puppet" account is bootstrapped with version "x" and "" has an IAM Boundary 19 | 20 | Examples: 21 | 22 | | Role | 23 | | PuppetCodePipelineRole | 24 | | SourceRolePermissionsBoundary | 25 | | PuppetGenerateRolePermissionBoundary | 26 | | PuppetDeployRolePermissionBoundary | 27 | | PuppetProvisioningRolePermissionsBoundary | 28 | | CloudFormationDeployRolePermissionsBoundary | 29 | 30 | Scenario: First bootstrap 31 | Given a "puppet" account has not been bootstrapped 32 | And the config has been set 33 | When I bootstrap a "puppet" account with version "x" 34 | Then the "puppet" account is bootstrapped with version "x" 35 | 36 | Scenario: Repeat bootstrap 37 | Given a "puppet" account has been bootstrapped with version "x" 38 | When I bootstrap a "puppet" account with version "x" 39 | Then the "puppet" account is bootstrapped with version "x" 40 | -------------------------------------------------------------------------------- /tests/features/management/bootstrapping-a-spoke-account.feature: -------------------------------------------------------------------------------- 1 | @behavioural @management @spoke-account 2 | Feature: bootstrapping a spoke account 3 | As an operator 4 | I would like to be able to bootstrap a spoke account 5 | So I can provision resources into it 6 | 7 | Scenario: First bootstrap 8 | Given a "spoke" account has not been bootstrapped 9 | When I bootstrap a "spoke" account with version "x" 10 | Then the "spoke" account is bootstrapped with version "x" 11 | 12 | Scenario: Repeat bootstrap 13 | Given a "spoke" account has been bootstrapped with version "x" 14 | When I bootstrap a "spoke" account with version "x" 15 | Then the "spoke" account is bootstrapped with version "x" 16 | 17 | @wip 18 | Scenario: Bootstrap new version 19 | Given a "spoke" account has been bootstrapped with version "x" 20 | When I bootstrap a "spoke" account with version "y" 21 | Then the "spoke" account is bootstrapped with version "y" 22 | -------------------------------------------------------------------------------- /tests/features/steps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-service-catalog-puppet/2068d809452f04bc12f7886b90cc0e4bf20dfd30/tests/features/steps/__init__.py -------------------------------------------------------------------------------- /tests/features/steps/constants.py: -------------------------------------------------------------------------------- 1 | BOUNDARY_ARN_TO_USE = 'arn:aws:iam::aws:policy/AWSCodePipelineFullAccess' -------------------------------------------------------------------------------- /tests/features/steps/test_givens.py: -------------------------------------------------------------------------------- 1 | from behave import given, when, then, step 2 | 3 | from servicecatalog_puppet import config, constants, sdk 4 | from betterboto import client as betterboto_client 5 | 6 | 7 | @given(u'a "{account_type}" account has not been bootstrapped') 8 | def step_impl(context, account_type): 9 | if account_type == "puppet": 10 | sdk.uninstall() 11 | elif account_type == "spoke": 12 | sdk.release_spoke() 13 | 14 | 15 | @given(u'a "{account_type}" account has been bootstrapped with version "{version}"') 16 | def step_impl(context, account_type, version): 17 | with betterboto_client.ClientContextManager('cloudformation') as cloudformation: 18 | if account_type == "puppet": 19 | stacks = cloudformation.describe_stacks_single_page( 20 | StackName=constants.BOOTSTRAP_STACK_NAME 21 | ).get('Stacks', []) 22 | elif account_type == "spoke": 23 | stacks = cloudformation.describe_stacks_single_page( 24 | StackName=f"{constants.BOOTSTRAP_STACK_NAME}-spoke" 25 | ).get('Stacks', []) 26 | 27 | completed = False 28 | for stack in stacks: 29 | if stack.get('StackStatus') == 'CREATE_COMPLETE': 30 | completed = True 31 | break 32 | 33 | if not completed: 34 | raise Exception("not bootstrapped correctly or at all") 35 | 36 | 37 | @given(u'the config has been set') 38 | def step_impl(context): 39 | sdk.upload_config({ 40 | "regions": [ 41 | 'eu-west-1', 42 | 'eu-west-2', 43 | ], 44 | "should_collect_cloudformation_events": False, 45 | "should_forward_events_to_eventbridge": False, 46 | "should_forward_failures_to_opscenter": False, 47 | }) 48 | -------------------------------------------------------------------------------- /tests/features/steps/test_thens.py: -------------------------------------------------------------------------------- 1 | from behave import given, when, then, step 2 | 3 | from servicecatalog_puppet import config, constants, sdk 4 | from betterboto import client as betterboto_client 5 | 6 | 7 | @then(u'the "{account_type}" account is bootstrapped with version "{version}" and "{role}" has an IAM Boundary') 8 | def step_impl(context, account_type, version, role): 9 | param_name = "" 10 | 11 | if account_type == "spoke": 12 | param_name = constants.SPOKE_VERSION_SSM_PARAM_NAME 13 | elif account_type == "puppet": 14 | param_name = constants.PUPPET_VERSION_SSM_PARAM_NAME 15 | 16 | with betterboto_client.ClientContextManager('ssm') as ssm: 17 | ssm.get_parameter(Name=param_name) 18 | 19 | 20 | @then(u'the "{account_type}" account is bootstrapped with version "{version}"') 21 | def step_impl(context, account_type, version): 22 | param_name = "" 23 | 24 | if account_type == "spoke": 25 | param_name = constants.SPOKE_VERSION_SSM_PARAM_NAME 26 | elif account_type == "puppet": 27 | param_name = constants.PUPPET_VERSION_SSM_PARAM_NAME 28 | 29 | with betterboto_client.ClientContextManager('ssm') as ssm: 30 | ssm.get_parameter(Name=param_name) 31 | -------------------------------------------------------------------------------- /tests/features/steps/test_whens.py: -------------------------------------------------------------------------------- 1 | from behave import when 2 | 3 | from servicecatalog_puppet import config, sdk 4 | 5 | from . import constants 6 | 7 | roles = { 8 | "PuppetCodePipelineRole": 'puppet_code_pipeline_role_permission_boundary', 9 | "SourceRolePermissionsBoundary": "source_role_permissions_boundary", 10 | "PuppetGenerateRolePermissionBoundary": "puppet_generate_role_permission_boundary", 11 | "PuppetDeployRolePermissionBoundary": "puppet_deploy_role_permission_boundary", 12 | "PuppetProvisioningRolePermissionsBoundary": "puppet_provisioning_role_permissions_boundary", 13 | "CloudFormationDeployRolePermissionsBoundary": "cloud_formation_deploy_role_permissions_boundary", 14 | 15 | } 16 | 17 | 18 | @when(u'I bootstrap a "{account_type}" account with version "{version}" and set an IAM Boundary for "{role}"') 19 | def step_impl(context, account_type, version, role): 20 | if account_type == "puppet": 21 | args = { 22 | "with_manual_approvals": False 23 | } 24 | args[roles.get(role)] = constants.BOUNDARY_ARN_TO_USE 25 | sdk.bootstrap(**args) 26 | 27 | elif account_type == "spoke": 28 | sdk.bootstrap_spoke( 29 | config.get_puppet_account_id(), 30 | "arn:aws:iam::aws:policy/AdministratorAccess" 31 | ) 32 | 33 | 34 | @when(u'I bootstrap a "{account_type}" account with version "{version}"') 35 | def step_impl(context, account_type, version): 36 | if account_type == "puppet": 37 | sdk.bootstrap(False) 38 | elif account_type == "spoke": 39 | sdk.bootstrap_spoke( 40 | config.get_puppet_account_id(), 41 | "arn:aws:iam::aws:policy/AdministratorAccess" 42 | ) 43 | --------------------------------------------------------------------------------