From 52ac92da210db2fbfcf7cdffbc20c22e7575db06 Mon Sep 17 00:00:00 2001 From: Guru Senthil <132626272+aws-gurusen@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:48:39 -0700 Subject: [PATCH] Version 2.0.0 Release (#44) --- .devcontainer/devcontainer.json | 2 +- .nvmrc | 2 +- .pre-commit-config.yaml | 10 +- CHANGELOG.md | 20 +- Makefile | 27 +- NOTICE.txt | 502 + Pipfile | 1 + Pipfile.lock | 323 +- README.md | 32 +- deployment/module-build/build-cdk-assets.sh | 5 +- deployment/script_supported_regions.py | 15 +- .../diagrams/deployment-order.svg | 3 +- documentation/networking/README.md | 4 +- makefiles/common_config.mk | 4 +- makefiles/global_targets.mk | 11 + makefiles/module_targets.mk | 37 +- source/lib/.nvmrc | 2 +- source/lib/.pre-commit-config.yaml | 2 +- source/lib/Pipfile | 1 + source/lib/Pipfile.lock | 72 +- source/lib/cms_common/auth/auth_configs.py | 18 +- .../lib/cms_common/auth/tests/fixture_auth.py | 24 +- source/lib/cms_common/config/regex.py | 1 + .../lib/cms_common/constructs/app_registry.py | 26 +- .../cms_common/constructs/cmk_encrypted_s3.py | 45 + .../constructs/lambda_dependencies.py | 6 +- .../test_app_registry_snapshot.json | 23 +- .../test_cmk_encrypted_s3_snapshot.json | 256 + .../constructs/tests/fixture_constructs.py | 15 + .../constructs/tests/test_cmk_encrypted_s3.py | 17 + .../cms_common/constructs/vpc_construct.py | 13 + .../vpc_prefix_list_lookup_custom_resource.py | 12 - .../cms_common/policy_generators/__init__.py | 2 +- .../cms_common/policy_generators/ec2_vpc.py | 2 +- .../lib/cms_common/policy_generators/kms.py | 20 +- .../lib/cms_common/resource_names/__init__.py | 2 +- source/lib/cms_common/resource_names/auth.py | 22 +- .../resource_names/module_short_names.py | 1 + .../resource_names/tests/test_auth.py | 25 +- source/lib/conftest.py | 1 + source/lib/pyproject.toml | 1 + source/lib/setup.py | 2 +- source/modules/acdp/.nvmrc | 2 +- source/modules/acdp/.pre-commit-config.yaml | 2 +- source/modules/acdp/Makefile | 21 +- source/modules/acdp/Pipfile | 1 + source/modules/acdp/Pipfile.lock | 58 +- .../postman/postman-acdp-build-api.json | 12 +- ...acdp-github-cicd-sequence-diagram.plantuml | 141 + .../acdp-github-cicd-sequence-diagram.svg | 1 + source/modules/acdp/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 20 - .../acdp/source/infrastructure/acdp_stack.py | 26 +- .../constructs/cloudformation_role.py | 4 +- .../constructs/module_integration.py | 14 +- .../infrastructure/constructs/pipelines.py | 4 + .../test_snapshot/test_acdp_snapshot.json | 548 +- source/modules/auth_setup/.nvmrc | 2 +- .../auth_setup/.pre-commit-config.yaml | 2 +- source/modules/auth_setup/Pipfile | 1 + source/modules/auth_setup/Pipfile.lock | 70 +- source/modules/auth_setup/README.md | 10 +- source/modules/auth_setup/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 2 +- .../source/infrastructure/auth_setup_stack.py | 1 + .../constructs/module_integration.py | 20 +- .../constructs/oauth2_configurations.py | 10 +- .../test_auth_setup_snapshot.json | 22 + source/modules/backstage/.eslintrc.js | 4 + source/modules/backstage/.nvmrc | 2 +- .../modules/backstage/.pre-commit-config.yaml | 11 +- source/modules/backstage/Makefile | 32 +- source/modules/backstage/Pipfile | 3 + source/modules/backstage/Pipfile.lock | 1365 +- .../modules/backstage/app-config.local.yaml | 61 +- .../backstage/app-config.production.yaml | 8 +- source/modules/backstage/backstage.json | 2 +- source/modules/backstage/cdk/.nvmrc | 2 +- source/modules/backstage/cdk/Makefile | 1 + source/modules/backstage/cdk/Pipfile | 1 + source/modules/backstage/cdk/Pipfile.lock | 1296 +- .../backstage/cdk/deployment/build-s3-dist.sh | 2 + .../modules/backstage/cdk/source/conftest.py | 18 + .../infrastructure/acdp_backstage_stack.py | 22 +- .../constructs/backstage_container.py | 25 + .../infrastructure/constructs/cognito_user.py | 43 + .../constructs/module_integration.py | 44 +- .../test_acdp_backstage_snapshot.json | 376 +- .../deployment/run-backstage-lint.sh | 2 +- .../backstage/deployment/run-backstage-tsc.sh | 8 + .../backstage/deployment/run-cfn-nag.sh | 1 - .../backstage/deployment/run-unit-tests.sh | 9 +- source/modules/backstage/package.json | 22 +- .../backstage/packages/app/.eslintrc.js | 6 + .../backstage/packages/app/package.json | 61 +- .../backstage/packages/app/src/App.tsx | 36 +- .../backstage/packages/app/src/apis/apis.ts | 54 + .../EntityPage/EntityConditions.tsx | 38 + .../components/EntityPage/EntityContent.tsx | 108 + .../src/components/EntityPage/EntityPage.tsx | 248 + .../app/src/components/EntityPage/index.ts | 4 + .../app/src/components/HomePage/HomePage.tsx | 87 + .../app/src/components/HomePage/index.ts | 4 + .../app/src/components/Root/LogoFull.tsx | 7 +- .../app/src/components/Root/LogoIcon.tsx | 5 +- .../packages/app/src/components/Root/Root.tsx | 13 +- .../src/components/SearchPage/SearchPage.tsx | 127 + .../app/src/components/SearchPage/index.ts | 4 + .../backstage/packages/backend/.eslintrc.js | 4 + .../backstage/packages/backend/Dockerfile | 2 +- .../backstage/packages/backend/package.json | 56 +- .../backstage/packages/backend/src/index.ts | 45 +- .../src/middleware/customErrorHandler.ts | 100 + .../packages/backend/src/middleware/index.ts | 4 + .../plugins/acdp-backend/package.json | 37 +- .../src/actions/acdp-catalog-create.test.ts | 12 +- .../src/actions/acdp-catalog-create.ts | 392 +- .../src/actions/acdp-configure.test.ts | 7 +- .../src/actions/acdp-configure.ts | 26 +- .../src/actions/mocks/actions-mocks.ts | 142 + .../acdp-backend/src/actions/mocks/index.ts | 4 + .../acdp-backend/src/api/acdp-base-api.ts | 39 + .../src/api/acdp-build-api.test.ts | 45 +- .../acdp-backend/src/api/acdp-build-api.ts | 56 +- .../src/api/acdp-metrics-api.test.ts | 88 + .../acdp-backend/src/api/acdp-metrics-api.ts | 59 + .../plugins/acdp-backend/src/api/index.ts | 1 + .../src/api/mocks/acdp-build-api.mock.ts | 72 + .../src/api/mocks/acdp-metrics-api.mock.ts | 43 + .../acdp-backend/src/api/mocks/index.ts | 5 + .../plugins/acdp-backend/src/index.ts | 6 +- .../acdp-backend/src/mocks/build-mocks.ts | 38 + .../acdp-backend/src/mocks/common-mocks.ts | 137 + .../plugins/acdp-backend/src/mocks/index.ts | 6 + .../acdp-backend/src/mocks/metrics-mocks.ts | 8 + .../scaffolder-acdp-actions-module/module.ts | 1 - .../plugins/acdp-backend/src/plugin.ts | 29 +- .../src/router/acdp-base-router.test.ts | 39 + .../src/router/acdp-base-router.ts | 43 + .../src/router/acdp-build-router.test.ts | 140 + .../src/router/acdp-build-router.ts | 83 + .../src/router/acdp-metrics-router.test.ts | 115 + .../src/router/acdp-metrics-router.ts | 85 + .../plugins/acdp-backend/src/router/index.ts | 6 + .../src/service/acdp-base-service.ts | 97 + .../src/service/acdp-build-service.test.ts | 43 +- .../src/service/acdp-build-service.ts | 501 +- .../src/service/acdp-metrics-service.test.ts | 130 + .../src/service/acdp-metrics-service.ts | 177 + .../plugins/acdp-backend/src/service/index.ts | 6 + .../service/mocks/acdp-build-service.mock.ts | 35 + .../mocks/acdp-metrics-service.mock.ts | 74 + .../acdp-backend/src/service/mocks/index.ts | 5 + .../acdp-backend/src/service/utils/index.ts | 5 + .../src/service/utils/ssm-helper.ts | 36 + .../acdp-backend/src/service/utils/utils.ts | 135 + .../acdp-backend/src/utils/aws-s3-helper.ts | 102 +- .../src/utils/error-handling-helper.ts | 24 + .../plugins/acdp-backend/src/utils/index.ts | 1 + .../acdp-backend/src/utils/location-helper.ts | 48 +- .../acdp-backend/src/utils/validators.ts | 15 +- .../plugins/acdp-common/package.json | 11 +- .../plugins/acdp-common/src/constants.ts | 15 +- .../plugins/acdp-common/src/index.ts | 2 + .../src/interfaces/acdp-ops-metrics.ts | 10 + .../PartnerOffering.v1beta1.schema.json | 90 + .../PartnerOfferingEntityV1beta1.test.ts | 96 + .../schemas/PartnerOfferingEntityV1beta1.ts | 88 + .../.eslintrc.js | 4 + .../acdp-partner-offering-backend/README.md | 14 + .../dev/index.ts | 12 + .../package.json | 52 + .../src/index.ts | 5 + .../src/module.ts | 43 + .../PartnerOfferingEntitiesProcessor.test.ts | 69 + .../PartnerOfferingEntitiesProcessor.ts | 57 + .../src/processor/index.ts | 20 + .../src/setupTests.ts | 4 + .../acdp-partner-offering/.eslintrc.js | 4 + .../plugins/acdp-partner-offering/README.md | 15 + .../acdp-partner-offering/dev/index.tsx | 18 + .../acdp-partner-offering/package.json | 58 + .../PartnerOfferingCard/CardHeader.test.tsx | 186 + .../PartnerOfferingCard/CardHeader.tsx | 84 + .../PartnerOfferingCard/CardLink.tsx | 49 + .../PartnerOfferingCard.test.tsx | 384 + .../PartnerOfferingCard.tsx | 218 + .../components/PartnerOfferingCard/index.ts | 22 + .../PartnerOfferingCategoryPicker.test.tsx | 156 + .../PartnerOfferingCategoryPicker.tsx | 110 + .../PartnerOfferingCategoryPicker/index.ts | 20 + .../PartnerOfferingGroup.test.tsx | 176 + .../PartnerOfferingGroup.tsx | 90 + .../components/PartnerOfferingGroup/index.ts | 23 + .../PartnerOfferingGroups.test.tsx | 231 + .../PartnerOfferingGroups.tsx | 120 + .../components/PartnerOfferingGroups/index.ts | 19 + .../src/components/PartnerOfferingList.tsx | 111 + .../src/components/types.ts | 26 + .../acdp-partner-offering/src/index.ts | 4 + .../acdp-partner-offering/src/plugin.test.ts | 10 + .../acdp-partner-offering/src/plugin.ts | 29 + .../acdp-partner-offering/src/routes.ts | 8 + .../acdp-partner-offering/src/setupTests.ts | 4 + .../backstage/plugins/acdp/dev/index.tsx | 15 +- .../backstage/plugins/acdp/package.json | 29 +- .../plugins/acdp/src/api/AcdpBaseApi.ts | 51 + .../plugins/acdp/src/api/AcdpBuildApi.test.ts | 53 +- .../plugins/acdp/src/api/AcdpBuildApi.ts | 59 +- .../acdp/src/api/AcdpMetricsApi.test.ts | 62 + .../plugins/acdp/src/api/AcdpMetricsApi.ts | 55 + .../backstage/plugins/acdp/src/api/index.ts | 2 + .../components/CodeBuildWidget/AboutField.tsx | 57 + .../CodeBuildWidget/BuildHistoryTable.tsx | 7 +- .../BuildStatus/BuildStatus.tsx | 58 + .../CodeBuildWidget/BuildStatus/Status.tsx | 158 + .../CodeBuildWidget/BuildStatus/index.ts | 4 + .../CodeBuildWidget/CodeBuildWidget.test.tsx | 20 +- .../CodeBuildWidget/CodeBuildWidget.tsx | 14 +- .../CodeBuildWidgetContent.tsx | 158 + .../CodeBuildWidget/MostRecentBuild.tsx | 32 +- .../TeardownConfirmDialog.tsx | 30 +- .../TeardownConfirmDialog/index.ts | 3 +- .../useTeardownConfirmDialogState.ts | 17 +- .../MyApplicationsWidget.test.tsx | 35 + .../MyApplicationsWidget.tsx | 39 + .../MyApplicationsWidgetContent.tsx | 209 + .../components/MyApplicationsWidget/index.ts | 4 + .../backstage/plugins/acdp/src/index.ts | 2 - .../plugins/acdp/src/mocks/common-mocks.ts | 37 + .../plugins/acdp/src/mocks/index.tsx | 6 + .../acdp/src/mocks/mockAcdpBuildApi.ts | 118 + .../acdp/src/mocks/mockAcdpMetricsApi.ts | 41 + .../backstage/plugins/acdp/src/plugin.test.ts | 12 +- .../backstage/plugins/acdp/src/plugin.ts | 45 +- .../plugins/acdp/src/utils/flags.tsx | 20 + .../backstage/plugins/acdp/src/utils/index.ts | 3 +- .../acdp/src/utils/parseCodeBuildArn.ts | 25 + .../auth-oauth2-provider-module/package.json | 12 +- .../package.json | 10 +- .../src/module.ts | 2 +- source/modules/backstage/pyproject.toml | 1 + source/modules/backstage/yarn.lock | 8372 +++--- source/modules/cms_alerts/.nvmrc | 2 +- .../cms_alerts/.pre-commit-config.yaml | 2 +- source/modules/cms_alerts/Pipfile | 1 + source/modules/cms_alerts/Pipfile.lock | 60 +- source/modules/cms_alerts/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 40 - .../source/infrastructure/cms_alerts_stack.py | 23 +- .../constructs/authorization_lambda.py | 6 +- .../constructs/incoming_alerts_construct.py | 16 +- .../constructs/module_integration.py | 14 +- .../constructs/notification_construct.py | 32 +- .../infrastructure/constructs/publish_api.py | 16 +- .../user_subscriptions_construct.py | 18 +- .../test_cms_alerts_snapshot.json | 568 +- source/modules/cms_api/.nvmrc | 2 +- .../modules/cms_api/.pre-commit-config.yaml | 2 +- source/modules/cms_api/Pipfile | 1 + source/modules/cms_api/Pipfile.lock | 64 +- source/modules/cms_api/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 16 - .../assets/graphql/schemas/vss_schema.graphql | 22 +- .../source/infrastructure/cms_api_stack.py | 49 +- .../constructs/athena_data_source.py | 79 +- .../constructs/authorization_lambda.py | 6 +- .../constructs/module_integration.py | 13 +- .../test_snapshot/test_cms_api_snapshot.json | 275 +- source/modules/cms_auth/.nvmrc | 2 +- .../modules/cms_auth/.pre-commit-config.yaml | 2 +- source/modules/cms_auth/Pipfile | 1 + source/modules/cms_auth/Pipfile.lock | 66 +- source/modules/cms_auth/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 24 - .../source/infrastructure/cms_auth_stack.py | 25 +- .../authorization_code_exchange_lambda.py | 18 +- .../constructs/module_integration.py | 21 +- .../constructs/token_validation_lambda.py | 14 +- ...ture_authorization_code_exchange_lambda.py | 14 +- .../fixtures/fixture_shared_jwt_mocks.py | 4 +- .../fixture_token_validation_lambda.py | 6 +- .../test_snapshot/test_cms_auth_snapshot.json | 171 +- source/modules/cms_config/.acdp/template.yaml | 2 +- source/modules/cms_config/.nvmrc | 2 +- .../cms_config/.pre-commit-config.yaml | 2 +- source/modules/cms_config/Pipfile | 1 + source/modules/cms_config/Pipfile.lock | 66 +- source/modules/cms_config/pyproject.toml | 2 +- .../source/.cdk-nag-suppression-list.json | 8 - .../source/infrastructure/cms_config_stack.py | 24 +- .../test_cms_config_snapshot.json | 230 +- source/modules/cms_connect_store/.nvmrc | 2 +- .../cms_connect_store/.pre-commit-config.yaml | 2 +- source/modules/cms_connect_store/Pipfile | 1 + source/modules/cms_connect_store/Pipfile.lock | 66 +- .../modules/cms_connect_store/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 8 - .../infrastructure/cms_connect_store_stack.py | 26 +- .../constructs/alerts_construct.py | 18 +- .../constructs/iot_core_to_s3_json.py | 6 +- .../constructs/iot_core_to_s3_parquet.py | 4 +- .../fixtures/fixture_vehicle_trigger_alarm.py | 12 +- .../test_connect_and_store_snapshot.json | 430 +- source/modules/cms_ev_battery_health/.nvmrc | 2 +- .../.pre-commit-config.yaml | 2 +- source/modules/cms_ev_battery_health/Pipfile | 1 + .../cms_ev_battery_health/Pipfile.lock | 66 +- .../modules/cms_ev_battery_health/README.md | 5 + .../cms_ev_battery_health/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 46 +- .../source/.cfn-nag-suppression-list.json | 10 +- .../cms_ev_battery_health_stack.py | 36 +- .../constructs/athena_data_source.py | 6 +- .../constructs/grafana_alerts.py | 4 +- .../constructs/grafana_api_key.py | 4 +- .../constructs/grafana_dashboard.py | 4 +- .../constructs/process_alerts.py | 16 +- .../constructs/provision_alerts.py | 4 +- .../constructs/s3_to_grafana.py | 60 +- .../tests/fixtures/fixture_process_alerts.py | 12 +- .../test_cms_ev_battery_health_snapshot.json | 715 +- .../.acdp/template.yaml | 2 +- source/modules/cms_fleetwise_connector/.nvmrc | 2 +- .../.pre-commit-config.yaml | 2 +- .../modules/cms_fleetwise_connector/Pipfile | 1 + .../cms_fleetwise_connector/Pipfile.lock | 64 +- .../cms_fleetwise_connector/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 32 +- .../query_vehicle_vins/function/main.py | 9 +- .../time_range_handler/function/main.py | 22 +- .../unload_vehicle_data/function/main.py | 9 +- .../cms_fleetwise_connector_stack.py | 24 +- .../constructs/s3_glue_athena.py | 12 +- .../fleetwise_timestream_query_vin_task.py | 12 +- ...wise_timestream_time_range_handler_task.py | 4 +- .../fleetwise_timestream_unload_to_s3_task.py | 16 +- .../source/tests/fixtures/fixture_shared.py | 1 + ...test_cms_fleetwise_connector_snapshot.json | 475 +- .../.acdp/deploy.buildspec.yaml | 14 + .../.acdp/teardown.buildspec.yaml | 10 + .../.acdp/template.yaml | 96 + .../.acdp/update.buildspec.yaml | 9 + .../modules/cms_predictive_maintenance/.nvmrc | 1 + .../.pre-commit-config.yaml | 120 + .../.python-version | 1 + .../cms_predictive_maintenance/Makefile | 51 + .../cms_predictive_maintenance/Pipfile | 53 + .../cms_predictive_maintenance/Pipfile.lock | 2616 ++ .../cms_predictive_maintenance/README.md | 238 + .../cms_predictive_maintenance/__init__.py | 3 + .../cms_predictive_maintenance/cdk.json | 38 + .../deployment/build-s3-dist.sh | 43 + .../deployment/cdk-solution-helper/README.md | 159 + .../deployment/cdk-solution-helper/index.js | 337 + .../cdk-solution-helper/package-lock.json | 13 + .../cdk-solution-helper/package.json | 6 + .../deployment/run-cfn-nag.sh | 65 + .../deployment/run-unit-tests.sh | 51 + .../deployment/upload-s3-dist.sh | 36 + ...maintenance-architecture-diagram-genai.svg | 3 + ...ve-maintenance-architecture-diagram-ml.svg | 3 + ...aintenance-genai-sequence-diagram.plantuml | 65 + ...ive-maintenance-genai-sequence-diagram.svg | 1 + ...aintenance-mlops-sequence-diagram.plantuml | 98 + ...ive-maintenance-mlops-sequence-diagram.svg | 1 + .../cms_predictive_maintenance/mkdocs.yml | 18 + .../cms_predictive_maintenance/pyproject.toml | 84 + .../cms_predictive_maintenance/setup.py | 34 + .../source/.cdk-nag-suppression-list.json | 234 + .../source/.cfn-nag-suppression-list.json | 234 + .../source/__init__.py | 3 + .../cms_predictive_maintenance/source/app.py | 80 + .../source/config.yaml | 36 + .../source/conftest.py | 224 + .../source/handlers/__init__.py | 3 + .../handlers/agent_action_group/__init__.py | 3 + .../agent_action_group/function/__init__.py | 3 + .../agent_action_group/function/main.py | 90 + .../agent_action_group/function/openapi.json | 45 + .../source/handlers/authorization/__init__.py | 3 + .../authorization/function/__init__.py | 3 + .../handlers/authorization/function/main.py | 76 + .../authorization/function/tests/__init__.py | 3 + .../tests/fixture_authorization_function.py | 72 + .../authorization/function/tests/test_main.py | 134 + .../handlers/custom_resource/__init__.py | 3 + .../custom_resource/function/__init__.py | 3 + .../custom_resource/function/lib/__init__.py | 3 + .../function/lib/custom_resource_type_enum.py | 17 + .../custom_resource/function/lib/pipeline.py | 325 + .../function/lib/pipeline_steps/__init__.py | 3 + .../function/lib/pipeline_steps/data.py | 38 + .../function/lib/pipeline_steps/evaluate.py | 64 + .../function/lib/pipeline_steps/preprocess.py | 59 + .../function/lib/pipeline_steps/train.py | 173 + .../handlers/custom_resource/function/main.py | 342 + .../function/tests/__init__.py | 3 + .../tests/fixture_custom_resource_function.py | 44 + .../function/tests/test_main.py | 181 + .../deploy_pipeline_model/__init__.py | 3 + .../function/__init__.py | 3 + .../deploy_pipeline_model/function/main.py | 87 + .../function/tests/__init__.py | 3 + .../fixture_deploy_pipeline_model_function.py | 29 + .../function/tests/test_main.py | 203 + .../source/handlers/predict_api/__init__.py | 3 + .../handlers/predict_api/function/__init__.py | 3 + .../handlers/predict_api/function/main.py | 199 + .../predict_api/function/tests/__init__.py | 3 + .../tests/fixture_predict_api_function.py | 55 + .../predict_api/function/tests/test_main.py | 196 + .../source/infrastructure/__init__.py | 3 + .../cms_predictive_maintenance_stack.py | 295 + .../infrastructure/constructs/__init__.py | 3 + .../constructs/agent_action_group.py | 164 + .../infrastructure/constructs/api/__init__.py | 3 + .../constructs/api/authorization_lambda.py | 96 + .../constructs/api/interface.py | 18 + .../constructs/api/predict_api.py | 405 + .../constructs/chatbot/__init__.py | 3 + .../constructs/chatbot/bedrock/__init__.py | 3 + .../constructs/chatbot/bedrock/agent.py | 200 + .../constructs/chatbot/bedrock/data_source.py | 161 + .../chatbot/bedrock/knowledge_base.py | 121 + .../constructs/chatbot/bedrock/role.py | 28 + .../constructs/chatbot/chatbot.py | 140 + .../constructs/chatbot/interface.py | 64 + .../chatbot/vector_database/__init__.py | 3 + .../chatbot/vector_database/collection.py | 25 + .../chatbot/vector_database/data_access.py | 61 + .../chatbot/vector_database/index.py | 93 + .../chatbot/vector_database/security.py | 105 + .../constructs/module_integration.py | 169 + .../constructs/predictor/__init__.py | 3 + .../constructs/predictor/interface.py | 14 + .../constructs/predictor/predictor.py | 64 + .../predictor/sagemaker/__init__.py | 3 + .../predictor/sagemaker/pipeline.py | 391 + .../constructs/predictor/sagemaker/studio.py | 173 + .../source/infrastructure/tests/__init__.py | 3 + ...t_cms_predictive_maintenance_snapshot.json | 6511 +++++ .../tests/fixture_infrastructure.py | 18 + .../test_cms_predictive_maintenance_stack.py | 56 + .../test_scripts/__init__.py | 3 + .../test_scripts/call_predict_api.py | 80 + .../test_scripts/chatbot_query_agent.py | 44 + .../chatbot_query_knowledge_base.py | 44 + .../test_scripts/create_sagemaker_pipeline.py | 52 + source/modules/cms_provisioning/.nvmrc | 2 +- .../cms_provisioning/.pre-commit-config.yaml | 2 +- source/modules/cms_provisioning/Pipfile | 1 + source/modules/cms_provisioning/Pipfile.lock | 66 +- .../modules/cms_provisioning/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 32 - .../infrastructure/cms_provisioning_stack.py | 24 +- .../constructs/initial_connection.py | 4 +- .../constructs/post_provisioning.py | 4 +- .../constructs/pre_provisioning.py | 6 +- .../test_cms_provisioning_snapshot.json | 325 +- source/modules/cms_sample/.nvmrc | 2 +- .../cms_sample/.pre-commit-config.yaml | 2 +- source/modules/cms_sample/Pipfile | 1 + source/modules/cms_sample/Pipfile.lock | 49 +- source/modules/cms_sample/pyproject.toml | 1 + .../source/infrastructure/cms_sample_stack.py | 42 +- .../test_cms_sample_snapshot.json | 50 +- .../.acdp/deploy.buildspec.yaml | 2 +- .../cms_vehicle_simulator/.acdp/template.yaml | 2 +- source/modules/cms_vehicle_simulator/.nvmrc | 2 +- .../.pre-commit-config.yaml | 3 +- source/modules/cms_vehicle_simulator/Makefile | 6 +- source/modules/cms_vehicle_simulator/Pipfile | 1 + .../cms_vehicle_simulator/Pipfile.lock | 72 +- .../cms_vehicle_simulator/pyproject.toml | 1 + .../source/.cdk-nag-suppression-list.json | 40 +- .../source/console/package-lock.json | 22113 +++++++++------- .../source/console/package.json | 10 +- .../cms_vehicle_simulator_stack.py | 24 +- .../constructs/module_integration.py | 2 +- .../test_cms_vehicle_simulator_snapshot.json | 478 +- source/modules/vpc/.nvmrc | 2 +- source/modules/vpc/.pre-commit-config.yaml | 2 +- source/modules/vpc/Makefile | 2 +- source/modules/vpc/Pipfile | 1 + source/modules/vpc/Pipfile.lock | 9 +- source/modules/vpc/source/template.yaml | 27 +- 487 files changed, 48916 insertions(+), 18790 deletions(-) create mode 100644 source/lib/cms_common/constructs/cmk_encrypted_s3.py create mode 100644 source/lib/cms_common/constructs/tests/__snapshots__/test_cmk_encrypted_s3/test_cmk_encrypted_s3_snapshot.json create mode 100644 source/lib/cms_common/constructs/tests/test_cmk_encrypted_s3.py create mode 100644 source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.plantuml create mode 100644 source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.svg create mode 100644 source/modules/backstage/.eslintrc.js create mode 100644 source/modules/backstage/cdk/source/conftest.py create mode 100644 source/modules/backstage/cdk/source/infrastructure/constructs/cognito_user.py create mode 100755 source/modules/backstage/deployment/run-backstage-tsc.sh create mode 100644 source/modules/backstage/packages/app/.eslintrc.js create mode 100644 source/modules/backstage/packages/app/src/apis/apis.ts create mode 100644 source/modules/backstage/packages/app/src/components/EntityPage/EntityConditions.tsx create mode 100644 source/modules/backstage/packages/app/src/components/EntityPage/EntityContent.tsx create mode 100644 source/modules/backstage/packages/app/src/components/EntityPage/EntityPage.tsx create mode 100644 source/modules/backstage/packages/app/src/components/EntityPage/index.ts create mode 100644 source/modules/backstage/packages/app/src/components/HomePage/HomePage.tsx create mode 100644 source/modules/backstage/packages/app/src/components/HomePage/index.ts create mode 100644 source/modules/backstage/packages/app/src/components/SearchPage/SearchPage.tsx create mode 100644 source/modules/backstage/packages/app/src/components/SearchPage/index.ts create mode 100644 source/modules/backstage/packages/backend/.eslintrc.js create mode 100644 source/modules/backstage/packages/backend/src/middleware/customErrorHandler.ts create mode 100644 source/modules/backstage/packages/backend/src/middleware/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/actions/mocks/actions-mocks.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/actions/mocks/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/api/acdp-base-api.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.test.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-build-api.mock.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-metrics-api.mock.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/api/mocks/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/mocks/build-mocks.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/mocks/common-mocks.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/mocks/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/mocks/metrics-mocks.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.test.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.test.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.test.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/router/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/acdp-base-service.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.test.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-build-service.mock.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-metrics-service.mock.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/mocks/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/utils/index.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/utils/ssm-helper.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/service/utils/utils.ts create mode 100644 source/modules/backstage/plugins/acdp-backend/src/utils/error-handling-helper.ts create mode 100644 source/modules/backstage/plugins/acdp-common/src/interfaces/acdp-ops-metrics.ts create mode 100644 source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOffering.v1beta1.schema.json create mode 100644 source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.test.ts create mode 100644 source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/.eslintrc.js create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/README.md create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/dev/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/package.json create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/src/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/src/module.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.test.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering-backend/src/setupTests.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/.eslintrc.js create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/README.md create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/dev/index.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/package.json create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.test.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardLink.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.test.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.test.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.test.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.test.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingList.tsx create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/components/types.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/index.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/plugin.test.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/plugin.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/routes.ts create mode 100644 source/modules/backstage/plugins/acdp-partner-offering/src/setupTests.ts create mode 100644 source/modules/backstage/plugins/acdp/src/api/AcdpBaseApi.ts create mode 100644 source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.test.ts create mode 100644 source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.ts create mode 100644 source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/AboutField.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/BuildStatus.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/Status.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/index.ts create mode 100644 source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/CodeBuildWidgetContent.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/MyApplicationsWidget/MyApplicationsWidget.test.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/MyApplicationsWidget/MyApplicationsWidget.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/MyApplicationsWidget/MyApplicationsWidgetContent.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/components/MyApplicationsWidget/index.ts create mode 100644 source/modules/backstage/plugins/acdp/src/mocks/common-mocks.ts create mode 100644 source/modules/backstage/plugins/acdp/src/mocks/index.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/mocks/mockAcdpBuildApi.ts create mode 100644 source/modules/backstage/plugins/acdp/src/mocks/mockAcdpMetricsApi.ts create mode 100644 source/modules/backstage/plugins/acdp/src/utils/flags.tsx create mode 100644 source/modules/backstage/plugins/acdp/src/utils/parseCodeBuildArn.ts create mode 100644 source/modules/cms_predictive_maintenance/.acdp/deploy.buildspec.yaml create mode 100644 source/modules/cms_predictive_maintenance/.acdp/teardown.buildspec.yaml create mode 100644 source/modules/cms_predictive_maintenance/.acdp/template.yaml create mode 100644 source/modules/cms_predictive_maintenance/.acdp/update.buildspec.yaml create mode 100644 source/modules/cms_predictive_maintenance/.nvmrc create mode 100644 source/modules/cms_predictive_maintenance/.pre-commit-config.yaml create mode 100644 source/modules/cms_predictive_maintenance/.python-version create mode 100644 source/modules/cms_predictive_maintenance/Makefile create mode 100644 source/modules/cms_predictive_maintenance/Pipfile create mode 100644 source/modules/cms_predictive_maintenance/Pipfile.lock create mode 100644 source/modules/cms_predictive_maintenance/README.md create mode 100644 source/modules/cms_predictive_maintenance/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/cdk.json create mode 100755 source/modules/cms_predictive_maintenance/deployment/build-s3-dist.sh create mode 100644 source/modules/cms_predictive_maintenance/deployment/cdk-solution-helper/README.md create mode 100644 source/modules/cms_predictive_maintenance/deployment/cdk-solution-helper/index.js create mode 100644 source/modules/cms_predictive_maintenance/deployment/cdk-solution-helper/package-lock.json create mode 100644 source/modules/cms_predictive_maintenance/deployment/cdk-solution-helper/package.json create mode 100755 source/modules/cms_predictive_maintenance/deployment/run-cfn-nag.sh create mode 100755 source/modules/cms_predictive_maintenance/deployment/run-unit-tests.sh create mode 100755 source/modules/cms_predictive_maintenance/deployment/upload-s3-dist.sh create mode 100644 source/modules/cms_predictive_maintenance/documentation/architecture/diagrams/cms-predictive-maintenance-architecture-diagram-genai.svg create mode 100644 source/modules/cms_predictive_maintenance/documentation/architecture/diagrams/cms-predictive-maintenance-architecture-diagram-ml.svg create mode 100644 source/modules/cms_predictive_maintenance/documentation/sequence/cms-predictive-maintenance-genai-sequence-diagram.plantuml create mode 100644 source/modules/cms_predictive_maintenance/documentation/sequence/cms-predictive-maintenance-genai-sequence-diagram.svg create mode 100644 source/modules/cms_predictive_maintenance/documentation/sequence/cms-predictive-maintenance-mlops-sequence-diagram.plantuml create mode 100644 source/modules/cms_predictive_maintenance/documentation/sequence/cms-predictive-maintenance-mlops-sequence-diagram.svg create mode 100644 source/modules/cms_predictive_maintenance/mkdocs.yml create mode 100644 source/modules/cms_predictive_maintenance/pyproject.toml create mode 100644 source/modules/cms_predictive_maintenance/setup.py create mode 100644 source/modules/cms_predictive_maintenance/source/.cdk-nag-suppression-list.json create mode 100644 source/modules/cms_predictive_maintenance/source/.cfn-nag-suppression-list.json create mode 100644 source/modules/cms_predictive_maintenance/source/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/app.py create mode 100644 source/modules/cms_predictive_maintenance/source/config.yaml create mode 100644 source/modules/cms_predictive_maintenance/source/conftest.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/agent_action_group/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/agent_action_group/function/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/agent_action_group/function/main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/agent_action_group/function/openapi.json create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/authorization/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/authorization/function/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/authorization/function/main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/authorization/function/tests/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/authorization/function/tests/fixture_authorization_function.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/authorization/function/tests/test_main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/custom_resource_type_enum.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/pipeline.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/pipeline_steps/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/pipeline_steps/data.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/pipeline_steps/evaluate.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/pipeline_steps/preprocess.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/lib/pipeline_steps/train.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/tests/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/tests/fixture_custom_resource_function.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/custom_resource/function/tests/test_main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/deploy_pipeline_model/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/deploy_pipeline_model/function/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/deploy_pipeline_model/function/main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/deploy_pipeline_model/function/tests/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/deploy_pipeline_model/function/tests/fixture_deploy_pipeline_model_function.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/deploy_pipeline_model/function/tests/test_main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/predict_api/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/predict_api/function/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/predict_api/function/main.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/predict_api/function/tests/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/predict_api/function/tests/fixture_predict_api_function.py create mode 100644 source/modules/cms_predictive_maintenance/source/handlers/predict_api/function/tests/test_main.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/cms_predictive_maintenance_stack.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/agent_action_group.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/api/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/api/authorization_lambda.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/api/interface.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/api/predict_api.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/bedrock/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/bedrock/agent.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/bedrock/data_source.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/bedrock/knowledge_base.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/bedrock/role.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/chatbot.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/interface.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/vector_database/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/vector_database/collection.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/vector_database/data_access.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/vector_database/index.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/chatbot/vector_database/security.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/module_integration.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/predictor/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/predictor/interface.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/predictor/predictor.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/predictor/sagemaker/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/predictor/sagemaker/pipeline.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/constructs/predictor/sagemaker/studio.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/tests/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/tests/__snapshots__/test_cms_predictive_maintenance_stack/test_cms_predictive_maintenance_snapshot.json create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/tests/fixture_infrastructure.py create mode 100644 source/modules/cms_predictive_maintenance/source/infrastructure/tests/test_cms_predictive_maintenance_stack.py create mode 100644 source/modules/cms_predictive_maintenance/test_scripts/__init__.py create mode 100644 source/modules/cms_predictive_maintenance/test_scripts/call_predict_api.py create mode 100644 source/modules/cms_predictive_maintenance/test_scripts/chatbot_query_agent.py create mode 100644 source/modules/cms_predictive_maintenance/test_scripts/chatbot_query_knowledge_base.py create mode 100644 source/modules/cms_predictive_maintenance/test_scripts/create_sagemaker_pipeline.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fe765db2..545d57b7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ "version": "latest" }, "ghcr.io/devcontainers/features/node:1": { - "version": "18.17" + "version": "18.20" }, "ghcr.io/devcontainers/features/python:1": { "version": "3.12", diff --git a/.nvmrc b/.nvmrc index aacb5181..0305213f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.17 +18.20 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c651c007..5acd10fb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -111,7 +111,7 @@ repos: - id: pip-audit name: (ROOT) Pip audit exclude: ^(source/|.nightswatch/) - - repo: https://github.com/pre-commit/mirrors-prettier + - repo: https://github.com/pre-commit/mirrors-prettier # Archived, look to replace eventually rev: v3.1.0 hooks: - id: prettier @@ -279,6 +279,14 @@ repos: files: ^source/modules/vpc verbose: true require_serial: true + - id: cms-predictive-maintenance + name: (Predictive Maintenance) + language: script + args: ["--module-path", "source/modules/cms_predictive_maintenance", "--files-list"] + entry: ./deployment/script_run_module_hooks.py + files: ^source/modules/cms_predictive_maintenance + verbose: true + require_serial: true - id: nightswatch name: (NightsWatch) language: script diff --git a/CHANGELOG.md b/CHANGELOG.md index a078cd65..adf93b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0] - 2024-10-23 + +### Added + +#### All + +- Add AWS MyApplications support to CloudFormation deployments via the awsApplication tag + +#### Automotive Cloud Developer Portal + +- Upgrade Backstage to v1.29. +- Add metrics tab to ACDP scaffolded components with monthly cost and a link to the deployment's MyApplications page. +- Add partner offerings page. + +#### CMS + +- Add CMS Predictive Maintenance module. + ## [1.2.2] - 2024-09-26 ### Fixed -- Update rollup package to resolve CVE +- Update rollup package to resolve CVE. ## [1.2.1] - 2024-09-18 diff --git a/Makefile b/Makefile index e14ef2af..316c6326 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,12 @@ .DEFAULT_GOAL := help SHELL := /bin/bash +DEFAULTS.NODE_VERSION := $(shell cat .nvmrc 2> /dev/null) +DEFAULTS.PYTHON_VERSION := $(shell cat .python-version) + +NODE_VERSION ?= ${DEFAULTS.NODE_VERSION} +PYTHON_VERSION ?= ${DEFAULTS.PYTHON_VERSION} + include makefiles/common_config.mk include makefiles/global_targets.mk @@ -114,11 +120,6 @@ verify-module: ## Run all verifications for CMS. CAUTION: Takes a long time. @$(call run-module-target,verify-module,${SubMakeDirs}) @printf "%bFinished verify-module.%b\n" "${GREEN}" "${NC}" -.PHONY: verify-required-tools -verify-required-tools: ## Checks the environment for the required dependencies. - @$(call run-module-target,verify-required-tools,${SubMakeDirs}) - @printf "%bFinished verify-required-tools.%b\n" "${GREEN}" "${NC}" - .PHONY: cfn-nag cfn-nag: ## Run cfn-nag for the entire solution. @$(call run-module-target,cfn-nag,${SubMakeDirs}) @@ -149,7 +150,7 @@ version: root-version ## Display solution name and current version and each modu ## INSTALL ## ======================================================== .PHONY: root-install -root-install: ## Using pipenv, installs pip dependencies for root. +root-install: verify-required-tools ## Using pipenv, installs pip dependencies for root. @printf "%bInstalling root pip dependencies.%b\n" "${MAGENTA}" "${NC}" @pipenv install --dev --python ${PYTHON_VERSION} @pipenv clean --python ${PYTHON_VERSION} @@ -217,13 +218,13 @@ clean-build-artifacts-all: ## Cleans up existing build files, including venvs, d .PHONY: create-rc-file create-rc-file: ## Create rc file for environment variables which are likely to be customized. Default values provided where able for default CMS deployment. @[[ -f .cmsrc ]] || printf "%bInstead of using this target, you can run the following command.\n%b" "${MAGENTA}" "${NC}" - @[[ -f .cmsrc ]] || printf "%bcat > .cmsrc < .cmsrc < .cmsrc; \ + printf "#!/bin/bash\nexport DEFAULT_USER_EMAIL=\"%s\"\nexport VPC_NAME=\"%s\"\nexport IDENTITY_PROVIDER_ID=\"%s\"\nexport FULLY_QUALIFIED_DOMAIN_NAME=\"%s\"\nexport ROUTE53_HOSTED_ZONE_ID=\"%s\"\nexport CUSTOM_ACM_CERTIFICATE_ARN=\"%s\"\nexport IS_PUBLIC_FACING=\"%s\"\nexport USE_BACKSTAGE_AUTH_REDIRECT_FLOW=\"%s\"\nexport BACKSTAGE_ADDITIONAL_SCOPES=\"%s\"\nexport SHOULD_CREATE_COGNITO_RESOURCES=\"%s\"\nexport BACKSTAGE_NAME=\"%s\"\nexport BACKSTAGE_ORG=\"%s\"\n" "$$DEFAULT_USER_EMAIL" "$$VPC_NAME" "$$IDENTITY_PROVIDER_ID" "$$FULLY_QUALIFIED_DOMAIN_NAME" "$$ROUTE53_HOSTED_ZONE_ID" "$$CUSTOM_ACM_CERTIFICATE_ARN" "$$IS_PUBLIC_FACING" "$$USE_BACKSTAGE_AUTH_REDIRECT_FLOW" "$$BACKSTAGE_ADDITIONAL_SCOPES" "$$SHOULD_CREATE_COGNITO_RESOURCES" "$$BACKSTAGE_NAME" "$$BACKSTAGE_ORG" > .cmsrc; \ printf "%b.cmsrc is now populated. Run \`source .cmsrc\` before performing a deployment.\n%b" "${MAGENTA}" "${NC}" .PHONY: generate-python-requirements-files -generate-python-requirements-files: ## Generates requirements.txt files from the pipfiles throughout the solution. - @printf "%bGenerating requirements.txt from pipfiles.%b\n" "${MAGENTA}" "${NC}"\ - find ${SOLUTION_PATH} \( -name .venv -o -name node_modules -o -name "cdk.out" \) -prune -false -o -name "Pipfile" -execdir bash -c "echo; PIPENV_PIPFILE={} pipenv requirements 1> requirements.txt;" \; +generate-python-requirements-files: ## Generates requirements.txt files using the python binary in the .venv environments throughout the solution. + @printf "%bGenerating requirements.txt from virtual environments (.venv) python binaries.%b\n" "${MAGENTA}" "${NC}" + find ${SOLUTION_PATH} \( -name node_modules -o -name "cdk.out" \) -prune -false -o -name ".venv" -type d -exec bash -c '{}/bin/python -m pip freeze > "{}/../requirements.txt"' \; ## ======================================================== ## HELPERS diff --git a/NOTICE.txt b/NOTICE.txt index a6cd6334..a21983c8 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2933,6 +2933,506 @@ aws_lambda_powertools under the MIT No Attribution License (MIT-0) license cattrs under the MIT license exceptiongroup under the MIT license requests under the Apache-2.0 license +postgres license under the Unlicense license +docker/library/node under the MIT license +semver under the ISC license. +retry under the MIT license. +proggy under the ISC license. +normalize-package-data under the BSD-2-Clause license. +node-gyp under the MIT license. +ms under the MIT license. +minimatch under the ISC license. +http-cache-semantics under the BSD-2-Clause license. +graceful-fs under the ISC license. +debug under the MIT license. +@sigstore/verify under the Apache-2.0 license. +@sigstore/core under the Apache-2.0 license. +@npmcli/redact under the ISC license. +@npmcli/config under the ISC license. +@jest/types under the MIT license. +@types/node under the MIT license. +cliui under the ISC license. +yargs under the MIT license. +yargs-parser under the ISC license. +react under the MIT license. +@types/react under the MIT license. +@react-native-community/cli under the MIT license. +mime under the MIT license. +ieee754 under the BSD-3-Clause license. +js-yaml under the MIT license. +parse-json under the MIT license. +serve-static under the MIT license. +send under the MIT license. +http-errors under the MIT license. +range-parser under the MIT license. +yaml under the ISC license. +connect under the MIT license. +ws under the MIT license. +@react-native/codegen under the MIT license. +@babel/core under the MIT license. +@babel/types under the MIT license. +@babel/parser under the MIT license. +globals under the MIT license. +json5 under the MIT license. +@babel/plugin-bugfix-safari-class-field-initializer-scope under the MIT license. +@babel/plugin-transform-duplicate-named-capturing-groups-regex under the MIT license. +resolve under the MIT license. +once under the ISC license. +invariant under the MIT license. +source-map-support under the MIT license. +source-map under the BSD-3-Clause license. +minimist under the MIT license. +chromium-edge-launcher under the Apache-2.0 license. +node-fetch under the MIT license. +node-forge under the BSD-3-Clause license. +metro-babel-transformer under the MIT license. +@jridgewell/source-map under the MIT license. +abort-controller under the MIT license. +@sinonjs/fake-timers under the BSD-3-Clause license. +stack-utils under the MIT license. +uuid under the MIT license. +cookie under the MIT license. +js-cookie under the MIT license. +graphql under the MIT license. +@turf/helpers under the MIT license. +quick-lru under the MIT license. +@smithy/types under the Apache-2.0 license. +events under the MIT license. +@mapbox/jsonlint-lines-primitives license unknown or missing +pbf under the BSD-3-Clause license. +protocol-buffers-schema under the MIT license. +lodash under the MIT license. +traverse under the MIT license. +is-array-buffer under the MIT license. +react-dom under the MIT license. +@xstate/react under the MIT license. +use-sync-external-store under the MIT license. +@floating-ui/core under the MIT license. +mapbox-gl under the SEE LICENSE IN LICENSE.txt license(s). +aria-query under the Apache-2.0 license. +@testing-library/react under the MIT license. +@testing-library/dom under the MIT license. +deep-equal under the MIT license. +@types/react-dom under the MIT license. +@types/jest under the MIT license. +expect under the MIT license. +sax under the ISC license. +util under the MIT license. +xml2js under the MIT license. +@popperjs/core under the MIT license. +@restart/ui under the MIT license. +@swc/helpers under the Apache-2.0 license. +warning under the MIT license. +prop-types under the MIT license. +react-transition-group under the BSD-3-Clause license. +react-router-dom under the MIT license. +react-router under the MIT license. +webpack under the MIT license. +eslint-scope under the BSD-2-Clause license. +@xtuc/long under the Apache-2.0 license. +@xtuc/ieee754 under the BSD-3-Clause license. +@webassemblyjs/utf8 under the MIT license. +connect-history-api-fallback under the MIT license. +express under the MIT license. +body-parser under the MIT license. +qs under the BSD-3-Clause license. +methods under the MIT license. +http-proxy under the MIT license. +serve-index under the MIT license. +sockjs under the MIT license. +@svgr/core under the MIT license. +@svgr/babel-preset under the MIT license. +q under the MIT license. +@istanbuljs/schema under the MIT license. +istanbul-lib-coverage under the BSD-3-Clause license. +postcss-normalize-display-values under the MIT license. +eslint under the MIT license. +keyv under the MIT license. +@typescript-eslint/eslint-plugin under the MIT license. +@typescript-eslint/parser under the BSD-2-Clause license. +@typescript-eslint/types under the MIT license. +@typescript-eslint/utils under the MIT license. +@rtsao/scc under the MIT license. +html-minifier-terser under the MIT license. +jest under the MIT license. +@jest/core under the MIT license. +istanbul-lib-report under the BSD-3-Clause license. +istanbul-reports under the BSD-3-Clause license. +jsdom under the MIT license. +tough-cookie under the BSD-3-Clause license. +address under the MIT license. +recursive-readdir under the MIT license. +json-schema under the BSD-3-Clause license. +rollup under the MIT license. +sourcemap-codec under the MIT license. +prettier under the MIT license. +@aws-sdk/client-cost-explorer under the Apache-2.0 license. +@aws-sdk/client-service-catalog-appregistry under the Apache-2.0 license. +@aws-sdk/core under the Apache-2.0 license. +@azure/abort-controller under the MIT license. +@backstage/plugin-techdocs-common under the Apache-2.0 license. +@backstage/types under the Apache-2.0 license. +@changesets/types under the MIT license. +@date-io/core under the MIT license. +@emotion/cache under the MIT license. +@fastify/busboy under the MIT license. +@floating-ui/utils under the MIT license. +@gitbeaker/core under the MIT license. +@google-cloud/promisify under the Apache-2.0 license. +@google-cloud/storage under the Apache-2.0 license. +@graphiql/react under the MIT license. +@graphql-tools/utils under the MIT license. +@graphql-typed-document-node/core under the MIT license. +@headlessui/react under the MIT license. +@ioredis/commands under the MIT license. +@jsep-plugin/assignment under the MIT license. +@jsonjoy.com/util under the Apache-2.0 license. +@keyv/serialize under the MIT license. +@lezer/highlight under the MIT license. +@material-table/core under the MIT license. +@material-ui/core under the MIT license. +@material-ui/types under the MIT license. +@material-ui/utils under the MIT license. +@module-federation/runtime under the MIT license. +@motionone/dom under the MIT license. +@motionone/types under the MIT license. +@motionone/utils under the MIT license. +@mui/system under the MIT license. +@mui/types under the MIT license. +@mui/utils under the MIT license. +@nx/devkit under the MIT license. +@octokit/core under the MIT license. +@octokit/request under the MIT license. +@octokit/rest under the MIT license. +@octokit/types under the MIT license. +@opentelemetry/api under the Apache-2.0 license. +@protobufjs/base64 under the BSD-3-Clause license. +@rjsf/core under the Apache-2.0 license. +@rjsf/utils under the Apache-2.0 license. +@smithy/abort-controller under the Apache-2.0 license. +@smithy/config-resolver under the Apache-2.0 license. +@smithy/core under the Apache-2.0 license. +@smithy/credential-provider-imds under the Apache-2.0 license. +@smithy/eventstream-codec under the Apache-2.0 license. +@smithy/eventstream-serde-browser under the Apache-2.0 license. +@smithy/eventstream-serde-config-resolver under the Apache-2.0 license. +@smithy/eventstream-serde-node under the Apache-2.0 license. +@smithy/eventstream-serde-universal under the Apache-2.0 license. +@smithy/fetch-http-handler under the Apache-2.0 license. +@smithy/hash-node under the Apache-2.0 license. +@smithy/invalid-dependency under the Apache-2.0 license. +@smithy/is-array-buffer under the Apache-2.0 license. +@smithy/md5-js under the Apache-2.0 license. +@smithy/middleware-content-length under the Apache-2.0 license. +@smithy/middleware-endpoint under the Apache-2.0 license. +@smithy/middleware-retry under the Apache-2.0 license. +@smithy/middleware-serde under the Apache-2.0 license. +@smithy/middleware-stack under the Apache-2.0 license. +@smithy/node-config-provider under the Apache-2.0 license. +@smithy/node-http-handler under the Apache-2.0 license. +@smithy/property-provider under the Apache-2.0 license. +@smithy/protocol-http under the Apache-2.0 license. +@smithy/querystring-builder under the Apache-2.0 license. +@smithy/querystring-parser under the Apache-2.0 license. +@smithy/service-error-classification under the Apache-2.0 license. +@smithy/shared-ini-file-loader under the Apache-2.0 license. +@smithy/signature-v4 under the Apache-2.0 license. +@smithy/smithy-client under the Apache-2.0 license. +@smithy/url-parser under the Apache-2.0 license. +@smithy/util-body-length-browser under the Apache-2.0 license. +@smithy/util-body-length-node under the Apache-2.0 license. +@smithy/util-buffer-from under the Apache-2.0 license. +@smithy/util-config-provider under the Apache-2.0 license. +@smithy/util-defaults-mode-browser under the Apache-2.0 license. +@smithy/util-defaults-mode-node under the Apache-2.0 license. +@smithy/util-endpoints under the Apache-2.0 license. +@smithy/util-hex-encoding under the Apache-2.0 license. +@smithy/util-middleware under the Apache-2.0 license. +@smithy/util-uri-escape under the Apache-2.0 license. +@smithy/util-waiter under the Apache-2.0 license. +@stoplight/better-ajv-errors under the Apache-2.0 license. +@stoplight/path under the Apache-2.0 license. +@stoplight/types under the Apache-2.0 license. +@swc/core under the Apache-2.0 license. +@swc/types under the Apache-2.0 license. +@types/request under the MIT license. +@types/zen-observable under the MIT license. +@whatwg-node/fetch under the MIT license. +@whatwg-node/node-fetch under the MIT license. +@zkochan/js-yaml under the MIT license. +@zxing/text-encoding under the Apache-2.0 license. +app license unknown or missing +btoa-lite under the MIT license. +buildcheck license unknown or missing +busboy license unknown or missing +cacheable-request under the MIT license. +caseless under the Apache-2.0 license. +codemirror under the MIT license. +cookie-parser under the MIT license. +cookiejar under the MIT license. +cookies under the MIT license. +cors under the MIT license. +date-fns under the MIT license. +debounce-promise under the MIT license. +docker-modem under the Apache-2.0 license. +dockerode under the Apache-2.0 license. +dompurify under the Apache-2.0 license. +es-aggregate-error under the MIT license. +find-root under the MIT license. +history under the MIT license. +hoist-non-react-statics under the BSD-3-Clause license. +js-levenshtein under the MIT license. +json-pointer under the MIT license. +jsonwebtoken under the MIT license. +long under the Apache-2.0 license. +lru.min under the MIT license. +lunr under the MIT license. +luxon under the MIT license. +multer under the MIT license. +neotraverse under the MIT license. +oauth under the MIT license. +ono under the MIT license. +passport-github2 license unknown or missing +passport-oauth2 under the MIT license. +passport-strategy license unknown or missing +passport under the MIT license. +pg-format under the MIT license. +punycode.js under the MIT license. +ramda under the MIT license. +react-redux under the MIT license. +react-sparklines under the MIT license. +request under the Apache-2.0 license. +responselike under the MIT license. +sanitize-html under the MIT license. +seq-queue license unknown or missing +set-cookie-parser under the MIT license. +sinon under the BSD-3-Clause license. +ssh2 license unknown or missing +streamsearch license unknown or missing +superagent under the MIT license. +supertest under the MIT license. +triple-beam under the MIT license. +undici under the MIT license. +urijs under the MIT license. +xml-crypto under the MIT license. +xml-encryption under the MIT license. +yauzl under the MIT license. +zod-validation-error under the MIT license. +aws-cdk-lib under the Apache-2.0 license. +aws-cdk.asset-awscli-v1 under the Apache-2.0 license. +aws-cdk.asset-kubectl-v20 under the Apache-2.0 license. +aws-cdk.asset-node-proxy-agent-v6 under the Apache-2.0 license. +Babel under the 0BSD license. +cdk-nag under the Apache-2.0 license. +certifi under the MPL-2.0 license. +cfgv under the MIT license. +charset-normalizer under the MIT license. +click under the 0BSD license. +colorama under the 0BSD license. +constructs under the Apache-2.0 license. +distlib under the PSF-2.0 license. +filelock under the The Unlicense (Unlicense) license(s). +ghp-import under the Apache-2.0 license. +identify under the MIT license. +idna under the 0BSD license. +importlib_resources under the Apache-2.0 license. +Jinja2 under the 0BSD license. +jsii under the Apache-2.0 license. +Markdown under the 0BSD license. +markdown-inline-graphviz-extension under the MIT license. +MarkupSafe under the 0BSD license. +mdx-truly-sane-lists under the MIT license. +mergedeep under the MIT license. +mkdocs under the 0BSD license. +mkdocs-get-deps under the MIT license. +mkdocs-material under the MIT license. +mkdocs-material-extensions under the MIT license. +mkdocs-monorepo-plugin under the Apache-2.0 license. +mkdocs-techdocs-core under the Apache-2.0 license. +nodeenv under the 0BSD license. +packaging under the Apache-2.0 license. +paginate under the MIT license. +pathspec under the MPL-2.0 license. +plantuml-markdown under the 0BSD license. +platformdirs under the MIT license. +pre-commit under the MIT license. +publication under the MIT license. +Pygments under the 0BSD license. +pymdown-extensions under the MIT license. +python-dateutil under the Apache-2.0 license. +python-slugify under the MIT license. +PyYAML under the MIT license. +pyyaml_env_tag under the MIT license. +regex under the Apache-2.0 license. +six under the MIT license. +text-unidecode under the GNU General Public License v2 or later (GPLv2+) license(s). +typeguard under the MIT license. +types-PyYAML under the Apache-2.0 license. +typing_extensions under the PSF-2.0 license. +urllib3 under the MIT license. +virtualenv under the MIT license. +watchdog under the Apache-2.0 license. +annotated-types under the MIT license. +antlr4-python3-runtime under the 0BSD license. +astroid under the LGPL-2.1-or-later license. +aws-cdk.integ-tests-alpha under the Apache-2.0 license. +aws-lambda-powertools under the MIT license. +aws-sam-translator under the Apache-2.0 license. +aws-solutions-constructs.aws-cloudfront-s3 under the Apache-2.0 license. +aws-solutions-constructs.aws-lambda-stepfunctions under the Apache-2.0 license. +aws-solutions-constructs.core under the Apache-2.0 license. +aws-solutions-constructs.resources under the Apache-2.0 license. +aws-xray-sdk under the Apache-2.0 license. +aws_secretsmanager_caching under the Apache-2.0 license. +awscrt under the Apache-2.0 license. +awsiotsdk under the Apache-2.0 license. +blessed under the MIT license. +boto3 under the Apache-2.0 license. +boto3-stubs under the MIT license. +botocore under the Apache-2.0 license. +botocore-stubs under the MIT license. +cffi under the MIT license. +cfn-lint under the MIT license. +chalice under the Apache-2.0 license. +coverage under the Apache-2.0 license. +cryptography under the Apache-2.0 license. +dill under the 0BSD license. +docker under the Apache-2.0 license. +fastjsonschema under the 0BSD license. +graphql-core under the MIT license. +iniconfig under the MIT license. +isort under the MIT license. +joserfc under the 0BSD license. +jsondiff under the MIT license. +jsonpatch under the 0BSD license. +jsonpath-ng under the Apache-2.0 license. +jsonschema-path under the Apache-2.0 license. +jsonschema-specifications under the MIT license. +lazy-object-proxy under the 0BSD license. +libcst under the Apache-2.0 license. +markdown-it-py under the MIT license. +mccabe under the MIT license. +moto under the Apache-2.0 license. +mpmath under the 0BSD license. +multipart under the MIT license. +mypy under the MIT license. +mypy-boto3-cloudformation under the MIT license. +mypy-boto3-cognito-idp under the MIT license. +mypy-boto3-dynamodb under the MIT license. +mypy-boto3-ec2 under the MIT license. +mypy-boto3-iot under the MIT license. +mypy-boto3-lambda under the MIT license. +mypy-boto3-rds under the MIT license. +mypy-boto3-resourcegroupstaggingapi under the MIT license. +mypy-boto3-s3 under the MIT license. +mypy-boto3-secretsmanager under the MIT license. +mypy-boto3-sqs under the MIT license. +mypy-boto3-ssm under the MIT license. +mypy-boto3-stepfunctions under the MIT license. +mypy-extensions under the MIT license. +networkx under the 0BSD license. +openapi-schema-validator under the 0BSD license. +openapi-spec-validator under the Apache-2.0 license. +pathable under the Apache-2.0 license. +pluggy under the MIT license. +ply under the 0BSD license. +py-partiql-parser under the MIT license. +pycln under the MIT license. +pycparser under the 0BSD license. +pydantic under the MIT license. +pydantic_core under the MIT license. +pylint under the GPL-2.0-or-later license. +pyparsing under the MIT license. +pytest under the MIT license. +pytest-cov under the MIT license. +pytest-mock under the MIT license. +python-editor under the Apache-2.0 license. +readchar under the MIT license. +referencing under the MIT license. +responses under the Apache-2.0 license. +rfc3339-validator under the MIT license. +rich under the MIT license. +rpds-py under the MIT license. +s3transfer under the Apache-2.0 license. +shellingham under the ISC license. +sympy under the 0BSD license. +syrupy under the Apache-2.0 license. +toml under the MIT license. +tomlkit under the MIT license. +typer under the MIT license. +types-awscrt under the MIT license. +types-boto3 under the MIT license. +types-python-dateutil under the Apache-2.0 license. +types-requests under the Apache-2.0 license. +types-s3transfer under the MIT license. +types-setuptools under the Apache-2.0 license. +types-toml under the Apache-2.0 license. +Werkzeug under the 0BSD license. +wheel under the MIT license. +wrapt under the 0BSD license. +xmltodict under the MIT license. +zipp under the MIT license. +freezegun under the Apache-2.0 license. +markdown_to_json under the MIT license. +types-urllib3 under the Apache-2.0 license. +dataclass-type-validator under the MIT license. +mypy-boto3-grafana under the MIT license. +aws-cdk.cloud-assembly-schema under the Apache-2.0 license. +aws-solutions-constructs.aws-apigateway-lambda under the Apache-2.0 license. +babel under the 0BSD license. +cloudpickle under the 0BSD license. +Events under the 0BSD license. +fsspec under the 0BSD license. +google-pasta under the Apache-2.0 license. +importlib-metadata under the Apache-2.0 license. +mkdocs-redirects under the MIT license. +multiprocess under the 0BSD license. +mypy-boto3-bedrock-agent under the MIT license. +mypy-boto3-efs under the MIT license. +mypy-boto3-opensearchserverless under the MIT license. +mypy-boto3-sagemaker under the MIT license. +mypy-boto3-sagemaker-runtime under the MIT license. +numpy under the Apache-2.0 license. +opensearch-py under the Apache-2.0 license. +pandas under the Apache-2.0 license. +pandas-stubs under the 0BSD license. +pathos under the 0BSD license. +pox under the 0BSD license. +ppft under the 0BSD license. +pre_commit under the MIT license. +protobuf under the BSD-3-Clause license. +psutil under the 0BSD license. +pytz under the MIT license. +sagemaker under the Apache-2.0 license. +schema under the MIT license. +smdebug-rulesconfig under the Apache-2.0 license. +tblib under the 0BSD license. +tenacity under the Apache-2.0 license. +torch under the 0BSD license. +tqdm under the MIT license. +types-pytz under the Apache-2.0 license. +tzdata under the Apache-2.0 license. +backoff under the MIT license. +mypy-boto3-timestream-query under the MIT license. +grafanalib under the Apache-2.0 license. +PyJWT under the MIT license. +mypy-boto3-athena under the MIT license. +aws-solutions-constructs.aws-sns-sqs under the Apache-2.0 license. +aws-solutions-constructs.aws-sqs-lambda under the Apache-2.0 license. +mypy-boto3-dynamodbstreams under the MIT license. +mypy-boto3-sns under the MIT license. +pyhumps under the MIT license. +pyjsparser under the MIT license. +cached-property under the 0BSD license. +Cerberus under the ISC license. +orderedmultidict under the Unlicense license. +pep517 under the MIT license. +pip-shims under the ISC license. +pipenv-setup under the MIT license. +pipfile under the Apache-2.0 license. +plette under the ISC license. +requirementslib under the MIT license. +vistir under the ISC license. ******************** OPEN SOURCE LICENSES @@ -2968,3 +3468,5 @@ MIT/X11 - None MPL-2.0 - https://opensource.org/licenses/MPL-2.0 Unlicense - https://github.com/streamich/fast-shallow-equal/blob/master/LICENSE Zlib - https://github.com/marcello3d/node-tosource/blob/main/LICENSE +PSF-2.0 - https://spdx.org/licenses/PSF-2.0.html +Python-2.0 - https://spdx.org/licenses/Python-2.0.html diff --git a/Pipfile b/Pipfile index 944c1d65..83a5d9ab 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ types-pyyaml = "*" types-python-dateutil = "*" types-requests = ">=2.32.0" types-setuptools = "*" +urllib3 = ">=2.2.2" wheel = "*" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index 412dc27e..c94ec6a4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d0a613ddd6222121044b9e044a4711918b9ab14adecc6fd737186d2be2dea6b6" + "sha256": "5013e1ad5d1c33004e57b61dfdee121920ad03fd9cc549784f63b6273112f251" }, "pipfile-spec": 6, "requires": { @@ -19,20 +19,20 @@ "develop": { "astroid": { "hashes": [ - "sha256:3eae9ea67c11c858cdd2c91337d2e816bd019ac897ca07d7b346ac10105fceb3", - "sha256:7099b5a60985529d8d46858befa103b82d0d05a5a5e8b816b5303ed96075e1d9" + "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a", + "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25" ], "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.3" + "version": "==3.2.4" }, "boto3": { "hashes": [ - "sha256:2f3e88b10b8fcc5f6100a9d74cd28230edc9d4fa226d99dd40a3ab38ac213673", - "sha256:b8433d481d50b68a0162c0379c0dd4aabfc3d1ad901800beb5b87815997511c1" + "sha256:06eac4757de2a9c6020381205cb902f05964caad80b56e58c8931284a133b4cb", + "sha256:b9587131372a808bf6f99c5ed8b11be55cd113261cc3b437a917b4acc6c30bfe" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.8" }, "boto3-stubs": { "extras": [ @@ -41,27 +41,27 @@ "iot" ], "hashes": [ - "sha256:4fc2f8d41ee7fb4952dc8d6a27bb616e7c0c5f9b71f3ad6d179f27df1bb69b0c", - "sha256:c19d0c4386ec160e6c06567b960b6f7c765acc4ed40f01f371c8e7a0b514520b" + "sha256:cfe2c813d8a43b91cccd7d1d4230b781fe3572e2d13e42d5ba60d78b42638bc8", + "sha256:e5767b1aabdbc5e84915e533f852605bbb07cfe1b86fe172ab27654a2f24b48d" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.8" }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:4b820cf680ab5d778bd2fe4feeef1ff8a2b96d5c535d4638ab30f703ade282f8", + "sha256:adf389eb8fd87775f193300e3431d1353f925807ad3a39958172cb644f0d60a1" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.8" }, "botocore-stubs": { "hashes": [ - "sha256:3eb325b4b186e8d8e49d485cfe383f478ab8c07e0f4690e5ee9bcb6faceec4af", - "sha256:eded7afc301a08b5355ab8843c9d9bdea7dc43ece3e9628f186897dae7a56ceb" + "sha256:a49f9db19259dc280fee73d74f3a171310f3916a12e679b6c4bf8f719cb37461", + "sha256:dac2ebedcd8b0c98be89d6e708fa9ce6d5d12e1bc6b629e77ae8c0d289088c49" ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.144" + "markers": "python_version >= '3.8'", + "version": "==1.35.8" }, "certifi": { "hashes": [ @@ -216,11 +216,11 @@ }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.8" }, "isort": { "hashes": [ @@ -379,93 +379,93 @@ }, "mypy": { "hashes": [ - "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9", - "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d", - "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0", - "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3", - "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3", - "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade", - "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31", - "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7", - "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e", - "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7", - "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c", - "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b", - "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e", - "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531", - "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04", - "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a", - "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37", - "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a", - "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f", - "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84", - "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d", - "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f", - "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a", - "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf", - "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7", - "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02", - "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3" + "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", + "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce", + "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", + "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b", + "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", + "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", + "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", + "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", + "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86", + "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", + "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", + "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", + "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", + "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", + "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", + "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", + "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", + "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", + "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", + "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", + "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", + "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", + "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", + "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", + "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1", + "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b", + "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.10.1" + "version": "==1.11.2" }, "mypy-boto3-cloudformation": { "hashes": [ - "sha256:526e928c504fa2880b1774aa10629a04fe0ec70ed2864ab3d3f7772386a1a925", - "sha256:a02e201d1a9d9a8fb4db5b942d5c537a4e8861c611f0d986126674ac557cb9e8" + "sha256:0d037d9d6bdb439a84e2391ba987a4e03fcedfad0e881db1cf0f7861d275907c", + "sha256:5da07e14a206a7f0015434d1730a6a68a33167ea6746343189dd1742cfcfdb7d" ], - "version": "==1.34.111" + "version": "==1.35.0" }, "mypy-boto3-dynamodb": { "hashes": [ - "sha256:62e4fd85c621561f145828de14752a51380182d492cb039043d7f46bef872c34", - "sha256:d23c857568ae7c8c8fc1fbd96709a1dd00c140f917d74e09423fd44677097abf" + "sha256:1e503c89a5aa65f2b90fc7c861d3630a21544822f30b38e67e4f52463111abb9", + "sha256:75f224d8b78f6d3126eead645aea6c0a8bc2828614f302c168de1d3dad490d11" ], - "version": "==1.34.131" + "version": "==1.35.0" }, "mypy-boto3-ec2": { "hashes": [ - "sha256:3953ba88c08e633a2c671a9d442ddbb4bdcbb05f9aa179aeef42ae2376999a20", - "sha256:79e5e929947e81f45e711a4059aacfbda0418027d407b3b3c917ac3f163ea9f2" + "sha256:b3e17ee6082a107d7d6d7ac44062264a9fb711c5d6d9e0ce16837cda26d1be7c", + "sha256:f4cdbe524ff4039668cc168e3c6f9c68048481ab33dfb0f5d892bbf2428d1ef2" ], - "version": "==1.34.143" + "version": "==1.35.8" }, "mypy-boto3-iot": { "hashes": [ - "sha256:6161a8b4e3ca96363807424bd48f9ac64e0c259224f38ad5c6866ef6dcc11acb", - "sha256:825f93f6042def95281608a7df104484ab7b3f0a8af867d1f133e724467f9c8f" + "sha256:3bff1d347ea40cd31f8e69d7cbdffc4c11af16f5d0e15fa45a140c4c04f8cae8", + "sha256:6d5c6a195b57777788834b011d41b7009feb8f4d009d5132f9c9cca303963b6a" ], - "version": "==1.34.52" + "version": "==1.35.0" }, "mypy-boto3-lambda": { "hashes": [ - "sha256:7b81d2a5604fb592e92fe0b284ecd259de071703360a33b71c9b54df46d81c9c", - "sha256:e21022d2eef12aa731af80790410afdba9412b056339823252813bae2adbf553" + "sha256:2e78c12a7ba4d2d9c99b75fad58804fd99820e954ab557f14f099d6c85a882ab", + "sha256:b59e45facfc166eddb1d5c2696aa8127463455f9e439e3438494965bcd97c97d" ], - "version": "==1.34.77" + "version": "==1.35.3" }, "mypy-boto3-rds": { "hashes": [ - "sha256:66b50037073434eb894b00f1597ba39f1e86789717ff9676c5a099f31bc39c9a", - "sha256:ce4872ee69cd10b895e11945b6c3255493b642e0410e48a2cfc6afe689cf6209" + "sha256:8861b551854cabec2efbe40db506297e9526e1496a1e55843136df716a2b7a00", + "sha256:c252857561219ecc0a03b2d3936081d7a54a59d1caa01e69deb8cdea761dab76" ], - "version": "==1.34.135" + "version": "==1.35.0" }, "mypy-boto3-s3": { "hashes": [ - "sha256:47ded5f06accc10ff9db9d55c85cca88e4f028ec360d7cfcea90377e525cba56", - "sha256:7f9770d1f0e9f6fc2ced96daf5c0792b2dbbb4a4f874f28200ff3c940d0815c3" + "sha256:74d8f3492eeff768ff6f69ac6d40bf68b40aa6e54ebe10a8d098fc3d24a54abf", + "sha256:f7300b559dee5435872625448becf159abe36b19cd7006dd78e0d51610312183" ], - "version": "==1.34.138" + "version": "==1.35.2" }, "mypy-boto3-sqs": { "hashes": [ - "sha256:bdbc623235ffc8127cb8753f49323f74a919df552247b0b2caaf85cf9bb495b8", - "sha256:e92aefacfa08e7094b79002576ef261e4075f5af9c25219fc47fb8452f53fc5f" + "sha256:61752f1c2bf2efa3815f64d43c25b4a39dbdbd9e472ae48aa18d7c6d2a7a6eb8", + "sha256:9fd6e622ed231c06f7542ba6f8f0eea92046cace24defa95d0d0ce04e7caee0c" ], - "version": "==1.34.121" + "version": "==1.35.0" }, "mypy-extensions": { "hashes": [ @@ -501,12 +501,12 @@ }, "pre-commit": { "hashes": [ - "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a", - "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5" + "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", + "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==3.7.1" + "version": "==3.8.0" }, "pycln": { "hashes": [ @@ -527,12 +527,12 @@ }, "pylint": { "hashes": [ - "sha256:32cd6c042b5004b8e857d727708720c54a676d1e22917cf1a2df9b4d4868abd6", - "sha256:e9b7171e242dcc6ebd0aaa7540481d1a72860748a0a7816b8fe6cf6c80a6fe7e" + "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f", + "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3" ], "index": "pypi", "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.5" + "version": "==3.2.6" }, "python-dateutil": { "hashes": [ @@ -544,60 +544,62 @@ }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "requests": { "hashes": [ @@ -610,11 +612,11 @@ }, "rich": { "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" + "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc", + "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" + "version": "==13.8.0" }, "s3transfer": { "hashes": [ @@ -626,12 +628,12 @@ }, "setuptools": { "hashes": [ - "sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d", - "sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207" + "sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f", + "sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==71.0.3" + "version": "==74.0.0" }, "shellingham": { "hashes": [ @@ -651,19 +653,19 @@ }, "tomlkit": { "hashes": [ - "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72", - "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264" + "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", + "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79" ], "markers": "python_version >= '3.8'", - "version": "==0.13.0" + "version": "==0.13.2" }, "typer": { "hashes": [ - "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914", - "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482" + "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", + "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722" ], "markers": "python_version >= '3.7'", - "version": "==0.12.3" + "version": "==0.12.5" }, "types-awscrt": { "hashes": [ @@ -683,21 +685,21 @@ }, "types-python-dateutil": { "hashes": [ - "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202", - "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b" + "sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98", + "sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.9.0.20240316" + "version": "==2.9.0.20240821" }, "types-pyyaml": { "hashes": [ - "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342", - "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6" + "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af", + "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==6.0.12.20240311" + "version": "==6.0.12.20240808" }, "types-requests": { "hashes": [ @@ -710,20 +712,20 @@ }, "types-s3transfer": { "hashes": [ - "sha256:02154cce46528287ad76ad1a0153840e0492239a0887e8833466eccf84b98da0", - "sha256:49a7c81fa609ac1532f8de3756e64b58afcecad8767933310228002ec7adff74" + "sha256:60167a3bfb5c536ec6cdb5818f7f9a28edca9dc3e0b5ff85ae374526fc5e576e", + "sha256:7a3fec8cd632e2b5efb665a355ef93c2a87fdd5a45b74a949f95a9e628a86356" ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==0.10.1" + "markers": "python_version >= '3.8'", + "version": "==0.10.2" }, "types-setuptools": { "hashes": [ - "sha256:842cbf399812d2b65042c9d6ff35113bbf282dee38794779aa1f94e597bafc35", - "sha256:bd0db2a4b9f2c49ac5564be4e0fb3125c4c46b1f73eafdcbceffa5b005cceca4" + "sha256:3a060681098eb3fbc2fea0a86f7f6af6aa1ca71906039d88d891ea2cecdd4dbf", + "sha256:b9eba9b68546031317a0fa506d4973641d987d74f79e7dd8369ad4f7a93dea17" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==70.3.0.20240710" + "version": "==73.0.0.20240822" }, "typing-extensions": { "hashes": [ @@ -738,6 +740,7 @@ "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.2.2" }, @@ -751,12 +754,12 @@ }, "wheel": { "hashes": [ - "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85", - "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81" + "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f", + "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==0.43.0" + "version": "==0.44.0" } } } diff --git a/README.md b/README.md index 1c4e9e17..86d470de 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ navigate to the [AWS Solution Page](https://aws.amazon.com/solutions/implementat - [AWS CLI](#aws-cli) - [AWS CDK Toolkit](#aws-cdk-toolkit) - [Verify Required Tool Installations](#verify-required-tool-installations) + - [Manual Steps](#manual-steps) + - [Create an Amazon Route 53 Hosted Zone](#create-an-amazon-route-53-hosted-zone) - [Install Solution Dependencies](#install-solution-dependencies) - - [Create an Amazon Route 53 Hosted Zone](#create-an-amazon-route-53-hosted-zone) - [Setup Environment Variables](#setup-environment-variables) - [Deploy](#deploy) - [Prerequisites](#prerequisites) @@ -104,7 +105,7 @@ For detailed information visit the modules' README - [Connect & Store](./source/modules/cms_connect_store/README.md) - [Config](./source/modules/cms_config/README.md) - [EV Battery Health](./source/modules/cms_ev_battery_health/README.md) -- [Fleetwise Connector](./source/modules/cms_fleetwise_connector/README.md) +- [FleetWise Connector](./source/modules/cms_fleetwise_connector/README.md) - [Provisioning](./source/modules/cms_provisioning/README.md) - [Vehicle Simulator](./source/modules/cms_vehicle_simulator/README.md) - [VPC](./source/modules/vpc/README.md) @@ -148,7 +149,7 @@ For tools not listed here, stable versions should work appropriately. | Dependency | Version | |------------|----------| -| [NodeJS](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) | 18.17.* | +| [NodeJS](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) | 18.20.* | | [Python](https://www.python.org) | 3.12.* | ### Install Required Tools (OSX/Linux) @@ -251,18 +252,18 @@ any errors are displayed, attempt to reinstall that tool. make verify-required-tools ``` -### Install Solution Dependencies +### Manual Steps -Now that you have the correct tools, you can install the dependencies used by the solution using `make install`. -After installing, activate the environment which contains the dependencies. +Some of the modules may need certain manual steps. Here is the list of modules which requires manual steps. +Please refer to module READMEs for instructions. -```bash -make install -``` +- [ACDP](#create-an-amazon-route-53-hosted-zone) +- [CMS EV Battery Health](./source/modules/cms_ev_battery_health/README.md/#manual-steps) +- [CMS Predictive Maintenance](./source/modules/cms_predictive_maintenance/README.md/#manual-steps) -### Create an Amazon Route 53 Hosted Zone +#### Create an Amazon Route 53 Hosted Zone -To deploy the solution, either an Amazon Route53 Hosted Zone or external DNS provider is required to be setup in your account. +To deploy ACDP, either an Amazon Route53 Hosted Zone or external DNS provider is required to be setup in your account. When using Route53, you can either use a Public or Private Hosted Zone, but if you use private, you must manually configure a TLS Certificate in ACM. You will provide the Route53 Hosted Zone ID and a fully qualified domain name for this @@ -270,6 +271,15 @@ deployment in the following step when you setup your environment variables. Creating a hosted zone is a manual step. For more details, see [Working with hosted zones](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-working-with.html). +### Install Solution Dependencies + +Now that you have the correct tools, you can install the dependencies used by the solution using `make install`. +After installing, activate the environment which contains the dependencies. + +```bash +make install +``` + ### Setup Environment Variables To deploy the solution, a variety of environment variables are required. These environment variables will be used to diff --git a/deployment/module-build/build-cdk-assets.sh b/deployment/module-build/build-cdk-assets.sh index e6ebfe7d..1e2526f9 100755 --- a/deployment/module-build/build-cdk-assets.sh +++ b/deployment/module-build/build-cdk-assets.sh @@ -42,10 +42,7 @@ printf "%b[Synth] Synthesize Stack\n%b" "${GREEN}" "${NC}" cd "$MODULE_ROOT_DIR" # Run cdk synth to generate CloudFormation template -# JSII_RUNTIME_PACKAGE_CACHE_ROOT is defined so lock collisions don't occur when modules are running concurrently -# - RuntimeError: EEXIST: file already exists, open '/.cache//aws-cdk-lib/2.130.0/.lock' -# - https://github.com/aws/jsii/blob/main/packages/%40jsii/kernel/src/tar-cache/default-cache-root.ts -JSII_RUNTIME_PACKAGE_CACHE_ROOT="$MODULE_ROOT_DIR/.cdk_cache" cdk synth --output="$STAGING_DIST_DIR" >> /dev/null +cdk synth --output="$STAGING_DIST_DIR" >> /dev/null printf "%b[Packing] Template artifacts\n%b" "${GREEN}" "${NC}" rm -f "$STAGING_DIST_DIR/tree.json" diff --git a/deployment/script_supported_regions.py b/deployment/script_supported_regions.py index 0eabff2d..8172e4f4 100644 --- a/deployment/script_supported_regions.py +++ b/deployment/script_supported_regions.py @@ -96,7 +96,7 @@ # "AWS CodeArtifact", # "Amazon AppFlow", # "AWS Elemental MediaTailor", - # "Amazon Bedrock", + "Amazon Bedrock", # "Amazon Managed Workflows for Apache Airflow", # "AWS IoT Device Defender", # "AWS Trusted Advisor", @@ -227,7 +227,7 @@ # "AWS Network Firewall", # "AWS Transit Gateway", # "AWS Private Certificate Authority", - # "Amazon SageMaker", + "Amazon SageMaker", # "AWS SimSpace Weaver", "AWS Key Management Service", # "AWS Budgets", @@ -249,7 +249,7 @@ # "Amazon Polly", # "Amazon DataZone", # "AWS Fault Injection Service", - # "Amazon OpenSearch Service", + "Amazon OpenSearch Service", # "AWS Serverless Application Repository" ] @@ -284,9 +284,9 @@ # "backup", # "backup-gateway", # "batch", - # "bedrock", - # "bedrock-agent-runtime", - # "bedrock-runtime", + "bedrock", + "bedrock-agent-runtime", + "bedrock-runtime", # "billingconductor", # "braket", # "cases", @@ -425,6 +425,7 @@ # "neptune-graph", # "networkmonitor", # "nimble", + "opensearchserverless" # "panorama", # "payment-cryptography.controlplane", # "payment-cryptography.dataplane", @@ -457,7 +458,7 @@ "s3", # "s3-outposts", # "s3express", - # "sagemaker.api", + "sagemaker.api", # "sagemaker.featurestore-runtime", # "sagemaker.metrics", # "sagemaker.runtime", diff --git a/documentation/architecture/diagrams/deployment-order.svg b/documentation/architecture/diagrams/deployment-order.svg index 2cbae457..42bf25e1 100644 --- a/documentation/architecture/diagrams/deployment-order.svg +++ b/documentation/architecture/diagrams/deployment-order.svg @@ -1,4 +1,3 @@ - -
Deployed by CodeBuild, Orchestrated by Backstage
AWS Account
CMS VPC
CMS
Config
Auth Setup (Cognito or OAuth IdP)
CMS API
CMS Alerts
CMS
EV Battery Health
Backstage
VPC
Auth Setup (Cognito or OAuth IdP)
Route53 HostedZone Registration
ACDP One-Click Deploment
Backstage
Deployment
ACDP CI/CD Engine Deployment
CMS Auth
CMS
Connect & Store
CMS Vehicle Provisioning
CMS Vehicle Simulator
CMS Sample Module
CMS FleetWise Connector
\ No newline at end of file +
Deployed by CodeBuild, Orchestrated by Backstage
Deployed by CodeBuild, Orchestrated by Backstage
AWS Account
AWS Account
CMS VPC
CMS VPC
CMS
Config
CMS <br>Config
Auth Setup (Cognito or OAuth IdP)
Auth Setup (Cognito or OAuth IdP)
CMS API
CMS API
CMS Alerts
CMS Alerts
CMS
EV Battery Health
CMS <br>EV Battery Health
Backstage
VPC
Backstage <br>VPC
Auth Setup (Cognito or OAuth IdP)
Auth Setup (Cognito or OAuth IdP)
Route53 HostedZone Registration
Route53 HostedZone Registration
CMS Predictive Maintenance
CMS Predictive Maintenance
ACDP One-Click Deploment
[Not supported by viewer]
Backstage
Deployment
Backstage<br>Deployment
ACDP CI/CD Engine Deployment
ACDP CI/CD Engine Deployment
CMS Auth
CMS Auth
CMS
Connect & Store
CMS <br>Connect & Store
CMS Vehicle Provisioning
CMS Vehicle Provisioning
CMS Vehicle Simulator
CMS Vehicle Simulator
CMS Sample Module
CMS Sample Module
CMS FleetWise Connector
CMS FleetWise Connector
\ No newline at end of file diff --git a/documentation/networking/README.md b/documentation/networking/README.md index 27d6d481..a6343c24 100644 --- a/documentation/networking/README.md +++ b/documentation/networking/README.md @@ -74,8 +74,6 @@ and an auto-generated public certificate. ### Scenario 2: Fully Public with an External DNS + Custom Certificate -![Public Deployment w/ External DNS + Custom Certificate](acdp-public-ext-dns.svg) - #### Description The network topology is the following: @@ -92,7 +90,7 @@ Note that in this scenario, the Route53HostedZoneId parameter is not set. #### Diagram -![Public Deployment](acdp-public.svg) +![Public Deployment w/ External DNS + Custom Certificate](acdp-public-ext-dns.svg) #### Steps diff --git a/makefiles/common_config.mk b/makefiles/common_config.mk index 580999f8..1fff9d13 100644 --- a/makefiles/common_config.mk +++ b/makefiles/common_config.mk @@ -15,7 +15,7 @@ export AWS_REGION ?= ${DEFAULTS.AWS_REGION} # ======================================================== export SOLUTION_NAME ?= connected-mobility-solution-on-aws export SOLUTION_DESCRIPTION ?= Accelerate development and deployment of connected vehicle assets with purpose-built, deployment-ready accelerators, and an Automotive Cloud Developer Portal -export SOLUTION_VERSION ?= v1.2.2 +export SOLUTION_VERSION ?= v2.0.0 export SOLUTION_AUTHOR = AWS Industrial Solutions Team export SOLUTION_ID = SO0241 # Path is relative to this file's location, moving this file requires updating this path. @@ -41,6 +41,8 @@ export LANG = en_US.UTF-8 # ======================================================== export REGIONAL_ASSET_BUCKET_BASE_NAME ?= acdp-assets-${AWS_ACCOUNT_ID} export REGIONAL_ASSET_BUCKET_NAME ?= ${REGIONAL_ASSET_BUCKET_BASE_NAME}-${AWS_REGION} +export REGIONAL_PUBLIC_ASSET_BUCKET_BASE_NAME ?= acdp-public-assets-${AWS_ACCOUNT_ID} +export REGIONAL_PUBLIC_ASSET_BUCKET_NAME ?= ${REGIONAL_PUBLIC_ASSET_BUCKET_BASE_NAME}-${AWS_REGION} export GLOBAL_ASSET_BUCKET_NAME ?= ${REGIONAL_ASSET_BUCKET_NAME} export GLOBAL_ASSET_BUCKET_REGION = $(shell BUCKET=${GLOBAL_ASSET_BUCKET_NAME} ${SOLUTION_PATH}/deployment/determine-bucket-region.sh) export REGIONAL_ASSET_BUCKET_REGION = $(shell BUCKET=${REGIONAL_ASSET_BUCKET_NAME} ${SOLUTION_PATH}/deployment/determine-bucket-region.sh) diff --git a/makefiles/global_targets.mk b/makefiles/global_targets.mk index f0b40f9f..8428e419 100644 --- a/makefiles/global_targets.mk +++ b/makefiles/global_targets.mk @@ -20,3 +20,14 @@ build-python-package: ## Wraps normal setup.py commands to leverage the environm .PHONY: install-python-package install-python-package: ## Wraps normal setup.py commands to leverage the environment variables defined in Makefiles @pipenv run python setup.py install + +.PHONY: verify-required-tools +verify-required-tools: ## Checks the environment for the required dependencies. + @[ "v${NODE_VERSION}" = "$$(node --version | cut -d "." -f 1-2)" ] || ( printf "%bNode version %s is required, as specified in .nvmrc. %s was found instead. Please install the correct version by running 'nvm install'.%b\n" "${RED}" "v${NODE_VERSION}" "$(shell node --version | cut -d "." -f 1-2)" "${NC}"; sh -c 'exit 1' ) + @[ $$(which npm) ] || ( printf "%bNpm is required and should be automatically installed with node. Please check your node installation. (https://www.npmjs.com/) %b\n" "${RED}" "${NC}"; sh -c 'exit 1' ) + @[ $$(which yarn) ] || ( printf "%bYarn is required, as specified in the README. Please see the following link for installation (OS specific): https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable%b\n" "${RED}" "${NC}"; sh -c 'exit 1' ) + @[ "Python ${PYTHON_VERSION}" = "$$(python --version | cut -d "." -f 1-2)" ] || ( printf "%bPython version %s is required, as specified in .python-version. %s was found instead. Please install the correct version by running 'pyenv install -s'%b\n" "${RED}" "Python ${PYTHON_VERSION}" "$(shell python --version | cut -d "." -f 1-2)" "${NC}"; sh -c 'exit 1' ) + @[ $$(which pipenv) ] || ( printf "%bpipenv is required, as specified in the README. Please see the following link for installation: https://pipenv.pypa.io/en/latest/installation.html%b\n" "${RED}" "${NC}"; sh -c 'exit 1' ) + @[ $$(which aws) ] || ( printf "%bThe aws CLI is required, as specified in the README. Please see the following link for installation: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html%b\n" "${RED}" "${NC}"; sh -c 'exit 1' ) + @[ $$(which cdk) ] || ( printf "%bThe aws-cdk CLI is required, as specified in the README. Please see the following link for installation: https://docs.aws.amazon.com/cdk/v2/guide/cli.html%b\n" "${RED}" "${NC}"; sh -c 'exit 1' ) + @printf "%bDependencies verified.%b\n" "${GREEN}" "${NC}" diff --git a/makefiles/module_targets.mk b/makefiles/module_targets.mk index bec2d4e5..13753da7 100644 --- a/makefiles/module_targets.mk +++ b/makefiles/module_targets.mk @@ -1,6 +1,12 @@ SHELL := /bin/bash +DEFAULTS.NODE_VERSION := $(shell cat ${MODULE_PATH}/.nvmrc 2> /dev/null) +DEFAULTS.PYTHON_VERSION := $(shell cat ${MODULE_PATH}/.python-version) + +export NODE_VERSION ?= ${DEFAULTS.NODE_VERSION} +export PYTHON_VERSION ?= ${DEFAULTS.PYTHON_VERSION} + # Custom location for library installation. OS level file restrictions causes issues. export MODULE_LIB_DIST_PATH = ${MODULE_PATH}/dist-lib @@ -13,13 +19,13 @@ export JSII_RUNTIME_PACKAGE_CACHE_ROOT = ${MODULE_PATH}/.cdk_cache ## COMMON TARGETS ## ======================================================== .PHONY: pipenv-install -pipenv-install: ## Using pipenv, installs pip dependencies. +pipenv-install: verify-required-tools ## Using pipenv, installs pip dependencies. @printf "%bInstalling pip dependencies: %s%b\n" "${MAGENTA}" "${MODULE_NAME}" "${NC}" @pipenv install --dev --python ${PYTHON_VERSION} @pipenv clean --python ${PYTHON_VERSION} .PHONY: cdk-solution-helper-install -cdk-solution-helper-install: ## Using npm, installs node modules for cdk-solution-helper. +cdk-solution-helper-install: verify-required-tools ## Using npm, installs node modules for cdk-solution-helper. @printf "%bInstalling cdk-solution-helper node dependencies: %s%b\n" "${MAGENTA}" "${MODULE_NAME}" "${NC}" @npm i --prefix deployment/cdk-solution-helper @@ -35,7 +41,7 @@ upload: create-upload-bucket ## Upload templates and build assets for the module .PHONY: destroy-stack destroy-stack: ## Delete the stack for the module. - @printf "%bDelete the module deployment.%b\n" "${MAGENTA}" "${NC}" + @printf "%bDeleting the module deployment.%b\n" "${MAGENTA}" "${NC}" @aws cloudformation delete-stack --stack-name "${STACK_NAME}" @aws cloudformation wait stack-delete-complete --stack-name "${STACK_NAME}" @@ -73,31 +79,6 @@ update-snapshots: ## Update snapshot files for the module. @printf "%bUpdating unit test snapshots.%b\n" "${MAGENTA}" "${NC}" pipenv run ${MODULE_PATH}/deployment/run-unit-tests.sh -r -s -.PHONY: verify-required-tools -verify-required-tools: ## Checks the environment for the required dependencies. -ifneq (v${NODE_VERSION}, $(shell node --version | cut -d "." -f 1-2)) - $(error Node version "v${NODE_VERSION}" is required, as specified in .nvmrc. "$(shell node --version | cut -d "." -f 1-2)" was found instead. Please install the correct version by running `nvm install`.) -endif -ifeq (, $(shell which npm)) - $(error Npm is required and should be automatically installed with node. Please check your node installation.`) -endif -ifeq (, $(shell which yarn)) - $(error Yarn is required, as specified in the README. Please see the following link for installation (OS specific): https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) -endif -ifneq (Python ${PYTHON_VERSION}, $(shell python --version | cut -d "." -f 1-2)) - $(error Python version "Python ${PYTHON_VERSION}" is required, as specified in .python-version. "$(shell python --version | cut -d "." -f 1-2)" was found instead. Please install the correct version by running `pyenv install -s`) -endif -ifeq (, $(shell which pipenv)) - $(error pipenv is required, as specified in the README. Please see the following link for installation: https://pipenv.pypa.io/en/latest/installation.html) -endif -ifeq (, $(shell which aws)) - $(error The aws CLI is required, as specified in the README. Please see the following link for installation: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) -endif -ifeq (, $(shell which cdk)) - $(error The aws-cdk CLI is required, as specified in the README. Please see the following link for installation: https://docs.aws.amazon.com/cdk/v2/guide/cli.html) -endif - @printf "%bDependencies verified.%b\n" "${GREEN}" "${NC}" - ## ======================================================== ## HELP COMMANDS ## ======================================================== diff --git a/source/lib/.nvmrc b/source/lib/.nvmrc index aacb5181..0305213f 100644 --- a/source/lib/.nvmrc +++ b/source/lib/.nvmrc @@ -1 +1 @@ -18.17 +18.20 diff --git a/source/lib/.pre-commit-config.yaml b/source/lib/.pre-commit-config.yaml index 7e72fc36..d92e730b 100644 --- a/source/lib/.pre-commit-config.yaml +++ b/source/lib/.pre-commit-config.yaml @@ -92,7 +92,7 @@ repos: hooks: - id: pip-audit name: (Common) Pip audit - - repo: https://github.com/pre-commit/mirrors-prettier + - repo: https://github.com/pre-commit/mirrors-prettier # Archived, look to replace eventually rev: v3.1.0 hooks: - id: prettier diff --git a/source/lib/Pipfile b/source/lib/Pipfile index f61a27dc..4c6649f7 100644 --- a/source/lib/Pipfile +++ b/source/lib/Pipfile @@ -32,6 +32,7 @@ types-setuptools = "*" types-urllib3 = "*" types-toml = "*" vistir = "==0.6.1" # Necessary for resolving a `vistir` version conflict with `pipenv-setup` +urllib3 = ">=2.2.2" wheel = "*" wrapt = "*" diff --git a/source/lib/Pipfile.lock b/source/lib/Pipfile.lock index 9b105599..90277114 100644 --- a/source/lib/Pipfile.lock +++ b/source/lib/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4c22a47abeb4659adab96b8438c0dec7fb4b51d8a70372fd03402bc240371d8b" + "sha256": "3bb9e1786bc0fc5e97c4ccbb735251e6e30deb0cbec16e6daee7be4f9f5f1838" }, "pipfile-spec": 6, "requires": { @@ -45,11 +45,11 @@ }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5", + "sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "cattrs": { "hashes": [ @@ -80,7 +80,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "six": { @@ -88,7 +88,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "typing-extensions": { @@ -266,12 +266,12 @@ }, "boto3": { "hashes": [ - "sha256:2f3e88b10b8fcc5f6100a9d74cd28230edc9d4fa226d99dd40a3ab38ac213673", - "sha256:b8433d481d50b68a0162c0379c0dd4aabfc3d1ad901800beb5b87815997511c1" + "sha256:69d5afb7a017d07dd6bdfb680d2912d5d369b3fafa0a45161207d9f393b14d7e", + "sha256:ac770fb53dde1743aec56bd8e56b7ee2e2f5ad42a37825968ec4ff8428822640" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "boto3-stubs": { "extras": [ @@ -280,27 +280,27 @@ "ssm" ], "hashes": [ - "sha256:4fc2f8d41ee7fb4952dc8d6a27bb616e7c0c5f9b71f3ad6d179f27df1bb69b0c", - "sha256:c19d0c4386ec160e6c06567b960b6f7c765acc4ed40f01f371c8e7a0b514520b" + "sha256:1303deb7d749a2c9bd5a30830e1d537c1474b70c5f429e3db9af5d3eaa7b9696", + "sha256:2d15ccfc44407baae897d3a531f9eb0826e207d70e92f5268e8703139bec2dba" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5", + "sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "botocore-stubs": { "hashes": [ - "sha256:3eb325b4b186e8d8e49d485cfe383f478ab8c07e0f4690e5ee9bcb6faceec4af", - "sha256:eded7afc301a08b5355ab8843c9d9bdea7dc43ece3e9628f186897dae7a56ceb" + "sha256:4c579734492713c142773c7405cf62b466dfaf5f05371b7510d3cd308b0ba6a5", + "sha256:69bcfc0c81eee294e681dc8049b7ca2a47a8f0510b88c05710f07dba6d0785c7" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.144" + "version": "==1.34.145" }, "cached-property": { "hashes": [ @@ -410,10 +410,10 @@ }, "cfn-lint": { "hashes": [ - "sha256:4d1fc2e12d672544518f82def7693438fa2ca8f5a17a67e7ad359f75162f9e79", - "sha256:8fc3b28fe315854268590361282f75cebb6b13a3ae1b753c8a1135107b465fa5" + "sha256:b144ea5af8a62cace5f239ef0456a0a7fcf472fe9767cd51844a0709ddfe5f30", + "sha256:bf34674a92a7b345a13521dda095101c8900388accc03bb8505aaf0a8b4a19d2" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "chardet": { "hashes": [ @@ -1043,10 +1043,10 @@ }, "mypy-boto3-ec2": { "hashes": [ - "sha256:3953ba88c08e633a2c671a9d442ddbb4bdcbb05f9aa179aeef42ae2376999a20", - "sha256:79e5e929947e81f45e711a4059aacfbda0418027d407b3b3c917ac3f163ea9f2" + "sha256:d6a200a341e65b584cc9af9861e8a331dc85d19a5316d9d0637d192824ec9375", + "sha256:f676e9382ca2ad1e419804a2759779018d7f0006ac86bd602479855073fafc79" ], - "version": "==1.34.143" + "version": "==1.34.145" }, "mypy-boto3-lambda": { "hashes": [ @@ -1057,10 +1057,10 @@ }, "mypy-boto3-rds": { "hashes": [ - "sha256:66b50037073434eb894b00f1597ba39f1e86789717ff9676c5a099f31bc39c9a", - "sha256:ce4872ee69cd10b895e11945b6c3255493b642e0410e48a2cfc6afe689cf6209" + "sha256:091a3605d4c2b010be2e61b306f7c877019344b3949b0bfceaee2db2a57bcc8c", + "sha256:2c28c05b4269c7dcd2ee0c3350784ad02a4c1764728b3335dd56bdbdd6ebd2f6" ], - "version": "==1.34.135" + "version": "==1.34.145" }, "mypy-boto3-s3": { "hashes": [ @@ -1071,10 +1071,10 @@ }, "mypy-boto3-secretsmanager": { "hashes": [ - "sha256:7ce9815d116fa1749971691355b1e1c8f462d46e7eaa9d84133b8db96dd3515f", - "sha256:ae2b398efa1a32214c3eddb6901efa67cfc24a893b113f549a06bb70bb43b402" + "sha256:986511caa6626edfed7eb11b63c929801e9468c58e15927dc6fc0339c4eb34cb", + "sha256:e5a82c05cce68168a3709e5f0d35066cf250961db1d8670f0111da66206814c7" ], - "version": "==1.34.128" + "version": "==1.34.145" }, "mypy-boto3-sqs": { "hashes": [ @@ -1190,7 +1190,7 @@ "sha256:6ceda7145a3088494d8ca68fded4b0473022dc62eb786a021c137632c44298b5" ], "index": "pypi", - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2' and python_version < '4'", "version": "==3.2.0" }, "pipfile": { @@ -1262,7 +1262,7 @@ "sha256:d1bf648df17077306100815d255d45430035b36f66bac635df04a323c61ba126" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.7.0'", + "markers": "python_full_version >= '3.7.0' and python_version < '4'", "version": "==2.4.0" }, "pycparser": { @@ -1433,7 +1433,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "pyyaml": { @@ -1762,7 +1762,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "sympy": { @@ -1779,7 +1779,7 @@ "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.8.1'", + "markers": "python_full_version >= '3.8.1' and python_version < '4'", "version": "==4.6.1" }, "toml": { @@ -1788,7 +1788,7 @@ "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], "index": "pypi", - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tomlkit": { diff --git a/source/lib/cms_common/auth/auth_configs.py b/source/lib/cms_common/auth/auth_configs.py index 47b74f5a..5eadf986 100644 --- a/source/lib/cms_common/auth/auth_configs.py +++ b/source/lib/cms_common/auth/auth_configs.py @@ -17,7 +17,7 @@ from botocore.exceptions import ClientError # Connected Mobility Solution on AWS -from ..resource_names.auth import AuthResourceNames +from ..resource_names.auth import AuthSetupResourceNames if TYPE_CHECKING: # Third Party Libraries @@ -81,8 +81,8 @@ def _get_ssm_client(user_agent_string: str) -> SSMClient: @lru_cache(maxsize=MAX_CACHE_SIZE_AUTH_CONFIG) -def _get_auth_resource_names(identity_provider_id: str) -> AuthResourceNames: - return AuthResourceNames.from_identity_provider_id(identity_provider_id) +def _get_auth_setup_resource_names(identity_provider_id: str) -> AuthSetupResourceNames: + return AuthSetupResourceNames.from_identity_provider_id(identity_provider_id) # Config getter functions @@ -90,8 +90,8 @@ def get_idp_config( user_agent_string: str, identity_provider_id: str, ) -> CMSIdPConfig: - auth_resource_names = _get_auth_resource_names(identity_provider_id) - idp_config_ssm_name = auth_resource_names.idp_config_secret_arn_ssm_parameter + auth_setup_resource_names = _get_auth_setup_resource_names(identity_provider_id) + idp_config_ssm_name = auth_setup_resource_names.idp_config_secret_arn_ssm_parameter return _get_config( user_agent_string=user_agent_string, ssm_name=idp_config_ssm_name, @@ -103,9 +103,9 @@ def get_service_client_config( user_agent_string: str, identity_provider_id: str, ) -> CMSClientConfig: - auth_resource_names = _get_auth_resource_names(identity_provider_id) + auth_setup_resource_names = _get_auth_setup_resource_names(identity_provider_id) client_config_ssm_name = ( - auth_resource_names.service_client_config_secret_arn_ssm_parameter + auth_setup_resource_names.service_client_config_secret_arn_ssm_parameter ) return _get_config( user_agent_string=user_agent_string, @@ -118,9 +118,9 @@ def get_user_client_config( user_agent_string: str, identity_provider_id: str, ) -> CMSClientConfig: - auth_resource_names = _get_auth_resource_names(identity_provider_id) + auth_setup_resource_names = _get_auth_setup_resource_names(identity_provider_id) user_client_config_ssm_name = ( - auth_resource_names.user_client_config_secret_arn_ssm_parameter + auth_setup_resource_names.user_client_config_secret_arn_ssm_parameter ) return _get_config( user_agent_string=user_agent_string, diff --git a/source/lib/cms_common/auth/tests/fixture_auth.py b/source/lib/cms_common/auth/tests/fixture_auth.py index 6f39233c..f1cbc531 100644 --- a/source/lib/cms_common/auth/tests/fixture_auth.py +++ b/source/lib/cms_common/auth/tests/fixture_auth.py @@ -15,11 +15,11 @@ import boto3 # Connected Mobility Solution on AWS -from ...resource_names.auth import AuthResourceNames +from ...resource_names.auth import AuthSetupResourceNames TEST_USER_AGENT_STRING = "test-user-agent-string" TEST_IDENTITY_PROVIDER_ID = "test_idp" -TEST_AUTH_RESOURCE_NAMES_CLASS = AuthResourceNames.from_identity_provider_id( +TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS = AuthSetupResourceNames.from_identity_provider_id( TEST_IDENTITY_PROVIDER_ID ) @@ -56,13 +56,13 @@ def fixture_mock_idp_config_valid( def moto_boto() -> None: secretsmanager_client: SecretsManagerClient = boto3.client("secretsmanager") secret_arn = secretsmanager_client.create_secret( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.idp_config_secret, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.idp_config_secret, SecretString=json.dumps(idp_config_secret_string_valid), )["ARN"] ssm_client: SSMClient = boto3.client("ssm") ssm_client.put_parameter( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.idp_config_secret_arn_ssm_parameter, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.idp_config_secret_arn_ssm_parameter, Value=secret_arn, Type="String", ) @@ -76,13 +76,13 @@ def fixture_mock_idp_config_invalid_json() -> Callable[[], None]: def moto_boto() -> None: secretsmanager_client: SecretsManagerClient = boto3.client("secretsmanager") secret_arn = secretsmanager_client.create_secret( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.idp_config_secret, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.idp_config_secret, SecretString="Not a valid json string", )["ARN"] ssm_client: SSMClient = boto3.client("ssm") ssm_client.put_parameter( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.idp_config_secret_arn_ssm_parameter, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.idp_config_secret_arn_ssm_parameter, Value=secret_arn, Type="String", ) @@ -96,13 +96,13 @@ def fixture_mock_idp_config_invalid_data_format() -> Callable[[], None]: def moto_boto() -> None: secretsmanager_client: SecretsManagerClient = boto3.client("secretsmanager") secret_arn = secretsmanager_client.create_secret( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.idp_config_secret, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.idp_config_secret, SecretString=json.dumps({"incorrect_key": "value"}), )["ARN"] ssm_client: SSMClient = boto3.client("ssm") ssm_client.put_parameter( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.idp_config_secret_arn_ssm_parameter, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.idp_config_secret_arn_ssm_parameter, Value=secret_arn, Type="String", ) @@ -129,13 +129,13 @@ def fixture_mock_service_client_config_valid( def moto_boto() -> None: secretsmanager_client: SecretsManagerClient = boto3.client("secretsmanager") secret_arn = secretsmanager_client.create_secret( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.service_client_config_secret, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.service_client_config_secret, SecretString=json.dumps(service_client_config_secret_string_valid), )["ARN"] ssm_client: SSMClient = boto3.client("ssm") ssm_client.put_parameter( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.service_client_config_secret_arn_ssm_parameter, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.service_client_config_secret_arn_ssm_parameter, Value=secret_arn, Type="String", ) @@ -161,13 +161,13 @@ def fixture_mock_user_client_config_valid( def moto_boto() -> None: secretsmanager_client: SecretsManagerClient = boto3.client("secretsmanager") secret_arn = secretsmanager_client.create_secret( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.user_client_config_secret, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.user_client_config_secret, SecretString=json.dumps(user_client_config_secret_string_valid), )["ARN"] ssm_client: SSMClient = boto3.client("ssm") ssm_client.put_parameter( - Name=TEST_AUTH_RESOURCE_NAMES_CLASS.user_client_config_secret_arn_ssm_parameter, + Name=TEST_AUTH_SETUP_RESOURCE_NAMES_CLASS.user_client_config_secret_arn_ssm_parameter, Value=secret_arn, Type="String", ) diff --git a/source/lib/cms_common/config/regex.py b/source/lib/cms_common/config/regex.py index 89681434..5b01dedd 100644 --- a/source/lib/cms_common/config/regex.py +++ b/source/lib/cms_common/config/regex.py @@ -5,6 +5,7 @@ class RegexPattern: EMAIL = r"^[_A-Za-z0-9-\+]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,})$" + OPTIONAL_EMAIL = r"(^$)|^[_A-Za-z0-9-\+]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,})$" DOMAIN_NAME = r"^([A-Za-z0-9][A-Za-z0-9-]*\.)+[A-Za-z]+$" CALLBACK_URLS = ( r"^[a-zA_Z]{1}[a-zA-Z0-9+-.]*:\/\/(www\.)?[a-zA-Z0-9\/@%._\+~=-]*\b\/?$" diff --git a/source/lib/cms_common/constructs/app_registry.py b/source/lib/cms_common/constructs/app_registry.py index 2d75ac51..6cdd05db 100644 --- a/source/lib/cms_common/constructs/app_registry.py +++ b/source/lib/cms_common/constructs/app_registry.py @@ -6,7 +6,7 @@ from dataclasses import dataclass # AWS Libraries -from aws_cdk import Stack, aws_servicecatalogappregistry +from aws_cdk import Stack, Tags, aws_servicecatalogappregistry from constructs import Construct @@ -22,14 +22,14 @@ class AppRegistryInputs: class AppRegistryConstruct(Construct): def __init__( self, - scope: Construct, + scope: Stack, # Scope should be the top-level stack of which we are registering construct_id: str, app_registry_inputs: AppRegistryInputs, ) -> None: super().__init__(scope, construct_id) - region = Stack.of(self).region - account = Stack.of(self).account + region = scope.region + account = scope.account cfn_application = aws_servicecatalogappregistry.CfnApplication( self, @@ -58,14 +58,10 @@ def __init__( attribute_group=attribute_group.attr_id, ) - # Associate stacks with application registry, including this stack. - for child in Stack.of(self).node.find_all(): - if Stack.is_stack(child): - stack = Stack.of(child) - aws_servicecatalogappregistry.CfnResourceAssociation( - stack, - "app-registry-application-stack-association", - application=cfn_application.attr_id, - resource=stack.stack_id, - resource_type="CFN_STACK", - ) + # Add awsApplication tag to stack associated with Application to facilitate "onboard"-ing application to myApplications dashboard + # Onboarding of application is not complete until top-level Stack gets the tag, which will need to be manually done post-deploy + Tags.of(scope).add( + "awsApplication", + cfn_application.attr_application_tag_value, + exclude_resource_types=[cfn_application.cfn_resource_type], + ) diff --git a/source/lib/cms_common/constructs/cmk_encrypted_s3.py b/source/lib/cms_common/constructs/cmk_encrypted_s3.py new file mode 100644 index 00000000..c4c824ff --- /dev/null +++ b/source/lib/cms_common/constructs/cmk_encrypted_s3.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# AWS Libraries +from aws_cdk import RemovalPolicy, aws_kms, aws_s3 +from constructs import Construct + + +class CMKEncryptedS3Construct(Construct): + def __init__( + self, scope: Construct, construct_id: str, retain_on_stack_delete: bool = True + ) -> None: + super().__init__(scope, construct_id) + + self.key = aws_kms.Key( + self, + "cmk-key", + enable_key_rotation=True, + ) + + self.log_bucket = aws_s3.Bucket( + self, + "log-bucket", + enforce_ssl=True, + block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL, + versioned=True, + encryption=aws_s3.BucketEncryption.KMS, + encryption_key=self.key, + ) + + self.bucket: aws_s3.Bucket = aws_s3.Bucket( + self, + "cmk-encrypted-bucket", + enforce_ssl=True, + encryption_key=self.key, + encryption=aws_s3.BucketEncryption.KMS, + server_access_logs_bucket=self.log_bucket, + block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL, + versioned=True, + auto_delete_objects=(not retain_on_stack_delete), + removal_policy=RemovalPolicy.RETAIN + if retain_on_stack_delete + else RemovalPolicy.DESTROY, + ) diff --git a/source/lib/cms_common/constructs/lambda_dependencies.py b/source/lib/cms_common/constructs/lambda_dependencies.py index 01ed2bbc..a03277c5 100644 --- a/source/lib/cms_common/constructs/lambda_dependencies.py +++ b/source/lib/cms_common/constructs/lambda_dependencies.py @@ -47,7 +47,6 @@ def __init__( with open(pipfile_path, "r", encoding="utf-8") as pipfile: new_pipfile = toml.load(pipfile) with open(requirements, "w", encoding="utf-8") as requirements_file: - for package, constraint in new_pipfile["packages"].items(): if package not in ["boto3", "aws-cdk-lib"]: self.req_formatter( @@ -71,7 +70,10 @@ def __init__( self.dependency_layer = aws_lambda.LayerVersion( self, "lambda-dependency-layer-version", - code=aws_lambda.Code.from_asset(dependency_layer_path), + code=aws_lambda.Code.from_asset( + dependency_layer_path, + exclude=["**/tests/*"], + ), compatible_architectures=[ aws_lambda.Architecture.X86_64, aws_lambda.Architecture.ARM_64, diff --git a/source/lib/cms_common/constructs/tests/__snapshots__/test_app_registry/test_app_registry_snapshot.json b/source/lib/cms_common/constructs/tests/__snapshots__/test_app_registry/test_app_registry_snapshot.json index ebb64274..8a173134 100644 --- a/source/lib/cms_common/constructs/tests/__snapshots__/test_app_registry/test_app_registry_snapshot.json +++ b/source/lib/cms_common/constructs/tests/__snapshots__/test_app_registry/test_app_registry_snapshot.json @@ -7,21 +7,6 @@ } }, "Resources": { - "appregistryapplicationstackassociation": { - "Properties": { - "Application": { - "Fn::GetAtt": [ - "testappregistryappregistryapplication2A74C8E2", - "Id" - ] - }, - "Resource": { - "Ref": "AWS::StackId" - }, - "ResourceType": "CFN_STACK" - }, - "Type": "AWS::ServiceCatalogAppRegistry::ResourceAssociation" - }, "testappregistryappregistryapplication2A74C8E2": { "Properties": { "Name": { @@ -82,6 +67,14 @@ } ] ] + }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "testappregistryappregistryapplication2A74C8E2", + "ApplicationTagValue" + ] + } } }, "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroup" diff --git a/source/lib/cms_common/constructs/tests/__snapshots__/test_cmk_encrypted_s3/test_cmk_encrypted_s3_snapshot.json b/source/lib/cms_common/constructs/tests/__snapshots__/test_cmk_encrypted_s3/test_cmk_encrypted_s3_snapshot.json new file mode 100644 index 00000000..b88f047c --- /dev/null +++ b/source/lib/cms_common/constructs/tests/__snapshots__/test_cmk_encrypted_s3/test_cmk_encrypted_s3_snapshot.json @@ -0,0 +1,256 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value" + } + }, + "Resources": { + "testcmkencrypteds3cmkencryptedbucket46929EB8": { + "DeletionPolicy": "Retain", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "testcmkencrypteds3cmkkey97BACFB2", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "testcmkencrypteds3logbucket4E5C121A" + } + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain" + }, + "testcmkencrypteds3cmkencryptedbucketPolicy51763352": { + "Properties": { + "Bucket": { + "Ref": "testcmkencrypteds3cmkencryptedbucket46929EB8" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "testcmkencrypteds3cmkencryptedbucket46929EB8", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testcmkencrypteds3cmkencryptedbucket46929EB8", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + }, + "Type": "AWS::S3::BucketPolicy" + }, + "testcmkencrypteds3cmkkey97BACFB2": { + "DeletionPolicy": "Retain", + "Properties": { + "EnableKeyRotation": true, + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "Service": "logging.s3.amazonaws.com" + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain" + }, + "testcmkencrypteds3logbucket4E5C121A": { + "DeletionPolicy": "Retain", + "Properties": { + "AccessControl": "LogDeliveryWrite", + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "testcmkencrypteds3cmkkey97BACFB2", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "OwnershipControls": { + "Rules": [ + { + "ObjectOwnership": "ObjectWriter" + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain" + }, + "testcmkencrypteds3logbucketPolicyB0601A0B": { + "Properties": { + "Bucket": { + "Ref": "testcmkencrypteds3logbucket4E5C121A" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "testcmkencrypteds3logbucket4E5C121A", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testcmkencrypteds3logbucket4E5C121A", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + }, + "Type": "AWS::S3::BucketPolicy" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} diff --git a/source/lib/cms_common/constructs/tests/fixture_constructs.py b/source/lib/cms_common/constructs/tests/fixture_constructs.py index e5061831..f9f52bcc 100644 --- a/source/lib/cms_common/constructs/tests/fixture_constructs.py +++ b/source/lib/cms_common/constructs/tests/fixture_constructs.py @@ -20,6 +20,7 @@ # Connected Mobility Solution on AWS from ..app_unique_id import AppUniqueId from ..cdk_lambda_vpc_config_construct import CDKLambdasVpcConfigConstruct +from ..cmk_encrypted_s3 import CMKEncryptedS3Construct from ..custom_resource_lambda import CustomResourceLambdaConstruct from ..identity_provider_config import IdentityProviderConfig from ..lambda_dependencies import LambdaDependenciesConstruct, LambdaDependencyError @@ -64,6 +65,16 @@ def fixture_app_unique_id_cfn_parameter( return dict(app_unique_id_stack.to_json()["Parameters"])["AppUniqueId"] +@pytest.fixture(name="cmk_encrpyted_s3_stack", scope="session") +def fixture_cmk_encrpyted_s3_stack() -> assertions.Template: + stack = Stack() + CMKEncryptedS3Construct( + stack, + "test-cmk-encrypted-s3", + ) + return assertions.Template.from_stack(stack) + + @pytest.fixture(name="empty_lambda_dependencies_stack", scope="session") def fixture_empty_lambda_dependencies_stack() -> assertions.Template: with tempfile.TemporaryDirectory() as tmpdirname: @@ -119,6 +130,7 @@ def fixture_custom_resource_lambda_stack() -> assertions.Template: vpc_config=VpcConfig( vpc_name="test-vpc-name", vpc_id="test-vpc-id", + vpc_cidr_block="test-cidr-block", public_subnets=["test-vpc-public-subnet-1", "test-vpc-public-subnet-2"], private_subnets=[ "test-vpc-private-subnet-1", @@ -161,6 +173,7 @@ def fixture_cdk_lambda_vpc_config_construct_stack_template() -> assertions.Templ vpc_config=VpcConfig( vpc_name="test-vpc-name", vpc_id="test-vpc-id", + vpc_cidr_block="test-cidr-block", public_subnets=["test-vpc-public-subnet-1", "test-vpc-public-subnet-2"], private_subnets=[ "test-vpc-private-subnet-1", @@ -196,6 +209,7 @@ def fixture_vpc_construct_stack_template() -> assertions.Template: vpc_config=VpcConfig( vpc_name="test-vpc-name", vpc_id="test-vpc-id", + vpc_cidr_block="test-cidr-block", public_subnets=["test-vpc-public-subnet-1", "test-vpc-public-subnet-2"], private_subnets=[ "test-vpc-private-subnet-1", @@ -222,6 +236,7 @@ def fixture_vpc_construct_stack() -> VpcConstruct: vpc_config=VpcConfig( vpc_name="test-vpc-name", vpc_id="test-vpc-id", + vpc_cidr_block="test-cidr-block", public_subnets=["test-vpc-public-subnet-1", "test-vpc-public-subnet-2"], private_subnets=[ "test-vpc-private-subnet-1", diff --git a/source/lib/cms_common/constructs/tests/test_cmk_encrypted_s3.py b/source/lib/cms_common/constructs/tests/test_cmk_encrypted_s3.py new file mode 100644 index 00000000..f46b1396 --- /dev/null +++ b/source/lib/cms_common/constructs/tests/test_cmk_encrypted_s3.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Third Party Libraries +from syrupy.types import SerializableData + +# AWS Libraries +from aws_cdk import assertions + + +def test_cmk_encrypted_s3_snapshot( + cmk_encrpyted_s3_stack: assertions.Template, + snapshot_json_with_matcher: SerializableData, +) -> None: + + assert cmk_encrpyted_s3_stack.to_json() == snapshot_json_with_matcher diff --git a/source/lib/cms_common/constructs/vpc_construct.py b/source/lib/cms_common/constructs/vpc_construct.py index 54d9be40..96739848 100644 --- a/source/lib/cms_common/constructs/vpc_construct.py +++ b/source/lib/cms_common/constructs/vpc_construct.py @@ -45,6 +45,7 @@ def get_vpc_name(scope: Construct, app_unique_id: str) -> str: class VpcConfig: vpc_name: str vpc_id: str + vpc_cidr_block: str public_subnets: List[str] private_subnets: List[str] isolated_subnets: List[str] @@ -60,6 +61,11 @@ def create_vpc_config(vpc_name: str) -> VpcConfig: prefix=vpc_ssm_prefix, name="vpcid" ) ), + vpc_cidr_block=resolve_ssm_parameter( + parameter_name=ResourceName.slash_separated( + prefix=vpc_ssm_prefix, name="cidr" + ) + ), public_subnets=[ resolve_ssm_parameter( parameter_name=ResourceName.slash_separated( @@ -123,6 +129,7 @@ def __init__( construct_id: str, vpc_id: str, vpc_name: str, + vpc_cidr_block: str, public_subnets: List[aws_ec2.ISubnet], private_subnets: List[aws_ec2.ISubnet], isolated_subnets: List[aws_ec2.ISubnet], @@ -131,6 +138,7 @@ def __init__( super().__init__(scope, construct_id) self._vpc_id = vpc_id self._vpc_name = vpc_name + self._vpc_cidr_block = vpc_cidr_block self._vpc_arn = Stack.of(self).format_arn( service="ec2", resource="vpc", resource_name=self.vpc_id ) @@ -164,6 +172,10 @@ def isolated_subnets(self) -> List[aws_ec2.ISubnet]: def vpc_arn(self) -> str: return self._vpc_arn + @property + def vpc_cidr_block(self) -> str: + return self._vpc_cidr_block + def select_subnets(self, selection: aws_ec2.SubnetSelection) -> Dict[str, Any]: ### As of now this function only supports selection of subnet by types selected_subnets = None @@ -275,6 +287,7 @@ def __init__( "cms-vpc", vpc_id=vpc_config.vpc_id, vpc_name=vpc_config.vpc_name, + vpc_cidr_block=vpc_config.vpc_cidr_block, public_subnets=self.public_subnets, private_subnets=self.private_subnets, isolated_subnets=self.isolated_subnets, diff --git a/source/lib/cms_common/constructs/vpc_prefix_list_lookup_custom_resource.py b/source/lib/cms_common/constructs/vpc_prefix_list_lookup_custom_resource.py index 7813b3ba..451bd2da 100644 --- a/source/lib/cms_common/constructs/vpc_prefix_list_lookup_custom_resource.py +++ b/source/lib/cms_common/constructs/vpc_prefix_list_lookup_custom_resource.py @@ -128,18 +128,6 @@ def __init__( lambda_function_security_group = provider_function.node.find_child( "SecurityGroup" ).node.default_child - NagSuppression.add_inline_suppression( - lambda_function, - suppression={ - "rules_to_suppress": [ - { - "id": "AwsSolutions-L1", - "reason": "Log retention lambda uses policies that require wildcard permissions", - }, - ] - }, - nag_type=NagType.CDK_NAG, - ) NagSuppression.add_inline_suppression( node=lambda_function, suppression={ diff --git a/source/lib/cms_common/policy_generators/__init__.py b/source/lib/cms_common/policy_generators/__init__.py index 55d7470d..09f20691 100644 --- a/source/lib/cms_common/policy_generators/__init__.py +++ b/source/lib/cms_common/policy_generators/__init__.py @@ -5,4 +5,4 @@ # Connected Mobility Solution on AWS from .cloudwatch import generate_lambda_cloudwatch_logs_policy_document from .ec2_vpc import generate_ec2_vpc_policy -from .kms import generate_kms_policy_statement +from .kms import generate_kms_policy_statement_from_key_id diff --git a/source/lib/cms_common/policy_generators/ec2_vpc.py b/source/lib/cms_common/policy_generators/ec2_vpc.py index 69bf68bb..6d288b15 100644 --- a/source/lib/cms_common/policy_generators/ec2_vpc.py +++ b/source/lib/cms_common/policy_generators/ec2_vpc.py @@ -62,7 +62,7 @@ def generate_ec2_vpc_policy( "ec2:DeleteNetworkInterface", ], effect=aws_iam.Effect.ALLOW, - resources=["*"], + resources=["*"], # NOSONAR ), ] ) diff --git a/source/lib/cms_common/policy_generators/kms.py b/source/lib/cms_common/policy_generators/kms.py index b4e021f8..f4c6bc5d 100644 --- a/source/lib/cms_common/policy_generators/kms.py +++ b/source/lib/cms_common/policy_generators/kms.py @@ -8,10 +8,10 @@ from constructs import Construct -def generate_kms_policy_statement( +def generate_kms_policy_statement_from_key_id( self: Construct, kms_encryption_key_id: str, allow_encrypt: bool ) -> aws_iam.PolicyStatement: - policy_permissions = ["kms:Decrypt"] + policy_permissions = ["kms:Decrypt", "kms:DescribeKey"] encrypt_permissions = ["kms:Encrypt", "kms:GenerateDataKey"] if allow_encrypt: policy_permissions.extend(encrypt_permissions) @@ -27,3 +27,19 @@ def generate_kms_policy_statement( ), ], ) + + +def generate_kms_policy_statement_from_key_arn( + kms_encryption_key_arn: str, allow_encrypt: bool +) -> aws_iam.PolicyStatement: + policy_permissions = ["kms:Decrypt", "kms:DescribeKey"] + encrypt_permissions = ["kms:Encrypt", "kms:GenerateDataKey"] + if allow_encrypt: + policy_permissions.extend(encrypt_permissions) + return aws_iam.PolicyStatement( + effect=aws_iam.Effect.ALLOW, + actions=policy_permissions, + resources=[ + kms_encryption_key_arn, + ], + ) diff --git a/source/lib/cms_common/resource_names/__init__.py b/source/lib/cms_common/resource_names/__init__.py index 0e3de8b6..c658ee90 100644 --- a/source/lib/cms_common/resource_names/__init__.py +++ b/source/lib/cms_common/resource_names/__init__.py @@ -3,6 +3,6 @@ # SPDX-License-Identifier: Apache-2.0 # Connected Mobility Solution on AWS -from .auth import AuthResourceNames +from .auth import AuthResourceNames, AuthSetupResourceNames from .config import ConfigResourceNames from .module_short_names import CMSModuleShortNames diff --git a/source/lib/cms_common/resource_names/auth.py b/source/lib/cms_common/resource_names/auth.py index 71a76a3f..1d2facdb 100644 --- a/source/lib/cms_common/resource_names/auth.py +++ b/source/lib/cms_common/resource_names/auth.py @@ -10,7 +10,7 @@ @dataclass(frozen=True) -class AuthResourceNames: +class AuthSetupResourceNames: auth_prefix: str idp_config_secret: str idp_config_secret_arn_ssm_parameter: str @@ -18,14 +18,15 @@ class AuthResourceNames: service_client_config_secret_arn_ssm_parameter: str user_client_config_secret: str user_client_config_secret_arn_ssm_parameter: str + user_pool_id: str @classmethod def from_identity_provider_id( cls, identity_provider_id: str - ) -> "AuthResourceNames": + ) -> "AuthSetupResourceNames": auth_prefix = f"/solution/{CMSModuleShortNames.AUTH}" auth_prefix_with_id = f"{auth_prefix}/{identity_provider_id}" - return AuthResourceNames( + return AuthSetupResourceNames( auth_prefix=auth_prefix_with_id, idp_config_secret=f"{auth_prefix_with_id}/idp-config", idp_config_secret_arn_ssm_parameter=f"{auth_prefix_with_id}/idp-config/secret/arn", @@ -33,4 +34,19 @@ def from_identity_provider_id( service_client_config_secret_arn_ssm_parameter=f"{auth_prefix_with_id}/service-client-config/default/secret/arn", user_client_config_secret=f"{auth_prefix_with_id}/user-client-config/default", user_client_config_secret_arn_ssm_parameter=f"{auth_prefix_with_id}/user-client-config/default/secret/arn", + user_pool_id=f"{auth_prefix_with_id}/user-pool/id" + ) + + +@dataclass(frozen=True) +class AuthResourceNames: + token_validation_lambda_arn: str + authorization_code_exchange_lambda_arn: str + + @classmethod + def from_app_unique_id(cls, app_unique_id: str) -> "AuthResourceNames": + auth_prefix = f"/solution/{app_unique_id}/{CMSModuleShortNames.AUTH}" + return AuthResourceNames( + token_validation_lambda_arn=f"{auth_prefix}/token-validation-lambda/arn", + authorization_code_exchange_lambda_arn=f"{auth_prefix}/authorization-code-flow/authorization-code-exchange-lambda/arn", ) diff --git a/source/lib/cms_common/resource_names/module_short_names.py b/source/lib/cms_common/resource_names/module_short_names.py index 1f7d149f..3142c2a6 100644 --- a/source/lib/cms_common/resource_names/module_short_names.py +++ b/source/lib/cms_common/resource_names/module_short_names.py @@ -20,6 +20,7 @@ class CMSModuleShortNames: PROVISIONING: str = "provisioning" SAMPLE: str = "sample" VEHICLE_SIMULATOR: str = "vehicle-simulator" + PREDICTIVE_MAINTENANCE: str = "predictive-maintenance" # pylint: enable=invalid-name diff --git a/source/lib/cms_common/resource_names/tests/test_auth.py b/source/lib/cms_common/resource_names/tests/test_auth.py index d667cb5a..47f7b1c5 100644 --- a/source/lib/cms_common/resource_names/tests/test_auth.py +++ b/source/lib/cms_common/resource_names/tests/test_auth.py @@ -6,13 +6,17 @@ import pytest # Connected Mobility Solution on AWS -from ..auth import AuthResourceNames +from ..auth import AuthResourceNames, AuthSetupResourceNames TEST_IDENTITY_PROVIDER_ID = "test-idp" +TEST_APP_UNIQUE_ID = "test-app" -auth_resource_names = AuthResourceNames.from_identity_provider_id( +auth_setup_resource_names = AuthSetupResourceNames.from_identity_provider_id( identity_provider_id=TEST_IDENTITY_PROVIDER_ID ) +auth_resource_names = AuthResourceNames.from_app_unique_id( + app_unique_id=TEST_APP_UNIQUE_ID +) @pytest.mark.parametrize( @@ -42,5 +46,22 @@ ), ], ) +def test_auth_setup(attribute: str, expected_attribute_value: str) -> None: + assert getattr(auth_setup_resource_names, attribute) == expected_attribute_value + + +@pytest.mark.parametrize( + "attribute, expected_attribute_value", + [ + ( + "token_validation_lambda_arn", + f"/solution/{TEST_APP_UNIQUE_ID}/auth/token-validation-lambda/arn", + ), + ( + "authorization_code_exchange_lambda_arn", + f"/solution/{TEST_APP_UNIQUE_ID}/auth/authorization-code-flow/authorization-code-exchange-lambda/arn", + ), + ], +) def test_auth(attribute: str, expected_attribute_value: str) -> None: assert getattr(auth_resource_names, attribute) == expected_attribute_value diff --git a/source/lib/conftest.py b/source/lib/conftest.py index f1c56b53..f7c212eb 100644 --- a/source/lib/conftest.py +++ b/source/lib/conftest.py @@ -34,6 +34,7 @@ fixture_app_unique_id_cfn_parameter, fixture_app_unique_id_stack, fixture_cdk_lambda_vpc_config_construct_stack_template, + fixture_cmk_encrpyted_s3_stack, fixture_custom_resource_lambda_stack, fixture_identity_provider_config_stack, fixture_empty_lambda_dependencies_stack, diff --git a/source/lib/pyproject.toml b/source/lib/pyproject.toml index f85d4abf..e6ac7ebf 100644 --- a/source/lib/pyproject.toml +++ b/source/lib/pyproject.toml @@ -7,6 +7,7 @@ fail_under = 80.0 omit = [ "**/scripts/*", "setup.py", + "conftest.py", "**/tests/*", "**/*_dependency_layer/**/*", ] diff --git a/source/lib/setup.py b/source/lib/setup.py index f69443e1..0088b7db 100644 --- a/source/lib/setup.py +++ b/source/lib/setup.py @@ -52,7 +52,7 @@ def finalize_options(self) -> None: "toml>=0.10.2", ], name="cms_common", - version="1.2.2", + version="2.0.0", description="Common library used in CMS modules", packages=find_packages( exclude=[ diff --git a/source/modules/acdp/.nvmrc b/source/modules/acdp/.nvmrc index aacb5181..0305213f 100644 --- a/source/modules/acdp/.nvmrc +++ b/source/modules/acdp/.nvmrc @@ -1 +1 @@ -18.17 +18.20 diff --git a/source/modules/acdp/.pre-commit-config.yaml b/source/modules/acdp/.pre-commit-config.yaml index de3993bd..8a652823 100644 --- a/source/modules/acdp/.pre-commit-config.yaml +++ b/source/modules/acdp/.pre-commit-config.yaml @@ -92,7 +92,7 @@ repos: hooks: - id: pip-audit name: (ACDP) Pip audit - - repo: https://github.com/pre-commit/mirrors-prettier + - repo: https://github.com/pre-commit/mirrors-prettier # Archived, look to replace eventually rev: v3.1.0 hooks: - id: prettier diff --git a/source/modules/acdp/Makefile b/source/modules/acdp/Makefile index d3f94006..665da159 100644 --- a/source/modules/acdp/Makefile +++ b/source/modules/acdp/Makefile @@ -37,9 +37,6 @@ include ${SOLUTION_PATH}/makefiles/common_config.mk include ${SOLUTION_PATH}/makefiles/global_targets.mk include ${SOLUTION_PATH}/makefiles/module_targets.mk -## ======================================================== -## BUILD AND DEPLOY -## ======================================================== .PHONY: install install: pipenv-install cdk-solution-helper-install ## Installs the resources and dependencies required to build the solution. @@ -59,6 +56,7 @@ deploy: verify-environment ## Deploy the stack for the module. "AcdpUniqueId"="${ACDP_UNIQUE_ID}" \ "VpcName"="${VPC_NAME}" \ "IdentityProviderId"="${IDENTITY_PROVIDER_ID}" \ + "DefaultUserEmail"="${DEFAULT_USER_EMAIL}" \ "UseBackstageAuthRedirectFlow"="${USE_BACKSTAGE_AUTH_REDIRECT_FLOW}" \ "BackstageAdditionalScopes"="${BACKSTAGE_ADDITIONAL_SCOPES}" \ "FullyQualifiedDomainName"="${FULLY_QUALIFIED_DOMAIN_NAME}" \ @@ -71,17 +69,16 @@ deploy: verify-environment ## Deploy the stack for the module. "BackstageLocalAssetDiscoveryRefreshMins"="${BACKSTAGE_S3_DISCOVERY_REFRESH_MINS}" \ -.PHONY: destroy-ecr -destroy-ecr: ## Destroy the ECR images since CloudFormation cannot. - @printf "%bDelete the ECR repository.%b\n" "${MAGENTA}" "${NC}" - aws ecr delete-repository --repository-name "${ACDP_UNIQUE_ID}-backstage" --force || true +.PHONY: destroy-backstage +destroy-backstage: ## Delete the backstage stack and ecr repository + @printf "%bCalling Backstage destroy target.%b\n" "${MAGENTA}" "${NC}" + @cd ../backstage && make destroy + @cd ../acdp + @printf "%bBackstage destroy finished.%b\n" "${MAGENTA}" "${NC}" .PHONY: destroy -destroy: destroy-ecr destroy-stack ## Delete the stack for the module. - @printf "%bDelete the module deployment.%b\n" "${MAGENTA}" "${NC}" - aws ecr delete-repository --repository-name "${ACDP_UNIQUE_ID}-backstage" --force || true - aws cloudformation delete-stack \ - --stack-name ${STACK_NAME} \ +destroy: destroy-backstage destroy-stack ## Delete the stack for the module. + @printf "%bDestroy finished.%b\n" "${GREEN}" "${NC}" ## ======================================================== ## UTILITY diff --git a/source/modules/acdp/Pipfile b/source/modules/acdp/Pipfile index cbf78d83..22ebd1f6 100644 --- a/source/modules/acdp/Pipfile +++ b/source/modules/acdp/Pipfile @@ -35,6 +35,7 @@ types-requests = ">=2.32.0" types-setuptools = "*" types-urllib3 = "*" types-toml = "*" +urllib3 = ">=2.2.2" wheel = "*" wrapt = "*" diff --git a/source/modules/acdp/Pipfile.lock b/source/modules/acdp/Pipfile.lock index c3a3086c..ed61c1bb 100644 --- a/source/modules/acdp/Pipfile.lock +++ b/source/modules/acdp/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4580c96642630890a15b2b04b9902c53c1582010e877dd2f319c51a654b2422a" + "sha256": "c76badb5fb4d1eb566606162f816cab25834589a11d8b139309fb1d07e036ed3" }, "pipfile-spec": 6, "requires": { @@ -37,11 +37,11 @@ }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5", + "sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "certifi": { "hashes": [ @@ -175,7 +175,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "requests": { @@ -192,7 +192,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "typing-extensions": { @@ -368,39 +368,39 @@ }, "boto3": { "hashes": [ - "sha256:2f3e88b10b8fcc5f6100a9d74cd28230edc9d4fa226d99dd40a3ab38ac213673", - "sha256:b8433d481d50b68a0162c0379c0dd4aabfc3d1ad901800beb5b87815997511c1" + "sha256:69d5afb7a017d07dd6bdfb680d2912d5d369b3fafa0a45161207d9f393b14d7e", + "sha256:ac770fb53dde1743aec56bd8e56b7ee2e2f5ad42a37825968ec4ff8428822640" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "boto3-stubs": { "extras": [ "essential" ], "hashes": [ - "sha256:4fc2f8d41ee7fb4952dc8d6a27bb616e7c0c5f9b71f3ad6d179f27df1bb69b0c", - "sha256:c19d0c4386ec160e6c06567b960b6f7c765acc4ed40f01f371c8e7a0b514520b" + "sha256:1303deb7d749a2c9bd5a30830e1d537c1474b70c5f429e3db9af5d3eaa7b9696", + "sha256:2d15ccfc44407baae897d3a531f9eb0826e207d70e92f5268e8703139bec2dba" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5", + "sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "botocore-stubs": { "hashes": [ - "sha256:3eb325b4b186e8d8e49d485cfe383f478ab8c07e0f4690e5ee9bcb6faceec4af", - "sha256:eded7afc301a08b5355ab8843c9d9bdea7dc43ece3e9628f186897dae7a56ceb" + "sha256:4c579734492713c142773c7405cf62b466dfaf5f05371b7510d3cd308b0ba6a5", + "sha256:69bcfc0c81eee294e681dc8049b7ca2a47a8f0510b88c05710f07dba6d0785c7" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.144" + "version": "==1.34.145" }, "cattrs": { "hashes": [ @@ -990,10 +990,10 @@ }, "mypy-boto3-ec2": { "hashes": [ - "sha256:3953ba88c08e633a2c671a9d442ddbb4bdcbb05f9aa179aeef42ae2376999a20", - "sha256:79e5e929947e81f45e711a4059aacfbda0418027d407b3b3c917ac3f163ea9f2" + "sha256:d6a200a341e65b584cc9af9861e8a331dc85d19a5316d9d0637d192824ec9375", + "sha256:f676e9382ca2ad1e419804a2759779018d7f0006ac86bd602479855073fafc79" ], - "version": "==1.34.143" + "version": "==1.34.145" }, "mypy-boto3-lambda": { "hashes": [ @@ -1004,10 +1004,10 @@ }, "mypy-boto3-rds": { "hashes": [ - "sha256:66b50037073434eb894b00f1597ba39f1e86789717ff9676c5a099f31bc39c9a", - "sha256:ce4872ee69cd10b895e11945b6c3255493b642e0410e48a2cfc6afe689cf6209" + "sha256:091a3605d4c2b010be2e61b306f7c877019344b3949b0bfceaee2db2a57bcc8c", + "sha256:2c28c05b4269c7dcd2ee0c3350784ad02a4c1764728b3335dd56bdbdd6ebd2f6" ], - "version": "==1.34.135" + "version": "==1.34.145" }, "mypy-boto3-s3": { "hashes": [ @@ -1106,7 +1106,7 @@ "sha256:d1bf648df17077306100815d255d45430035b36f66bac635df04a323c61ba126" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.7.0'", + "markers": "python_full_version >= '3.7.0' and python_version < '4'", "version": "==2.4.0" }, "pygments": { @@ -1166,7 +1166,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "python-slugify": { @@ -1374,7 +1374,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "syrupy": { @@ -1383,7 +1383,7 @@ "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.8.1'", + "markers": "python_full_version >= '3.8.1' and python_version < '4'", "version": "==4.6.1" }, "text-unidecode": { @@ -1399,7 +1399,7 @@ "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], "index": "pypi", - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tomlkit": { diff --git a/source/modules/acdp/documentation/postman/postman-acdp-build-api.json b/source/modules/acdp/documentation/postman/postman-acdp-build-api.json index b022f1d9..655fc7c1 100644 --- a/source/modules/acdp/documentation/postman/postman-acdp-build-api.json +++ b/source/modules/acdp/documentation/postman/postman-acdp-build-api.json @@ -78,14 +78,14 @@ "raw": "{\n \"entityRef\":\"{{Entity_Ref}}\",\n \"action\": \"deploy\"\n}" }, "url": { - "raw": "{{Base_URL}}/api/acdp-backend/startBuild", + "raw": "{{Base_URL}}/api/acdp-backend/start-build", "host": [ "{{Base_URL}}" ], "path": [ "api", "acdp-backend", - "startBuild" + "start-build" ] } }, @@ -106,14 +106,14 @@ "raw": "{\n \"entityRef\":\"{{Entity_Ref}}\",\n \"action\": \"update\"\n}" }, "url": { - "raw": "{{Base_URL}}/api/acdp-backend/startBuild", + "raw": "{{Base_URL}}/api/acdp-backend/start-build", "host": [ "{{Base_URL}}" ], "path": [ "api", "acdp-backend", - "startBuild" + "start-build" ] } }, @@ -134,14 +134,14 @@ "raw": "{\n \"entityRef\":\"{{Entity_Ref}}\",\n \"action\": \"teardown\"\n}" }, "url": { - "raw": "{{Base_URL}}/api/acdp-backend/startBuild", + "raw": "{{Base_URL}}/api/acdp-backend/start-build", "host": [ "{{Base_URL}}" ], "path": [ "api", "acdp-backend", - "startBuild" + "start-build" ] } }, diff --git a/source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.plantuml b/source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.plantuml new file mode 100644 index 00000000..c7696f90 --- /dev/null +++ b/source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.plantuml @@ -0,0 +1,141 @@ +@startuml acdp-github-cicd-sequence-diagram +'Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +'SPDX-License-Identifier: MIT (For details, see https://github.com/awslabs/aws-icons-for-plantuml/blob/master/LICENSE) + +!define SPRITESURL https://raw.githubusercontent.com/plantuml-stdlib/gilbarbara-plantuml-sprites/v1.1/sprites +!includeurl SPRITESURL/github-icon.puml +!includeurl SPRITESURL/github-actions.puml +!includeurl SPRITESURL/spotify-icon.puml + +!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v13.0/dist +!include AWSPuml/AWSCommon.puml +!include AWSPuml/Compute/Lambda.puml +!include AWSPuml/DeveloperTools/CodeBuild.puml +!include AWSPuml/Storage/SimpleStorageService.puml +!include AWSPuml/ManagementGovernance/CloudFormation.puml +!include AWSPuml/Containers/ElasticContainerRegistry.puml +!include AWSPuml/ManagementGovernance/Proton.puml + +sprite $backstage-icon [50x50/16] { +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEEEEEEDDEEEEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEEEFEDFFDEFEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEEFEEF8AFEDFEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEFEDF7237FFDEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEFEEF7233249FFDEFEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEFEEF623323336CFEDFEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEFEEF623236323237FFDEFEEEEEEEEEE +EEEEEEEEEEEEEEEEEEFEEF623239FB523324AFFDFEEEEEEEEE +EEEEEEEEEEEEEEEEEFDFF62323AFDFF9423236DFDEEEEEEEEE +EEEEEEEEEEEEEEEEFDFF52323AFDEDEFF632323AFDFEEEEEEE +EEEEEEEEEEEEEEEFDFF52323BFDFEEEDFFB52323AFDFEEEEEE +EEEEEEEEEEEEEEFDFF52334BFDFEEEEFEDFF62324FFEEEEEEE +EEEEEEEEEEEEEFDFF423339FDDEFEEEEEFDFF42328FDFEEEEE +EEEEEEEEEEEEFDFD42323336EFEDFEEEEEFDF62326FDEEEEEE +EEEEEEEEEEEFDFD42324323248FFDEFEEFEDF52327FDEEEEEE +EEEEEEEEEEFDFC42324FC523325AFFDEEDDF933339FDFEEEEE +EEEEEEEEEFDFB42325FFFF9423236EFFFFF833325FDEEEEEEE +EEEEEEEEFDFB32325FFDEEFF7323246887433324EBEFEEEEEE +EEEEEEEFDFA32325FFEFEEDEFB5333222223225F76FDEEEEEE +EEEEEEEDFA32325FFEFEEEFEDFF83333333349F73AFDFEEEEE +EEEEEEEF933336FDDFEEEEEEFDDF933389ACF9426FDFEEEEEE +EEEEEEEF523335BFFDEFEEEEEFEDF5225F754225FAFFEEEEEE +EEEEEEFDF8333337FFDEFEEEEEEDF6233C42226F66FDEEEEEE +EEEEFEFA5CF6333349FFDEEEEEEFF4233D536AF63BFDFEEEEE +EEEEFEFA337FA433336CFFEEEEFF63334FCDD8426FDFEEEEEE +EEEEEEFD63349F8434347DFFFFD633338F743225F9FEEEEEEE +EEEEEEFDFA5336CD6334346776433336FF42337F57FDEEEEEE +EEEEFEFA5AF74348FA5343333334336F7C647CF53CFDFEEEEE +EEEEEEFB447EC6435AF8544444345AF55FDEB7428FDFEEEEEE +EEEEEEEE74459F95446BFA87778BFA539F744337F9FEEEEEEE +EEEEEEFDFB6446BF744468ABCBA85437FE53348F57FDFEEEEE +EEEEEEFB59F95447FC6444444444448F7C768EC54DFEEEEEEE +EEEEEEFB557CE75459FA654444457BF56FDDA7439FDFEEEEEE +EEEEEEEF95558FB6556AFC9888ACF954BF754448FEFEEEEEEE +EEEEEEFCDD7556AF9555689BBA975449FD64459FEEEEEEEEEE +EEEEEEFB69FA6557DE755555555545AF7C879EFEEEEEEEEEEE +EEEEEEFC657BF96569FB865555568DD67FFFFFDEEEEEEEEEEE +EEEEEEEFA7668ED7656AEEBA9ABED855CFEEDEEEEEEEEEEEEE +EEEEEEEEFD8666AFA756689AAA97655AECFEEEEEEEEEEEEEEE +EEEEEEEEDFFB8667BF966666665556BD7CFEEEEEEEEEEEEEEE +EEEEEEEEFEEFFA7669ED976666679FC68FEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEFD96679CFCBBBDFC866DFEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEFFB8667899A997666BFEFEEEEEEEEEEEEEEE +EEEEEEEEEEEEEFEEFFA76666666668CFEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEFFD98777778AFFEEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEFFFDCBCDFFFEEEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEFFFFFEEEEEEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +} + +'Comment out to use default PlantUML sequence formatting +skinparam participant { + BackgroundColor AWS_BG_COLOR + BorderColor AWS_BORDER_COLOR +} +skinparam sequence { + ArrowThickness 2 + LifeLineBorderColor AWS_COLOR + LifeLineBackgroundColor AWS_BORDER_COLOR + BoxBorderColor AWS_COLOR +} + +actor User as user + +box ACDP Configuration and CI/CD +participant "<$backstage-icon>\nGitHub" as sb << Spotify Backstage >> +participant "<$github-actions>\nGitHub" as gha << GitHub Actions >> +participant "$CodeBuildIMG()\nBackstage PipelineProjects" as bcb << CodeBuild >> +participant "<$github-icon>\nGitHub" as gh << GitHub >> +participant "$CloudFormationIMG()\nCloudFormation" as cfn << CloudFormation >> +participant "$SimpleStorageServiceIMG()\nCloudFormation\nTemplate Bucket" as s3 << S3 Asset >> +endbox + +'ACDP Deployment +user -> sb++ #CC2264: Launch Backstage template +sb -> gh++ #3F8624: Create repo +return +sb -> gha++ #D86613: Create GitHub actions from template +gha -> bcb++ #F68D05: Trigger test +bcb -> gh++ #3F8624: Fetch repo +return +return +gha -> bcb++ #F68D05: Trigger build +bcb -> gh++ #3F8624: Fetch repo +return +return +gha -> bcb++ #F68D05: Trigger deploy +bcb -> gh++ #3F8624: Fetch repo +return +bcb -> s3++ #3F8624: Write template +return +bcb -> cfn++ #CC2264: Deploy infrastructure +return +return +return +return + +user -> gh #3F8624: Push commit to tracked branch +activate gh #3F8624 +user <-- gh +gh -> gha++ #D86613: Actions triggered +gh <-- gha +deactivate gh +gha -> bcb++ #F68D05: Trigger test +bcb -> gh++ #3F8624: Fetch repo +return +return +gha -> bcb++ #F68D05: Trigger build +bcb -> gh++ #3F8624: Fetch repo +return +return +gha -> bcb++ #F68D05: Trigger deploy +bcb -> gh++ #3F8624: Fetch repo +return +bcb -> s3++ #3F8624: Write template +return +bcb -> cfn++ #CC2264: Deploy infrastructure +return +return +@enduml diff --git a/source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.svg b/source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.svg new file mode 100644 index 00000000..61460dd6 --- /dev/null +++ b/source/modules/acdp/documentation/sequence/acdp-github-cicd-sequence-diagram.svg @@ -0,0 +1 @@ +ACDP Configuration and CI/CDUserUser«Spotify Backstage»GitHub«Spotify Backstage»GitHub«GitHub Actions»GitHub«GitHub Actions»GitHub«CodeBuild»Backstage PipelineProjects«CodeBuild»Backstage PipelineProjects«GitHub»GitHub«GitHub»GitHub«CloudFormation»CloudFormation«CloudFormation»CloudFormation«S3 Asset»CloudFormationTemplate Bucket«S3 Asset»CloudFormationTemplate BucketLaunch BackstagetemplateCreate repoCreate GitHub actionsfrom templateTrigger testFetch repoTrigger buildFetch repoTrigger deployFetch repoWrite templateDeploy infrastructurePush commit to trackedbranchActions triggeredTrigger testFetch repoTrigger buildFetch repoTrigger deployFetch repoWrite templateDeploy infrastructure \ No newline at end of file diff --git a/source/modules/acdp/pyproject.toml b/source/modules/acdp/pyproject.toml index ae9ea11f..df73e7c4 100644 --- a/source/modules/acdp/pyproject.toml +++ b/source/modules/acdp/pyproject.toml @@ -9,6 +9,7 @@ omit = [ "setup.py", "**/tests/*", "source/app.py", + "source/tests/conftest.py", "**/*_dependency_layer/**/*" ] diff --git a/source/modules/acdp/source/.cdk-nag-suppression-list.json b/source/modules/acdp/source/.cdk-nag-suppression-list.json index 36615913..3c291b38 100644 --- a/source/modules/acdp/source/.cdk-nag-suppression-list.json +++ b/source/modules/acdp/source/.cdk-nag-suppression-list.json @@ -9,10 +9,6 @@ }, "/acdp/custom-resource-construct/lambda-function/Resource": { "rules_to_suppress": [ - { - "id": "AwsSolutions-L1", - "reason": "Some libraries used throughout the solution are not yet supported in Python 3.11. For consistency, all lambdas are currently kept at Python 3.10. Future refactoring of unsupported libraries will enable the use of 3.11 throughout the solution." - }, { "id": "AwsSolutions-IAM5", "reason": "Log groups have wildcards." @@ -224,14 +220,6 @@ } ] }, - "/acdp/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-L1", - "reason": "Cannot update runtime of the lambda function because it belongs to an AWS managed construct." - } - ] - }, "/acdp/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource": { "rules_to_suppress": [ { @@ -272,14 +260,6 @@ } ] }, - "/acdp/acdp/metrics-construct/lambda-function/Resource": { - "rules_to_suppress": [ - { - "id": "AwsSolutions-L1", - "reason": "Some libraries used throughout the solution are not yet supported in Python 3.11. For consistency, all lambdas are currently kept at Python 3.10. Future refactoring of unsupported libraries will enable the use of 3.11 throughout the solution." - } - ] - }, "/acdp/acdp/pipelines-construct/backstage-build-pipeline-project/Resource": { "rules_to_suppress": [ { diff --git a/source/modules/acdp/source/infrastructure/acdp_stack.py b/source/modules/acdp/source/infrastructure/acdp_stack.py index 2b8f36da..605dc515 100644 --- a/source/modules/acdp/source/infrastructure/acdp_stack.py +++ b/source/modules/acdp/source/infrastructure/acdp_stack.py @@ -18,6 +18,7 @@ from cms_common.constructs.cdk_lambda_vpc_config_construct import ( CDKLambdasVpcConfigConstruct, ) +from cms_common.constructs.cmk_encrypted_s3 import CMKEncryptedS3Construct from cms_common.constructs.custom_resource_lambda import CustomResourceLambdaConstruct from cms_common.constructs.lambda_dependencies import LambdaDependenciesConstruct from cms_common.constructs.vpc_construct import VpcConstruct @@ -25,7 +26,6 @@ # Connected Mobility Solution on AWS from .constructs.backstage_assets import BackstageAssetsConstruct from .constructs.cloudformation_role import CloudFormationRoleConstruct -from .constructs.cmk_encrypted_s3 import CMKEncryptedS3Construct from .constructs.deployment_uuid_construct import DeploymentUUIDConstruct from .constructs.module_deploy import ModuleDeployCodeBuildConstruct from .constructs.module_integration import ModuleInputsConstruct, ModuleOutputsConstruct @@ -58,6 +58,18 @@ def __init__( }, ) + AppRegistryConstruct( + self, + "acdp-app-registry", + app_registry_inputs=AppRegistryInputs( + application_name=Aws.STACK_NAME, + application_type=solution_config_inputs.application_type, + solution_id=solution_config_inputs.solution_id, + solution_name=solution_config_inputs.solution_name, + solution_version=solution_config_inputs.solution_version, + ), + ) + local_asset_bucket_construct = CMKEncryptedS3Construct( self, "backstage-asset-bucket-construct" ) @@ -143,18 +155,6 @@ def __init__( ) -> None: super().__init__(scope, construct_id) - AppRegistryConstruct( - self, - "acdp-app-registry", - app_registry_inputs=AppRegistryInputs( - application_name=Aws.STACK_NAME, - application_type=solution_config_inputs.application_type, - solution_id=solution_config_inputs.solution_id, - solution_name=solution_config_inputs.solution_name, - solution_version=solution_config_inputs.solution_version, - ), - ) - cloudformation_role = CloudFormationRoleConstruct(self, "cloudformation-role") backstage_assets = BackstageAssetsConstruct( diff --git a/source/modules/acdp/source/infrastructure/constructs/cloudformation_role.py b/source/modules/acdp/source/infrastructure/constructs/cloudformation_role.py index f16e1ab5..4e5bc384 100644 --- a/source/modules/acdp/source/infrastructure/constructs/cloudformation_role.py +++ b/source/modules/acdp/source/infrastructure/constructs/cloudformation_role.py @@ -25,8 +25,8 @@ def __init__( statements=[ aws_iam.PolicyStatement( effect=aws_iam.Effect.ALLOW, - resources=["*"], - actions=["*"], + resources=["*"], # NOSONAR + actions=["*"], # NOSONAR ), ] ) diff --git a/source/modules/acdp/source/infrastructure/constructs/module_integration.py b/source/modules/acdp/source/infrastructure/constructs/module_integration.py index c3993178..00ef16cd 100644 --- a/source/modules/acdp/source/infrastructure/constructs/module_integration.py +++ b/source/modules/acdp/source/infrastructure/constructs/module_integration.py @@ -15,13 +15,11 @@ # CMS Common Library from cms_common.config.regex import RegexPattern from cms_common.config.resource_names import ResourceName, ResourcePrefix +from cms_common.constructs.cmk_encrypted_s3 import CMKEncryptedS3Construct from cms_common.constructs.identity_provider_config import IdentityProviderConfig from cms_common.constructs.vpc_construct import create_vpc_config from cms_common.resource_names.module_short_names import CMSModuleShortNames -# Connected Mobility Solution on AWS -from .cmk_encrypted_s3 import CMKEncryptedS3Construct - MINUTES_IN_A_DAY = 1440 @@ -106,6 +104,16 @@ def __init__( leading_slash=True, ) + self.default_user_email = CfnParameter( + Stack.of(self), + "DefaultUserEmail", + type="String", + description="The user E-Mail to access backstage UI", + allowed_pattern=RegexPattern.OPTIONAL_EMAIL, + default="", + constraint_description="User E-Mail must be a valid E-Mail address", + ).value_as_string + # Backstage Domain Inputs # Rather than creating SSM parameters in ModuleOutputsConstruct similar to other config, # these SSM Parameters are created immediately so they can be used in the Pipelines construct, specifically the backstage pipeline environment diff --git a/source/modules/acdp/source/infrastructure/constructs/pipelines.py b/source/modules/acdp/source/infrastructure/constructs/pipelines.py index a709a22d..fe47ddc3 100644 --- a/source/modules/acdp/source/infrastructure/constructs/pipelines.py +++ b/source/modules/acdp/source/infrastructure/constructs/pipelines.py @@ -362,6 +362,10 @@ def __init__( # pylint: disable=too-many-locals value=module_inputs.backstage_auth_config_inputs.identity_provider_id, type=aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, ), + "DEFAULT_USER_EMAIL": aws_codebuild.BuildEnvironmentVariable( + value=module_inputs.default_user_email, + type=aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, + ), "AWS_REGION": aws_codebuild.BuildEnvironmentVariable( value=Stack.of(self).region, type=aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT, diff --git a/source/modules/acdp/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_snapshot.json b/source/modules/acdp/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_snapshot.json index 30e7fcc1..58f0b239 100644 --- a/source/modules/acdp/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_snapshot.json +++ b/source/modules/acdp/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_snapshot.json @@ -219,6 +219,13 @@ "Description": "Optional when using a Public Hosted Zone with Route53. Required when HostedZoneId is Private or not provided. Provide a custom ACM Certificate ARN to use for TLS.", "Type": "String" }, + "DefaultUserEmail": { + "AllowedPattern": "(^$)|^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$", + "ConstraintDescription": "User E-Mail must be a valid E-Mail address", + "Default": "", + "Description": "The user E-Mail to access backstage UI", + "Type": "String" + }, "FullyQualifiedDomainName": { "AllowedPattern": "^([A-Za-z0-9][A-Za-z0-9-]*\\.)+[A-Za-z]+$", "ConstraintDescription": "Fully Qualified Domain Name must be a valid domain name", @@ -293,6 +300,17 @@ "value" ] }, + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ], "Timeout": 900 }, "Type": "AWS::Lambda::Function" @@ -324,6 +342,17 @@ ] ] } + ], + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } ] }, "Type": "AWS::IAM::Role" @@ -352,10 +381,7 @@ }, "Type": "AWS::IAM::Policy" }, - "acdpacdpappregistryappregistryapplicationF3E7BCE9": { - "DependsOn": [ - "ssmappuniqueidD1DCE51D" - ], + "acdpappregistryappregistryapplication0BC5FAC5": { "Properties": { "Name": { "Fn::Join": [ @@ -374,42 +400,28 @@ } ] ] - }, - "Tags": { - "Solutions:DeploymentUUID": { - "Fn::GetAtt": [ - "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", - "SolutionUUID" - ] - } } }, "Type": "AWS::ServiceCatalogAppRegistry::Application" }, - "acdpacdpappregistryappregistryapplicationattributeassociationD25D643C": { - "DependsOn": [ - "ssmappuniqueidD1DCE51D" - ], + "acdpappregistryappregistryapplicationattributeassociation3418E518": { "Properties": { "Application": { "Fn::GetAtt": [ - "acdpacdpappregistryappregistryapplicationF3E7BCE9", + "acdpappregistryappregistryapplication0BC5FAC5", "Id" ] }, "AttributeGroup": { "Fn::GetAtt": [ - "acdpacdpappregistrydefaultapplicationattributesF28A0104", + "acdpappregistrydefaultapplicationattributes4DDA2479", "Id" ] } }, "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation" }, - "acdpacdpappregistrydefaultapplicationattributesF28A0104": { - "DependsOn": [ - "ssmappuniqueidD1DCE51D" - ], + "acdpappregistrydefaultapplicationattributes4DDA2479": { "Properties": { "Attributes": { "ApplicationType": "test-application-type", @@ -437,10 +449,10 @@ ] }, "Tags": { - "Solutions:DeploymentUUID": { + "awsApplication": { "Fn::GetAtt": [ - "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", - "SolutionUUID" + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" ] } } @@ -695,6 +707,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -762,6 +783,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -835,6 +865,15 @@ "Type": "NO_SOURCE" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -1126,6 +1165,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -1357,6 +1405,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -1406,6 +1463,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1442,6 +1505,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1481,6 +1550,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1514,6 +1589,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1547,6 +1628,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1580,6 +1667,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1613,6 +1706,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1646,6 +1745,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1682,6 +1787,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1715,6 +1826,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1746,6 +1863,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1777,6 +1900,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1808,6 +1937,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1839,6 +1974,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1870,6 +2011,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1901,6 +2048,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1934,6 +2087,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1970,6 +2129,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2003,6 +2168,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2036,6 +2207,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2067,6 +2244,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2098,6 +2281,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2129,6 +2318,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2177,6 +2372,12 @@ "deploymentuuidconstructdeploymentuuidcustomresourceC885F329", "SolutionUUID" ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2225,6 +2426,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -2321,6 +2531,15 @@ "Type": "CODEPIPELINE" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -2516,6 +2735,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -2829,6 +3057,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3009,6 +3246,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3053,6 +3299,15 @@ "RestrictPublicBuckets": true }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3126,6 +3381,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3229,6 +3493,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3314,6 +3587,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3399,6 +3681,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3571,6 +3862,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3630,6 +3930,13 @@ "Ref": "IdentityProviderId" } }, + { + "Name": "DEFAULT_USER_EMAIL", + "Type": "PLAINTEXT", + "Value": { + "Ref": "DefaultUserEmail" + } + }, { "Name": "AWS_REGION", "Type": "PLAINTEXT", @@ -3748,6 +4055,15 @@ "Type": "CODEPIPELINE" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -4169,6 +4485,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -4555,6 +4880,15 @@ ] }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -4747,6 +5081,15 @@ ] }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -4865,21 +5208,6 @@ }, "Type": "AWS::IAM::Policy" }, - "appregistryapplicationstackassociation": { - "Properties": { - "Application": { - "Fn::GetAtt": [ - "acdpacdpappregistryappregistryapplicationF3E7BCE9", - "Id" - ] - }, - "Resource": { - "Ref": "AWS::StackId" - }, - "ResourceType": "CFN_STACK" - }, - "Type": "AWS::ServiceCatalogAppRegistry::ResourceAssociation" - }, "backstageassetbucketconstructcmkencryptedbucketE8CD3782": { "DeletionPolicy": "Retain", "Properties": { @@ -4909,6 +5237,17 @@ "IgnorePublicAcls": true, "RestrictPublicBuckets": true }, + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ], "VersioningConfiguration": { "Status": "Enabled" } @@ -4991,10 +5330,34 @@ } }, "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "Service": "logging.s3.amazonaws.com" + }, + "Resource": "*" } ], "Version": "2012-10-17" - } + }, + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ] }, "Type": "AWS::KMS::Key", "UpdateReplacePolicy": "Retain" @@ -5007,7 +5370,13 @@ "ServerSideEncryptionConfiguration": [ { "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "AES256" + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "backstageassetbucketconstructcmkkeyDD56344B", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" } } ] @@ -5025,6 +5394,17 @@ "IgnorePublicAcls": true, "RestrictPublicBuckets": true }, + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ], "VersioningConfiguration": { "Status": "Enabled" } @@ -5103,6 +5483,17 @@ "IpProtocol": "-1" } ], + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ], "VpcId": { "Fn::Join": [ "", @@ -5169,6 +5560,17 @@ ] }, "Runtime": "python3.12", + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ], "Timeout": 300, "VpcConfig": { "SecurityGroupIds": [ @@ -5443,6 +5845,17 @@ }, "PolicyName": "ec2-policy" } + ], + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } ] }, "Type": "AWS::IAM::Role" @@ -5471,6 +5884,17 @@ "IpProtocol": "-1" } ], + "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + } + ], "VpcId": { "Fn::Join": [ "", @@ -5515,6 +5939,14 @@ ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Fn::If": [ @@ -5543,6 +5975,14 @@ ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Ref": "FullyQualifiedDomainName" @@ -5565,6 +6005,14 @@ ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Ref": "IsPublicFacing" @@ -5587,6 +6035,14 @@ ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Fn::If": [ @@ -5614,6 +6070,14 @@ ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "acdpappregistryappregistryapplication0BC5FAC5", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Ref": "AcdpUniqueId" diff --git a/source/modules/auth_setup/.nvmrc b/source/modules/auth_setup/.nvmrc index aacb5181..0305213f 100644 --- a/source/modules/auth_setup/.nvmrc +++ b/source/modules/auth_setup/.nvmrc @@ -1 +1 @@ -18.17 +18.20 diff --git a/source/modules/auth_setup/.pre-commit-config.yaml b/source/modules/auth_setup/.pre-commit-config.yaml index e81b5a62..4556d29f 100644 --- a/source/modules/auth_setup/.pre-commit-config.yaml +++ b/source/modules/auth_setup/.pre-commit-config.yaml @@ -92,7 +92,7 @@ repos: hooks: - id: pip-audit name: (Auth Setup) Pip audit - - repo: https://github.com/pre-commit/mirrors-prettier + - repo: https://github.com/pre-commit/mirrors-prettier # Archived, look to replace eventually rev: v3.1.0 hooks: - id: prettier diff --git a/source/modules/auth_setup/Pipfile b/source/modules/auth_setup/Pipfile index d0a7b4ae..7a02c015 100644 --- a/source/modules/auth_setup/Pipfile +++ b/source/modules/auth_setup/Pipfile @@ -31,6 +31,7 @@ types-pyyaml = "*" types-requests = ">=2.32.0" types-setuptools = ">=65.6.0.1" types-toml = ">=0.10.2" +urllib3 = ">=2.2.2" wheel = "*" [requires] diff --git a/source/modules/auth_setup/Pipfile.lock b/source/modules/auth_setup/Pipfile.lock index eb439dc5..d0b6b4b5 100644 --- a/source/modules/auth_setup/Pipfile.lock +++ b/source/modules/auth_setup/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e02f72e223eb5d3c7b1e07ca5f23b1f941f7d17498e8394677b9d71df5e96155" + "sha256": "2028deaecad127531da03063e9c0c8a9118fee09af81c8ed1121cf072899fb1c" }, "pipfile-spec": 6, "requires": { @@ -37,11 +37,11 @@ }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5", + "sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "certifi": { "hashes": [ @@ -175,7 +175,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "requests": { @@ -192,7 +192,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "typing-extensions": { @@ -390,12 +390,12 @@ }, "boto3": { "hashes": [ - "sha256:2f3e88b10b8fcc5f6100a9d74cd28230edc9d4fa226d99dd40a3ab38ac213673", - "sha256:b8433d481d50b68a0162c0379c0dd4aabfc3d1ad901800beb5b87815997511c1" + "sha256:69d5afb7a017d07dd6bdfb680d2912d5d369b3fafa0a45161207d9f393b14d7e", + "sha256:ac770fb53dde1743aec56bd8e56b7ee2e2f5ad42a37825968ec4ff8428822640" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "boto3-stubs": { "extras": [ @@ -405,27 +405,27 @@ "ssm" ], "hashes": [ - "sha256:4fc2f8d41ee7fb4952dc8d6a27bb616e7c0c5f9b71f3ad6d179f27df1bb69b0c", - "sha256:c19d0c4386ec160e6c06567b960b6f7c765acc4ed40f01f371c8e7a0b514520b" + "sha256:1303deb7d749a2c9bd5a30830e1d537c1474b70c5f429e3db9af5d3eaa7b9696", + "sha256:2d15ccfc44407baae897d3a531f9eb0826e207d70e92f5268e8703139bec2dba" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:2e72e262de02adcb0264ac2bac159a28f55dbba8d9e52aa0308773a42950dff5", + "sha256:edf0fb4c02186ae29b76263ac5fda18b0a085d334a310551c9984407cf1079e6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.34.145" }, "botocore-stubs": { "hashes": [ - "sha256:3eb325b4b186e8d8e49d485cfe383f478ab8c07e0f4690e5ee9bcb6faceec4af", - "sha256:eded7afc301a08b5355ab8843c9d9bdea7dc43ece3e9628f186897dae7a56ceb" + "sha256:4c579734492713c142773c7405cf62b466dfaf5f05371b7510d3cd308b0ba6a5", + "sha256:69bcfc0c81eee294e681dc8049b7ca2a47a8f0510b88c05710f07dba6d0785c7" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.144" + "version": "==1.34.145" }, "cattrs": { "hashes": [ @@ -520,10 +520,10 @@ }, "cfn-lint": { "hashes": [ - "sha256:4d1fc2e12d672544518f82def7693438fa2ca8f5a17a67e7ad359f75162f9e79", - "sha256:8fc3b28fe315854268590361282f75cebb6b13a3ae1b753c8a1135107b465fa5" + "sha256:b144ea5af8a62cace5f239ef0456a0a7fcf472fe9767cd51844a0709ddfe5f30", + "sha256:bf34674a92a7b345a13521dda095101c8900388accc03bb8505aaf0a8b4a19d2" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "charset-normalizer": { "hashes": [ @@ -1249,10 +1249,10 @@ }, "mypy-boto3-ec2": { "hashes": [ - "sha256:3953ba88c08e633a2c671a9d442ddbb4bdcbb05f9aa179aeef42ae2376999a20", - "sha256:79e5e929947e81f45e711a4059aacfbda0418027d407b3b3c917ac3f163ea9f2" + "sha256:d6a200a341e65b584cc9af9861e8a331dc85d19a5316d9d0637d192824ec9375", + "sha256:f676e9382ca2ad1e419804a2759779018d7f0006ac86bd602479855073fafc79" ], - "version": "==1.34.143" + "version": "==1.34.145" }, "mypy-boto3-lambda": { "hashes": [ @@ -1263,10 +1263,10 @@ }, "mypy-boto3-rds": { "hashes": [ - "sha256:66b50037073434eb894b00f1597ba39f1e86789717ff9676c5a099f31bc39c9a", - "sha256:ce4872ee69cd10b895e11945b6c3255493b642e0410e48a2cfc6afe689cf6209" + "sha256:091a3605d4c2b010be2e61b306f7c877019344b3949b0bfceaee2db2a57bcc8c", + "sha256:2c28c05b4269c7dcd2ee0c3350784ad02a4c1764728b3335dd56bdbdd6ebd2f6" ], - "version": "==1.34.135" + "version": "==1.34.145" }, "mypy-boto3-s3": { "hashes": [ @@ -1277,10 +1277,10 @@ }, "mypy-boto3-secretsmanager": { "hashes": [ - "sha256:7ce9815d116fa1749971691355b1e1c8f462d46e7eaa9d84133b8db96dd3515f", - "sha256:ae2b398efa1a32214c3eddb6901efa67cfc24a893b113f549a06bb70bb43b402" + "sha256:986511caa6626edfed7eb11b63c929801e9468c58e15927dc6fc0339c4eb34cb", + "sha256:e5a82c05cce68168a3709e5f0d35066cf250961db1d8670f0111da66206814c7" ], - "version": "==1.34.128" + "version": "==1.34.145" }, "mypy-boto3-sqs": { "hashes": [ @@ -1424,7 +1424,7 @@ "sha256:d1bf648df17077306100815d255d45430035b36f66bac635df04a323c61ba126" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.7.0'", + "markers": "python_full_version >= '3.7.0' and python_version < '4'", "version": "==2.4.0" }, "pycparser": { @@ -1602,7 +1602,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "python-slugify": { @@ -1939,7 +1939,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "sympy": { @@ -1956,7 +1956,7 @@ "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.8.1'", + "markers": "python_full_version >= '3.8.1' and python_version < '4'", "version": "==4.6.1" }, "text-unidecode": { @@ -1972,7 +1972,7 @@ "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], "index": "pypi", - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tomlkit": { diff --git a/source/modules/auth_setup/README.md b/source/modules/auth_setup/README.md index 6f3ddff8..1a7255d2 100644 --- a/source/modules/auth_setup/README.md +++ b/source/modules/auth_setup/README.md @@ -33,6 +33,7 @@ - [IdP Config](#idp-config) - [User Client Config](#user-client-config) - [Service Client Config](#service-client-config) + - [SSM Parameter](#ssm-parameter) - [Cost Scaling](#cost-scaling) - [Collection of Operational Metrics](#collection-of-operational-metrics) - [License](#license) @@ -180,12 +181,13 @@ are detailed below. If using the optional Cognito infrastructure deployment which is provided by this module, deploy the module with the CloudFormation parameter `ShouldCreateCognitoResources` set to "True". This will deploy a basic Cognito infrastructure including a user pool, service app client, and user app client, to your account. It will also populate the three configuration -secrets with values specific to the Cognito deployment. +secrets and one SSM Parameter with values specific to the Cognito deployment. #### 2. Empty Config Deploy If using your own identity provider, and you do not have existing configuration secrets from a previous deployment, -the Auth Setup module will deploy the three configuration secrets in the expected JSON format with empty values. These +the Auth Setup module will deploy the three configuration secrets in the expected JSON format as well as one +SSM Parameter with empty values. These values can be populated after the deployment with values specific to your identity provider. To execute this deployment, deploy the module with the CloudFormation parameter `ShouldCreateCognitoResources` set to "False", and do not provide values for the existing secret arn parameters. @@ -250,6 +252,10 @@ source code if desired. - client_secret - Client Secret of Service client +### SSM Parameter + +- Cognito user pool Id + ## Cost Scaling Cost will scale depending on usage of optional Cognito deployments. Without the Cognito deployment, cost is minimal. diff --git a/source/modules/auth_setup/pyproject.toml b/source/modules/auth_setup/pyproject.toml index 5f904d0b..feda3a9a 100644 --- a/source/modules/auth_setup/pyproject.toml +++ b/source/modules/auth_setup/pyproject.toml @@ -9,6 +9,7 @@ omit = [ "setup.py", "**/tests/*", "source/app.py", + "source/tests/conftest.py", "**/*_dependency_layer/**/*" ] diff --git a/source/modules/auth_setup/source/.cdk-nag-suppression-list.json b/source/modules/auth_setup/source/.cdk-nag-suppression-list.json index 4f3926fc..7c593261 100644 --- a/source/modules/auth_setup/source/.cdk-nag-suppression-list.json +++ b/source/modules/auth_setup/source/.cdk-nag-suppression-list.json @@ -22,7 +22,7 @@ "rules_to_suppress": [ { "id": "AwsSolutions-L1", - "reason": "The lambda resource is defined by the L2/L3 constructs and cannot be modified." + "reason": "Lambda created by CDK constructs cannot use a different runtime" } ] }, diff --git a/source/modules/auth_setup/source/infrastructure/auth_setup_stack.py b/source/modules/auth_setup/source/infrastructure/auth_setup_stack.py index c9a8d8f6..7818f03e 100644 --- a/source/modules/auth_setup/source/infrastructure/auth_setup_stack.py +++ b/source/modules/auth_setup/source/infrastructure/auth_setup_stack.py @@ -75,4 +75,5 @@ def __init__( idp_config_secret_arn=oauth2_configurations.idp_config_secret.secret_arn, service_client_config_secret_arn=oauth2_configurations.service_client_config_secret.secret_arn, user_client_config_secret_arn=oauth2_configurations.user_client_config_secret.secret_arn, + user_pool_id=cognito_construct.users_construct.user_pool.user_pool_id, ) diff --git a/source/modules/auth_setup/source/infrastructure/constructs/module_integration.py b/source/modules/auth_setup/source/infrastructure/constructs/module_integration.py index 6e70fa49..461fda0b 100644 --- a/source/modules/auth_setup/source/infrastructure/constructs/module_integration.py +++ b/source/modules/auth_setup/source/infrastructure/constructs/module_integration.py @@ -15,7 +15,7 @@ # CMS Common Library from cms_common.config.regex import RegexPattern from cms_common.constructs.identity_provider_config import IdentityProviderConfig -from cms_common.resource_names.auth import AuthResourceNames +from cms_common.resource_names.auth import AuthSetupResourceNames @define(auto_attribs=True, frozen=True) @@ -105,13 +105,14 @@ def __init__( scope: Construct, construct_id: str, module_inputs_construct: ModuleInputsConstruct, + user_pool_id: str, idp_config_secret_arn: str, service_client_config_secret_arn: str, user_client_config_secret_arn: str, ) -> None: super().__init__(scope, construct_id) - auth_resource_names = AuthResourceNames.from_identity_provider_id( + auth_setup_resource_names = AuthSetupResourceNames.from_identity_provider_id( module_inputs_construct.stack_config.identity_provider_id ) @@ -120,7 +121,7 @@ def __init__( "ssm-idp-config-secret-arn", string_value=idp_config_secret_arn, description="Secret Arn for IdP configurations needed to facilitate authentication and authorization via OAuth 2.0 identity providers.", - parameter_name=auth_resource_names.idp_config_secret_arn_ssm_parameter, + parameter_name=auth_setup_resource_names.idp_config_secret_arn_ssm_parameter, simple_name=False, ) @@ -129,7 +130,7 @@ def __init__( "ssm-service-client-config-secret-arn", string_value=service_client_config_secret_arn, description="Secret Arn for service client configuration needed for OAuth 2.0 operations.", - parameter_name=auth_resource_names.service_client_config_secret_arn_ssm_parameter, + parameter_name=auth_setup_resource_names.service_client_config_secret_arn_ssm_parameter, simple_name=False, ) @@ -138,6 +139,15 @@ def __init__( "ssm-user-client-config-secret-arn", string_value=user_client_config_secret_arn, description="Secret Arn for user client configuration needed for OAuth 2.0 operations.", - parameter_name=auth_resource_names.user_client_config_secret_arn_ssm_parameter, + parameter_name=auth_setup_resource_names.user_client_config_secret_arn_ssm_parameter, + simple_name=False, + ) + + aws_ssm.StringParameter( + self, + "ssm-cognito-user-pool-id", + string_value=user_pool_id, + description="User pool id for Cognito user pool", + parameter_name=auth_setup_resource_names.user_pool_id, simple_name=False, ) diff --git a/source/modules/auth_setup/source/infrastructure/constructs/oauth2_configurations.py b/source/modules/auth_setup/source/infrastructure/constructs/oauth2_configurations.py index 3de8c8e7..302bbcbe 100644 --- a/source/modules/auth_setup/source/infrastructure/constructs/oauth2_configurations.py +++ b/source/modules/auth_setup/source/infrastructure/constructs/oauth2_configurations.py @@ -10,7 +10,7 @@ from constructs import Construct # CMS Common Library -from cms_common.resource_names.auth import AuthResourceNames +from cms_common.resource_names.auth import AuthSetupResourceNames # Connected Mobility Solution on AWS from .cognito import CognitoConstruct @@ -29,7 +29,7 @@ def __init__( ) -> None: super().__init__(scope, construct_id) - auth_resource_names = AuthResourceNames.from_identity_provider_id( + auth_setup_resource_names = AuthSetupResourceNames.from_identity_provider_id( module_inputs_construct.stack_config.identity_provider_id ) @@ -83,7 +83,7 @@ def __init__( "idp-config", description="IdP configurations needed to facilitate authentication and authorization via OAuth 2.0 identity providers.", removal_policy=RemovalPolicy.DESTROY, - secret_name=auth_resource_names.idp_config_secret, + secret_name=auth_setup_resource_names.idp_config_secret, secret_string_value=SecretValue.unsafe_plain_text( # Safe usage. No secret values exposed in template. idp_config_json ), @@ -125,7 +125,7 @@ def __init__( "service-client-config", description="Service client configuration needed for OAuth 2.0 operations.", removal_policy=RemovalPolicy.DESTROY, - secret_name=auth_resource_names.service_client_config_secret, + secret_name=auth_setup_resource_names.service_client_config_secret, secret_string_value=SecretValue.unsafe_plain_text( # Safe usage. No values exposed in template. service_client_config_json ), @@ -157,7 +157,7 @@ def __init__( "user-client-config", description="User client configuration needed for OAuth 2.0 operations.", removal_policy=RemovalPolicy.DESTROY, - secret_name=auth_resource_names.user_client_config_secret, + secret_name=auth_setup_resource_names.user_client_config_secret, secret_string_value=SecretValue.unsafe_plain_text( # Safe usage. No secret values exposed in template. user_client_config_json ), diff --git a/source/modules/auth_setup/source/tests/infrastructure/__snapshots__/test_snapshot/test_auth_setup_snapshot.json b/source/modules/auth_setup/source/tests/infrastructure/__snapshots__/test_snapshot/test_auth_setup_snapshot.json index 65ed98f3..d6ac97e8 100644 --- a/source/modules/auth_setup/source/tests/infrastructure/__snapshots__/test_snapshot/test_auth_setup_snapshot.json +++ b/source/modules/auth_setup/source/tests/infrastructure/__snapshots__/test_snapshot/test_auth_setup_snapshot.json @@ -941,6 +941,28 @@ }, "Type": "AWS::Cognito::UserPoolResourceServer" }, + "moduleoutputsssmcognitouserpoolid8A5E259C": { + "Properties": { + "Description": "User pool id for Cognito user pool", + "Name": { + "Fn::Join": [ + "", + [ + "/solution/auth/", + { + "Ref": "IdentityProviderId" + }, + "/user-pool/id" + ] + ] + }, + "Type": "String", + "Value": { + "Ref": "cognitousersuserpool51A5F544" + } + }, + "Type": "AWS::SSM::Parameter" + }, "moduleoutputsssmidpconfigsecretarnCF8D6D64": { "Properties": { "Description": "Secret Arn for IdP configurations needed to facilitate authentication and authorization via OAuth 2.0 identity providers.", diff --git a/source/modules/backstage/.eslintrc.js b/source/modules/backstage/.eslintrc.js new file mode 100644 index 00000000..709e25dd --- /dev/null +++ b/source/modules/backstage/.eslintrc.js @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +module.exports = require("@backstage/cli/config/eslint-factory")(__dirname); diff --git a/source/modules/backstage/.nvmrc b/source/modules/backstage/.nvmrc index aacb5181..0305213f 100644 --- a/source/modules/backstage/.nvmrc +++ b/source/modules/backstage/.nvmrc @@ -1 +1 @@ -18.17 +18.20 diff --git a/source/modules/backstage/.pre-commit-config.yaml b/source/modules/backstage/.pre-commit-config.yaml index 2e455603..bc2c2d6e 100644 --- a/source/modules/backstage/.pre-commit-config.yaml +++ b/source/modules/backstage/.pre-commit-config.yaml @@ -92,7 +92,7 @@ repos: hooks: - id: pip-audit name: (Backstage) Pip audit - - repo: https://github.com/pre-commit/mirrors-prettier + - repo: https://github.com/pre-commit/mirrors-prettier # Archived, look to replace eventually rev: v3.1.0 hooks: - id: prettier @@ -124,9 +124,14 @@ repos: types_or: [python, pyi] args: ["--strict", "--cache-dir", "./source/modules/backstage/.mypy_cache", "--config-file", "./source/modules/backstage/pyproject.toml"] language: system - - id: run-tsc-backstage - name: (Backstage) Run tsc on Backstage + - id: run-backstage-lint + name: (Backstage) Run lint on Backstage entry: source/modules/backstage/deployment/run-backstage-lint.sh language: system + pass_filenames: false + - id: run-backstage-tsc + name: (Backstage) Run tsc on Backstage + entry: source/modules/backstage/deployment/run-backstage-tsc.sh + language: system types_or: [ts, tsx] pass_filenames: false diff --git a/source/modules/backstage/Makefile b/source/modules/backstage/Makefile index c28d51e2..ad3beff4 100644 --- a/source/modules/backstage/Makefile +++ b/source/modules/backstage/Makefile @@ -24,9 +24,6 @@ include ${SOLUTION_PATH}/makefiles/common_config.mk include ${SOLUTION_PATH}/makefiles/global_targets.mk include ${SOLUTION_PATH}/makefiles/module_targets.mk -## ======================================================== -## INSTALL -## ======================================================== .PHONY: yarn-install yarn-install: ## Using yarn, installs node dependencies for all modules. @printf "%bInstalling node dependencies using yarn.%b\n" "${MAGENTA}" "${NC}" @@ -37,19 +34,31 @@ install: pipenv-install yarn-install ## Installs the resources and dependencies @cd cdk; make install @printf "%bInstall finished.%b\n" "${GREEN}" "${NC}" -## ======================================================== -## BUILD AND DEPLOY -## ======================================================== .PHONY: deploy deploy: ## Deploy the stack for the module. - export ACDP_ASSET_BUCKET=$$(aws ssm get-parameter --name /solution/${ACDP_UNIQUE_ID}/config/acdp-asset-config/local/asset-bucket/name --with-decryption --query "Parameter.Value" --output text 2> /dev/null); \ - export S3_PATH="s3://$${ACDP_ASSET_BUCKET}/${S3_ASSET_KEY_PREFIX}"; \ + @printf "%bCopying ACDP assets to S3.\n%b" "${MAGENTA}" "${NC}" + @ACDP_ASSET_BUCKET=$$(aws ssm get-parameter --name /solution/${ACDP_UNIQUE_ID}/config/acdp-asset-config/local/asset-bucket/name --with-decryption --query "Parameter.Value" --output text 2> /dev/null); \ + S3_PATH="s3://$${ACDP_ASSET_BUCKET}/${S3_ASSET_KEY_PREFIX}"; \ aws s3 cp --recursive ${MODULE_PATH}/deployment/regional-s3-assets/${MODULE_NAME}/ $${S3_PATH}; \ - echo "Copied assets to $${S3_PATH}" + printf "%bCopied assets to %s\n%b" "${GREEN}" "$${S3_PATH}" "${NC}" - export ACDP_BACKSTAGE_PIPELINE_NAME=$$(aws ssm get-parameter --name /solution/${ACDP_UNIQUE_ID}/config/codepipeline-project/name --with-decryption --query "Parameter.Value" --output text 2> /dev/null); \ + @printf "%bBeginning Backstage pipeline execution.\n%b" "${MAGENTA}" "${NC}" + @ACDP_BACKSTAGE_PIPELINE_NAME=$$(aws ssm get-parameter --name /solution/${ACDP_UNIQUE_ID}/config/codepipeline-project/name --with-decryption --query "Parameter.Value" --output text 2> /dev/null); \ aws codepipeline start-pipeline-execution --name $${ACDP_BACKSTAGE_PIPELINE_NAME}; \ - echo "Started Pipeline: $${ACDP_BACKSTAGE_PIPELINE_NAME}" + printf "%bStarted Pipeline: %s\n%b" "${GREEN}" "$${ACDP_BACKSTAGE_PIPELINE_NAME}" "${NC}" + +.PHONY: destroy-ecr +destroy-ecr: ## Destroy the ECR images since CloudFormation cannot. + @printf "%bDeleting the ECR repository.%b\n" "${MAGENTA}" "${NC}" + aws ecr delete-repository --repository-name "${ACDP_UNIQUE_ID}-backstage" --force || true + @printf "%bECR repository deleted.%b\n" "${GREEN}" "${NC}" + +.PHONY: destroy +destroy: destroy-ecr ## Delete the backstage stack. + @printf "%bDeleting the backstage deployment.%b\n" "${MAGENTA}" "${NC}" + aws cloudformation delete-stack --stack-name "${ACDP_UNIQUE_ID}--acdp-backstage" + aws cloudformation wait stack-delete-complete --stack-name "${ACDP_UNIQUE_ID}--acdp-backstage" || true + @printf "%bDestroy finished.%b\n" "${GREEN}" "${NC}" ## ======================================================== ## LOCAL UTILITY @@ -82,7 +91,6 @@ run-postgres-local: ## Start a local instance of postgres for use with Backstage fi; \ done - .PHONY: stop-backstage-local stop-backstage-local: docker-compose stop && rm -f ./docker_postgres.log diff --git a/source/modules/backstage/Pipfile b/source/modules/backstage/Pipfile index 48d74199..7aa187ca 100644 --- a/source/modules/backstage/Pipfile +++ b/source/modules/backstage/Pipfile @@ -8,6 +8,8 @@ name = "pypi" [dev-packages] cms_common = {path = "../../lib", editable = true} attrs = ">=22.1.0" +moto = {extras = ["all"], version = ">=5.0.6"} +boto3-stubs = {extras = ["essential", "cognito-idp", "ssm"], version = "*"} cdk-nag = "*" exceptiongroup = "*" jinja2 = "*" @@ -29,6 +31,7 @@ types-requests = ">=2.32.0" types-setuptools = "*" types-urllib3 = "*" types-toml = "*" +urllib3 = ">=2.2.2" wheel = "*" wrapt = "*" freezegun="*" diff --git a/source/modules/backstage/Pipfile.lock b/source/modules/backstage/Pipfile.lock index 5ac3033e..d4ce48ae 100644 --- a/source/modules/backstage/Pipfile.lock +++ b/source/modules/backstage/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9e824f1e0b1191404038949de65a2889395843ae7471c9d5720b7a7854fbe39d" + "sha256": "b66a38f15576a07dabf466fddb4382d4ab3f603e58494e59d1b3f792a6f62c64" }, "pipfile-spec": 6, "requires": { @@ -17,30 +17,45 @@ }, "default": {}, "develop": { + "annotated-types": { + "hashes": [ + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.0" + }, + "antlr4-python3-runtime": { + "hashes": [ + "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", + "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8" + ], + "version": "==4.13.2" + }, "astroid": { "hashes": [ - "sha256:3eae9ea67c11c858cdd2c91337d2e816bd019ac897ca07d7b346ac10105fceb3", - "sha256:7099b5a60985529d8d46858befa103b82d0d05a5a5e8b816b5303ed96075e1d9" + "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a", + "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25" ], "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.3" + "version": "==3.2.4" }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "aws-cdk-lib": { "hashes": [ - "sha256:12dbb0b26e4701c8fd4f4fbbac8b082aefbab9f4c26e53e56604cf69540abd4a", - "sha256:40965ad6bce38dcb50ad9897e70b0cd9692ccb19c942303cc792bcbd5b113ae0" + "sha256:b9d663c9846f62fd8a494d081ae58acccc414773fd545a3e194d273d9bafd9e5", + "sha256:f11ac6b4a9c022780c2d2b3faccdc35e1d244e7bb5ae446f014bf48e8a8048af" ], "markers": "python_version ~= '3.8'", - "version": "==2.149.0" + "version": "==2.156.0" }, "aws-cdk.asset-awscli-v1": { "hashes": [ @@ -60,11 +75,19 @@ }, "aws-cdk.asset-node-proxy-agent-v6": { "hashes": [ - "sha256:b62cb10c69a42cab135e6bc670e3d2d3121fd4f53a0f61e53449da4b12738a6f", - "sha256:ef2ff0634ab037e2ebddbe69d7c92515a847c6c8bb2abdfc85b089f5e87761cb" + "sha256:1f292c0631f86708ba4ee328b3a2b229f7e46ea1c79fbde567ee9eb119c2b0e2", + "sha256:24a388b69a44d03bae6dbf864c4e25ba650d4b61c008b4568b94ffbb9a69e40e" + ], + "markers": "python_version ~= '3.8'", + "version": "==2.1.0" + }, + "aws-cdk.cloud-assembly-schema": { + "hashes": [ + "sha256:81290bd790c9aa7f051353aa1d6553325d6979851b0b7da147ba06b7653bf23c", + "sha256:bf509eb4fc97d1e60a7d18b533855eb50926dc1a7422336e2bfa78ad73979705" ], "markers": "python_version ~= '3.8'", - "version": "==2.0.3" + "version": "==36.0.24" }, "aws-lambda-powertools": { "extras": [ @@ -72,11 +95,19 @@ "validation" ], "hashes": [ - "sha256:024aec66b7f1b453a622117d1ba5df01dfbd06c92fbd25839eae3df4fdc27233", - "sha256:3c8a44dcfdb9fad49f161db6bf79d12e727bf440ae2c3d5896905ec8250b8624" + "sha256:48116250c1771c7b8d4977ad2d475271074d86964107ccfd3fc6775e51984d88", + "sha256:5c371a0c0430cf7bca1696748cb0d85079aac2c51056cbee10e5435029b35ca4" ], "markers": "python_version >= '3.8' and python_full_version < '4.0.0'", - "version": "==2.41.0" + "version": "==2.43.1" + }, + "aws-sam-translator": { + "hashes": [ + "sha256:0cdfbc598f384c430c3ec064f6008d80c5a0d58f1dc45ca4e331ae5c43cb4697", + "sha256:9ebf4b53c226338e6b89d14d8583bc4559b87f0be52ed8d577c5a1dc2db14962" + ], + "markers": "python_version >= '3.8' and python_version != '4.0' and python_version <= '4.0'", + "version": "==1.91.0" }, "aws-xray-sdk": { "hashes": [ @@ -85,29 +116,42 @@ ], "version": "==2.14.0" }, + "boto3": { + "hashes": [ + "sha256:7bc78d7140c353b10a637927fe4bc4c4d95a464d1b8f515d5844def2ee52cbd5", + "sha256:c3e138e9041d59cd34cdc28a587dfdc899dba02ea26ebc3e10fb4bc88e5cf31b" + ], + "markers": "python_version >= '3.8'", + "version": "==1.35.14" + }, "boto3-stubs": { + "extras": [ + "cognito-idp", + "essential", + "ssm" + ], "hashes": [ - "sha256:4fc2f8d41ee7fb4952dc8d6a27bb616e7c0c5f9b71f3ad6d179f27df1bb69b0c", - "sha256:c19d0c4386ec160e6c06567b960b6f7c765acc4ed40f01f371c8e7a0b514520b" + "sha256:c9b3c92b5b9b1278ca03bbb942075c5f9378f4bd26d7bce3ab1068246b088928", + "sha256:cfa0d7189862cbd02c6cef1c6ce597728340056687547e8a2c50d2033bf979b6" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.14" }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:24823135232f88266b66ae8e1d0f3d40872c14cd976781f7fe52b8f0d79035a0", + "sha256:8515a2fc7ca5bcf0b10016ba05ccf2d642b7cb77d8773026ff2fa5aa3bf38d2e" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.14" }, "botocore-stubs": { "hashes": [ - "sha256:3eb325b4b186e8d8e49d485cfe383f478ab8c07e0f4690e5ee9bcb6faceec4af", - "sha256:eded7afc301a08b5355ab8843c9d9bdea7dc43ece3e9628f186897dae7a56ceb" + "sha256:5a0f68e9aeebfbce5a2be80302974b450f022ccedd073deb366cb278b74b8612", + "sha256:755a818b016658b410e4c52cb135953d09fd1db086c829ed87c367971d7084da" ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.144" + "markers": "python_version >= '3.8'", + "version": "==1.35.13" }, "cattrs": { "hashes": [ @@ -119,12 +163,93 @@ }, "cdk-nag": { "hashes": [ - "sha256:39c967e3d8ad31eca557ac116001a279b9a9c1c80750c4fbda26d7da88978086", - "sha256:42971a90193b26db858a5bd6b81a68199145e4315e3ea08849f79eba47bef07c" + "sha256:6a33dbad938b66946f2d89a8a010a6e2b9cb42c8703aa3b4991b6ad572596b8a", + "sha256:c96ead451197dde434451c5bfef2c63edd0c7e766dd4a39268d9a8b8632da612" ], "index": "pypi", "markers": "python_version ~= '3.8'", - "version": "==2.28.161" + "version": "==2.28.195" + }, + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "cffi": { + "hashes": [ + "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", + "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", + "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", + "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", + "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", + "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", + "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", + "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", + "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", + "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", + "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", + "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", + "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.17.1" }, "cfgv": { "hashes": [ @@ -134,6 +259,109 @@ "markers": "python_version >= '3.8'", "version": "==3.4.0" }, + "cfn-lint": { + "hashes": [ + "sha256:14c2faa79b421c0ceeb09e201f225ff984efea39b1dd34ba98979e4107b709d9", + "sha256:30fac1eec8acb1fb5f66300c8f2e17aaffad9788ccb7dc7f12bd0aee571300d1" + ], + "version": "==1.12.4" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, "click": { "hashes": [ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", @@ -159,61 +387,114 @@ "toml" ], "hashes": [ - "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382", - "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1", - "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac", - "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee", - "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166", - "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57", - "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c", - "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b", - "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51", - "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da", - "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450", - "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2", - "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd", - "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d", - "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d", - "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6", - "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca", - "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169", - "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1", - "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713", - "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b", - "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6", - "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c", - "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605", - "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463", - "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b", - "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6", - "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5", - "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63", - "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c", - "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783", - "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44", - "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca", - "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8", - "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d", - "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390", - "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933", - "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67", - "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b", - "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03", - "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b", - "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791", - "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb", - "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807", - "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6", - "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2", - "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428", - "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd", - "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c", - "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94", - "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8", - "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b" - ], - "markers": "python_version >= '3.8'", - "version": "==7.6.0" + "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", + "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", + "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", + "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", + "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", + "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", + "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", + "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", + "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", + "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", + "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", + "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", + "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", + "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", + "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", + "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", + "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", + "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", + "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", + "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", + "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", + "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", + "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", + "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", + "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", + "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", + "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", + "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", + "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", + "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", + "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", + "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", + "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", + "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", + "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", + "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", + "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", + "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", + "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", + "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", + "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", + "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", + "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", + "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", + "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", + "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", + "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", + "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", + "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", + "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", + "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", + "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", + "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", + "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", + "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", + "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", + "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", + "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", + "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", + "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", + "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", + "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", + "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", + "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", + "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", + "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", + "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", + "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", + "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", + "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", + "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", + "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc" + ], + "markers": "python_version >= '3.8'", + "version": "==7.6.1" + }, + "cryptography": { + "hashes": [ + "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", + "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", + "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", + "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", + "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", + "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", + "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", + "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", + "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", + "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", + "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", + "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", + "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2", + "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", + "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", + "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365", + "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96", + "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", + "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", + "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", + "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", + "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", + "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", + "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172", + "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", + "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", + "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" + ], + "markers": "python_version >= '3.7'", + "version": "==43.0.1" }, "dill": { "hashes": [ @@ -230,6 +511,13 @@ ], "version": "==0.3.8" }, + "docker": { + "hashes": [ + "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", + "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0" + ], + "version": "==7.1.0" + }, "exceptiongroup": { "hashes": [ "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", @@ -263,6 +551,13 @@ "markers": "python_version >= '3.7'", "version": "==1.5.1" }, + "graphql-core": { + "hashes": [ + "sha256:1604f2042edc5f3114f49cac9d77e25863be51b23a54a61a23245cf32f6476f0", + "sha256:acbe2e800980d0e39b4685dd058c2f4042660b89ebca38af83020fd872ff1264" + ], + "version": "==3.2.4" + }, "identify": { "hashes": [ "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf", @@ -271,13 +566,21 @@ "markers": "python_version >= '3.8'", "version": "==2.6.0" }, + "idna": { + "hashes": [ + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" + ], + "markers": "python_version >= '3.6'", + "version": "==3.8" + }, "importlib-resources": { "hashes": [ - "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c", - "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145" + "sha256:20600c8b7361938dc0bb2d5ec0297802e575df486f5a544fa414da65e13721f7", + "sha256:dda242603d1c9cd836c3368b1174ed74cb4049ecd209e7a1a0104620c18c5c11" ], "markers": "python_version >= '3.8'", - "version": "==6.4.0" + "version": "==6.4.4" }, "iniconfig": { "hashes": [ @@ -312,13 +615,117 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "joserfc": { + "hashes": [ + "sha256:1de2c3ac203db8fceb2e84c1e78ba357030b195c21af046a1411711927654a09", + "sha256:298a9820c76576f8ca63375d1851cc092f3f225508305c7a36c4632cec38f7bc" + ], + "version": "==1.0.0" + }, "jsii": { "hashes": [ - "sha256:043c4d3d0d09af3c7265747f4da9c95770232477f75c846640df4c63d01b19cb", - "sha256:b78b87f8316560040ad0b9dca1682d73b6532a33acf4ecf56185d1ae5edb54fa" + "sha256:24b96349230ca22f50fcd69c501e69b6c486acf37bbe0b5869f4c185572b079e", + "sha256:7eaa46e8cd9546edc6bba81d0b32df9f8ed8f5848305277d261cccfe00b9c1eb" ], "markers": "python_version ~= '3.8'", - "version": "==1.101.0" + "version": "==1.103.1" + }, + "jsondiff": { + "hashes": [ + "sha256:658d162c8a86ba86de26303cd86a7b37e1b2c1ec98b569a60e2ca6180545f7fe", + "sha256:b1f0f7e2421881848b1d556d541ac01a91680cfcc14f51a9b62cdf4da0e56722" + ], + "version": "==2.2.1" + }, + "jsonpatch": { + "hashes": [ + "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", + "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==1.33" + }, + "jsonpath-ng": { + "hashes": [ + "sha256:086c37ba4917304850bd837aeab806670224d3f038fe2833ff593a672ef0a5fa", + "sha256:8f22cd8273d7772eea9aaa84d922e0841aa36fdb8a2c6b7f6c3791a16a9bc0be" + ], + "version": "==1.6.1" + }, + "jsonpointer": { + "hashes": [ + "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", + "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.0" + }, + "jsonschema": { + "hashes": [ + "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", + "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566" + ], + "markers": "python_version >= '3.8'", + "version": "==4.23.0" + }, + "jsonschema-path": { + "hashes": [ + "sha256:203aff257f8038cd3c67be614fe6b2001043408cb1b4e36576bc4921e09d83c4", + "sha256:f02e5481a4288ec062f8e68c808569e427d905bedfecb7f2e4c69ef77957c382" + ], + "markers": "python_full_version >= '3.8.0' and python_full_version < '4.0.0'", + "version": "==0.3.3" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", + "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" + ], + "markers": "python_version >= '3.8'", + "version": "==2023.12.1" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56", + "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4", + "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8", + "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282", + "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757", + "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424", + "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b", + "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255", + "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70", + "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94", + "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074", + "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c", + "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee", + "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9", + "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9", + "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69", + "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f", + "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3", + "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9", + "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d", + "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977", + "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b", + "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43", + "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658", + "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a", + "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd", + "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83", + "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4", + "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696", + "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05", + "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3", + "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6", + "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895", + "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4", + "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba", + "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03", + "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c" + ], + "markers": "python_version >= '3.8'", + "version": "==1.10.0" }, "libcst": { "hashes": [ @@ -450,39 +857,127 @@ "markers": "python_version >= '3.7'", "version": "==0.1.2" }, + "moto": { + "extras": [ + "all" + ], + "hashes": [ + "sha256:984377a9c4536543fc09f49a1d5210c61c4a4f55c79719f7d9f8dcdd9bf55ea5", + "sha256:ddf8864f0d61af88fd07a4e5eac428c6bebf4fcd10023f8e756e65e9e7b7e4a5" + ], + "markers": "python_version >= '3.8'", + "version": "==5.0.13" + }, + "mpmath": { + "hashes": [ + "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", + "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" + ], + "version": "==1.3.0" + }, + "multipart": { + "hashes": [ + "sha256:96352d67fa1f704e2bcbec9726d7fb316533bd010f0c66639f930fb59b734931", + "sha256:fa98838d40c967bb19589626a0fb8a5c40c421dda2febe1b0351fcf626e24651" + ], + "version": "==0.2.5" + }, "mypy": { "hashes": [ - "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9", - "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d", - "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0", - "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3", - "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3", - "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade", - "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31", - "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7", - "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e", - "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7", - "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c", - "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b", - "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e", - "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531", - "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04", - "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a", - "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37", - "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a", - "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f", - "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84", - "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d", - "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f", - "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a", - "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf", - "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7", - "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02", - "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3" + "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", + "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce", + "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", + "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b", + "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", + "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", + "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", + "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", + "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86", + "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", + "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", + "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", + "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", + "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", + "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", + "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", + "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", + "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", + "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", + "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", + "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", + "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", + "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", + "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", + "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1", + "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b", + "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.10.1" + "version": "==1.11.2" + }, + "mypy-boto3-cloudformation": { + "hashes": [ + "sha256:0d037d9d6bdb439a84e2391ba987a4e03fcedfad0e881db1cf0f7861d275907c", + "sha256:5da07e14a206a7f0015434d1730a6a68a33167ea6746343189dd1742cfcfdb7d" + ], + "version": "==1.35.0" + }, + "mypy-boto3-cognito-idp": { + "hashes": [ + "sha256:27a895e147b80237ecfdcfad230f4ecb70a4c0e3931bae1b8ed41dac1a313354", + "sha256:626d359599c300636a14d06460a8f7e330fae822e9592e3bd5c54608b9a9d7b0" + ], + "version": "==1.35.0" + }, + "mypy-boto3-dynamodb": { + "hashes": [ + "sha256:1e503c89a5aa65f2b90fc7c861d3630a21544822f30b38e67e4f52463111abb9", + "sha256:75f224d8b78f6d3126eead645aea6c0a8bc2828614f302c168de1d3dad490d11" + ], + "version": "==1.35.0" + }, + "mypy-boto3-ec2": { + "hashes": [ + "sha256:b3e17ee6082a107d7d6d7ac44062264a9fb711c5d6d9e0ce16837cda26d1be7c", + "sha256:f4cdbe524ff4039668cc168e3c6f9c68048481ab33dfb0f5d892bbf2428d1ef2" + ], + "version": "==1.35.8" + }, + "mypy-boto3-lambda": { + "hashes": [ + "sha256:2e78c12a7ba4d2d9c99b75fad58804fd99820e954ab557f14f099d6c85a882ab", + "sha256:b59e45facfc166eddb1d5c2696aa8127463455f9e439e3438494965bcd97c97d" + ], + "version": "==1.35.3" + }, + "mypy-boto3-rds": { + "hashes": [ + "sha256:8861b551854cabec2efbe40db506297e9526e1496a1e55843136df716a2b7a00", + "sha256:c252857561219ecc0a03b2d3936081d7a54a59d1caa01e69deb8cdea761dab76" + ], + "version": "==1.35.0" + }, + "mypy-boto3-s3": { + "hashes": [ + "sha256:74d8f3492eeff768ff6f69ac6d40bf68b40aa6e54ebe10a8d098fc3d24a54abf", + "sha256:f7300b559dee5435872625448becf159abe36b19cd7006dd78e0d51610312183" + ], + "version": "==1.35.2" + }, + "mypy-boto3-sqs": { + "hashes": [ + "sha256:61752f1c2bf2efa3815f64d43c25b4a39dbdbd9e472ae48aa18d7c6d2a7a6eb8", + "sha256:9fd6e622ed231c06f7542ba6f8f0eea92046cace24defa95d0d0ce04e7caee0c" + ], + "version": "==1.35.0" + }, + "mypy-boto3-ssm": { + "hashes": [ + "sha256:d3bc98ee5cc4da149a4ef210094f985a84c4d4f7a7c499ec5c6b041df27a1097", + "sha256:ee4bfdf91e7e59d556c172d1de8898cb8fd05893be089ac59a1d69a406d45b55" + ], + "version": "==1.35.0" }, "mypy-extensions": { "hashes": [ @@ -492,6 +987,14 @@ "markers": "python_version >= '3.5'", "version": "==1.0.0" }, + "networkx": { + "hashes": [ + "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9", + "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2" + ], + "markers": "python_version >= '3.10'", + "version": "==3.3" + }, "nodeenv": { "hashes": [ "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", @@ -500,6 +1003,21 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", "version": "==1.9.1" }, + "openapi-schema-validator": { + "hashes": [ + "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804", + "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8" + ], + "markers": "python_full_version >= '3.8.0' and python_full_version < '4.0.0'", + "version": "==0.6.2" + }, + "openapi-spec-validator": { + "hashes": [ + "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959", + "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7" + ], + "version": "==0.7.1" + }, "packaging": { "hashes": [ "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", @@ -508,6 +1026,14 @@ "markers": "python_version >= '3.8'", "version": "==24.1" }, + "pathable": { + "hashes": [ + "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab", + "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14" + ], + "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", + "version": "==0.4.3" + }, "pathspec": { "hashes": [ "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", @@ -532,14 +1058,21 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, + "ply": { + "hashes": [ + "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", + "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce" + ], + "version": "==3.11" + }, "pre-commit": { "hashes": [ - "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a", - "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5" + "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", + "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==3.7.1" + "version": "==3.8.0" }, "publication": { "hashes": [ @@ -548,6 +1081,13 @@ ], "version": "==0.0.3" }, + "py-partiql-parser": { + "hashes": [ + "sha256:90d278818385bd60c602410c953ee78f04ece599d8cd21c656fc5e47399577a1", + "sha256:ed07f8edf4b55e295cab4f5fd3e2ba3196cee48a43fe210d53ddd6ffce1cf1ff" + ], + "version": "==0.5.5" + }, "pycln": { "hashes": [ "sha256:1f3eefb7be18a9ee06c3bdd0ba2e91218cd39317e20130325f107e96eb84b9f6", @@ -557,6 +1097,117 @@ "markers": "python_version < '4' and python_full_version >= '3.7.0'", "version": "==2.4.0" }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, + "pydantic": { + "hashes": [ + "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598", + "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370" + ], + "markers": "python_version >= '3.8'", + "version": "==2.9.0" + }, + "pydantic-core": { + "hashes": [ + "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4", + "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123", + "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b", + "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437", + "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79", + "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5", + "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0", + "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf", + "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44", + "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f", + "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced", + "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6", + "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604", + "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c", + "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329", + "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653", + "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515", + "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7", + "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f", + "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2", + "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59", + "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30", + "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f", + "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af", + "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501", + "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41", + "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec", + "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e", + "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960", + "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b", + "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac", + "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb", + "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e", + "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73", + "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a", + "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43", + "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2", + "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa", + "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8", + "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49", + "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6", + "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703", + "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589", + "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100", + "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178", + "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c", + "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae", + "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7", + "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce", + "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465", + "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8", + "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece", + "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2", + "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472", + "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0", + "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81", + "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622", + "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f", + "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd", + "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78", + "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57", + "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa", + "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac", + "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69", + "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d", + "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e", + "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2", + "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0", + "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87", + "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc", + "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2", + "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd", + "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576", + "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad", + "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80", + "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a", + "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354", + "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e", + "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac", + "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940", + "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342", + "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1", + "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854", + "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936", + "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5", + "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc", + "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474", + "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6", + "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae" + ], + "markers": "python_version >= '3.8'", + "version": "==2.23.2" + }, "pygments": { "hashes": [ "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", @@ -567,21 +1218,28 @@ }, "pylint": { "hashes": [ - "sha256:32cd6c042b5004b8e857d727708720c54a676d1e22917cf1a2df9b4d4868abd6", - "sha256:e9b7171e242dcc6ebd0aaa7540481d1a72860748a0a7816b8fe6cf6c80a6fe7e" + "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b", + "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e" ], "index": "pypi", "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.5" + "version": "==3.2.7" + }, + "pyparsing": { + "hashes": [ + "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", + "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032" + ], + "version": "==3.1.4" }, "pytest": { "hashes": [ - "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343", - "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977" + "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", + "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.2.2" + "version": "==8.3.2" }, "pytest-cov": { "hashes": [ @@ -611,77 +1269,313 @@ }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "referencing": { + "hashes": [ + "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", + "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de" + ], + "markers": "python_version >= '3.8'", + "version": "==0.35.1" + }, + "regex": { + "hashes": [ + "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c", + "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535", + "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24", + "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce", + "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc", + "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5", + "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce", + "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53", + "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d", + "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c", + "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908", + "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8", + "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024", + "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281", + "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a", + "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169", + "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364", + "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa", + "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be", + "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53", + "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759", + "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e", + "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b", + "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52", + "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610", + "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05", + "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2", + "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca", + "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0", + "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293", + "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289", + "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e", + "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f", + "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c", + "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94", + "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad", + "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46", + "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9", + "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9", + "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee", + "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9", + "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1", + "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9", + "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799", + "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1", + "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b", + "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf", + "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5", + "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2", + "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e", + "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51", + "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506", + "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73", + "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7", + "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5", + "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57", + "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4", + "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd", + "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b", + "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41", + "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe", + "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59", + "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8", + "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f", + "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e", + "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750", + "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1", + "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96", + "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc", + "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440", + "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe", + "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38", + "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950", + "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2", + "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd", + "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce", + "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66", + "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3", + "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86" + ], + "markers": "python_version >= '3.8'", + "version": "==2024.7.24" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "responses": { + "hashes": [ + "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb", + "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba" + ], + "markers": "python_version >= '3.8'", + "version": "==0.25.3" + }, + "rfc3339-validator": { + "hashes": [ + "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", + "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.1.4" }, "rich": { "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" + "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc", + "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" + "version": "==13.8.0" + }, + "rpds-py": { + "hashes": [ + "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", + "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", + "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5", + "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", + "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", + "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", + "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29", + "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", + "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b", + "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", + "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", + "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", + "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", + "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a", + "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", + "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", + "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03", + "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", + "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22", + "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e", + "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", + "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", + "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752", + "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", + "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253", + "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", + "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", + "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5", + "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", + "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7", + "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", + "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", + "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", + "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", + "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec", + "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", + "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921", + "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", + "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074", + "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580", + "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", + "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", + "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", + "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", + "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", + "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", + "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", + "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", + "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789", + "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", + "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", + "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c", + "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232", + "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", + "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c", + "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", + "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", + "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", + "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751", + "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", + "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda", + "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", + "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", + "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", + "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8", + "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", + "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", + "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1", + "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2", + "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", + "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", + "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965", + "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", + "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", + "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b", + "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", + "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", + "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", + "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de", + "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", + "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", + "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", + "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", + "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", + "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1", + "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", + "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", + "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", + "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364", + "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", + "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", + "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420", + "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5", + "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24", + "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c", + "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", + "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f", + "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e", + "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab", + "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08", + "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", + "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", + "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8" + ], + "markers": "python_version >= '3.8'", + "version": "==0.20.0" + }, + "s3transfer": { + "hashes": [ + "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6", + "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69" + ], + "markers": "python_version >= '3.8'", + "version": "==0.10.2" }, "setuptools": { "hashes": [ - "sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d", - "sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207" + "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308", + "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==71.0.3" + "version": "==74.1.2" }, "shellingham": { "hashes": [ @@ -699,14 +1593,22 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "sympy": { + "hashes": [ + "sha256:401449d84d07be9d0c7a46a64bd54fe097667d5e7181bfe67ec777be9e01cb13", + "sha256:c51d75517712f1aed280d4ce58506a4a88d635d6b5dd48b39102a7ae1f3fcfe9" + ], + "markers": "python_version >= '3.8'", + "version": "==1.13.2" + }, "syrupy": { "hashes": [ - "sha256:203e52f9cb9fa749cf683f29bd68f02c16c3bc7e7e5fe8f2fc59bdfe488ce133", - "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36" + "sha256:be002267a512a4bedddfae2e026c93df1ea928ae10baadc09640516923376d41", + "sha256:f9d4485f3f27d0e5df6ed299cac6fa32eb40a441915d988e82be5a4bdda335c8" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.8.1'", - "version": "==4.6.1" + "markers": "python_full_version >= '3.8.1'", + "version": "==4.7.1" }, "toml": { "hashes": [ @@ -719,11 +1621,11 @@ }, "tomlkit": { "hashes": [ - "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72", - "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264" + "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", + "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79" ], "markers": "python_version >= '3.8'", - "version": "==0.13.0" + "version": "==0.13.2" }, "typeguard": { "hashes": [ @@ -735,19 +1637,19 @@ }, "typer": { "hashes": [ - "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914", - "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482" + "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", + "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722" ], "markers": "python_version >= '3.7'", - "version": "==0.12.3" + "version": "==0.12.5" }, "types-awscrt": { "hashes": [ - "sha256:0839fe12f0f914d8f7d63ed777c728cb4eccc2d5d79a26e377d12b0604e7bf0e", - "sha256:84a9f4f422ec525c314fdf54c23a1e73edfbcec968560943ca2d41cfae623b38" + "sha256:47f002cab99e25c10aee873d0f6d906a7144d58373b6ba68629993ed2d64f65b", + "sha256:c2e58067a88818cb2aa2bdd39032aa41ce4b2637cef703edad477424f72530ee" ], - "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.21.2" + "markers": "python_version >= '3.8'", + "version": "==0.21.4" }, "types-boto3": { "hashes": [ @@ -759,47 +1661,47 @@ }, "types-python-dateutil": { "hashes": [ - "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202", - "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b" + "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6", + "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.9.0.20240316" + "version": "==2.9.0.20240906" }, "types-pyyaml": { "hashes": [ - "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342", - "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6" + "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af", + "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==6.0.12.20240311" + "version": "==6.0.12.20240808" }, "types-requests": { "hashes": [ - "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358", - "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3" + "sha256:e97fd015a5ed982c9ddcd14cc4afba9d111e0e06b797c8f776d14602735e9bd6", + "sha256:f46ecb55f5e1a37a58be684cf3f013f166da27552732ef2469a0cc8e62a72881" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.32.0.20240712" + "version": "==2.32.0.20240905" }, "types-s3transfer": { "hashes": [ - "sha256:02154cce46528287ad76ad1a0153840e0492239a0887e8833466eccf84b98da0", - "sha256:49a7c81fa609ac1532f8de3756e64b58afcecad8767933310228002ec7adff74" + "sha256:60167a3bfb5c536ec6cdb5818f7f9a28edca9dc3e0b5ff85ae374526fc5e576e", + "sha256:7a3fec8cd632e2b5efb665a355ef93c2a87fdd5a45b74a949f95a9e628a86356" ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==0.10.1" + "markers": "python_version >= '3.8'", + "version": "==0.10.2" }, "types-setuptools": { "hashes": [ - "sha256:842cbf399812d2b65042c9d6ff35113bbf282dee38794779aa1f94e597bafc35", - "sha256:bd0db2a4b9f2c49ac5564be4e0fb3125c4c46b1f73eafdcbceffa5b005cceca4" + "sha256:8c8e4c1cfaaa68d4457dbfe575938c09b2db5f9ef1b7a639e739ba541d3a20ae", + "sha256:e51329b968617885ce45f4089b389c6563f26ebb3de8ef4505917174eed23f79" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==70.3.0.20240710" + "version": "==74.1.0.20240906" }, "types-toml": { "hashes": [ @@ -826,11 +1728,20 @@ "markers": "python_version >= '3.8'", "version": "==4.12.2" }, + "tzdata": { + "hashes": [ + "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", + "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" + ], + "markers": "python_version >= '3.9'", + "version": "==2024.1" + }, "urllib3": { "hashes": [ "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.2.2" }, @@ -842,14 +1753,22 @@ "markers": "python_version >= '3.7'", "version": "==20.26.3" }, + "werkzeug": { + "hashes": [ + "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", + "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.4" + }, "wheel": { "hashes": [ - "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85", - "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81" + "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f", + "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==0.43.0" + "version": "==0.44.0" }, "wrapt": { "hashes": [ @@ -927,6 +1846,14 @@ "index": "pypi", "markers": "python_version >= '3.6'", "version": "==1.16.0" + }, + "xmltodict": { + "hashes": [ + "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56", + "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852" + ], + "markers": "python_version >= '3.4'", + "version": "==0.13.0" } } } diff --git a/source/modules/backstage/app-config.local.yaml b/source/modules/backstage/app-config.local.yaml index f944b2bb..71f16534 100644 --- a/source/modules/backstage/app-config.local.yaml +++ b/source/modules/backstage/app-config.local.yaml @@ -82,70 +82,77 @@ auth: config: additionalScopes: ${BACKSTAGE_ADDITIONAL_SCOPES} - scaffolder: # see https://backstage.io/docs/features/software-templates/configuration for software template options concurrentTasksLimit: 10 catalog: rules: - - allow: [Component, System, API, Group, User, Resource, Location, Template] + - allow: [Component, System, API, Group, User, Resource, Location, Template, PartnerOffering] orphanStrategy: delete processingInterval: { minutes: 1 } locations: + - type: url + target: "https://${REGIONAL_PUBLIC_ASSET_BUCKET_NAME}.s3.${AWS_REGION}.amazonaws.com/${SOLUTION_NAME}/v1/backstage-assets/partner-offerings.yaml" + rules: + - allow: [Location, PartnerOffering] + - type: file + target: ${SOLUTION_PATH}/source/modules/cms_api/deployment/regional-s3-assets/backstage/templates/cms-api.template.yaml + rules: + - allow: [Location, Template] - type: file - target: ../../modules/cms_api/deployment/regional-s3-assets/backstage/templates/cms-api.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_alerts/deployment/regional-s3-assets/backstage/templates/cms-alerts.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_alerts/deployment/regional-s3-assets/backstage/templates/cms-alerts.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_provisioning/deployment/regional-s3-assets/backstage/templates/cms-provisioning.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_provisioning/deployment/regional-s3-assets/backstage/templates/cms-provisioning.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_connect_store/deployment/regional-s3-assets/backstage/templates/cms-connect-store.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_connect_store/deployment/regional-s3-assets/backstage/templates/cms-connect-store.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_ev_battery_health/deployment/regional-s3-assets/backstage/templates/cms-ev-battery-health.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_ev_battery_health/deployment/regional-s3-assets/backstage/templates/cms-ev-battery-health.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_vehicle_simulator/deployment/regional-s3-assets/backstage/templates/cms-vehicle-simulator.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_vehicle_simulator/deployment/regional-s3-assets/backstage/templates/cms-vehicle-simulator.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_auth/deployment/regional-s3-assets/backstage/templates/cms-auth.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_auth/deployment/regional-s3-assets/backstage/templates/cms-auth.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_fleetwise_connector/deployment/regional-s3-assets/backstage/templates/cms-fleetwise-connector.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_fleetwise_connector/deployment/regional-s3-assets/backstage/templates/cms-fleetwise-connector.template.yaml + target: ${SOLUTION_PATH}/source/modules/vpc/deployment/regional-s3-assets/backstage/templates/vpc.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/vpc/deployment/regional-s3-assets/backstage/templates/vpc.template.yaml + target: ${SOLUTION_PATH}/source/modules/auth_setup/deployment/regional-s3-assets/backstage/templates/auth-setup.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/auth_setup/deployment/regional-s3-assets/backstage/templates/auth-setup.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_sample/deployment/regional-s3-assets/backstage/templates/cms-sample.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_sample/deployment/regional-s3-assets/backstage/templates/cms-sample.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_config/deployment/regional-s3-assets/backstage/templates/cms-config.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] - type: file - target: ../../modules/cms_config/deployment/regional-s3-assets/backstage/templates/cms-config.template.yaml + target: ${SOLUTION_PATH}/source/modules/cms_predictive_maintenance/deployment/regional-s3-assets/backstage/templates/cms_predictive_maintenance.template.yaml rules: - - allow: [Template] + - allow: [Location, Template] # For local testing of AWS integration, uncomment this and fill in # providers: # rules: - # - allow: [Component, System, API, Group, User, Resource, Location, Template] + # - allow: [Component, System, API, Group, User, Resource, Location, Template, PartnerOffering] # awsS3: # acdpTemplateResourceBucket: # bucketName: ${REGIONAL_ASSET_BUCKET_NAME} diff --git a/source/modules/backstage/app-config.production.yaml b/source/modules/backstage/app-config.production.yaml index b30229b7..c284f41f 100644 --- a/source/modules/backstage/app-config.production.yaml +++ b/source/modules/backstage/app-config.production.yaml @@ -97,6 +97,10 @@ catalog: frequency: minutes: ${LOCAL_ASSET_BUCKET_DISCOVERY_REFRESH_FREQ} timeout: { minutes: 3 } - + locations: + - type: url + target: "https://github.com/aws-solutions/connected-mobility-solution-on-aws-public-assets/blob/release/v1/backstage-assets/all.yaml" + rules: + - allow: [Location, PartnerOffering] rules: - - allow: [Component, System, API, Group, User, Resource, Location, Template] + - allow: [Component, System, API, Group, User, Resource, Location, Template, PartnerOffering] diff --git a/source/modules/backstage/backstage.json b/source/modules/backstage/backstage.json index b88ec35f..041f4ffe 100644 --- a/source/modules/backstage/backstage.json +++ b/source/modules/backstage/backstage.json @@ -1,3 +1,3 @@ { - "version": "1.28.3" + "version": "1.29.2" } diff --git a/source/modules/backstage/cdk/.nvmrc b/source/modules/backstage/cdk/.nvmrc index aacb5181..0305213f 100644 --- a/source/modules/backstage/cdk/.nvmrc +++ b/source/modules/backstage/cdk/.nvmrc @@ -1 +1 @@ -18.17 +18.20 diff --git a/source/modules/backstage/cdk/Makefile b/source/modules/backstage/cdk/Makefile index f6f8a003..00050e5f 100644 --- a/source/modules/backstage/cdk/Makefile +++ b/source/modules/backstage/cdk/Makefile @@ -68,6 +68,7 @@ deploy: ## Deploy the stack for the module. --parameter-overrides \ "AcdpUniqueId"="${ACDP_UNIQUE_ID}" \ "VpcName"="${VPC_NAME}" \ + "DefaultUserEmail"="${DEFAULT_USER_EMAIL}" \ "IdentityProviderId"="${IDENTITY_PROVIDER_ID}" \ "Route53HostedZoneId"="${ROUTE53_HOSTED_ZONE_ID}" \ "FullyQualifiedDomainName"="${FULLY_QUALIFIED_DOMAIN_NAME}" \ diff --git a/source/modules/backstage/cdk/Pipfile b/source/modules/backstage/cdk/Pipfile index efce2771..bf001e39 100644 --- a/source/modules/backstage/cdk/Pipfile +++ b/source/modules/backstage/cdk/Pipfile @@ -20,6 +20,7 @@ pytest-cov = "*" pytest-mock = "*" setuptools = ">=71.0.0" syrupy = "*" +urllib3 = ">=2.2.2" types-boto3 = ">=1.0.2" types-setuptools = ">=65.6.0.1" types-pyyaml = "*" diff --git a/source/modules/backstage/cdk/Pipfile.lock b/source/modules/backstage/cdk/Pipfile.lock index b4a33a22..a18007dc 100644 --- a/source/modules/backstage/cdk/Pipfile.lock +++ b/source/modules/backstage/cdk/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4f2d46c2017238804445818891a72b51695c0a98b3ffdacf7c807b02b6e5bc18" + "sha256": "859639b4bfeca5be2acceca9f1fd05ec540a13d857879981f140daa03e447d8f" }, "pipfile-spec": 6, "requires": { @@ -27,35 +27,35 @@ }, "antlr4-python3-runtime": { "hashes": [ - "sha256:3cd282f5ea7cfb841537fe01f143350fdb1c0b1ce7981443a2fa8513fddb6d1a", - "sha256:78ec57aad12c97ac039ca27403ad61cb98aaec8a3f9bb8144f889aa0fa28b943" + "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", + "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8" ], - "version": "==4.13.1" + "version": "==4.13.2" }, "astroid": { "hashes": [ - "sha256:3eae9ea67c11c858cdd2c91337d2e816bd019ac897ca07d7b346ac10105fceb3", - "sha256:7099b5a60985529d8d46858befa103b82d0d05a5a5e8b816b5303ed96075e1d9" + "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a", + "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25" ], "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.3" + "version": "==3.2.4" }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "aws-cdk-lib": { "hashes": [ - "sha256:12dbb0b26e4701c8fd4f4fbbac8b082aefbab9f4c26e53e56604cf69540abd4a", - "sha256:40965ad6bce38dcb50ad9897e70b0cd9692ccb19c942303cc792bcbd5b113ae0" + "sha256:1e20addd72affcb8ad5f677c0f6ada46234b74842327546236376d4181b57781", + "sha256:da20df35555c0ecae0eac503c4333ef76bc1da9ed69a8e52d5ab5f9c44f4b5c8" ], "index": "pypi", "markers": "python_version ~= '3.8'", - "version": "==2.149.0" + "version": "==2.157.0" }, "aws-cdk.asset-awscli-v1": { "hashes": [ @@ -75,11 +75,19 @@ }, "aws-cdk.asset-node-proxy-agent-v6": { "hashes": [ - "sha256:b62cb10c69a42cab135e6bc670e3d2d3121fd4f53a0f61e53449da4b12738a6f", - "sha256:ef2ff0634ab037e2ebddbe69d7c92515a847c6c8bb2abdfc85b089f5e87761cb" + "sha256:1f292c0631f86708ba4ee328b3a2b229f7e46ea1c79fbde567ee9eb119c2b0e2", + "sha256:24a388b69a44d03bae6dbf864c4e25ba650d4b61c008b4568b94ffbb9a69e40e" ], "markers": "python_version ~= '3.8'", - "version": "==2.0.3" + "version": "==2.1.0" + }, + "aws-cdk.cloud-assembly-schema": { + "hashes": [ + "sha256:81290bd790c9aa7f051353aa1d6553325d6979851b0b7da147ba06b7653bf23c", + "sha256:bf509eb4fc97d1e60a7d18b533855eb50926dc1a7422336e2bfa78ad73979705" + ], + "markers": "python_version ~= '3.8'", + "version": "==36.0.24" }, "aws-lambda-powertools": { "extras": [ @@ -87,19 +95,19 @@ "validation" ], "hashes": [ - "sha256:024aec66b7f1b453a622117d1ba5df01dfbd06c92fbd25839eae3df4fdc27233", - "sha256:3c8a44dcfdb9fad49f161db6bf79d12e727bf440ae2c3d5896905ec8250b8624" + "sha256:48116250c1771c7b8d4977ad2d475271074d86964107ccfd3fc6775e51984d88", + "sha256:5c371a0c0430cf7bca1696748cb0d85079aac2c51056cbee10e5435029b35ca4" ], "markers": "python_version >= '3.8' and python_full_version < '4.0.0'", - "version": "==2.41.0" + "version": "==2.43.1" }, "aws-sam-translator": { "hashes": [ - "sha256:843be1b5ca7634f700ad0c844a7e0dc42858f35da502e91691473eadd1731ded", - "sha256:fff1005d0b1f3cb511d0ac7e85f54af06afc9d9e433df013a2338d7a0168d174" + "sha256:0cdfbc598f384c430c3ec064f6008d80c5a0d58f1dc45ca4e331ae5c43cb4697", + "sha256:9ebf4b53c226338e6b89d14d8583bc4559b87f0be52ed8d577c5a1dc2db14962" ], "markers": "python_version >= '3.8' and python_version != '4.0' and python_version <= '4.0'", - "version": "==1.89.0" + "version": "==1.91.0" }, "aws-xray-sdk": { "hashes": [ @@ -110,35 +118,40 @@ }, "boto3": { "hashes": [ - "sha256:2f3e88b10b8fcc5f6100a9d74cd28230edc9d4fa226d99dd40a3ab38ac213673", - "sha256:b8433d481d50b68a0162c0379c0dd4aabfc3d1ad901800beb5b87815997511c1" + "sha256:9b96c210678cf430b16b49dee87db30f46044602bb9a605a465e1900f468a43f", + "sha256:9c5b0ce4a25bb78d659478d1c552f1dbb7ff275aab3263bb41cdbef8bca28693" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.16" }, "boto3-stubs": { + "extras": [ + "cognito-idp", + "essential", + "ssm" + ], "hashes": [ - "sha256:4fc2f8d41ee7fb4952dc8d6a27bb616e7c0c5f9b71f3ad6d179f27df1bb69b0c", - "sha256:c19d0c4386ec160e6c06567b960b6f7c765acc4ed40f01f371c8e7a0b514520b" + "sha256:39b77ede4914704c2ee5e97fd3486d6af26745cbedf6bc06f33c0ffadd0fb2c9", + "sha256:7dee283bd3a5272fe759a43e22fc0658b5ee35679cb4932e33ad0c602f559b61" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.16" }, "botocore": { "hashes": [ - "sha256:4215db28d25309d59c99507f1f77df9089e5bebbad35f6e19c7c44ec5383a3e8", - "sha256:a2cf26e1bf10d5917a2285e50257bc44e94a1d16574f282f3274f7a5d8d1f08b" + "sha256:1b48c94e8a4bbe23143f3d1c21a32b9ffc7476b651ef42371ab45d678f6dbfbc", + "sha256:3564a980d95ff2861a6ca74313173d8778aa659125c63cf49c93ad23896c63b1" ], "markers": "python_version >= '3.8'", - "version": "==1.34.144" + "version": "==1.35.16" }, "botocore-stubs": { "hashes": [ - "sha256:3eb325b4b186e8d8e49d485cfe383f478ab8c07e0f4690e5ee9bcb6faceec4af", - "sha256:eded7afc301a08b5355ab8843c9d9bdea7dc43ece3e9628f186897dae7a56ceb" + "sha256:7181c2edf169a4dc89f9932cbd8eb82fb6b54ac59784685058f4c6ad180fce92", + "sha256:bfdabe90607dbcb923042da5886eecdcc5839e7c976ccc2ccbd091dc690a633f" ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.144" + "markers": "python_version >= '3.8'", + "version": "==1.35.16" }, "cattrs": { "hashes": [ @@ -150,78 +163,93 @@ }, "cdk-nag": { "hashes": [ - "sha256:39c967e3d8ad31eca557ac116001a279b9a9c1c80750c4fbda26d7da88978086", - "sha256:42971a90193b26db858a5bd6b81a68199145e4315e3ea08849f79eba47bef07c" + "sha256:6a33dbad938b66946f2d89a8a010a6e2b9cb42c8703aa3b4991b6ad572596b8a", + "sha256:c96ead451197dde434451c5bfef2c63edd0c7e766dd4a39268d9a8b8632da612" ], "index": "pypi", "markers": "python_version ~= '3.8'", - "version": "==2.28.161" + "version": "==2.28.195" }, "certifi": { "hashes": [ - "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", - "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.7.4" + "version": "==2024.8.30" }, "cffi": { "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", + "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", + "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", + "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", + "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", + "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", + "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", + "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", + "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", + "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", + "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", + "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", + "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" ], "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.16.0" + "version": "==1.17.1" }, "cfgv": { "hashes": [ @@ -233,10 +261,10 @@ }, "cfn-lint": { "hashes": [ - "sha256:b144ea5af8a62cace5f239ef0456a0a7fcf472fe9767cd51844a0709ddfe5f30", - "sha256:bf34674a92a7b345a13521dda095101c8900388accc03bb8505aaf0a8b4a19d2" + "sha256:14c2faa79b421c0ceeb09e201f225ff984efea39b1dd34ba98979e4107b709d9", + "sha256:30fac1eec8acb1fb5f66300c8f2e17aaffad9788ccb7dc7f12bd0aee571300d1" ], - "version": "==1.8.1" + "version": "==1.12.4" }, "charset-normalizer": { "hashes": [ @@ -359,99 +387,114 @@ "toml" ], "hashes": [ - "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382", - "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1", - "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac", - "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee", - "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166", - "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57", - "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c", - "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b", - "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51", - "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da", - "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450", - "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2", - "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd", - "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d", - "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d", - "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6", - "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca", - "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169", - "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1", - "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713", - "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b", - "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6", - "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c", - "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605", - "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463", - "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b", - "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6", - "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5", - "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63", - "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c", - "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783", - "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44", - "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca", - "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8", - "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d", - "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390", - "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933", - "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67", - "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b", - "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03", - "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b", - "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791", - "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb", - "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807", - "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6", - "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2", - "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428", - "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd", - "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c", - "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94", - "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8", - "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b" + "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", + "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", + "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", + "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", + "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", + "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", + "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", + "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", + "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", + "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", + "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", + "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", + "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", + "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", + "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", + "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", + "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", + "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", + "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", + "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", + "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", + "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", + "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", + "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", + "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", + "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", + "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", + "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", + "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", + "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", + "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", + "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", + "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", + "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", + "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", + "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", + "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", + "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", + "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", + "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", + "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", + "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", + "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", + "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", + "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", + "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", + "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", + "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", + "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", + "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", + "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", + "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", + "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", + "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", + "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", + "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", + "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", + "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", + "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", + "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", + "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", + "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", + "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", + "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", + "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", + "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", + "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", + "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", + "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", + "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", + "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", + "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc" ], "markers": "python_version >= '3.8'", - "version": "==7.6.0" + "version": "==7.6.1" }, "cryptography": { "hashes": [ - "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", - "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", - "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", - "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", - "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", - "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", - "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", - "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", - "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", - "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", - "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", - "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", - "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", - "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", - "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", - "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", - "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", - "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", - "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", - "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", - "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", - "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", - "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", - "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", - "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", - "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", - "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", - "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", - "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", - "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", - "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", - "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" + "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", + "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", + "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", + "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", + "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", + "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", + "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", + "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", + "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", + "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", + "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", + "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", + "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2", + "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", + "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", + "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365", + "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96", + "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", + "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", + "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", + "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", + "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", + "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", + "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172", + "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", + "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", + "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" ], "markers": "python_version >= '3.7'", - "version": "==42.0.8" + "version": "==43.0.1" }, "dill": { "hashes": [ @@ -484,18 +527,18 @@ }, "filelock": { "hashes": [ - "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb", - "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7" + "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec", + "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609" ], "markers": "python_version >= '3.8'", - "version": "==3.15.4" + "version": "==3.16.0" }, "graphql-core": { "hashes": [ - "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676", - "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3" + "sha256:1604f2042edc5f3114f49cac9d77e25863be51b23a54a61a23245cf32f6476f0", + "sha256:acbe2e800980d0e39b4685dd058c2f4042660b89ebca38af83020fd872ff1264" ], - "version": "==3.2.3" + "version": "==3.2.4" }, "identify": { "hashes": [ @@ -507,19 +550,19 @@ }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.8" }, "importlib-resources": { "hashes": [ - "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c", - "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145" + "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", + "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717" ], "markers": "python_version >= '3.8'", - "version": "==6.4.0" + "version": "==6.4.5" }, "iniconfig": { "hashes": [ @@ -562,18 +605,18 @@ }, "jsii": { "hashes": [ - "sha256:043c4d3d0d09af3c7265747f4da9c95770232477f75c846640df4c63d01b19cb", - "sha256:b78b87f8316560040ad0b9dca1682d73b6532a33acf4ecf56185d1ae5edb54fa" + "sha256:24b96349230ca22f50fcd69c501e69b6c486acf37bbe0b5869f4c185572b079e", + "sha256:7eaa46e8cd9546edc6bba81d0b32df9f8ed8f5848305277d261cccfe00b9c1eb" ], "markers": "python_version ~= '3.8'", - "version": "==1.101.0" + "version": "==1.103.1" }, "jsondiff": { "hashes": [ - "sha256:8a33728db8fdd118414e2c92778d64b77c90e09459b8dd601e56941edacb8542", - "sha256:9b00c9752b572474760e9767285567688c7d5aa8899423e5de7345825eda041a" + "sha256:658d162c8a86ba86de26303cd86a7b37e1b2c1ec98b569a60e2ca6180545f7fe", + "sha256:b1f0f7e2421881848b1d556d541ac01a91680cfcc14f51a9b62cdf4da0e56722" ], - "version": "==2.1.2" + "version": "==2.2.1" }, "jsonpatch": { "hashes": [ @@ -791,11 +834,11 @@ "all" ], "hashes": [ - "sha256:606b641f4c6ef69f28a84147d6d6806d052011e7ae7b0fe46ae8858e7a27a0a3", - "sha256:bdba9bec0afcde9f99b58c5271d6458dbfcda0a0a1e9beaecd808d2591db65ea" + "sha256:0f849243269fd03372426c302b18cb605302da32620d7f0266be6a40735b2acd", + "sha256:c738ffe85d3844ef37b865951736c4faf2e0f3e4f05db87bdad97a6c01b88174" ], "markers": "python_version >= '3.8'", - "version": "==5.0.11" + "version": "==5.0.14" }, "mpmath": { "hashes": [ @@ -813,37 +856,37 @@ }, "mypy": { "hashes": [ - "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9", - "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d", - "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0", - "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3", - "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3", - "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade", - "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31", - "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7", - "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e", - "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7", - "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c", - "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b", - "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e", - "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531", - "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04", - "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a", - "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37", - "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a", - "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f", - "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84", - "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d", - "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f", - "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a", - "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf", - "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7", - "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02", - "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3" + "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", + "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce", + "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", + "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b", + "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", + "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", + "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", + "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", + "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86", + "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", + "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", + "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", + "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", + "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", + "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", + "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", + "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", + "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", + "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", + "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", + "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", + "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", + "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", + "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", + "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1", + "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b", + "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.10.1" + "version": "==1.11.2" }, "mypy-extensions": { "hashes": [ @@ -910,11 +953,11 @@ }, "platformdirs": { "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" + "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", + "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.3.2" }, "pluggy": { "hashes": [ @@ -933,12 +976,12 @@ }, "pre-commit": { "hashes": [ - "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a", - "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5" + "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", + "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==3.7.1" + "version": "==3.8.0" }, "publication": { "hashes": [ @@ -949,10 +992,10 @@ }, "py-partiql-parser": { "hashes": [ - "sha256:90d278818385bd60c602410c953ee78f04ece599d8cd21c656fc5e47399577a1", - "sha256:ed07f8edf4b55e295cab4f5fd3e2ba3196cee48a43fe210d53ddd6ffce1cf1ff" + "sha256:622d7b0444becd08c1f4e9e73b31690f4b1c309ab6e5ed45bf607fe71319309f", + "sha256:6339f6bf85573a35686529fc3f491302e71dd091711dfe8df3be89a93767f97b" ], - "version": "==0.5.5" + "version": "==0.5.6" }, "pycln": { "hashes": [ @@ -973,106 +1016,106 @@ }, "pydantic": { "hashes": [ - "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", - "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8" + "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2", + "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612" ], "markers": "python_version >= '3.8'", - "version": "==2.8.2" + "version": "==2.9.1" }, "pydantic-core": { "hashes": [ - "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", - "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f", - "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686", - "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482", - "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006", - "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83", - "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6", - "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88", - "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86", - "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a", - "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6", - "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a", - "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6", - "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6", - "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43", - "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c", - "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4", - "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e", - "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203", - "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd", - "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1", - "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24", - "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc", - "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc", - "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3", - "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598", - "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98", - "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331", - "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2", - "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a", - "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6", - "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", - "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91", - "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa", - "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b", - "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0", - "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840", - "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c", - "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", - "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3", - "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231", - "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1", - "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953", - "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250", - "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a", - "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2", - "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20", - "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434", - "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab", - "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703", - "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a", - "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2", - "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac", - "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611", - "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121", - "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e", - "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b", - "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09", - "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906", - "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9", - "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7", - "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b", - "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987", - "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c", - "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b", - "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e", - "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237", - "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1", - "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19", - "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b", - "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad", - "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0", - "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94", - "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312", - "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f", - "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669", - "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1", - "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe", - "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99", - "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a", - "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a", - "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52", - "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c", - "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad", - "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1", - "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a", - "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f", - "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a", - "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27" + "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801", + "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec", + "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295", + "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba", + "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e", + "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e", + "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4", + "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211", + "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea", + "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c", + "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835", + "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d", + "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c", + "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c", + "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61", + "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83", + "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb", + "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1", + "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5", + "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690", + "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b", + "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7", + "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70", + "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a", + "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8", + "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd", + "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee", + "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1", + "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab", + "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958", + "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5", + "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b", + "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961", + "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c", + "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25", + "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4", + "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4", + "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f", + "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326", + "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab", + "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8", + "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b", + "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6", + "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8", + "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01", + "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc", + "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d", + "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e", + "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b", + "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855", + "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700", + "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a", + "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa", + "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541", + "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791", + "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162", + "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611", + "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef", + "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe", + "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5", + "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba", + "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28", + "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa", + "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27", + "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4", + "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b", + "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2", + "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c", + "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8", + "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb", + "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c", + "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e", + "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305", + "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8", + "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4", + "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433", + "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45", + "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16", + "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed", + "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0", + "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d", + "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710", + "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48", + "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423", + "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf", + "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9", + "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63", + "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5", + "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb" ], "markers": "python_version >= '3.8'", - "version": "==2.20.1" + "version": "==2.23.3" }, "pygments": { "hashes": [ @@ -1092,28 +1135,28 @@ }, "pylint": { "hashes": [ - "sha256:32cd6c042b5004b8e857d727708720c54a676d1e22917cf1a2df9b4d4868abd6", - "sha256:e9b7171e242dcc6ebd0aaa7540481d1a72860748a0a7816b8fe6cf6c80a6fe7e" + "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b", + "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e" ], "index": "pypi", "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.5" + "version": "==3.2.7" }, "pyparsing": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", + "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032" ], - "version": "==3.1.2" + "version": "==3.1.4" }, "pytest": { "hashes": [ - "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343", - "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.2.2" + "version": "==8.3.3" }, "pytest-cov": { "hashes": [ @@ -1143,60 +1186,62 @@ }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "referencing": { "hashes": [ @@ -1208,88 +1253,88 @@ }, "regex": { "hashes": [ - "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649", - "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35", - "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb", - "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68", - "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5", - "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133", - "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0", - "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d", - "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da", - "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f", - "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d", - "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53", - "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa", - "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a", - "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890", - "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67", - "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c", - "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2", - "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced", - "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741", - "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f", - "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa", - "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf", - "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4", - "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5", - "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2", - "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384", - "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7", - "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014", - "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704", - "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5", - "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2", - "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49", - "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1", - "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694", - "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629", - "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6", - "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435", - "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c", - "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835", - "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e", - "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201", - "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62", - "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5", - "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16", - "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f", - "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1", - "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f", - "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f", - "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145", - "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3", - "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed", - "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143", - "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca", - "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9", - "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa", - "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850", - "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80", - "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe", - "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656", - "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388", - "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1", - "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294", - "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3", - "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d", - "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b", - "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40", - "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600", - "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c", - "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569", - "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456", - "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9", - "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb", - "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e", - "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f", - "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d", - "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a", - "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a", - "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796" + "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c", + "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535", + "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24", + "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce", + "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc", + "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5", + "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce", + "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53", + "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d", + "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c", + "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908", + "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8", + "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024", + "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281", + "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a", + "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169", + "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364", + "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa", + "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be", + "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53", + "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759", + "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e", + "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b", + "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52", + "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610", + "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05", + "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2", + "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca", + "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0", + "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293", + "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289", + "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e", + "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f", + "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c", + "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94", + "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad", + "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46", + "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9", + "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9", + "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee", + "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9", + "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1", + "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9", + "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799", + "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1", + "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b", + "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf", + "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5", + "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2", + "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e", + "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51", + "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506", + "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73", + "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7", + "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5", + "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57", + "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4", + "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd", + "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b", + "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41", + "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe", + "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59", + "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8", + "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f", + "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e", + "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750", + "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1", + "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96", + "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc", + "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440", + "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe", + "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38", + "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950", + "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2", + "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd", + "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce", + "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66", + "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3", + "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86" ], "markers": "python_version >= '3.8'", - "version": "==2024.5.15" + "version": "==2024.7.24" }, "requests": { "hashes": [ @@ -1317,116 +1362,120 @@ }, "rich": { "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" + "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06", + "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" + "version": "==13.8.1" }, "rpds-py": { "hashes": [ - "sha256:0121803b0f424ee2109d6e1f27db45b166ebaa4b32ff47d6aa225642636cd834", - "sha256:06925c50f86da0596b9c3c64c3837b2481337b83ef3519e5db2701df695453a4", - "sha256:071d4adc734de562bd11d43bd134330fb6249769b2f66b9310dab7460f4bf714", - "sha256:1540d807364c84516417115c38f0119dfec5ea5c0dd9a25332dea60b1d26fc4d", - "sha256:15e65395a59d2e0e96caf8ee5389ffb4604e980479c32742936ddd7ade914b22", - "sha256:19d02c45f2507b489fd4df7b827940f1420480b3e2e471e952af4d44a1ea8e34", - "sha256:1c26da90b8d06227d7769f34915913911222d24ce08c0ab2d60b354e2d9c7aff", - "sha256:1d16089dfa58719c98a1c06f2daceba6d8e3fb9b5d7931af4a990a3c486241cb", - "sha256:1dd46f309e953927dd018567d6a9e2fb84783963650171f6c5fe7e5c41fd5666", - "sha256:2575efaa5d949c9f4e2cdbe7d805d02122c16065bfb8d95c129372d65a291a0b", - "sha256:3208f9aea18991ac7f2b39721e947bbd752a1abbe79ad90d9b6a84a74d44409b", - "sha256:329c719d31362355a96b435f4653e3b4b061fcc9eba9f91dd40804ca637d914e", - "sha256:3384d278df99ec2c6acf701d067147320b864ef6727405d6470838476e44d9e8", - "sha256:34a01a4490e170376cd79258b7f755fa13b1a6c3667e872c8e35051ae857a92b", - "sha256:354f3a91718489912f2e0fc331c24eaaf6a4565c080e00fbedb6015857c00582", - "sha256:37f46bb11858717e0efa7893c0f7055c43b44c103e40e69442db5061cb26ed34", - "sha256:3b4cf5a9497874822341c2ebe0d5850fed392034caadc0bad134ab6822c0925b", - "sha256:3f148c3f47f7f29a79c38cc5d020edcb5ca780020fab94dbc21f9af95c463581", - "sha256:443cec402ddd650bb2b885113e1dcedb22b1175c6be223b14246a714b61cd521", - "sha256:462b0c18fbb48fdbf980914a02ee38c423a25fcc4cf40f66bacc95a2d2d73bc8", - "sha256:474bc83233abdcf2124ed3f66230a1c8435896046caa4b0b5ab6013c640803cc", - "sha256:4d438e4c020d8c39961deaf58f6913b1bf8832d9b6f62ec35bd93e97807e9cbc", - "sha256:4fdc9afadbeb393b4bbbad75481e0ea78e4469f2e1d713a90811700830b553a9", - "sha256:5039e3cef7b3e7a060de468a4a60a60a1f31786da94c6cb054e7a3c75906111c", - "sha256:5095a7c838a8647c32aa37c3a460d2c48debff7fc26e1136aee60100a8cd8f68", - "sha256:52e466bea6f8f3a44b1234570244b1cff45150f59a4acae3fcc5fd700c2993ca", - "sha256:535d4b52524a961d220875688159277f0e9eeeda0ac45e766092bfb54437543f", - "sha256:57dbc9167d48e355e2569346b5aa4077f29bf86389c924df25c0a8b9124461fb", - "sha256:5a4b07cdf3f84310c08c1de2c12ddadbb7a77568bcb16e95489f9c81074322ed", - "sha256:5c872814b77a4e84afa293a1bee08c14daed1068b2bb1cc312edbf020bbbca2b", - "sha256:5f83689a38e76969327e9b682be5521d87a0c9e5a2e187d2bc6be4765f0d4600", - "sha256:688aa6b8aa724db1596514751ffb767766e02e5c4a87486ab36b8e1ebc1aedac", - "sha256:6b130bd4163c93798a6b9bb96be64a7c43e1cec81126ffa7ffaa106e1fc5cef5", - "sha256:6b31f059878eb1f5da8b2fd82480cc18bed8dcd7fb8fe68370e2e6285fa86da6", - "sha256:6d45080095e585f8c5097897313def60caa2046da202cdb17a01f147fb263b81", - "sha256:6f2f78ef14077e08856e788fa482107aa602636c16c25bdf59c22ea525a785e9", - "sha256:6fe87efd7f47266dfc42fe76dae89060038f1d9cb911f89ae7e5084148d1cc08", - "sha256:75969cf900d7be665ccb1622a9aba225cf386bbc9c3bcfeeab9f62b5048f4a07", - "sha256:75a6076289b2df6c8ecb9d13ff79ae0cad1d5fb40af377a5021016d58cd691ec", - "sha256:78d57546bad81e0da13263e4c9ce30e96dcbe720dbff5ada08d2600a3502e526", - "sha256:79e205c70afddd41f6ee79a8656aec738492a550247a7af697d5bd1aee14f766", - "sha256:7c98298a15d6b90c8f6e3caa6457f4f022423caa5fa1a1ca7a5e9e512bdb77a4", - "sha256:7ec72df7354e6b7f6eb2a17fa6901350018c3a9ad78e48d7b2b54d0412539a67", - "sha256:81ea573aa46d3b6b3d890cd3c0ad82105985e6058a4baed03cf92518081eec8c", - "sha256:8344127403dea42f5970adccf6c5957a71a47f522171fafaf4c6ddb41b61703a", - "sha256:8445f23f13339da640d1be8e44e5baf4af97e396882ebbf1692aecd67f67c479", - "sha256:850720e1b383df199b8433a20e02b25b72f0fded28bc03c5bd79e2ce7ef050be", - "sha256:88cb4bac7185a9f0168d38c01d7a00addece9822a52870eee26b8d5b61409213", - "sha256:8a790d235b9d39c70a466200d506bb33a98e2ee374a9b4eec7a8ac64c2c261fa", - "sha256:8b1a94b8afc154fbe36978a511a1f155f9bd97664e4f1f7a374d72e180ceb0ae", - "sha256:8d6ad132b1bc13d05ffe5b85e7a01a3998bf3a6302ba594b28d61b8c2cf13aaf", - "sha256:8eb488ef928cdbc05a27245e52de73c0d7c72a34240ef4d9893fdf65a8c1a955", - "sha256:90bf55d9d139e5d127193170f38c584ed3c79e16638890d2e36f23aa1630b952", - "sha256:9133d75dc119a61d1a0ded38fb9ba40a00ef41697cc07adb6ae098c875195a3f", - "sha256:93a91c2640645303e874eada51f4f33351b84b351a689d470f8108d0e0694210", - "sha256:959179efb3e4a27610e8d54d667c02a9feaa86bbabaf63efa7faa4dfa780d4f1", - "sha256:9625367c8955e4319049113ea4f8fee0c6c1145192d57946c6ffcd8fe8bf48dd", - "sha256:9da6f400eeb8c36f72ef6646ea530d6d175a4f77ff2ed8dfd6352842274c1d8b", - "sha256:9e65489222b410f79711dc3d2d5003d2757e30874096b2008d50329ea4d0f88c", - "sha256:a3e2fd14c5d49ee1da322672375963f19f32b3d5953f0615b175ff7b9d38daed", - "sha256:a5a7c1062ef8aea3eda149f08120f10795835fc1c8bc6ad948fb9652a113ca55", - "sha256:a5da93debdfe27b2bfc69eefb592e1831d957b9535e0943a0ee8b97996de21b5", - "sha256:a6e605bb9edcf010f54f8b6a590dd23a4b40a8cb141255eec2a03db249bc915b", - "sha256:a707b158b4410aefb6b054715545bbb21aaa5d5d0080217290131c49c2124a6e", - "sha256:a8b6683a37338818646af718c9ca2a07f89787551057fae57c4ec0446dc6224b", - "sha256:aa5476c3e3a402c37779e95f7b4048db2cb5b0ed0b9d006983965e93f40fe05a", - "sha256:ab1932ca6cb8c7499a4d87cb21ccc0d3326f172cfb6a64021a889b591bb3045c", - "sha256:ae8b6068ee374fdfab63689be0963333aa83b0815ead5d8648389a8ded593378", - "sha256:b0906357f90784a66e89ae3eadc2654f36c580a7d65cf63e6a616e4aec3a81be", - "sha256:b0da31853ab6e58a11db3205729133ce0df26e6804e93079dee095be3d681dc1", - "sha256:b1c30841f5040de47a0046c243fc1b44ddc87d1b12435a43b8edff7e7cb1e0d0", - "sha256:b228e693a2559888790936e20f5f88b6e9f8162c681830eda303bad7517b4d5a", - "sha256:b7cc6cb44f8636fbf4a934ca72f3e786ba3c9f9ba4f4d74611e7da80684e48d2", - "sha256:ba0ed0dc6763d8bd6e5de5cf0d746d28e706a10b615ea382ac0ab17bb7388633", - "sha256:bc9128e74fe94650367fe23f37074f121b9f796cabbd2f928f13e9661837296d", - "sha256:bcf426a8c38eb57f7bf28932e68425ba86def6e756a5b8cb4731d8e62e4e0223", - "sha256:bec35eb20792ea64c3c57891bc3ca0bedb2884fbac2c8249d9b731447ecde4fa", - "sha256:c3444fe52b82f122d8a99bf66777aed6b858d392b12f4c317da19f8234db4533", - "sha256:c5c9581019c96f865483d031691a5ff1cc455feb4d84fc6920a5ffc48a794d8a", - "sha256:c6feacd1d178c30e5bc37184526e56740342fd2aa6371a28367bad7908d454fc", - "sha256:c8f77e661ffd96ff104bebf7d0f3255b02aa5d5b28326f5408d6284c4a8b3248", - "sha256:cb0f6eb3a320f24b94d177e62f4074ff438f2ad9d27e75a46221904ef21a7b05", - "sha256:ce84a7efa5af9f54c0aa7692c45861c1667080814286cacb9958c07fc50294fb", - "sha256:cf902878b4af334a09de7a45badbff0389e7cf8dc2e4dcf5f07125d0b7c2656d", - "sha256:dab8d921b55a28287733263c0e4c7db11b3ee22aee158a4de09f13c93283c62d", - "sha256:dc9ac4659456bde7c567107556ab065801622396b435a3ff213daef27b495388", - "sha256:dd36b712d35e757e28bf2f40a71e8f8a2d43c8b026d881aa0c617b450d6865c9", - "sha256:e19509145275d46bc4d1e16af0b57a12d227c8253655a46bbd5ec317e941279d", - "sha256:e21cc693045fda7f745c790cb687958161ce172ffe3c5719ca1764e752237d16", - "sha256:e54548e0be3ac117595408fd4ca0ac9278fde89829b0b518be92863b17ff67a2", - "sha256:e5b9fc03bf76a94065299d4a2ecd8dfbae4ae8e2e8098bbfa6ab6413ca267709", - "sha256:e8481b946792415adc07410420d6fc65a352b45d347b78fec45d8f8f0d7496f0", - "sha256:ebcbf356bf5c51afc3290e491d3722b26aaf5b6af3c1c7f6a1b757828a46e336", - "sha256:ef9101f3f7b59043a34f1dccbb385ca760467590951952d6701df0da9893ca0c", - "sha256:f2afd2164a1e85226fcb6a1da77a5c8896c18bfe08e82e8ceced5181c42d2179", - "sha256:f629ecc2db6a4736b5ba95a8347b0089240d69ad14ac364f557d52ad68cf94b0", - "sha256:f68eea5df6347d3f1378ce992d86b2af16ad7ff4dcb4a19ccdc23dea901b87fb", - "sha256:f757f359f30ec7dcebca662a6bd46d1098f8b9fb1fcd661a9e13f2e8ce343ba1", - "sha256:fb37bd599f031f1a6fb9e58ec62864ccf3ad549cf14bac527dbfa97123edcca4" + "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", + "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", + "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5", + "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", + "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", + "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", + "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29", + "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", + "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b", + "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", + "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", + "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", + "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", + "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a", + "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", + "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", + "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03", + "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", + "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22", + "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e", + "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", + "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", + "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752", + "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", + "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253", + "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", + "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", + "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5", + "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", + "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7", + "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", + "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", + "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", + "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", + "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec", + "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", + "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921", + "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", + "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074", + "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580", + "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", + "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", + "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", + "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", + "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", + "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", + "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", + "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", + "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789", + "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", + "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", + "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c", + "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232", + "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", + "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c", + "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", + "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", + "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", + "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751", + "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", + "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda", + "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", + "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", + "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", + "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8", + "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", + "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", + "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1", + "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2", + "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", + "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", + "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965", + "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", + "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", + "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b", + "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", + "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", + "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", + "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de", + "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", + "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", + "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", + "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", + "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", + "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1", + "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", + "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", + "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", + "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364", + "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", + "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", + "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420", + "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5", + "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24", + "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c", + "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", + "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f", + "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e", + "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab", + "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08", + "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", + "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", + "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8" ], "markers": "python_version >= '3.8'", - "version": "==0.19.0" + "version": "==0.20.0" }, "s3transfer": { "hashes": [ @@ -1438,12 +1487,12 @@ }, "setuptools": { "hashes": [ - "sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d", - "sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207" + "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308", + "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==71.0.3" + "version": "==74.1.2" }, "shellingham": { "hashes": [ @@ -1463,20 +1512,20 @@ }, "sympy": { "hashes": [ - "sha256:3b6af8f4d008b9a1a6a4268b335b984b23835f26d1d60b0526ebc71d48a25f57", - "sha256:6b0b32a4673fb91bd3cac3b55406c8e01d53ae22780be467301cc452f6680c92" + "sha256:401449d84d07be9d0c7a46a64bd54fe097667d5e7181bfe67ec777be9e01cb13", + "sha256:c51d75517712f1aed280d4ce58506a4a88d635d6b5dd48b39102a7ae1f3fcfe9" ], "markers": "python_version >= '3.8'", - "version": "==1.13.0" + "version": "==1.13.2" }, "syrupy": { "hashes": [ - "sha256:203e52f9cb9fa749cf683f29bd68f02c16c3bc7e7e5fe8f2fc59bdfe488ce133", - "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36" + "sha256:be002267a512a4bedddfae2e026c93df1ea928ae10baadc09640516923376d41", + "sha256:f9d4485f3f27d0e5df6ed299cac6fa32eb40a441915d988e82be5a4bdda335c8" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.8.1'", - "version": "==4.6.1" + "markers": "python_full_version >= '3.8.1'", + "version": "==4.7.1" }, "toml": { "hashes": [ @@ -1488,11 +1537,11 @@ }, "tomlkit": { "hashes": [ - "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72", - "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264" + "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", + "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79" ], "markers": "python_version >= '3.8'", - "version": "==0.13.0" + "version": "==0.13.2" }, "typeguard": { "hashes": [ @@ -1504,19 +1553,19 @@ }, "typer": { "hashes": [ - "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914", - "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482" + "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", + "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722" ], "markers": "python_version >= '3.7'", - "version": "==0.12.3" + "version": "==0.12.5" }, "types-awscrt": { "hashes": [ - "sha256:0839fe12f0f914d8f7d63ed777c728cb4eccc2d5d79a26e377d12b0604e7bf0e", - "sha256:84a9f4f422ec525c314fdf54c23a1e73edfbcec968560943ca2d41cfae623b38" + "sha256:117ff2b1bb657f09d01b7e0ce3fe3fa6e039be12d30b826896182725c9ce85b1", + "sha256:9f7f47de68799cb2bcb9e486f48d77b9f58962b92fba43cb8860da70b3c57d1b" ], - "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.21.2" + "markers": "python_version >= '3.8'", + "version": "==0.21.5" }, "types-boto3": { "hashes": [ @@ -1528,29 +1577,29 @@ }, "types-pyyaml": { "hashes": [ - "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342", - "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6" + "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af", + "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==6.0.12.20240311" + "version": "==6.0.12.20240808" }, "types-s3transfer": { "hashes": [ - "sha256:02154cce46528287ad76ad1a0153840e0492239a0887e8833466eccf84b98da0", - "sha256:49a7c81fa609ac1532f8de3756e64b58afcecad8767933310228002ec7adff74" + "sha256:60167a3bfb5c536ec6cdb5818f7f9a28edca9dc3e0b5ff85ae374526fc5e576e", + "sha256:7a3fec8cd632e2b5efb665a355ef93c2a87fdd5a45b74a949f95a9e628a86356" ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==0.10.1" + "markers": "python_version >= '3.8'", + "version": "==0.10.2" }, "types-setuptools": { "hashes": [ - "sha256:842cbf399812d2b65042c9d6ff35113bbf282dee38794779aa1f94e597bafc35", - "sha256:bd0db2a4b9f2c49ac5564be4e0fb3125c4c46b1f73eafdcbceffa5b005cceca4" + "sha256:0abdb082552ca966c1e5fc244e4853adc62971f6cd724fb1d8a3713b580e5a65", + "sha256:15b38c8e63ca34f42f6063ff4b1dd662ea20086166d5ad6a102e670a52574120" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==70.3.0.20240710" + "version": "==74.1.0.20240907" }, "typing-extensions": { "hashes": [ @@ -1565,33 +1614,34 @@ "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" ], - "markers": "python_version >= '3.10'", + "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==2.2.2" }, "virtualenv": { "hashes": [ - "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a", - "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589" + "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55", + "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c" ], "markers": "python_version >= '3.7'", - "version": "==20.26.3" + "version": "==20.26.4" }, "werkzeug": { "hashes": [ - "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", - "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8" + "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", + "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" ], "markers": "python_version >= '3.8'", - "version": "==3.0.3" + "version": "==3.0.4" }, "wheel": { "hashes": [ - "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85", - "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81" + "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f", + "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==0.43.0" + "version": "==0.44.0" }, "wrapt": { "hashes": [ @@ -1679,12 +1729,12 @@ }, "zipp": { "hashes": [ - "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19", - "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c" + "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064", + "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==3.19.2" + "version": "==3.20.1" } } } diff --git a/source/modules/backstage/cdk/deployment/build-s3-dist.sh b/source/modules/backstage/cdk/deployment/build-s3-dist.sh index a86758f2..2bcfbabd 100755 --- a/source/modules/backstage/cdk/deployment/build-s3-dist.sh +++ b/source/modules/backstage/cdk/deployment/build-s3-dist.sh @@ -8,6 +8,7 @@ deployment_dir="$root_dir/deployment" staging_dist_dir="$deployment_dir/staging" template_dist_dir="$deployment_dir/global-s3-assets" build_dist_dir="$deployment_dir/regional-s3-assets" +lambda_zip_output_path="$root_dir/dist/lambda" printf "%b[VirtualEnv] Activating venv found in %s\n%b" "${GREEN}" "${root_dir}" "${NC}" source "$root_dir/.venv/bin/activate" @@ -16,6 +17,7 @@ printf "%b[Init] Remove old dist files from previous runs\n%b" "${GREEN}" "${NC} rm -rf "$template_dist_dir" rm -rf "$build_dist_dir" rm -rf "$staging_dist_dir" +rm -rf "$lambda_zip_output_path" mkdir -p "$template_dist_dir" mkdir -p "$build_dist_dir" diff --git a/source/modules/backstage/cdk/source/conftest.py b/source/modules/backstage/cdk/source/conftest.py new file mode 100644 index 00000000..53e64c67 --- /dev/null +++ b/source/modules/backstage/cdk/source/conftest.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +# pylint: disable=unused-import + +# Standard Library +from unittest.mock import patch + +# Third Party Libraries +import pytest + +# Connected Mobility Solution on AWS +from .tests.infrastructure.fixtures.fixture_stack_templates import ( + fixture_acdp_backstage_stack_template, + fixture_snapshot_json_with_matcher, +) diff --git a/source/modules/backstage/cdk/source/infrastructure/acdp_backstage_stack.py b/source/modules/backstage/cdk/source/infrastructure/acdp_backstage_stack.py index 448a0996..e8f9c561 100644 --- a/source/modules/backstage/cdk/source/infrastructure/acdp_backstage_stack.py +++ b/source/modules/backstage/cdk/source/infrastructure/acdp_backstage_stack.py @@ -7,12 +7,13 @@ from typing import Any, List # AWS Libraries -from aws_cdk import CfnMapping, Duration, Stack, Tags, aws_rds, aws_ssm +from aws_cdk import Aws, CfnMapping, Duration, Stack, Tags, aws_rds, aws_ssm from constructs import Construct # CMS Common Library from cms_common.config.resource_names import ResourceName from cms_common.config.stack_inputs import S3AssetConfigInputs, SolutionConfigInputs +from cms_common.constructs.app_registry import AppRegistryConstruct, AppRegistryInputs from cms_common.constructs.app_unique_id import AppUniqueId from cms_common.constructs.cdk_lambda_vpc_config_construct import ( CDKLambdasVpcConfigConstruct, @@ -22,6 +23,7 @@ # Connected Mobility Solution on AWS from .constructs.aurora_database import AuroraDatabaseConstruct from .constructs.backstage_container import BackstageContainerConstruct +from .constructs.cognito_user import CognitoUserConstruct from .constructs.load_balancer import LoadBalancerConstruct from .constructs.module_integration import ModuleInputsConstruct @@ -50,6 +52,18 @@ def __init__( }, ) + AppRegistryConstruct( + self, + "backstage-app-registry", + app_registry_inputs=AppRegistryInputs( + application_name=Aws.STACK_NAME, + application_type=solution_config_inputs.application_type, + solution_id=solution_config_inputs.solution_id, + solution_name=solution_config_inputs.solution_name, + solution_version=solution_config_inputs.solution_version, + ), + ) + module_inputs = ModuleInputsConstruct( self, "module-inputs-construct", @@ -106,6 +120,12 @@ def __init__( # pylint: disable=too-many-locals subnets=module_inputs.vpc_config.private_subnets, ) + CognitoUserConstruct( + self, + "cognito-user-construct", + module_inputs=module_inputs, + ) + aurora_database_construct = AuroraDatabaseConstruct( self, "aurora-database-construct", diff --git a/source/modules/backstage/cdk/source/infrastructure/constructs/backstage_container.py b/source/modules/backstage/cdk/source/infrastructure/constructs/backstage_container.py index 8488c257..b18ea606 100644 --- a/source/modules/backstage/cdk/source/infrastructure/constructs/backstage_container.py +++ b/source/modules/backstage/cdk/source/infrastructure/constructs/backstage_container.py @@ -204,6 +204,31 @@ def __init__( ), ] ), + "servicecatalog-policy": aws_iam.PolicyDocument( + statements=[ + aws_iam.PolicyStatement( + effect=aws_iam.Effect.ALLOW, + actions=["servicecatalog:GetApplication"], + resources=[ + Stack.of(self).format_arn( + service="servicecatalog", + resource="", + resource_name="applications/*", + arn_format=ArnFormat.SLASH_RESOURCE_NAME, + ), + ], + ), + ] + ), + "costexplorer-policy": aws_iam.PolicyDocument( + statements=[ + aws_iam.PolicyStatement( + effect=aws_iam.Effect.ALLOW, + actions=["ce:GetCostAndUsage"], + resources=["*"], + ), + ] + ), }, ) diff --git a/source/modules/backstage/cdk/source/infrastructure/constructs/cognito_user.py b/source/modules/backstage/cdk/source/infrastructure/constructs/cognito_user.py new file mode 100644 index 00000000..13226e9e --- /dev/null +++ b/source/modules/backstage/cdk/source/infrastructure/constructs/cognito_user.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Standard Library + +# AWS Libraries +from aws_cdk import CfnCondition, Fn, aws_cognito +from constructs import Construct + +# Connected Mobility Solution on AWS +from ..constructs.module_integration import ModuleInputsConstruct + + +class CognitoUserConstruct(Construct): + def __init__( + self, + scope: Construct, + construct_id: str, + module_inputs: ModuleInputsConstruct, + ) -> None: + super().__init__(scope, construct_id) + + cognito_default_user_pool_user = aws_cognito.CfnUserPoolUser( + self, + "user-pool-user", + user_pool_id=module_inputs.user_pool_id, + username=Fn.select(0, Fn.split("@", module_inputs.default_user_email)), + desired_delivery_mediums=["EMAIL"], + force_alias_creation=True, + user_attributes=[ + {"name": "email", "value": module_inputs.default_user_email}, + {"name": "email_verified", "value": "True"}, + ], + ) + + cognito_default_user_pool_user.cfn_options.condition = CfnCondition( + self, + "should-create-cognito-default-user-condition", + expression=Fn.condition_not( + Fn.condition_equals(lhs=module_inputs.default_user_email, rhs="") + ), + ) diff --git a/source/modules/backstage/cdk/source/infrastructure/constructs/module_integration.py b/source/modules/backstage/cdk/source/infrastructure/constructs/module_integration.py index 607ee80c..7a8ea6b6 100644 --- a/source/modules/backstage/cdk/source/infrastructure/constructs/module_integration.py +++ b/source/modules/backstage/cdk/source/infrastructure/constructs/module_integration.py @@ -15,7 +15,7 @@ from cms_common.config.stack_inputs import SolutionConfigInputs from cms_common.constructs.identity_provider_config import IdentityProviderConfig from cms_common.constructs.vpc_construct import create_vpc_config -from cms_common.resource_names.auth import AuthResourceNames +from cms_common.resource_names.auth import AuthSetupResourceNames @dataclass(frozen=True) @@ -97,6 +97,16 @@ def __init__( # pylint: disable=too-many-locals type="String", ).value_as_string + self.default_user_email = CfnParameter( + Stack.of(self), + "DefaultUserEmail", + type="String", + description="The user to create default user for cognito", + allowed_pattern=RegexPattern.OPTIONAL_EMAIL, + constraint_description="User E-Mail must be a valid E-Mail address", + default="", + ).value_as_string + self.acdp_config_ssm_prefix_with_slash_prefix = ResourcePrefix.slash_separated( app_unique_id=self.acdp_uid, module_name="config", leading_slash=True ) @@ -144,24 +154,30 @@ def __init__( # pylint: disable=too-many-locals Stack.of(self) ) - auth_resource_names = AuthResourceNames.from_identity_provider_id( - identity_provider_id + self.auth_setup_resource_names = ( + AuthSetupResourceNames.from_identity_provider_id(identity_provider_id) ) - idp_config_secret_arn = ( - aws_ssm.StringParameter.from_string_parameter_attributes( - self, - "ssm-idp-config-secret-arn-parameter", - parameter_name=auth_resource_names.idp_config_secret_arn_ssm_parameter, - simple_name=False, - force_dynamic_reference=True, - ) + self.user_pool_id = aws_ssm.StringParameter.from_string_parameter_attributes( + self, + "deployment-uuid", + parameter_name=self.auth_setup_resource_names.user_pool_id, + simple_name=False, + force_dynamic_reference=True, + ).string_value + + idp_config_secret_arn = aws_ssm.StringParameter.from_string_parameter_attributes( + self, + "ssm-idp-config-secret-arn-parameter", + parameter_name=self.auth_setup_resource_names.idp_config_secret_arn_ssm_parameter, + simple_name=False, + force_dynamic_reference=True, ) user_client_config_secret_arn = aws_ssm.StringParameter.from_string_parameter_attributes( self, "ssm-user-client-config-secret-arn-parameter", - parameter_name=auth_resource_names.user_client_config_secret_arn_ssm_parameter, + parameter_name=self.auth_setup_resource_names.user_client_config_secret_arn_ssm_parameter, simple_name=False, force_dynamic_reference=True, ) @@ -528,12 +544,12 @@ def __init__( # pylint: disable=too-many-locals acdp_build_config_path_root_parameter = aws_ssm.StringParameter( self, - "ssm-acdp-build-ssm-prefix", + "ssm-acdp-build-config-ssm-prefix", string_value=self.acdp_build_ssm_prefix_with_slash_prefix, description="Description for acdp build config path root parameter", parameter_name=ResourceName.slash_separated( prefix=self.acdp_build_ssm_prefix_with_slash_prefix, - name="build-parameters", + name="build-config-ssm-prefix", ), simple_name=False, ) diff --git a/source/modules/backstage/cdk/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_backstage_snapshot.json b/source/modules/backstage/cdk/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_backstage_snapshot.json index 277e006a..695774d4 100644 --- a/source/modules/backstage/cdk/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_backstage_snapshot.json +++ b/source/modules/backstage/cdk/source/tests/infrastructure/__snapshots__/test_snapshot/test_acdp_backstage_snapshot.json @@ -1,5 +1,17 @@ { "Conditions": { + "acdpbackstagecognitouserconstructshouldcreatecognitodefaultusercondition5E568285": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "DefaultUserEmail" + }, + "" + ] + } + ] + }, "shouldcreateroute53recordscondition": { "Fn::Not": [ { @@ -109,6 +121,13 @@ "Description": "Optional when using a Public Hosted Zone with Route53. Required when HostedZoneId is Private or not provided. Provide a custom ACM Certificate ARN to use for TLS.", "Type": "String" }, + "DefaultUserEmail": { + "AllowedPattern": "(^$)|^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$", + "ConstraintDescription": "User E-Mail must be a valid E-Mail address", + "Default": "", + "Description": "The user to create default user for cognito", + "Type": "String" + }, "FullyQualifiedDomainName": { "AllowedPattern": "^([A-Za-z0-9][A-Za-z0-9-]*\\.)+[A-Za-z]+$", "ConstraintDescription": "Fully Qualified Domain Name must be a valid domain name", @@ -186,6 +205,15 @@ }, "StorageEncrypted": true, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -289,6 +317,12 @@ "/config/deployment-uuid}}" ] ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] } } }, @@ -328,6 +362,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -383,6 +426,15 @@ ] }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -480,6 +532,15 @@ "Properties": { "GroupDescription": "backstage/acdp-backstage/aurora-database-construct/database-security-group", "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -626,6 +687,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -678,6 +748,15 @@ ] }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -712,6 +791,15 @@ }, "RetentionInDays": 90, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -776,6 +864,15 @@ "Version": "2012-10-17" }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -808,6 +905,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -841,6 +947,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -1016,6 +1131,15 @@ ] }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -1052,7 +1176,7 @@ }, { "Name": "SOLUTION_VERSION", - "Value": "v1.2.2" + "Value": "v2.0.0" }, { "Name": "WEB_HOSTNAME", @@ -1621,7 +1745,7 @@ }, ":ssm:us-west-2:test-account-id:parameter", { - "Ref": "moduleinputsconstructssmacdpbuildssmprefix22C8087D" + "Ref": "moduleinputsconstructssmacdpbuildconfigssmprefixF424F2F4" } ] ] @@ -1685,6 +1809,15 @@ "FARGATE" ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -1740,6 +1873,12 @@ "/config/deployment-uuid}}" ] ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -1777,6 +1916,12 @@ "/config/deployment-uuid}}" ] ] + }, + "awsApplication": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] } }, "Type": "String", @@ -2040,6 +2185,43 @@ "Version": "2012-10-17" }, "PolicyName": "codebuild-policy" + }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": "servicecatalog:GetApplication", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":servicecatalog:us-west-2:test-account-id:/applications/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "servicecatalog-policy" + }, + { + "PolicyDocument": { + "Statement": [ + { + "Action": "ce:GetCostAndUsage", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "costexplorer-policy" } ], "RoleName": { @@ -2054,6 +2236,15 @@ ] }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -2648,7 +2839,7 @@ }, ":ssm:us-west-2:test-account-id:parameter", { - "Ref": "moduleinputsconstructssmacdpbuildssmprefix22C8087D" + "Ref": "moduleinputsconstructssmacdpbuildconfigssmprefixF424F2F4" } ] ] @@ -2742,6 +2933,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -2773,6 +2973,56 @@ }, "Type": "AWS::EC2::SecurityGroup" }, + "acdpbackstagecognitouserconstructuserpooluser22A8E947": { + "Condition": "acdpbackstagecognitouserconstructshouldcreatecognitodefaultusercondition5E568285", + "DependsOn": [ + "ssmappuniqueidregistermodule9C5C2C5D" + ], + "Properties": { + "DesiredDeliveryMediums": [ + "EMAIL" + ], + "ForceAliasCreation": true, + "UserAttributes": [ + { + "Name": "email", + "Value": { + "Ref": "DefaultUserEmail" + } + }, + { + "Name": "email_verified", + "Value": "True" + } + ], + "UserPoolId": { + "Fn::Join": [ + "", + [ + "{{resolve:ssm:/solution/auth/", + { + "Ref": "IdentityProviderId" + }, + "/user-pool/id}}" + ] + ] + }, + "Username": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "@", + { + "Ref": "DefaultUserEmail" + } + ] + } + ] + } + }, + "Type": "AWS::Cognito::UserPoolUser" + }, "acdpbackstageloadbalancerconstructalbaccesslogsbucket46F5BBD7": { "DeletionPolicy": "Retain", "DependsOn": [ @@ -2795,6 +3045,15 @@ "RestrictPublicBuckets": true }, "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3016,6 +3275,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3081,6 +3349,15 @@ "Port": 443, "Protocol": "HTTP", "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3155,6 +3432,15 @@ } ], "Tags": [ + { + "Key": "awsApplication", + "Value": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, { "Key": "Solutions:DeploymentUUID", "Value": { @@ -3251,7 +3537,71 @@ }, "Type": "AWS::Route53::RecordSet" }, - "moduleinputsconstructssmacdpbuildssmprefix22C8087D": { + "backstageappregistryappregistryapplication76DC0D11": { + "Properties": { + "Name": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName" + }, + "-us-west-2-test-account-id" + ] + ] + } + }, + "Type": "AWS::ServiceCatalogAppRegistry::Application" + }, + "backstageappregistryappregistryapplicationattributeassociation8E3E14B0": { + "Properties": { + "Application": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "Id" + ] + }, + "AttributeGroup": { + "Fn::GetAtt": [ + "backstageappregistrydefaultapplicationattributes08B65766", + "Id" + ] + } + }, + "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation" + }, + "backstageappregistrydefaultapplicationattributes08B65766": { + "Properties": { + "Attributes": { + "ApplicationType": "test-application-type", + "SolutionID": "test-solution-id", + "SolutionName": "test-solution-name", + "Version": "test-solution-version" + }, + "Description": "Attribute group for solution information", + "Name": { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackName" + }, + "-us-west-2-test-account-id" + ] + ] + }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + } + }, + "Type": "AWS::ServiceCatalogAppRegistry::AttributeGroup" + }, + "moduleinputsconstructssmacdpbuildconfigssmprefixF424F2F4": { "Properties": { "Description": "Description for acdp build config path root parameter", "Name": { @@ -3262,10 +3612,18 @@ { "Ref": "AcdpUniqueId" }, - "/acdp-build/build-parameters" + "/acdp-build/build-config-ssm-prefix" ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Fn::Join": [ @@ -3297,6 +3655,14 @@ ] ] }, + "Tags": { + "awsApplication": { + "Fn::GetAtt": [ + "backstageappregistryappregistryapplication76DC0D11", + "ApplicationTagValue" + ] + } + }, "Type": "String", "Value": { "Fn::Join": [ diff --git a/source/modules/backstage/deployment/run-backstage-lint.sh b/source/modules/backstage/deployment/run-backstage-lint.sh index 9d3b6d69..b13c5e1d 100755 --- a/source/modules/backstage/deployment/run-backstage-lint.sh +++ b/source/modules/backstage/deployment/run-backstage-lint.sh @@ -5,4 +5,4 @@ set -e && [[ "$DEBUG" == 'true' ]] && set -x # CD into one level above the deployment dir where this script is located cd "$(dirname "$0")"/.. -yarn tsc:full +yarn lint:all diff --git a/source/modules/backstage/deployment/run-backstage-tsc.sh b/source/modules/backstage/deployment/run-backstage-tsc.sh new file mode 100755 index 00000000..9d3b6d69 --- /dev/null +++ b/source/modules/backstage/deployment/run-backstage-tsc.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e && [[ "$DEBUG" == 'true' ]] && set -x + +# CD into one level above the deployment dir where this script is located +cd "$(dirname "$0")"/.. + +yarn tsc:full diff --git a/source/modules/backstage/deployment/run-cfn-nag.sh b/source/modules/backstage/deployment/run-cfn-nag.sh index b7702e49..3745f6e6 100755 --- a/source/modules/backstage/deployment/run-cfn-nag.sh +++ b/source/modules/backstage/deployment/run-cfn-nag.sh @@ -15,7 +15,6 @@ do esac done -# CD into one level above the deployment dir where this script is located cd "$(dirname "$0")"/.. # Get reference for all important folders diff --git a/source/modules/backstage/deployment/run-unit-tests.sh b/source/modules/backstage/deployment/run-unit-tests.sh index 5ed3f71d..56822336 100755 --- a/source/modules/backstage/deployment/run-unit-tests.sh +++ b/source/modules/backstage/deployment/run-unit-tests.sh @@ -23,7 +23,8 @@ done # Get reference for all important folders and files project_dir="$(dirname "$(dirname "$(realpath "$0")")")" -source_dir="$project_dir/cdk/source" +cdk_dir="$project_dir/cdk" +source_dir="$cdk_dir/source" root_dir="$(dirname "$(dirname "$(dirname "$project_dir")")")" module_name="$(basename "$project_dir")" @@ -31,6 +32,9 @@ python_coverage_report="$root_dir/coverage-reports/$module_name-coverage.xml" rm -f "$project_dir/.coverage" +# cd into the cdk directory as the cdk template is looking for a lambda located in dist/lambda folder which is generated inside cdk lambda +cd "$cdk_dir" + # Run test on package and save results to coverage_report_path in xml format pytest "$source_dir" \ --cov="$source_dir" \ @@ -39,6 +43,9 @@ pytest "$source_dir" \ ${generate_report:+--cov-report=xml:$python_coverage_report} \ ${snapshot_update:+--snapshot-update} +# cd back out to backstage directory +cd "$project_dir" + # <=====UNIQUE TO BACKSTAGE=====> # Run all ts tests for Backstage yarn --cwd "$project_dir" test:all diff --git a/source/modules/backstage/package.json b/source/modules/backstage/package.json index c3ffdc4f..5b581f30 100644 --- a/source/modules/backstage/package.json +++ b/source/modules/backstage/package.json @@ -1,12 +1,15 @@ { "name": "acdp-backstage", - "version": "1.2.2", + "version": "2.0.0", "private": true, "license": "Apache-2.0", "description": "Backstage implementation preconfigured to work with CMS", "engines": { "node": "18 || 20" }, + "backstage": { + "role": "cli" + }, "scripts": { "dev": "concurrently \"yarn start\" \"yarn start-backend\"", "start": "yarn workspace app start", @@ -31,7 +34,7 @@ ] }, "devDependencies": { - "@backstage/cli": "^0.26.10", + "@backstage/cli": "^0.26.11", "@types/supertest": "^2.0.14", "concurrently": "^8.0.1", "lerna": "^7.1.5", @@ -44,22 +47,9 @@ "resolutions": { "@types/react": "^18", "@types/react-dom": "^18", - "mysql2": "^3.9.8", "formidable": "^3.2.4", - "braces": "^3.0.3", "ws": "^8.17.1", - "inline-style-prefixer": "^7.0.1", - "fast-xml-parser": "^4.4.1", "axios": "^1.7.4", - "rollup": "^4.22.4" - }, - "lint-staged": { - "*.{js,jsx,ts,tsx,mjs,cjs}": [ - "eslint --fix", - "prettier --write" - ], - "*.{json,md}": [ - "prettier --write" - ] + "jsonpath-plus": "^10.0.7" } } diff --git a/source/modules/backstage/packages/app/.eslintrc.js b/source/modules/backstage/packages/app/.eslintrc.js new file mode 100644 index 00000000..db9d19a8 --- /dev/null +++ b/source/modules/backstage/packages/app/.eslintrc.js @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +module.exports = require("@backstage/cli/config/eslint-factory")(__dirname, { + ignorePatterns: ["public/"], +}); diff --git a/source/modules/backstage/packages/app/package.json b/source/modules/backstage/packages/app/package.json index 08e5ce75..fbae949a 100644 --- a/source/modules/backstage/packages/app/package.json +++ b/source/modules/backstage/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "1.2.2", + "version": "2.0.0", "private": true, "bundled": true, "license": "Apache-2.0", @@ -11,41 +11,43 @@ "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", - "clean": "backstage-cli package clean", - "test": "backstage-cli package test --coverage --silent", "lint": "backstage-cli package lint", - "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", - "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", - "cy:dev": "cypress open", - "cy:run": "cypress run --browser chrome" + "test": "backstage-cli package test --coverage", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/app-defaults": "^1.5.7", + "@backstage/app-defaults": "^1.5.9", "@backstage/catalog-model": "^1.5.0", - "@backstage/cli": "^0.26.10", - "@backstage/core-app-api": "^1.13.0", - "@backstage/core-components": "^0.14.8", + "@backstage/cli": "^0.26.11", + "@backstage/core-app-api": "^1.14.1", + "@backstage/core-components": "^0.14.9", "@backstage/core-plugin-api": "^1.9.3", - "@backstage/integration-react": "^1.1.28", - "@backstage/plugin-api-docs": "^0.11.6", - "@backstage/plugin-catalog": "^1.21.0", - "@backstage/plugin-catalog-common": "^1.0.24", - "@backstage/plugin-catalog-graph": "^0.4.6", - "@backstage/plugin-catalog-import": "^0.12.0", - "@backstage/plugin-catalog-react": "^1.12.1", - "@backstage/plugin-home": "^0.7.6", - "@backstage/plugin-org": "^0.6.26", - "@backstage/plugin-permission-react": "^0.4.23", - "@backstage/plugin-scaffolder": "^1.22.0", - "@backstage/plugin-search": "^1.4.12", - "@backstage/plugin-search-react": "^1.7.12", - "@backstage/plugin-techdocs": "^1.10.6", - "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.11", - "@backstage/plugin-techdocs-react": "^1.2.5", - "@backstage/plugin-user-settings": "^0.8.8", + "@backstage/integration-react": "^1.1.29", + "@backstage/plugin-api-docs": "^0.11.7", + "@backstage/plugin-catalog": "^1.21.1", + "@backstage/plugin-catalog-common": "^1.0.25", + "@backstage/plugin-catalog-graph": "^0.4.7", + "@backstage/plugin-catalog-import": "^0.12.1", + "@backstage/plugin-catalog-react": "^1.12.2", + "@backstage/plugin-home": "^0.7.8", + "@backstage/plugin-org": "^0.6.27", + "@backstage/plugin-permission-react": "^0.4.24", + "@backstage/plugin-scaffolder": "^1.23.0", + "@backstage/plugin-search": "^1.4.14", + "@backstage/plugin-search-react": "^1.7.13", + "@backstage/plugin-techdocs": "^1.10.7", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.12", + "@backstage/plugin-techdocs-react": "^1.2.6", + "@backstage/plugin-user-settings": "^0.8.10", "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.4", + "@material-ui/icons": "^4.11.3", "@react-hookz/web": "^23.1.0", "backstage-plugin-acdp": "*", + "backstage-plugin-acdp-common": "*", + "backstage-plugin-acdp-partner-offering": "*", "react": "^18.0.2", "react-dom": "^18.0.2", "react-router": "^6.3.0", @@ -53,7 +55,7 @@ "sanitize-html": "2.10.0" }, "devDependencies": { - "@backstage/test-utils": "^1.5.7", + "@backstage/test-utils": "^1.5.9", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", @@ -64,6 +66,7 @@ "@types/react-router": "*", "@types/react-router-dom": "*", "@types/sanitize-html": "^2.9.0", + "@types/zen-observable": "^0.8.7", "cross-env": "7.0.3", "cypress": "^13.3.0", "eslint": "^8", diff --git a/source/modules/backstage/packages/app/src/App.tsx b/source/modules/backstage/packages/app/src/App.tsx index 18e01599..44f35573 100644 --- a/source/modules/backstage/packages/app/src/App.tsx +++ b/source/modules/backstage/packages/app/src/App.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Route } from "react-router-dom"; + import { apiDocsPlugin, ApiExplorerPage } from "@backstage/plugin-api-docs"; import { CatalogEntityPage, @@ -25,11 +26,6 @@ import { TechDocsAddons } from "@backstage/plugin-techdocs-react"; import { ReportIssue } from "@backstage/plugin-techdocs-module-addons-contrib"; import { UserSettingsPage } from "@backstage/plugin-user-settings"; import { HomepageCompositionRoot } from "@backstage/plugin-home"; -import { apis } from "./apis"; -import { entityPage } from "./components/catalog/EntityPage"; -import { searchPage } from "./components/search/SearchPage"; -import { Root } from "./components/Root"; - import { AlertDisplay, OAuthRequestDialog, @@ -40,11 +36,16 @@ import { AppRouter, FlatRoutes } from "@backstage/core-app-api"; import { CatalogGraphPage } from "@backstage/plugin-catalog-graph"; import { RequirePermission } from "@backstage/plugin-permission-react"; import { catalogEntityCreatePermission } from "@backstage/plugin-catalog-common/alpha"; +import { useApi, configApiRef } from "@backstage/core-plugin-api"; +import { apis } from "./apis/apis"; import { oauth2ApiRef } from "./apis/oauth2Api"; -import { useApi, configApiRef } from "@backstage/core-plugin-api"; +import { Root } from "./components/Root"; +import { entityPage } from "./components/EntityPage"; +import { searchPage } from "./components/SearchPage"; +import { homePage } from "./components/HomePage"; -import { HomePage } from "./components/home/HomePage"; +import { AcdpPartnerOfferingPage } from "backstage-plugin-acdp-partner-offering"; const app = createApp({ apis, @@ -56,7 +57,6 @@ const app = createApp({ {configApi.getString("auth.environment") === "development" ? ( )} @@ -102,10 +100,13 @@ const app = createApp({ }, }); +// Backstage requires all extenensions to be part of a single React element tree, for this reason, we opt to use React.JSX.Element types directly +// rather than functional components (e.g. {homePage} vs ). Naming conventions are used appropriately to match this style. +// See documentation: https://backstage.io/docs/plugins/composability/#using-extensions-in-an-app const routes = ( }> - + {homePage} } /> } /> + } /> } /> + OAuth2.create({ + configApi, + discoveryApi, + oauthRequestApi, + provider: { + id: "oauth2", // This must match the id given to the SignInPage providers option and in the app-config auth section + title: "OAuth 2.0 Provider", + icon: UserIcon, + }, + environment: configApi.getString("auth.environment"), + defaultScopes: ["openid", "profile", "email"], + popupOptions: { + size: { + width: 500, + height: 500, + }, + }, + }), + }), + createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + ScmAuth.createDefaultApiFactory(), +]; diff --git a/source/modules/backstage/packages/app/src/components/EntityPage/EntityConditions.tsx b/source/modules/backstage/packages/app/src/components/EntityPage/EntityConditions.tsx new file mode 100644 index 00000000..b17e9f79 --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/EntityPage/EntityConditions.tsx @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Entity } from "@backstage/catalog-model"; + +import { constants } from "backstage-plugin-acdp-common"; + +export function hasDocs(): (entity: Entity) => boolean { + return (entity: Entity) => { + return Boolean( + entity.metadata.annotations?.[constants.BACKSTAGE_TECHDOCS_ANNOTATION], + ); + }; +} + +export function hasCicd(): (entity: Entity) => boolean { + return (entity: Entity) => { + return Boolean( + entity.metadata.annotations?.[ + constants.ACDP_DEPLOYMENT_TARGET_ANNOTATION + ], + ); + }; +} + +export function hasApis(): (entity: Entity) => boolean { + return (entity: Entity) => { + return ( + Boolean(entity.spec?.providesApis) || Boolean(entity.spec?.consumesApis) + ); + }; +} + +export function hasDependencies(): (entity: Entity) => boolean { + return (entity: Entity) => { + return Boolean(entity.spec?.dependsOn); + }; +} diff --git a/source/modules/backstage/packages/app/src/components/EntityPage/EntityContent.tsx b/source/modules/backstage/packages/app/src/components/EntityPage/EntityContent.tsx new file mode 100644 index 00000000..9e859b77 --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/EntityPage/EntityContent.tsx @@ -0,0 +1,108 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; +import { Grid } from "@material-ui/core"; + +import { + EntityConsumedApisCard, + EntityProvidedApisCard, +} from "@backstage/plugin-api-docs"; +import { + EntityAboutCard, + EntityDependsOnComponentsCard, + EntityDependsOnResourcesCard, + EntityHasSubcomponentsCard, + EntityLinksCard, + EntitySwitch, + EntityOrphanWarning, + EntityProcessingErrorsPanel, + hasCatalogProcessingErrors, + isOrphan, +} from "@backstage/plugin-catalog"; +import { EntityTechdocsContent } from "@backstage/plugin-techdocs"; +import { EntityCatalogGraphCard } from "@backstage/plugin-catalog-graph"; +import { TechDocsAddons } from "@backstage/plugin-techdocs-react"; +import { ReportIssue } from "@backstage/plugin-techdocs-module-addons-contrib"; + +import { + EntityAcdpBuildProjectOverviewCard, + EntityApplicationsDashboardLinkCard, +} from "backstage-plugin-acdp"; + +export const entityWarningContent = ( + <> + + + + + + + + + + + + + +); + +export const overviewContent = ( + + {entityWarningContent} + + + + + + + + + + + + + +); + +export const cicdContent = ( + + + +); + +export const metricsContent = ( + + + +); + +export const apiContent = ( + + + + + + + + +); + +export const dependenciesContent = ( + + + + + + + + +); + +export const techdocsContent = ( + + + + + +); diff --git a/source/modules/backstage/packages/app/src/components/EntityPage/EntityPage.tsx b/source/modules/backstage/packages/app/src/components/EntityPage/EntityPage.tsx new file mode 100644 index 00000000..2498802d --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/EntityPage/EntityPage.tsx @@ -0,0 +1,248 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; + +import { Grid } from "@material-ui/core"; + +import { + EntityApiDefinitionCard, + EntityConsumingComponentsCard, + EntityHasApisCard, + EntityProvidingComponentsCard, +} from "@backstage/plugin-api-docs"; +import { + EntityAboutCard, + EntityHasComponentsCard, + EntityHasResourcesCard, + EntityHasSystemsCard, + EntityLayout, + EntityLinksCard, + EntitySwitch, + isKind, +} from "@backstage/plugin-catalog"; +import { + EntityUserProfileCard, + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, +} from "@backstage/plugin-org"; +import { + Direction, + EntityCatalogGraphCard, +} from "@backstage/plugin-catalog-graph"; +import { + RELATION_API_CONSUMED_BY, + RELATION_API_PROVIDED_BY, + RELATION_CONSUMES_API, + RELATION_DEPENDENCY_OF, + RELATION_DEPENDS_ON, + RELATION_HAS_PART, + RELATION_PART_OF, + RELATION_PROVIDES_API, +} from "@backstage/catalog-model"; + +import { + entityWarningContent, + overviewContent, + cicdContent, + metricsContent, + dependenciesContent, + techdocsContent, + apiContent, +} from "./EntityContent"; +import { hasDocs, hasCicd, hasApis, hasDependencies } from "./EntityConditions"; + +const componentPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + {metricsContent} + + + + {apiContent} + + + + {dependenciesContent} + + + + {techdocsContent} + + +); + +/** + * NOTE: This page is designed to work on small screens such as mobile devices. + * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, + * since this does not default. If no breakpoints are used, the items will equitably share the available space. + * https://material-ui.com/components/grid/#basic-grid. + */ +const defaultEntityPage = ( + + + {overviewContent} + + +); + +const apiPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +const userPage = ( + + + + {entityWarningContent} + + + + + + + + + +); + +const groupPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + +); + +const systemPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + + + + + + + + + + +); + +const domainPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + +); + +export const entityPage = ( + + + + + + + + + +); diff --git a/source/modules/backstage/packages/app/src/components/EntityPage/index.ts b/source/modules/backstage/packages/app/src/components/EntityPage/index.ts new file mode 100644 index 00000000..585d228b --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/EntityPage/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./EntityPage"; diff --git a/source/modules/backstage/packages/app/src/components/HomePage/HomePage.tsx b/source/modules/backstage/packages/app/src/components/HomePage/HomePage.tsx new file mode 100644 index 00000000..f9e8ada8 --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/HomePage/HomePage.tsx @@ -0,0 +1,87 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; + +import { Grid, makeStyles } from "@material-ui/core"; + +import { Page, Header, Content } from "@backstage/core-components"; +import { + ClockConfig, + HeaderWorldClock, + HomePageStarredEntities, +} from "@backstage/plugin-home"; +import { HomePageSearchBar } from "@backstage/plugin-search"; +import { useUserProfile } from "@backstage/plugin-user-settings"; + +const clockConfigs: ClockConfig[] = [ + { + label: "East Coast", + timeZone: "America/New_York", + }, + { + label: "Central", + timeZone: "America/Chicago", + }, + { + label: "Mountain", + timeZone: "America/Denver", + }, + { + label: "Pacific", + timeZone: "America/Los_Angeles", + }, +]; + +const timeFormat: Intl.DateTimeFormatOptions = { + hour: "2-digit", + minute: "2-digit", + hour12: true, +}; + +const useStyles = makeStyles((theme) => ({ + searchBar: { + display: "flex", + maxWidth: "60vw", + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[1], + borderRadius: "50px", + margin: "auto", + }, +})); + +const HomePage = () => { + const userProfile = useUserProfile(); + const classes = useStyles(); + + return ( + +
+ +
+ + + + + + + + + + + + +
+ ); +}; + +export const homePage = ; diff --git a/source/modules/backstage/packages/app/src/components/HomePage/index.ts b/source/modules/backstage/packages/app/src/components/HomePage/index.ts new file mode 100644 index 00000000..65ceb935 --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/HomePage/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./HomePage"; diff --git a/source/modules/backstage/packages/app/src/components/Root/LogoFull.tsx b/source/modules/backstage/packages/app/src/components/Root/LogoFull.tsx index 37671bb5..73a7885a 100644 --- a/source/modules/backstage/packages/app/src/components/Root/LogoFull.tsx +++ b/source/modules/backstage/packages/app/src/components/Root/LogoFull.tsx @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import React from "react"; + +// Materual UI import { makeStyles } from "@material-ui/core"; const useStyles = makeStyles({ @@ -13,7 +15,8 @@ const useStyles = makeStyles({ fill: "#7df3e1", }, }); -const LogoFull = () => { + +export const LogoFull = () => { const classes = useStyles(); return ( @@ -29,5 +32,3 @@ const LogoFull = () => { ); }; - -export default LogoFull; diff --git a/source/modules/backstage/packages/app/src/components/Root/LogoIcon.tsx b/source/modules/backstage/packages/app/src/components/Root/LogoIcon.tsx index ce775e07..cda4e43c 100644 --- a/source/modules/backstage/packages/app/src/components/Root/LogoIcon.tsx +++ b/source/modules/backstage/packages/app/src/components/Root/LogoIcon.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import React from "react"; + import { makeStyles } from "@material-ui/core"; const useStyles = makeStyles({ @@ -14,7 +15,7 @@ const useStyles = makeStyles({ }, }); -const LogoIcon = () => { +export const LogoIcon = () => { const classes = useStyles(); return ( @@ -30,5 +31,3 @@ const LogoIcon = () => { ); }; - -export default LogoIcon; diff --git a/source/modules/backstage/packages/app/src/components/Root/Root.tsx b/source/modules/backstage/packages/app/src/components/Root/Root.tsx index 2e4a580b..398ca790 100644 --- a/source/modules/backstage/packages/app/src/components/Root/Root.tsx +++ b/source/modules/backstage/packages/app/src/components/Root/Root.tsx @@ -2,14 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 import React, { PropsWithChildren } from "react"; + import { makeStyles } from "@material-ui/core"; import HomeIcon from "@material-ui/icons/Home"; import ExtensionIcon from "@material-ui/icons/Extension"; import CategoryIcon from "@material-ui/icons/Category"; import LibraryBooks from "@material-ui/icons/LibraryBooks"; +import ShoppingCart from "@material-ui/icons/ShoppingCart"; import CreateComponentIcon from "@material-ui/icons/AddCircleOutline"; -import LogoFull from "./LogoFull"; -import LogoIcon from "./LogoIcon"; +import MenuIcon from "@material-ui/icons/Menu"; +import SearchIcon from "@material-ui/icons/Search"; + import { Settings as SidebarSettings, UserSettingsSignInAvatar, @@ -26,8 +29,9 @@ import { useSidebarOpenState, Link, } from "@backstage/core-components"; -import MenuIcon from "@material-ui/icons/Menu"; -import SearchIcon from "@material-ui/icons/Search"; + +import { LogoFull } from "./LogoFull"; +import { LogoIcon } from "./LogoIcon"; const useSidebarLogoStyles = makeStyles({ root: { @@ -72,6 +76,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + {/* End global nav */} diff --git a/source/modules/backstage/packages/app/src/components/SearchPage/SearchPage.tsx b/source/modules/backstage/packages/app/src/components/SearchPage/SearchPage.tsx new file mode 100644 index 00000000..fea99fe9 --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/SearchPage/SearchPage.tsx @@ -0,0 +1,127 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; + +import { makeStyles, Theme, Grid, Paper } from "@material-ui/core"; + +import { CatalogSearchResultListItem } from "@backstage/plugin-catalog"; +import { + catalogApiRef, + CATALOG_FILTER_EXISTS, +} from "@backstage/plugin-catalog-react"; +import { TechDocsSearchResultListItem } from "@backstage/plugin-techdocs"; +import { SearchType } from "@backstage/plugin-search"; +import { + SearchBar, + SearchFilter, + SearchResult, + SearchPagination, + useSearch, +} from "@backstage/plugin-search-react"; +import { + CatalogIcon, + Content, + DocsIcon, + Header, + Page, +} from "@backstage/core-components"; +import { useApi } from "@backstage/core-plugin-api"; + +const useStyles = makeStyles((theme: Theme) => ({ + bar: { + padding: theme.spacing(1, 0), + }, + filters: { + padding: theme.spacing(2), + marginTop: theme.spacing(2), + }, + filter: { + "& + &": { + marginTop: theme.spacing(2.5), + }, + }, +})); + +const SearchPage = () => { + const classes = useStyles(); + const { types } = useSearch(); + const catalogApi = useApi(catalogApiRef); + + return ( + +
+ + + + + + + + + , + }, + { + value: "techdocs", + name: "Documentation", + icon: , + }, + ]} + /> + + {types.includes("techdocs") && ( + { + // Return a list of entities which are documented. + const { items } = await catalogApi.getEntities({ + fields: ["metadata.name"], + filter: { + "metadata.annotations.backstage.io/techdocs-ref": + CATALOG_FILTER_EXISTS, + }, + }); + + const names = items.map((entity) => entity.metadata.name); + names.sort(); + return names; + }} + /> + )} + + + + + + + + } /> + } /> + + + + + + ); +}; + +export const searchPage = ; diff --git a/source/modules/backstage/packages/app/src/components/SearchPage/index.ts b/source/modules/backstage/packages/app/src/components/SearchPage/index.ts new file mode 100644 index 00000000..7ec51a59 --- /dev/null +++ b/source/modules/backstage/packages/app/src/components/SearchPage/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./SearchPage"; diff --git a/source/modules/backstage/packages/backend/.eslintrc.js b/source/modules/backstage/packages/backend/.eslintrc.js new file mode 100644 index 00000000..709e25dd --- /dev/null +++ b/source/modules/backstage/packages/backend/.eslintrc.js @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +module.exports = require("@backstage/cli/config/eslint-factory")(__dirname); diff --git a/source/modules/backstage/packages/backend/Dockerfile b/source/modules/backstage/packages/backend/Dockerfile index bc871a05..376ea7c0 100644 --- a/source/modules/backstage/packages/backend/Dockerfile +++ b/source/modules/backstage/packages/backend/Dockerfile @@ -9,7 +9,7 @@ # # Once the commands have been run, you can build the image using `yarn build-image` -FROM public.ecr.aws/docker/library/node:18.17.1-bullseye-slim +FROM public.ecr.aws/docker/library/node:18.20-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. diff --git a/source/modules/backstage/packages/backend/package.json b/source/modules/backstage/packages/backend/package.json index be3c6732..8eb2553d 100644 --- a/source/modules/backstage/packages/backend/package.json +++ b/source/modules/backstage/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "1.2.2", + "version": "2.0.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -13,42 +13,50 @@ "start": "backstage-cli package start", "build": "backstage-cli package build", "lint": "backstage-cli package lint", - "test": "backstage-cli package test --coverage --silent", + "test": "backstage-cli package test --coverage", "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", "build-image": "docker build ../.. -f Dockerfile --tag backstage" }, "dependencies": { - "@backstage/backend-common": "^0.23.2", - "@backstage/backend-defaults": "^0.3.3", - "@backstage/backend-plugin-api": "^0.6.21", - "@backstage/backend-tasks": "^0.5.26", + "@backstage/backend-common": "^0.23.3", + "@backstage/backend-defaults": "^0.4.1", + "@backstage/backend-plugin-api": "^0.7.0", + "@backstage/backend-tasks": "^0.5.27", "@backstage/catalog-client": "^1.6.5", "@backstage/catalog-model": "^1.5.0", "@backstage/config": "^1.2.0", - "@backstage/plugin-app-backend": "^0.3.70", - "@backstage/plugin-auth-backend": "^0.22.8", - "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.7", - "@backstage/plugin-auth-node": "^0.4.16", - "@backstage/plugin-catalog-backend": "^1.23.2", - "@backstage/plugin-catalog-backend-module-aws": "^0.3.16", - "@backstage/plugin-events-backend": "^0.3.8", - "@backstage/plugin-permission-common": "^0.7.14", - "@backstage/plugin-permission-node": "^0.7.32", - "@backstage/plugin-proxy-backend": "^0.5.2", - "@backstage/plugin-scaffolder-backend": "^1.22.11", - "@backstage/plugin-search-backend": "^1.5.13", - "@backstage/plugin-search-backend-module-pg": "^0.5.31", - "@backstage/plugin-search-backend-node": "^1.2.26", - "@backstage/plugin-techdocs-backend": "^1.10.8", + "@backstage/errors": "^1.2.4", + "@backstage/plugin-app-backend": "^0.3.71", + "@backstage/plugin-auth-backend": "^0.22.9", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.8", + "@backstage/plugin-auth-node": "^0.4.17", + "@backstage/plugin-catalog-backend": "^1.24.0", + "@backstage/plugin-catalog-backend-module-aws": "^0.3.17", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.20", + "@backstage/plugin-events-backend": "^0.3.9", + "@backstage/plugin-permission-common": "^0.8.0", + "@backstage/plugin-permission-node": "^0.8.0", + "@backstage/plugin-proxy-backend": "^0.5.3", + "@backstage/plugin-scaffolder-backend": "^1.23.0", + "@backstage/plugin-search-backend": "^1.5.14", + "@backstage/plugin-search-backend-module-catalog": "^0.1.28", + "@backstage/plugin-search-backend-module-pg": "^0.5.32", + "@backstage/plugin-search-backend-module-techdocs": "^0.1.27", + "@backstage/plugin-search-backend-node": "^1.2.27", + "@backstage/plugin-techdocs-backend": "^1.10.9", "app": "file:../app", "auth-oauth2-provider-module": "*", - "techdocs-custom-build-strategy-module": "*", "backstage-plugin-acdp-backend": "*", + "backstage-plugin-acdp-partner-offering-backend": "*", + "express": "^4.21.0", "jwt-decode": "^3.1.0", - "prettier": "^3" + "prettier": "^3", + "techdocs-custom-build-strategy-module": "*" }, "devDependencies": { - "@backstage/cli": "^0.26.10", + "@backstage/cli": "^0.26.11", "@types/cookie-parser": "1.4.3", "@types/dockerode": "3.3.17", "@types/lodash": "^4.17.0", diff --git a/source/modules/backstage/packages/backend/src/index.ts b/source/modules/backstage/packages/backend/src/index.ts index c5abd056..ab65ffb8 100644 --- a/source/modules/backstage/packages/backend/src/index.ts +++ b/source/modules/backstage/packages/backend/src/index.ts @@ -1,7 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// Backstage Plugins import authPlugin from "@backstage/plugin-auth-backend"; import authModuleGuestProvider from "@backstage/plugin-auth-backend-module-guest-provider"; import appPlugin from "@backstage/plugin-app-backend/alpha"; @@ -9,6 +8,7 @@ import catalogPlugin from "@backstage/plugin-catalog-backend/alpha"; import catalogModuleAwsS3EntityProvider from "@backstage/plugin-catalog-backend-module-aws/alpha"; import catalogModuleScaffolderEntityModel from "@backstage/plugin-catalog-backend-module-scaffolder-entity-model"; import { createBackend } from "@backstage/backend-defaults"; +import { rootHttpRouterServiceFactory } from "@backstage/backend-defaults/rootHttpRouter"; import scaffolderPlugin from "@backstage/plugin-scaffolder-backend/alpha"; import techdocsPlugin from "@backstage/plugin-techdocs-backend/alpha"; import proxyPlugin from "@backstage/plugin-proxy-backend/alpha"; @@ -16,13 +16,16 @@ import searchPlugin from "@backstage/plugin-search-backend/alpha"; import searchTechdocsCollator from "@backstage/plugin-search-backend-module-techdocs/alpha"; import searchCatalogCollator from "@backstage/plugin-search-backend-module-catalog/alpha"; -// Custom Modules/Plugins -import authModuleOAuth2Provider from "auth-oauth2-provider-module"; import acdpPlugin, { scaffolderModuleAcdpActions, } from "backstage-plugin-acdp-backend"; +import acdpPartnerOfferingPlugin from "backstage-plugin-acdp-partner-offering-backend"; + +import authModuleOAuth2Provider from "auth-oauth2-provider-module"; import techdocsModuleCustomBuildStrategy from "techdocs-custom-build-strategy-module"; +import { customErrorHandler } from "./middleware"; + const backend = createBackend(); // Auth @@ -57,4 +60,40 @@ backend.add(searchCatalogCollator); // ACDP backend.add(acdpPlugin); +// ACDP Partner Offering +backend.add(acdpPartnerOfferingPlugin); + +// Customize the root http router to insert custom error handling middleware which strips the "cause" from the express response +// Backstage Documentation: https://backstage.io/docs/backend-system/core-services/root-http-router#configuring-the-service +backend.add( + rootHttpRouterServiceFactory({ + // Provide all built-in middleware, excluding error. Then replace error with a custom error handler. + configure: ({ app, config, logger, middleware, routes }) => { + // Default middleware + app.use(middleware.helmet()); + app.use(middleware.cors()); + app.use(middleware.compression()); + app.use(middleware.logging()); + + // Routes registered by other plugins + app.use(routes); + + // Default middleware required after routes + app.use(middleware.notFound()); + + app.use( + customErrorHandler( + { + config: config, + logger: logger, + }, + { + showStackTraces: false, + }, + ), + ); + }, + }), +); + backend.start(); diff --git a/source/modules/backstage/packages/backend/src/middleware/customErrorHandler.ts b/source/modules/backstage/packages/backend/src/middleware/customErrorHandler.ts new file mode 100644 index 00000000..8681d89e --- /dev/null +++ b/source/modules/backstage/packages/backend/src/middleware/customErrorHandler.ts @@ -0,0 +1,100 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Request, Response, ErrorRequestHandler, NextFunction } from "express"; +import { + MiddlewareFactoryOptions, + MiddlewareFactoryErrorOptions, +} from "@backstage/backend-defaults/rootHttpRouter"; +import { + AuthenticationError, + ConflictError, + ErrorResponseBody, + InputError, + NotAllowedError, + NotFoundError, + NotModifiedError, + ServiceUnavailableError, + NotImplementedError, + serializeError, +} from "@backstage/errors"; + +// A copy of the Backstage error Middleware which also strips the "cause" from the response +// See original: https://github.com/backstage/backstage/blob/a9455ca766ff6cdf4639621ffe123e3e614d61d1/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts#L206 +export function customErrorHandler( + options: MiddlewareFactoryOptions, + errorOptions: MiddlewareFactoryErrorOptions, +): ErrorRequestHandler { + const showStackTraces = + errorOptions.showStackTraces ?? process.env.NODE_ENV === "development"; + + const logger = options.logger.child({ + type: "errorHandler", + }); + + return (error: Error, req: Request, res: Response, next: NextFunction) => { + const statusCode = getStatusCode(error); + if (errorOptions.logAllErrors || statusCode >= 500) { + logger.error(`Request failed with status ${statusCode}`, error); + } + + if (res.headersSent) { + // If the headers have already been sent, do not send the response again + // as this will throw an error in the backend. + next(error); + return; + } + + // Custom addition. Remove stack and cause from error response to avoid exposing stack trace on some errors + if (!showStackTraces) { + delete error.stack; + delete error.cause; + } + + const body: ErrorResponseBody = { + error: serializeError(error, { includeStack: showStackTraces }), + request: { method: req.method, url: req.url }, + response: { statusCode }, + }; + + res.status(statusCode).json(body); + }; +} + +function getStatusCode(error: Error): number { + // Look for common http library status codes + const knownStatusCodeFields = ["statusCode", "status"]; + for (const field of knownStatusCodeFields) { + const statusCode = (error as any)[field]; + if ( + typeof statusCode === "number" && + (statusCode | 0) === statusCode && // is whole integer + statusCode >= 100 && + statusCode <= 599 + ) { + return statusCode; + } + } + + // Handle well-known error types + switch (error.name) { + case NotModifiedError.name: + return 304; + case InputError.name: + return 400; + case AuthenticationError.name: + return 401; + case NotAllowedError.name: + return 403; + case NotFoundError.name: + return 404; + case ConflictError.name: + return 409; + case NotImplementedError.name: + return 501; + case ServiceUnavailableError.name: + return 503; + default: + return 500; + } +} diff --git a/source/modules/backstage/packages/backend/src/middleware/index.ts b/source/modules/backstage/packages/backend/src/middleware/index.ts new file mode 100644 index 00000000..ddb3d664 --- /dev/null +++ b/source/modules/backstage/packages/backend/src/middleware/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./customErrorHandler"; diff --git a/source/modules/backstage/plugins/acdp-backend/package.json b/source/modules/backstage/plugins/acdp-backend/package.json index 10918331..89aebe63 100644 --- a/source/modules/backstage/plugins/acdp-backend/package.json +++ b/source/modules/backstage/plugins/acdp-backend/package.json @@ -1,7 +1,7 @@ { "name": "backstage-plugin-acdp-backend", "description": "ACDP Backend plugin for Backstage", - "version": "1.2.2", + "version": "2.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,42 +24,47 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { + "aws-sdk-client-mock": "^3.0.0", "@aws-sdk/client-codebuild": "^3.621.0", + "@aws-sdk/client-cost-explorer": "^3.621.0", "@aws-sdk/client-s3": "^3.629.0", + "@aws-sdk/client-service-catalog-appregistry": "^3.635.0", "@aws-sdk/client-ssm": "^3.621.0", "@aws-sdk/lib-storage": "^3.621.0", - "@aws-sdk/util-arn-parser": "^3.568.0", - "@backstage/backend-defaults": "^0.3.3", + "@aws-sdk/util-arn-parser": "^3.495.0", + "@backstage/backend-common": "^0.23.3", + "@backstage/backend-defaults": "^0.4.1", + "@backstage/backend-plugin-api": "^0.7.0", + "@backstage/backend-test-utils": "^0.4.4", "@backstage/catalog-client": "^1.6.5", - "@backstage/backend-common": "^0.23.2", - "@backstage/backend-plugin-api": "^0.6.21", - "@backstage/backend-test-utils": "^0.4.3", "@backstage/catalog-model": "^1.5.0", "@backstage/config": "^1.2.0", - "@backstage/types": "^1.1.1", "@backstage/errors": "^1.2.4", - "@backstage/integration": "^1.12.0", + "@backstage/integration": "^1.13.0", "@backstage/integration-aws-node": "^0.1.12", - "@backstage/plugin-scaffolder-node": "^0.4.7", - "@backstage/plugin-scaffolder-node-test-utils": "^0.1.8", - "@backstage/plugin-techdocs-node": "^1.12.7", - "backstage-plugin-acdp-common": "*", + "@backstage/plugin-scaffolder-node": "^0.4.8", + "@backstage/plugin-scaffolder-node-test-utils": "^0.1.9", + "@backstage/plugin-techdocs-node": "^1.12.8", + "@backstage/types": "^1.1.1", "@types/express": "*", + "backstage-plugin-acdp-common": "*", "express": "^4.17.1", "express-promise-router": "^4.1.0", "node-fetch": "^2.6.7", "p-limit": "^3.1.0", - "recursive-readdir": "^2.2.2", "prettier": "^3.1.0", + "recursive-readdir": "^2.2.2", "winston": "^3.2.1", + "yaml": "^2.5.0", "yn": "^4.0.0", "zod": "^3.22.4" }, "devDependencies": { - "@backstage/cli": "^0.26.10", - "@types/supertest": "^2.0.12", + "@backstage/cli": "^0.26.11", + "@backstage/plugin-auth-backend": "^0.22.9", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.8", "@types/recursive-readdir": "*", - "aws-sdk-client-mock": "^3.0.0", + "@types/supertest": "^2.0.12", "msw": "^1.0.0", "supertest": "^6.2.4", "ts-jest": "^29.1.1" diff --git a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.test.ts b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.test.ts index 6d631ac8..e73e9c90 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.test.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.test.ts @@ -10,13 +10,15 @@ import { fetchContents } from "@backstage/plugin-scaffolder-node"; import { createAcdpCatalogCreateAction } from "."; import { mockDiscovery, + mockTemplateCatalogCreateInput, + mockedTemplateEntity, +} from "./mocks"; +import { mockUrlReader, mockConfig, mockIntegrations, - mockedTemplateEntity, - mockTemplateCatalogCreateInput, mockedCatalogEntity, -} from "../__mocks__/common-mocks"; +} from "../mocks"; import { stringifyEntityRef } from "@backstage/catalog-model"; import { Publisher, PublisherBase } from "@backstage/plugin-techdocs-node"; import { createMockDirectory } from "@backstage/backend-test-utils"; @@ -58,7 +60,7 @@ beforeEach(() => { describe("createAcdpCatalogCreateAction", () => { const workspacePath = createMockDirectory().resolve("/tmp"); - it("", async () => { + it("creates ACDP catalog action", async () => { mockedS3Client.on(PutObjectCommand).resolves({ ETag: "test", }); @@ -107,7 +109,7 @@ describe("createAcdpCatalogCreateAction", () => { }) ).handler(mockContext); - expect(catalogClient.getEntityByRef.mock.calls.length === 2); + expect(catalogClient.getEntityByRef.mock.calls.length).toBe(1); expect(fetchContents).toHaveBeenCalledTimes(2); expect(mockedS3Client.calls()).toHaveLength(1); expect(mockContext.output).toHaveBeenCalledTimes(2); diff --git a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.ts b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.ts index 17435411..84ce9212 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-catalog-create.ts @@ -1,19 +1,23 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Config } from "@backstage/config"; -import { JsonObject } from "@backstage/types"; +import * as path from "path"; +import { Logger } from "winston"; +import * as yaml from "yaml"; +import { z } from "zod"; + import { - createTemplateAction, - fetchContents, - ActionContext, -} from "@backstage/plugin-scaffolder-node"; + PutObjectCommand, + PutObjectCommandInput, + S3Client, +} from "@aws-sdk/client-s3"; + import { UrlReader, resolveSafeChildPath, PluginEndpointDiscovery, } from "@backstage/backend-common"; -import { DefaultAwsCredentialsManager } from "@backstage/integration-aws-node"; +import { AuthService } from "@backstage/backend-plugin-api"; import { CatalogClient, Location } from "@backstage/catalog-client"; import { CompoundEntityRef, @@ -22,24 +26,26 @@ import { Entity, parseLocationRef, } from "@backstage/catalog-model"; -import { Publisher, PublisherBase } from "@backstage/plugin-techdocs-node"; -import { ScmIntegrations } from "@backstage/integration"; +import { Config } from "@backstage/config"; import { InputError } from "@backstage/errors"; -import { AwsS3Helper } from "../utils/aws-s3-helper"; -import * as path from "path"; -import { getLocationForEntity } from "../utils/location-helper"; - -import * as yaml from "yaml"; -import { z } from "zod"; +import { ScmIntegrations } from "@backstage/integration"; +import { DefaultAwsCredentialsManager } from "@backstage/integration-aws-node"; import { - PutObjectCommand, - PutObjectCommandInput, - S3Client, -} from "@aws-sdk/client-s3"; + createTemplateAction, + fetchContents, + ActionContext, +} from "@backstage/plugin-scaffolder-node"; +import { Publisher, PublisherBase } from "@backstage/plugin-techdocs-node"; +import { JsonObject } from "@backstage/types"; + import { constants } from "backstage-plugin-acdp-common"; -import { Logger } from "winston"; -import { AuthService } from "@backstage/backend-plugin-api"; + +import { + AwsS3Helper, + getLocationForEntity, + awsApiCallWithErrorHandling, +} from "../utils"; interface CatalogConfig { bucketName: string; @@ -66,170 +72,6 @@ interface AcdpCatalogCreateActionInput { logger: Logger; } -export const createAcdpCatalogCreateAction = async ( - options: AcdpCatalogCreateActionInput, -) => { - const { config, catalogClient, discovery, logger, auth } = options; - - const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(config); - const credentialProvider = - await awsCredentialsManager.getCredentialProvider(); - const userAgentString = config.getString("acdp.metrics.userAgentString"); - - const catalogConfig: CatalogConfig = { - bucketName: config.getString("acdp.s3Catalog.bucketName"), - region: config.getString("acdp.s3Catalog.region"), - catalogPrefix: config.getString("acdp.s3Catalog.prefix"), - catalogItemAssetsPath: - config.getOptionalString("acdp.s3Catalog.catalogItemAssetsPath") ?? - "assets/", - allowUnsafeAccess: - config.getOptionalBoolean("acdp.allow-unsafe-local-dir-access") ?? false, - }; - - if (!catalogConfig.catalogItemAssetsPath.endsWith("/")) { - logger.error( - "acdp.s3Catalog.catalogItemAssetsPath must have a trailing slash", - ); - throw new Error("Invalid acdp.s3Catalog.catalogItemAssetsPath"); - } - - const techdocsPublisher = await Publisher.fromConfig(config, { - logger: logger, - discovery: discovery, - }); - await techdocsPublisher.getReadiness(); - - return createTemplateAction({ - id: "aws:acdp:catalog:create", - description: - "Writes the catalog-info.yaml and copies assets for your template to the backend s3 bucket", - schema: { - input: z.object({ - componentId: z - .string() - .describe( - "The unique component id which is used for the catalog-info name", - ), - assetsSourcePath: z - .string() - .optional() - .describe( - "optional: path to the assets used by this component to copy into the catalog item's assets folder", - ), - docsSiteSourcePath: z - .string() - .optional() - .describe( - "optional: path to the techdocs site folder to copy into the techdocs' assets store. Techdocs must be configured for this to work.", - ), - entity: z - .record(z.any()) - .describe( - "YAML body for the catalog-info.yaml content. It will automatically be updated with ACDP Metadata", - ), - }), - output: { - type: "object", - properties: { - s3Url: { - title: "S3 URL Path file was upload to", - type: "string", - }, - s3Uri: { - title: "S3 URI Path file was upload to", - type: "string", - }, - }, - }, - }, - - async handler(ctx) { - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: "catalog", - }); - - if ( - ctx.templateInfo === undefined || - ctx.templateInfo.baseUrl === undefined - ) { - throw new InputError("Unable to read template info"); - } - - const compoundEntity: CompoundEntityRef = { - kind: ctx.input.entity.kind.toLowerCase(), - namespace: ( - ctx.input.entity.metadata?.namespace ?? DEFAULT_NAMESPACE - ).toLowerCase(), - name: ctx.input.entity.metadata.name.toLowerCase(), - }; - const entity = { - kind: compoundEntity.kind, - metadata: { - namespace: compoundEntity.namespace, - name: compoundEntity.name, - }, - } as Entity; - - // Check if a registered entity already exists - const existingEntity = await catalogClient.getEntityByRef( - compoundEntity, - { - token: token, - }, - ); - if (existingEntity) - throw new Error( - `An entity the ref ${existingEntity.metadata.namespace}/${existingEntity.kind}/${existingEntity.metadata.name} already exists`, - ); - - const catalogEntityPathPrefix = `${catalogConfig.catalogPrefix}/${compoundEntity.namespace}/${compoundEntity.kind}/${compoundEntity.name}`; - - const s3Client = new S3Client({ - region: catalogConfig.region, - customUserAgent: userAgentString, - credentialDefaultProvider: () => - credentialProvider.sdkCredentialProvider, - }); - - if (ctx.input.docsSiteSourcePath != undefined) { - await copyDocsAssetsToCatalog({ - techdocsPublisher: techdocsPublisher, - catalogConfig: catalogConfig, - catalogCreateInput: options, - ctx: ctx, - entity: entity, - }); - } else { - ctx.logger.info( - "Skipping techdocs upload...docsSiteSourcePath is unset", - ); - } - - if (ctx.input.assetsSourcePath != undefined) { - await copyAssetsToCatalog({ - s3Client: s3Client, - catalogConfig: catalogConfig, - catalogCreateInput: options, - ctx: ctx, - catalogEntityPathPrefix: catalogEntityPathPrefix, - entity: entity, - }); - } else { - ctx.logger.info("Skipping assets upload...assetsSourcePath is unset"); - } - - await writeCatalogItemToS3({ - s3Client: s3Client, - catalogConfig: catalogConfig, - ctx: ctx, - catalogEntityPathPrefix: catalogEntityPathPrefix, - }); - }, - }); -}; - const copyDocsAssetsToCatalog = async (options: { techdocsPublisher: PublisherBase; catalogConfig: CatalogConfig; @@ -249,7 +91,7 @@ const copyDocsAssetsToCatalog = async (options: { catalogConfig.allowUnsafeAccess && templateBaseUrl.startsWith("file://") ) { - fetchBaseUrl = "file:///"; //allow access to full local filesystem for local development + fetchBaseUrl = "file:///"; // allow access to full local filesystem for local development } ctx.logger.info("Starting: Fetching docs from source location"); @@ -317,7 +159,7 @@ const copyAssetsToCatalog = async (options: { catalogConfig.allowUnsafeAccess && templateBaseUrl.startsWith("file://") ) { - fetchBaseUrl = "file:///"; //allow access to full local filesystem for local development + fetchBaseUrl = "file:///"; // allow access to full local filesystem for local development } const location = parseLocationRef(ctx.input.assetsSourcePath!) as Location; @@ -375,12 +217,12 @@ const writeCatalogItemToS3 = async (options: { // Inject required annotations into catalog-info.yaml - //For now, we only support 1 deployment target. in the future, this should come from inputs + // For now, we only support 1 deployment target. in the future, this should come from inputs ctx.input.entity.metadata.annotations[ constants.ACDP_DEPLOYMENT_TARGET_ANNOTATION ] = constants.ACDP_DEFAULT_DEPLOYMENT_TARGET; - if (ctx.input.docsSiteSourcePath != undefined) { + if (ctx.input.docsSiteSourcePath !== undefined) { ctx.input.entity.metadata.annotations["aws.amazon.com/techdocs-builder"] = "external"; ctx.input.entity.metadata.annotations["backstage.io/techdocs-ref"] = @@ -394,7 +236,7 @@ const writeCatalogItemToS3 = async (options: { `dir:${path.join(".", catalogConfig.catalogItemAssetsPath)}`; ctx.input.entity.metadata.annotations[constants.ACDP_ASSETS_STORED] = ( - ctx.input.assetsSourcePath != undefined + ctx.input.assetsSourcePath !== undefined ).toString(); ctx.input.entity.metadata.annotations[ANNOTATION_SOURCE_LOCATION] = @@ -405,8 +247,10 @@ const writeCatalogItemToS3 = async (options: { Bucket: catalogConfig.bucketName, Key: catalogInfoS3Key, }; - const putCatalogEntityResp = await s3Client.send( - new PutObjectCommand(putCatalogEntityInput), + const putCatalogEntityResp = await awsApiCallWithErrorHandling( + () => s3Client.send(new PutObjectCommand(putCatalogEntityInput)), + `Could not put catalog item in s3 bucket with bucket name: ${catalogConfig.bucketName} and key: ${catalogInfoS3Key}`, + ctx.logger, ); if (putCatalogEntityResp.ETag !== undefined) { @@ -421,3 +265,167 @@ const writeCatalogItemToS3 = async (options: { ); } }; + +export const createAcdpCatalogCreateAction = async ( + options: AcdpCatalogCreateActionInput, +) => { + const { config, catalogClient, discovery, logger, auth } = options; + + const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(config); + const credentialProvider = + await awsCredentialsManager.getCredentialProvider(); + const userAgentString = config.getString("acdp.metrics.userAgentString"); + + const catalogConfig: CatalogConfig = { + bucketName: config.getString("acdp.s3Catalog.bucketName"), + region: config.getString("acdp.s3Catalog.region"), + catalogPrefix: config.getString("acdp.s3Catalog.prefix"), + catalogItemAssetsPath: + config.getOptionalString("acdp.s3Catalog.catalogItemAssetsPath") ?? + "assets/", + allowUnsafeAccess: + config.getOptionalBoolean("acdp.allow-unsafe-local-dir-access") ?? false, + }; + + if (!catalogConfig.catalogItemAssetsPath.endsWith("/")) { + logger.error( + "acdp.s3Catalog.catalogItemAssetsPath must have a trailing slash", + ); + throw new Error("Invalid acdp.s3Catalog.catalogItemAssetsPath"); + } + + const techdocsPublisher = await Publisher.fromConfig(config, { + logger: logger, + discovery: discovery, + }); + await techdocsPublisher.getReadiness(); + + return createTemplateAction({ + id: "aws:acdp:catalog:create", + description: + "Writes the catalog-info.yaml and copies assets for your template to the backend s3 bucket", + schema: { + input: z.object({ + componentId: z + .string() + .describe( + "The unique component id which is used for the catalog-info name", + ), + assetsSourcePath: z + .string() + .optional() + .describe( + "optional: path to the assets used by this component to copy into the catalog item's assets folder", + ), + docsSiteSourcePath: z + .string() + .optional() + .describe( + "optional: path to the techdocs site folder to copy into the techdocs' assets store. Techdocs must be configured for this to work.", + ), + entity: z + .record(z.any()) + .describe( + "YAML body for the catalog-info.yaml content. It will automatically be updated with ACDP Metadata", + ), + }), + output: { + type: "object", + properties: { + s3Url: { + title: "S3 URL Path file was uploaded to", + type: "string", + }, + s3Uri: { + title: "S3 URI Path file was uploaded to", + type: "string", + }, + }, + }, + }, + + async handler(ctx) { + if ( + ctx.templateInfo === undefined || + ctx.templateInfo.baseUrl === undefined + ) { + throw new InputError("Unable to read template info"); + } + + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: "catalog", + }); + + const compoundEntity: CompoundEntityRef = { + kind: ctx.input.entity.kind.toLowerCase(), + namespace: ( + ctx.input.entity.metadata?.namespace ?? DEFAULT_NAMESPACE + ).toLowerCase(), + name: ctx.input.entity.metadata.name.toLowerCase(), + }; + const entity = { + kind: compoundEntity.kind, + metadata: { + namespace: compoundEntity.namespace, + name: compoundEntity.name, + }, + } as Entity; + + // Check if a registered entity already exists + const existingEntity = await catalogClient.getEntityByRef( + compoundEntity, + { + token: token, + }, + ); + if (existingEntity) + throw new Error( + `The entity ref ${existingEntity.metadata.namespace}/${existingEntity.kind}/${existingEntity.metadata.name} already exists`, + ); + + const catalogEntityPathPrefix = `${catalogConfig.catalogPrefix}/${compoundEntity.namespace}/${compoundEntity.kind}/${compoundEntity.name}`; + + const s3Client = new S3Client({ + region: catalogConfig.region, + customUserAgent: userAgentString, + credentialDefaultProvider: () => + credentialProvider.sdkCredentialProvider, + }); + + if (ctx.input.docsSiteSourcePath !== undefined) { + await copyDocsAssetsToCatalog({ + techdocsPublisher: techdocsPublisher, + catalogConfig: catalogConfig, + catalogCreateInput: options, + ctx: ctx, + entity: entity, + }); + } else { + ctx.logger.info( + "Skipping techdocs upload...docsSiteSourcePath is unset", + ); + } + + if (ctx.input.assetsSourcePath !== undefined) { + await copyAssetsToCatalog({ + s3Client: s3Client, + catalogConfig: catalogConfig, + catalogCreateInput: options, + ctx: ctx, + catalogEntityPathPrefix: catalogEntityPathPrefix, + entity: entity, + }); + } else { + ctx.logger.info("Skipping assets upload...assetsSourcePath is unset"); + } + + await writeCatalogItemToS3({ + s3Client: s3Client, + catalogConfig: catalogConfig, + ctx: ctx, + catalogEntityPathPrefix: catalogEntityPathPrefix, + }); + }, + }); +}; diff --git a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.test.ts b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.test.ts index f0d3c48b..0ae446ce 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.test.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.test.ts @@ -12,7 +12,7 @@ import { mockConfig, mockedCatalogEntity, mockIntegrations, -} from "../__mocks__/common-mocks"; +} from "../mocks"; import { stringifyEntityRef } from "@backstage/catalog-model"; import { createMockDirectory } from "@backstage/backend-test-utils"; import { createMockActionContext } from "@backstage/plugin-scaffolder-node-test-utils"; @@ -28,7 +28,7 @@ beforeEach(() => { describe("createAcdpConfigureAction", () => { const workspacePath = createMockDirectory().resolve("/tmp"); - it("", async () => { + it("creates ACDP configure action", async () => { const mockContext = createMockActionContext({ templateInfo: { entityRef: stringifyEntityRef(mockedCatalogEntity), @@ -46,6 +46,7 @@ describe("createAcdpConfigureAction", () => { id: "test", }, }); + await ( await createAcdpConfigureAction({ config: mockConfig, @@ -56,5 +57,7 @@ describe("createAcdpConfigureAction", () => { logger: getVoidLogger(), }) ).handler(mockContext); + + expect(mockedCodeBuildClient.commandCalls).toHaveLength(3); }); }); diff --git a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.ts b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.ts index 111cc44d..f7a2e311 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/actions/acdp-configure.ts @@ -1,20 +1,24 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Config } from "@backstage/config"; -import { createTemplateAction } from "@backstage/plugin-scaffolder-node"; +import { Logger } from "winston"; import { z } from "zod"; -import { DefaultAwsCredentialsManager } from "@backstage/integration-aws-node"; -import { AcdpBuildService } from "../service/acdp-build-service"; + +import { SourceType } from "@aws-sdk/client-codebuild"; + import { UrlReader } from "@backstage/backend-common"; +import { AuthService } from "@backstage/backend-plugin-api"; import { CatalogClient } from "@backstage/catalog-client"; -import { ScmIntegrations } from "@backstage/integration"; -import { Logger } from "winston"; -import { AcdpBuildAction, constants } from "backstage-plugin-acdp-common"; import { Entity, parseEntityRef } from "@backstage/catalog-model"; +import { Config } from "@backstage/config"; +import { ScmIntegrations } from "@backstage/integration"; +import { DefaultAwsCredentialsManager } from "@backstage/integration-aws-node"; +import { createTemplateAction } from "@backstage/plugin-scaffolder-node"; import { JsonObject } from "@backstage/types"; -import { SourceType } from "@aws-sdk/client-codebuild"; -import { AuthService } from "@backstage/backend-plugin-api"; + +import { AcdpBuildAction, constants } from "backstage-plugin-acdp-common"; + +import { AcdpBuildService } from "../service/acdp-build-service"; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -117,7 +121,7 @@ export const createAcdpConfigureAction = async (options: { const environmentVariables = [ { - name: "MODULE_STACK_NAME", + name: constants.MODULE_STACK_NAME_ENVIRONMENT_VARIABLE, value: `${entity?.metadata.namespace}-${entity?.metadata.name}`, }, ]; @@ -150,7 +154,7 @@ export const createAcdpConfigureAction = async (options: { constants.ACDP_DEPLOY_ON_CREATE_ANNOTATION ]; - if (shouldDeployAnnotation == "true") { + if (shouldDeployAnnotation === "true") { await acdpBuildService.startBuild({ entity: entity, action: AcdpBuildAction.DEPLOY, diff --git a/source/modules/backstage/plugins/acdp-backend/src/actions/mocks/actions-mocks.ts b/source/modules/backstage/plugins/acdp-backend/src/actions/mocks/actions-mocks.ts new file mode 100644 index 00000000..a83281dd --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/actions/mocks/actions-mocks.ts @@ -0,0 +1,142 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { PluginEndpointDiscovery } from "@backstage/backend-common"; +import { stringifyEntityRef } from "@backstage/catalog-model"; +import { mockedCatalogEntity } from "../../mocks"; + +export const mockTemplateCatalogCreateInput = { + assetsSourcePath: "dir:../acdp/cms-sample/", + componentId: "cms-sample", + docsSiteSourcePath: "dir:../docs/components/cms-sample/site/", + entity: { + apiVersion: "backstage.io/v1alpha1", + kind: "Component", + metadata: { + annotations: { + "aws.amazon.com/acdp-deploy-on-create": "true", + }, + description: "sample description", + name: "cms-sample", + namespace: "acdp", + }, + spec: { + lifecycle: "experimental", + owner: "test", + type: "service", + }, + }, +}; + +export const mockedTemplateEntity = { + apiVersion: "scaffolder.backstage.io/v1beta3", + kind: "Template", + metadata: { + description: + "A CDK Python app for showing a basic skeleton for a CMS module", + name: "cms-sample", + tags: ["cms", "guide", "sample"], + title: "CMS Sample Module", + }, + spec: { + output: { + links: [ + { + entityRef: stringifyEntityRef(mockedCatalogEntity), + icon: "catalog", + title: "Open in catalog", + }, + ], + }, + owner: "aws solutions", + parameters: [ + { + properties: { + componentId: { + default: "cms-sample", + description: "Unique name of the component", + pattern: "[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z]", + title: "Name", + type: "string", + "ui:field": "EntityNamePicker", + }, + description: { + default: + "A CDK Python app for showing a basic skeleton for a CMS module", + description: "Help others understand what this component is for.", + title: "Description", + type: "string", + }, + owner: { + description: "Owner of the component", + title: "Owner", + type: "string", + "ui:field": "OwnerPicker", + "ui:options": { + catalogFilter: { + kind: ["Group", "User"], + }, + }, + }, + }, + required: ["componentId", "owner"], + title: "Provide the required information", + }, + { + properties: { + appUniqueId: { + default: "cms", + description: + "Application unique identifier used to uniquely name resources within the stack", + title: "App Unique ID", + type: "string", + "ui:disabled": true, + }, + }, + required: ["appUniqueId"], + title: "Provide the Module Configuration", + }, + ], + steps: [ + { + action: "aws:acdp:catalog:create", + id: "acdpCatalogCreate", + input: mockTemplateCatalogCreateInput, + name: "ACDP S3 Catalog Write", + }, + { + action: "catalog:register", + id: "catalogRegister", + input: { + catalogInfoUrl: "https://test", + }, + name: "Backstage Catalog Register", + }, + { + action: "aws:acdp:configure", + id: "acdpConfigureDeploy", + input: { + buildParameters: [ + { + name: "CFN_TEMPLATE_URL", + value: + "https://acdp-assets.s3.us-west-2.amazonaws.com/connected-mobility-solution-on-aws/v0.0.0/cms-sample/cms-sample.template", + }, + { + name: "APP_UNIQUE_ID", + value: "cms", + }, + ], + entityRef: "dummy", + }, + name: "ACDP Deploy", + }, + ], + type: "service", + }, +}; + +export const mockDiscovery: jest.Mocked = { + getBaseUrl: jest.fn().mockResolvedValue("http://localhost:8080/api/acdp"), + getExternalBaseUrl: jest.fn(), +}; diff --git a/source/modules/backstage/plugins/acdp-backend/src/actions/mocks/index.ts b/source/modules/backstage/plugins/acdp-backend/src/actions/mocks/index.ts new file mode 100644 index 00000000..6db499f4 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/actions/mocks/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./actions-mocks"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-base-api.ts b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-base-api.ts new file mode 100644 index 00000000..c4e63154 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-base-api.ts @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Logger } from "winston"; + +import { CatalogClient } from "@backstage/catalog-client"; +import { Entity } from "@backstage/catalog-model"; +import { NotFoundError } from "@backstage/errors"; + +export interface AcdpBaseApiInput { + catalogClient: CatalogClient; +} + +export class AcdpBaseApi { + _logger: Logger; + _catalogClient: CatalogClient; + + public constructor(catalogClient: CatalogClient, logger: Logger) { + this._catalogClient = catalogClient; + this._logger = logger; + } + + public async getEntity( + entityRef: string, + backstageApiToken: string | undefined, + ): Promise { + const getEntityErrorMessage = `Could not find Entity for ref: '${entityRef}'`; + const entity = await this._catalogClient.getEntityByRef(entityRef, { + token: backstageApiToken, + }); + + if (entity === undefined) { + this._logger.error(getEntityErrorMessage); + throw new NotFoundError(getEntityErrorMessage); + } + + return entity; + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.test.ts b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.test.ts index 7ad0d306..36f2a99b 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.test.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.test.ts @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { mockClient } from "aws-sdk-client-mock"; -import { AcdpBuildApi } from "."; import { BatchGetProjectsCommand, CodeBuildClient, @@ -10,21 +8,29 @@ import { BatchGetBuildsCommand, StartBuildCommand, } from "@aws-sdk/client-codebuild"; +import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; +import { mockClient } from "aws-sdk-client-mock"; + +import { AcdpBuildAction, constants } from "backstage-plugin-acdp-common"; + +import { AcdpBuildApi } from "."; +import { getSsmParameterNameForEntitySourceConfig } from "../service/utils"; +import { MockedAcdpBuildService } from "../service/mocks/acdp-build-service.mock"; import { + mockSsmClientGetBuildParameters, mockUrlReader, mockedConfigData, mockedCatalogEntity, - resetMocks, + resetUrlReaderMocks, mockCatalogClient, -} from "../__mocks__/common-mocks"; -import { AcdpBuildAction, constants } from "backstage-plugin-acdp-common"; -import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; -import { MockedAcdpBuildService } from "../service/__mocks__/acdp-build-service.mock"; +} from "../mocks"; const mockedCodeBuildClient = mockClient(CodeBuildClient); const mockedSsmClient = mockClient(SSMClient); + let mockedAcdpBuildService: MockedAcdpBuildService; let acdpBuildApi: AcdpBuildApi; + beforeAll(async () => { mockedAcdpBuildService = new MockedAcdpBuildService(); acdpBuildApi = new AcdpBuildApi( @@ -36,35 +42,18 @@ beforeAll(async () => { beforeEach(() => { mockedCodeBuildClient.reset(); mockedSsmClient.reset(); - resetMocks(); + resetUrlReaderMocks(); }); function setupCommonBuildMocks() { mockedCodeBuildClient.on(StartBuildCommand).resolves({}); - mockedSsmClient - .on(GetParameterCommand, { - Name: mockedAcdpBuildService.getSsmParameterNameForEntityBuildParameters( - mockedCatalogEntity, - ), - }) - .resolves({ - Parameter: { - Value: JSON.stringify([ - { name: "MODULE_STACK_NAME", value: "acdp-cms-sample" }, - { - name: "CFN_TEMPLATE_URL", - value: - "https://acdp-assets.s3.us-west-2.amazonaws.com/connected-mobility-solution-on-aws/vX.X.X/cms-sample/cms-sample.template", - }, - { name: "APP_UNIQUE_ID", value: "cms" }, - ]), - }, - }); + mockSsmClientGetBuildParameters(mockedSsmClient); mockedSsmClient .on(GetParameterCommand, { - Name: mockedAcdpBuildService.getSsmParameterNameForEntitySourceConfig( + Name: getSsmParameterNameForEntitySourceConfig( + mockedConfigData.acdp.buildConfig.buildConfigStoreSsmPrefix, mockedCatalogEntity, ), }) diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.ts b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.ts index a120e9ff..6d53fd02 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-build-api.ts @@ -1,25 +1,25 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { AcdpBuildService } from "../service/acdp-build-service"; import { CatalogClient } from "@backstage/catalog-client"; import { Entity } from "@backstage/catalog-model"; -import { NotFoundError } from "@backstage/errors"; import { AcdpBuildAction, AcdpBuildProject, AcdpBuildProjectBuild, } from "backstage-plugin-acdp-common"; -export class AcdpBuildApi { - private catalogClient: CatalogClient; +import { AcdpBaseApi } from "./acdp-base-api"; +import { AcdpBuildService } from "../service/acdp-build-service"; + +export class AcdpBuildApi extends AcdpBaseApi { private acdpBuildService: AcdpBuildService; public constructor( catalogClient: CatalogClient, acdpBuildService: AcdpBuildService, ) { - this.catalogClient = catalogClient; + super(catalogClient, acdpBuildService._logger); this.acdpBuildService = acdpBuildService; } @@ -28,12 +28,12 @@ export class AcdpBuildApi { ): Promise { const codeBuildProject = await this.acdpBuildService.getProject(entity); - if (codeBuildProject === undefined) return undefined; - - return { - name: codeBuildProject.name, - arn: codeBuildProject.arn, - }; + return codeBuildProject + ? { + name: codeBuildProject.name, + arn: codeBuildProject.arn, + } + : undefined; } public async getBuilds(entity: Entity): Promise { @@ -65,29 +65,15 @@ export class AcdpBuildApi { const build = startBuildResponse.build; - if (build === undefined) return {}; - - return { - id: build.id, - arn: build.arn, - buildNumber: build.buildNumber, - startTime: build.startTime, - endTime: build.endTime, - projectName: build.currentPhase, - }; - } - - public async getEntity( - entityRef: string, - backstageApiToken: string | undefined, - ): Promise { - const entity = await this.catalogClient.getEntityByRef(entityRef, { - token: backstageApiToken, - }); - - if (entity === undefined) - throw new NotFoundError(`Could not find Entity for ref: '${entityRef}'`); - - return entity; + return build + ? { + id: build.id, + arn: build.arn, + buildNumber: build.buildNumber, + startTime: build.startTime, + endTime: build.endTime, + projectName: build.currentPhase, + } + : {}; } } diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.test.ts b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.test.ts new file mode 100644 index 00000000..fa692846 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.test.ts @@ -0,0 +1,88 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { constants } from "backstage-plugin-acdp-common"; + +import { AcdpMetricsApi } from "."; +import { + mockedCatalogEntity, + mockCatalogClient, + mockedApplicationArn, + mockedApplicationTag, +} from "../mocks"; +import { MockedAcdpMetricsService } from "../service/mocks"; + +let mockedAcdpMetricsService: MockedAcdpMetricsService; +let acdpMetricsApi: AcdpMetricsApi; + +beforeAll(async () => { + mockedAcdpMetricsService = new MockedAcdpMetricsService(); + acdpMetricsApi = new AcdpMetricsApi( + mockCatalogClient(mockedCatalogEntity), + mockedAcdpMetricsService, + ); +}); + +describe("AcdpMetricsApi", () => { + describe("getApplicationByEntity", () => { + it("should return application", async () => { + const application = + await acdpMetricsApi.getApplicationByEntity(mockedCatalogEntity); + + expect( + application?.applicationTag?.[ + constants.APP_REGISTRY_AWS_APPLICATION_TAG + ], + ).toEqual( + mockedAcdpMetricsService.mockedApplication.applicationTag?.[ + constants.APP_REGISTRY_AWS_APPLICATION_TAG + ], + ); + expect(application?.arn).toEqual( + mockedAcdpMetricsService.mockedApplication.arn, + ); + }); + }); + + describe("getApplicationByArn", () => { + it("should return application", async () => { + const application = + await acdpMetricsApi.getApplicationByArn(mockedApplicationArn); + + expect( + application?.applicationTag?.[ + constants.APP_REGISTRY_AWS_APPLICATION_TAG + ], + ).toEqual( + mockedAcdpMetricsService.mockedApplication.applicationTag?.[ + constants.APP_REGISTRY_AWS_APPLICATION_TAG + ], + ); + expect(application?.arn).toEqual( + mockedAcdpMetricsService.mockedApplication.arn, + ); + }); + }); + + describe("getNetUnblendedCurrentMonthCost", () => { + it("should return the correct cost for a valid tag", async () => { + const cost = await acdpMetricsApi.getNetUnblendedCurrentMonthCost( + mockedCatalogEntity, + mockedApplicationTag, + ); + + expect(cost).toEqual( + mockedAcdpMetricsService.mockedNetUnblendedCurrentMonthCost, + ); + }); + + it("should return empty cost for an invalid tag", async () => { + const cost = await acdpMetricsApi.getNetUnblendedCurrentMonthCost( + mockedCatalogEntity, + "invalid tag", + ); + + expect(cost).toEqual(""); + }); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.ts b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.ts new file mode 100644 index 00000000..a7458832 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/api/acdp-metrics-api.ts @@ -0,0 +1,59 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { CatalogClient } from "@backstage/catalog-client"; +import { Entity } from "@backstage/catalog-model"; +import { AcdpApplication } from "backstage-plugin-acdp-common"; + +import { AcdpMetricsService } from "../service"; +import { AcdpBaseApi } from "./acdp-base-api"; + +export class AcdpMetricsApi extends AcdpBaseApi { + private acdpMetricsService: AcdpMetricsService; + + public constructor( + catalogClient: CatalogClient, + acdpMetricsService: AcdpMetricsService, + ) { + super(catalogClient, acdpMetricsService._logger); + this.acdpMetricsService = acdpMetricsService; + } + + public async getApplicationByEntity( + entity: Entity, + ): Promise { + const appRegistryApplication = + await this.acdpMetricsService.getApplicationByEntity(entity); + + return appRegistryApplication + ? { + arn: appRegistryApplication.arn, + applicationTag: appRegistryApplication.applicationTag, + } + : undefined; + } + + public async getApplicationByArn( + arn: string, + ): Promise { + const appRegistryApplication = + await this.acdpMetricsService.getApplicationByArn(arn); + + return appRegistryApplication + ? { + arn: appRegistryApplication.arn, + applicationTag: appRegistryApplication.applicationTag, + } + : undefined; + } + + public async getNetUnblendedCurrentMonthCost( + entity: Entity, + awsApplicationTag: string, + ): Promise { + return await this.acdpMetricsService.getNetUnblendedCurrentMonthCost( + entity, + awsApplicationTag, + ); + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/index.ts b/source/modules/backstage/plugins/acdp-backend/src/api/index.ts index 7074a399..57af55c5 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/api/index.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/api/index.ts @@ -2,3 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 export * from "./acdp-build-api"; +export * from "./acdp-metrics-api"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-build-api.mock.ts b/source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-build-api.mock.ts new file mode 100644 index 00000000..a3a2f6f5 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-build-api.mock.ts @@ -0,0 +1,72 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { CatalogClient } from "@backstage/catalog-client"; +import { Entity } from "@backstage/catalog-model"; + +import { MockedAcdpBuildService } from "../../service/mocks"; +import { AcdpBuildApi } from ".."; +import { + AcdpBuildProject, + AcdpBuildProjectBuild, +} from "backstage-plugin-acdp-common"; + +export class MockedAcdpBuildApi extends AcdpBuildApi { + public constructor( + catalogClient: CatalogClient, + acdpBuildService: MockedAcdpBuildService, + ) { + super(catalogClient, acdpBuildService); + } + + public getProject(): Promise { + return Promise.resolve({ + name: "test-project", + arn: "arn:aws:codebuild:us-west-2:111111111111:project/test", + environment: { + type: "LINUX_CONTAINER", + image: "aws/codebuild/amazonlinux2-x86_64-standard:3.0", + computeType: "BUILD_GENERAL1_SMALL", + privilegedMode: false, + imagePullCredentialsType: "CODEBUILD", + }, + created: new Date("2022-05-20T13:58:29.342000-06:00"), + lastModified: new Date("2022-05-20T13:58:29.342000-06:00"), + }); + } + + public getBuilds(entity: Entity): Promise { + if (entity === undefined) return Promise.reject(); + + return Promise.resolve([ + { + arn: "arn:aws:codebuild:us-west-2:111111111111:build/test:test", + buildNumber: 1, + buildStatus: "SUCCEEDED", + currentPhase: "COMPLETED", + endTime: new Date("2022-04-14T23:34:38.397Z"), + startTime: new Date("2022-04-14T23:31:26.086Z"), + }, + { + arn: "arn:aws:codebuild:us-west-2:111111111111:build/test:test", + buildComplete: true, + buildNumber: 2, + buildStatus: "SUCCEEDED", + currentPhase: "COMPLETED", + endTime: new Date("2022-04-14T23:34:38.397Z"), + startTime: new Date("2022-04-14T23:31:26.086Z"), + }, + ]); + } + + public startBuild(entity: Entity): Promise { + return Promise.resolve(entity); + } + + public getEntity( + entityRef: string, + backstageApiToken: string | undefined, + ): Promise { + return super.getEntity(entityRef, backstageApiToken); + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-metrics-api.mock.ts b/source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-metrics-api.mock.ts new file mode 100644 index 00000000..9433c1a2 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/api/mocks/acdp-metrics-api.mock.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { CatalogClient } from "@backstage/catalog-client"; + +import { AcdpApplication } from "backstage-plugin-acdp-common"; + +import { AcdpMetricsApi } from ".."; +import { + mockedApplicationArn, + mockedApplicationTag, + mockedCurrentMonthCost, +} from "../../mocks"; +import { MockedAcdpMetricsService } from "../../service/mocks"; + +export class MockedAcdpMetricsApi extends AcdpMetricsApi { + mockedApplication: AcdpApplication; + + public constructor( + catalogClient: CatalogClient, + acdpMetricsService: MockedAcdpMetricsService, + ) { + super(catalogClient, acdpMetricsService); + this.mockedApplication = { + arn: mockedApplicationArn, + applicationTag: { + awsApplication: mockedApplicationTag, + }, + }; + } + + public getApplicationByEntity(): Promise { + return Promise.resolve(this.mockedApplication); + } + + public getApplicationByArn(): Promise { + return Promise.resolve(this.mockedApplication); + } + + public getNetUnblendedCurrentMonthCost(): Promise { + return Promise.resolve(mockedCurrentMonthCost); + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/api/mocks/index.ts b/source/modules/backstage/plugins/acdp-backend/src/api/mocks/index.ts new file mode 100644 index 00000000..ada6958e --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/api/mocks/index.ts @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./acdp-build-api.mock"; +export * from "./acdp-metrics-api.mock"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/index.ts b/source/modules/backstage/plugins/acdp-backend/src/index.ts index 41eea11f..b94a4f46 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/index.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/index.ts @@ -1,9 +1,9 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -export * from "./service/router"; -export * from "./api/acdp-build-api"; +export * from "./router"; +export * from "./api"; export * from "./actions"; -export * from "./service/acdp-build-service"; +export * from "./service"; export * from "./modules"; export { acdpPlugin as default } from "./plugin"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/mocks/build-mocks.ts b/source/modules/backstage/plugins/acdp-backend/src/mocks/build-mocks.ts new file mode 100644 index 00000000..0ed6187b --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/mocks/build-mocks.ts @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export const mockedConfigData = { + acdp: { + s3Catalog: { + bucketName: "bucket", + prefix: "test/backstage/catalog", + region: "us-west-2", + }, + buildConfig: { + buildConfigStoreSsmPrefix: "/local/backstage/acdp-build", + }, + deploymentDefaults: { + region: "us-west-2", + accountId: "111111111111", + codeBuildProjectArn: + "arn:aws:codebuild:us-west-2:111111111111:project/test", + }, + metrics: { + userAgentString: "local-user-agent", + }, + }, + techdocs: { + generator: { + runIn: "local", + }, + builder: "local", + publisher: { + type: "awsS3", + awsS3: { + bucketName: "bucket", + region: "us-west-2", + bucketRootPath: "test/backstage/techdocs", + }, + }, + }, +}; diff --git a/source/modules/backstage/plugins/acdp-backend/src/mocks/common-mocks.ts b/source/modules/backstage/plugins/acdp-backend/src/mocks/common-mocks.ts new file mode 100644 index 00000000..1ed7e01f --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/mocks/common-mocks.ts @@ -0,0 +1,137 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AwsStub } from "aws-sdk-client-mock"; +import { GetParameterCommand } from "@aws-sdk/client-ssm"; + +import { UrlReader } from "@backstage/backend-common"; +import { + CatalogClient, + CatalogRequestOptions, +} from "@backstage/catalog-client"; +import { + CompoundEntityRef, + Entity, + stringifyEntityRef, +} from "@backstage/catalog-model"; +import { ConfigReader } from "@backstage/config"; +import { ScmIntegrations } from "@backstage/integration"; +import { AwsCredentialProvider } from "@backstage/integration-aws-node"; + +import { mockedConfigData } from "./build-mocks"; +import { getSsmParameterNameForEntityBuildParameters } from "../service/utils"; + +export const mockedCatalogEntity = { + apiVersion: "backstage.io/v1alpha1", + kind: "Component", + metadata: { + uid: "uniqueId", + annotations: { + "aws.amazon.com/acdp-deploy-on-create": "true", + "aws.amazon.com/acdp-deployment-target": "default", + "aws.amazon.com/techdocs-builder": "external", + "backstage.io/techdocs-ref": "dir:.", + "aws.amazon.com/template-entity-ref": "template:default/cms-sample", + "aws.amazon.com/acdp-assets-ref": "dir:assets", + "backstage.io/source-location": + "url:https://test-bucket.s3.us-west-2.amazonaws.com/local/backstage/catalog/acdp/component/cms-sample/assets/", + }, + description: + "A CDK Python app for showing a basic skeleton for a CMS module", + name: "cms-sample", + namespace: "acdp", + }, + spec: { + lifecycle: "experimental", + owner: "group:default/asdf", + type: "service", + }, +}; + +export const mockConfig = new ConfigReader(mockedConfigData); + +export const mockCredentialsProvider = { + sdkCredentialProvider: jest.fn().mockResolvedValue({ + accessKeyId: "asdfasdf", + secretAccessKey: "asdfasdf", + sessionToken: "asdfasdf", + }), +} satisfies AwsCredentialProvider; + +export const mockUrlReader: jest.Mocked = { + readUrl: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), +}; + +export function resetUrlReaderMocks() { + mockUrlReader.readUrl.mockReset(); + mockUrlReader.readTree.mockReset(); + mockUrlReader.search.mockReset(); + + mockUrlReader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue(Buffer.from(JSON.stringify({ a: ["b", 7] }), "utf-8")), + }); +} + +export const mockCatalogClient = ( + entity?: Entity, +): jest.Mocked => { + const mock = { + getEntityByRef: jest.fn(), + getLocationById: jest.fn(), + }; + if (entity) { + const determineReturn = async ( + inputEntityRef: string | CompoundEntityRef, + _?: CatalogRequestOptions, + ) => { + if ( + (typeof inputEntityRef === "string" && + stringifyEntityRef(entity) === inputEntityRef) || + stringifyEntityRef(entity) === + stringifyEntityRef(inputEntityRef as CompoundEntityRef) + ) { + return entity; + } + + return undefined; + }; + mock.getEntityByRef.mockImplementation( + (inputEntityRef, catalogRequestOptions) => + determineReturn(inputEntityRef, catalogRequestOptions), + ); + } + return mock as Partial< + jest.Mocked + > as jest.Mocked; +}; + +export const mockIntegrations = ScmIntegrations.fromConfig(mockConfig); + +export const mockSsmClientGetBuildParameters = ( + mockedSsmClient: AwsStub, +) => { + mockedSsmClient + .on(GetParameterCommand, { + Name: getSsmParameterNameForEntityBuildParameters( + mockedConfigData.acdp.buildConfig.buildConfigStoreSsmPrefix, + mockedCatalogEntity, + ), + }) + .resolves({ + Parameter: { + Value: JSON.stringify([ + { name: "MODULE_STACK_NAME", value: "acdp-cms-sample" }, + { + name: "CFN_TEMPLATE_URL", + value: + "https://acdp-assets.s3.us-west-2.amazonaws.com/connected-mobility-solution-on-aws/vX.X.X/cms-sample/cms-sample.template", + }, + { name: "APP_UNIQUE_ID", value: "cms" }, + ]), + }, + }); +}; diff --git a/source/modules/backstage/plugins/acdp-backend/src/mocks/index.ts b/source/modules/backstage/plugins/acdp-backend/src/mocks/index.ts new file mode 100644 index 00000000..c09d562a --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/mocks/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./build-mocks"; +export * from "./metrics-mocks"; +export * from "./common-mocks"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/mocks/metrics-mocks.ts b/source/modules/backstage/plugins/acdp-backend/src/mocks/metrics-mocks.ts new file mode 100644 index 00000000..4e85ab3f --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/mocks/metrics-mocks.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export const mockedApplicationArn = + "arn:aws:servicecatalog:us-east-2:111111111111:/applications/test-application-id"; +export const mockedApplicationTag = + "arn:aws:resource-groups:us-east-2:111111111111:group/test-application-name/test-application-id"; +export const mockedCurrentMonthCost = "1000"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/modules/scaffolder-acdp-actions-module/module.ts b/source/modules/backstage/plugins/acdp-backend/src/modules/scaffolder-acdp-actions-module/module.ts index b341bffa..24ec9e49 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/modules/scaffolder-acdp-actions-module/module.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/modules/scaffolder-acdp-actions-module/module.ts @@ -25,7 +25,6 @@ export const scaffolderModuleAcdpActions = createBackendModule({ env.registerInit({ deps: { scaffolder: scaffolderActionsExtensionPoint, - // config: coreServices.rootConfig, reader: coreServices.urlReader, discovery: coreServices.discovery, auth: coreServices.auth, diff --git a/source/modules/backstage/plugins/acdp-backend/src/plugin.ts b/source/modules/backstage/plugins/acdp-backend/src/plugin.ts index 37f82d85..b87c8a35 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/plugin.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/plugin.ts @@ -7,9 +7,12 @@ import { } from "@backstage/backend-plugin-api"; import { DefaultAwsCredentialsManager } from "@backstage/integration-aws-node"; import { - createRouter, + createAcdpBuildRouter, + createAcdpMetricsRouter, AcdpBuildApi, AcdpBuildService, + AcdpMetricsService, + AcdpMetricsApi, } from "backstage-plugin-acdp-backend"; import { loggerToWinstonLogger } from "@backstage/backend-common"; import { CatalogClient } from "@backstage/catalog-client"; @@ -45,6 +48,7 @@ export const acdpPlugin = createBackendPlugin({ const integrations = ScmIntegrations.fromConfig(config); + // ACDP Build const acdpBuildService = new AcdpBuildService({ config: config, reader: reader, @@ -54,13 +58,30 @@ export const acdpPlugin = createBackendPlugin({ }); const acdpBuildApi = new AcdpBuildApi(catalogClient, acdpBuildService); httpRouter.use( - await createRouter({ + await createAcdpBuildRouter({ logger: winstonLogger, - config: config, acdpBuildApi: acdpBuildApi, auth: auth, httpAuth: httpAuth, - catalogClient: catalogClient, + }), + ); + + // ACDP Metrics + const acdpMetricsService = new AcdpMetricsService({ + config: config, + awsCredentialsProvider: await credsManager.getCredentialProvider(), + logger: winstonLogger, + }); + const acdpMetricsApi = new AcdpMetricsApi( + catalogClient, + acdpMetricsService, + ); + httpRouter.use( + await createAcdpMetricsRouter({ + logger: winstonLogger, + acdpMetricsApi: acdpMetricsApi, + auth: auth, + httpAuth: httpAuth, }), ); httpRouter.addAuthPolicy({ diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.test.ts b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.test.ts new file mode 100644 index 00000000..3476e259 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.test.ts @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import express, { NextFunction, Request, Response } from "express"; +import request from "supertest"; + +import { getVoidLogger } from "@backstage/backend-common"; + +import { createAcdpBaseRouter } from "."; +import { resetUrlReaderMocks } from "../mocks"; + +let app: express.Express; +const mockIsAuthenticated = (req: Request, _: Response, next: NextFunction) => { + req.user = { token: "test-token" }; + return next(); +}; + +beforeAll(async () => { + const logger = getVoidLogger(); + + const router = await createAcdpBaseRouter({ + logger: logger, + }); + + app = express().use(mockIsAuthenticated, router); +}); + +beforeEach(() => { + resetUrlReaderMocks(); +}); + +describe("GET /health", () => { + it("returns ok", async () => { + const response = await request(app).get("/health"); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: "ok" }); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.ts b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.ts new file mode 100644 index 00000000..77888f0f --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-base-router.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import express from "express"; +import Router from "express-promise-router"; +import { Logger } from "winston"; + +import { AuthService, HttpAuthService } from "@backstage/backend-plugin-api"; + +export interface AcdpBaseRouterOptions { + logger: Logger; +} + +export async function createAcdpBaseRouter( + options: AcdpBaseRouterOptions, +): Promise { + const { logger } = options; + + const router = Router(); + router.use(express.json()); + + router.get("/health", (_, response) => { + logger.info("PONG!"); + response.json({ status: "ok" }); + }); + + return router; +} + +export async function getAuthToken( + httpAuth: HttpAuthService, + auth: AuthService, + req: express.Request, + targetPluginId: string, +): Promise { + const credentials = await httpAuth.credentials(req); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: targetPluginId, + }); + + return token; +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.test.ts b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.test.ts new file mode 100644 index 00000000..48d11973 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.test.ts @@ -0,0 +1,140 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import express, { NextFunction, Request, Response } from "express"; +import request from "supertest"; + +import { getVoidLogger } from "@backstage/backend-common"; +import { mockServices } from "@backstage/backend-test-utils"; +import { stringifyEntityRef } from "@backstage/catalog-model"; + +import { createAcdpBuildRouter } from "."; +import { StartBuildInput } from "../utils"; +import { MockedAcdpBuildService } from "../service/mocks"; +import { MockedAcdpBuildApi } from "../api/mocks"; +import { + mockCatalogClient, + mockedCatalogEntity, + resetUrlReaderMocks, +} from "../mocks"; + +import { AcdpBuildAction } from "backstage-plugin-acdp-common"; + +let app: express.Express; +const mockIsAuthenticated = (req: Request, _: Response, next: NextFunction) => { + req.user = { token: "test-token" }; + return next(); +}; + +beforeAll(async () => { + const logger = getVoidLogger(); + + const acdpBuildService = new MockedAcdpBuildService(); + const router = await createAcdpBuildRouter({ + logger: logger, + acdpBuildApi: new MockedAcdpBuildApi( + mockCatalogClient(mockedCatalogEntity), + acdpBuildService, + ), + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), + }); + + app = express().use(mockIsAuthenticated, router); +}); + +beforeEach(() => { + resetUrlReaderMocks(); +}); + +describe("GET /project", () => { + it("should return 200 status for valid request", async () => { + const response = await request(app).get( + `/project?entityRef=${stringifyEntityRef(mockedCatalogEntity)}`, + ); + + expect(response.status).toEqual(200); + }); +}); + +describe("GET /builds", () => { + it("should return 200 status for valid request", async () => { + const response = await request(app).get( + `/builds?entityRef=${stringifyEntityRef(mockedCatalogEntity)}`, + ); + + expect(response.status).toEqual(200); + }); + + it("should return 400 status for missing entityRef", async () => { + const response = await request(app).get("/builds"); + + expect(response.status).toEqual(400); + }); +}); + +describe("POST /start-build for deploy", () => { + it("should return 200 for request with valid json body", async () => { + const requestBody: StartBuildInput = { + entityRef: stringifyEntityRef(mockedCatalogEntity), + action: AcdpBuildAction.DEPLOY, + }; + const response = await request(app).post("/start-build").send(requestBody); + + expect(response.status).toEqual(200); + }); + + it("should return 400 for request with invalid json body", async () => { + const requestBody: StartBuildInput = { + entityRef: "bad-value", + action: AcdpBuildAction.DEPLOY, + }; + const response = await request(app).post("/start-build").send(requestBody); + + expect(response.status).toEqual(400); + }); +}); + +describe("POST /start-build for Updates", () => { + it("should return 200 for request with valid json body", async () => { + const requestBody: StartBuildInput = { + entityRef: stringifyEntityRef(mockedCatalogEntity), + action: AcdpBuildAction.UPDATE, + }; + const response = await request(app).post("/start-build").send(requestBody); + + expect(response.status).toEqual(200); + }); + + it("should return 400 for request with invalid json body", async () => { + const requestBody: StartBuildInput = { + entityRef: "bad-value", + action: AcdpBuildAction.UPDATE, + }; + const response = await request(app).post("/start-build").send(requestBody); + + expect(response.status).toEqual(400); + }); +}); + +describe("POST /start-build for Teardown", () => { + it("should return 200 for request with valid json body", async () => { + const requestBody: StartBuildInput = { + entityRef: stringifyEntityRef(mockedCatalogEntity), + action: AcdpBuildAction.TEARDOWN, + }; + const response = await request(app).post("/start-build").send(requestBody); + + expect(response.status).toEqual(200); + }); + + it("should return 400 for request with invalid json body", async () => { + const requestBody: StartBuildInput = { + entityRef: "bad-value", + action: AcdpBuildAction.TEARDOWN, + }; + const response = await request(app).post("/start-build").send(requestBody); + + expect(response.status).toEqual(400); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.ts b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.ts new file mode 100644 index 00000000..18265247 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-build-router.ts @@ -0,0 +1,83 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import express from "express"; +import { Logger } from "winston"; + +import { AuthService, HttpAuthService } from "@backstage/backend-plugin-api"; + +import { createAcdpBaseRouter, getAuthToken } from "./acdp-base-router"; +import { AcdpBuildApi } from "../api"; +import { startBuildInputSchema, isValidEntityRef } from "../utils"; + +interface AcdpBuildRouterOptions { + logger: Logger; + acdpBuildApi: AcdpBuildApi; + auth: AuthService; + httpAuth: HttpAuthService; +} + +export async function createAcdpBuildRouter( + options: AcdpBuildRouterOptions, +): Promise { + const { logger, acdpBuildApi, httpAuth, auth } = options; + + const router = await createAcdpBaseRouter({ + logger: logger, + }); + + router.get("/project", async (req, res) => { + const token = await getAuthToken(httpAuth, auth, req, "catalog"); + + const entityRef = req.query.entityRef?.toString() ?? ""; + + if (!isValidEntityRef(entityRef)) { + res.status(400).json({ error: "Missing entityRef" }); + return; + } + + const entity = await acdpBuildApi.getEntity(entityRef, token); + const response = await acdpBuildApi.getProject(entity); + + res.status(200).json(response); + }); + + router.get("/builds", async (req, res) => { + const token = await getAuthToken(httpAuth, auth, req, "catalog"); + + const entityRef = req.query.entityRef?.toString() ?? ""; + + if (!isValidEntityRef(entityRef)) { + res.status(400).json({ error: "Missing entityRef" }); + return; + } + + const entity = await acdpBuildApi.getEntity(entityRef, token); + const response = await acdpBuildApi.getBuilds(entity); + + res.status(200).json(response); + }); + + router.post("/start-build", async (req, res) => { + const token = await getAuthToken(httpAuth, auth, req, "catalog"); + + const parsedBody = startBuildInputSchema.safeParse(req.body); + if (!parsedBody.success) { + logger.error(parsedBody.error.errors); + return res.status(400).json({ error: parsedBody.error.errors }); + } + + const entity = await acdpBuildApi.getEntity( + parsedBody.data.entityRef, + token, + ); + const response = await acdpBuildApi.startBuild( + entity, + parsedBody.data.action, + ); + + return res.status(200).json(response); + }); + + return router; +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.test.ts b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.test.ts new file mode 100644 index 00000000..2ee2b660 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.test.ts @@ -0,0 +1,115 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import express, { NextFunction, Request, Response } from "express"; +import request from "supertest"; + +import { getVoidLogger } from "@backstage/backend-common"; +import { mockServices } from "@backstage/backend-test-utils"; +import { stringifyEntityRef } from "@backstage/catalog-model"; + +import { createAcdpMetricsRouter } from "."; +import { + mockedApplicationArn, + mockedApplicationTag, + mockCatalogClient, + mockedCatalogEntity, + resetUrlReaderMocks, + mockedCurrentMonthCost, +} from "../mocks"; +import { MockedAcdpMetricsService } from "../service/mocks"; +import { MockedAcdpMetricsApi } from "../api/mocks"; + +let app: express.Express; +const mockIsAuthenticated = (req: Request, _: Response, next: NextFunction) => { + req.user = { token: "test-token" }; + return next(); +}; + +let mockedAcdpMetricsApi: MockedAcdpMetricsApi; + +beforeAll(async () => { + const mockedAcdpMetricsService = new MockedAcdpMetricsService(); + mockedAcdpMetricsApi = new MockedAcdpMetricsApi( + mockCatalogClient(mockedCatalogEntity), + mockedAcdpMetricsService, + ); + + const logger = getVoidLogger(); + + const router = await createAcdpMetricsRouter({ + logger: logger, + acdpMetricsApi: mockedAcdpMetricsApi, + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), + }); + + app = express().use(mockIsAuthenticated, router); +}); + +beforeEach(() => { + resetUrlReaderMocks(); +}); + +describe("GET /application/by-entity", () => { + it("should return 200 status for valid request", async () => { + const response = await request(app).get( + `/application/by-entity?entityRef=${stringifyEntityRef( + mockedCatalogEntity, + )}`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockedAcdpMetricsApi.mockedApplication); + }); + + it("should return 400 status for invalid request", async () => { + const response = await request(app).get( + `/application/by-entity?entityRef=invalid-entity-ref`, + ); + + expect(response.status).toEqual(400); + }); +}); + +describe("GET /application/by-arn", () => { + it("should return 200 status for valid request", async () => { + const response = await request(app).get( + `/application/by-arn?arn=${mockedApplicationArn}`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockedAcdpMetricsApi.mockedApplication); + }); + + it("should return 400 status for invalid request", async () => { + const response = await request(app).get( + `/application/by-arn?arb=invalid-application-arn`, + ); + + expect(response.status).toEqual(400); + }); +}); + +describe("GET /cost/current-month-net-unblended", () => { + it("should return 200 status for valid request", async () => { + const response = await request(app).get( + `/cost/current-month-net-unblended?entityRef=${stringifyEntityRef( + mockedCatalogEntity, + )}&awsApplicationTag=${mockedApplicationTag}`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockedCurrentMonthCost); + }); + + it("should return 400 status for invalid request", async () => { + const response = await request(app).get( + `/cost/current-month-net-unblended?entityRef=${stringifyEntityRef( + mockedCatalogEntity, + )}`, + ); + + expect(response.status).toEqual(400); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.ts b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.ts new file mode 100644 index 00000000..fd06aa39 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/acdp-metrics-router.ts @@ -0,0 +1,85 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import express from "express"; +import { Logger } from "winston"; + +import { AuthService, HttpAuthService } from "@backstage/backend-plugin-api"; + +import { createAcdpBaseRouter, getAuthToken } from "./acdp-base-router"; +import { AcdpMetricsApi } from "../api"; +import { isValidApplicationArn, isValidEntityRef } from "../utils"; + +interface AcdpMetricsRouterOptions { + logger: Logger; + acdpMetricsApi: AcdpMetricsApi; + auth: AuthService; + httpAuth: HttpAuthService; +} + +export async function createAcdpMetricsRouter( + options: AcdpMetricsRouterOptions, +): Promise { + const { logger, acdpMetricsApi, httpAuth, auth } = options; + + const router = await createAcdpBaseRouter({ + logger: logger, + }); + + router.get("/application/by-entity", async (req, res) => { + const token = await getAuthToken(httpAuth, auth, req, "catalog"); + + const entityRef = req.query.entityRef?.toString() ?? ""; + + if (!isValidEntityRef(entityRef)) { + res.status(400).json({ error: "Missing or invalid entityRef" }); + return; + } + + const entity = await acdpMetricsApi.getEntity(entityRef, token); + const response = await acdpMetricsApi.getApplicationByEntity(entity); + + res.status(200).json(response); + }); + + router.get("/application/by-arn", async (req, res) => { + const applicationArn = req.query.arn?.toString() ?? ""; + + if (!isValidApplicationArn(applicationArn)) { + res.status(400).json({ error: "Missing or invalid application arn" }); + return; + } + + const response = await acdpMetricsApi.getApplicationByArn(applicationArn); + + res.status(200).json(response); + }); + + router.get("/cost/current-month-net-unblended", async (req, res) => { + const token = await getAuthToken(httpAuth, auth, req, "catalog"); + + const entityRef = req.query.entityRef?.toString() ?? ""; + const awsApplicationTag = req.query.awsApplicationTag?.toString() ?? ""; + + let areQueryParamsValid = true; + if (!isValidEntityRef(entityRef)) { + res.status(400).json({ error: "Missing or invalid entityRef" }); + areQueryParamsValid = false; + } else if (awsApplicationTag === "") { + res.status(400).json({ error: "Missing awsApplicationTag" }); + areQueryParamsValid = false; + } + + if (!areQueryParamsValid) return; + + const entity = await acdpMetricsApi.getEntity(entityRef, token); + const response = await acdpMetricsApi.getNetUnblendedCurrentMonthCost( + entity, + awsApplicationTag, + ); + + res.status(200).json(response); + }); + + return router; +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/router/index.ts b/source/modules/backstage/plugins/acdp-backend/src/router/index.ts new file mode 100644 index 00000000..911c954c --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/router/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./acdp-base-router"; +export * from "./acdp-build-router"; +export * from "./acdp-metrics-router"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-base-service.ts b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-base-service.ts new file mode 100644 index 00000000..b0b9ab1f --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-base-service.ts @@ -0,0 +1,97 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Logger } from "winston"; + +import { Entity } from "@backstage/catalog-model"; +import { Config } from "@backstage/config"; +import { AwsCredentialProvider } from "@backstage/integration-aws-node"; + +import { EnvironmentVariable } from "@aws-sdk/client-codebuild"; +import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm"; + +import { constants, AcdpDeploymentTarget } from "backstage-plugin-acdp-common"; + +import { + getDeploymentTargetForEntity, + getSsmParameterNameForEntityBuildParameters, +} from "./utils"; + +import { awsApiCallWithErrorHandling } from "../utils"; + +export interface AcdpBaseServiceOptions { + config: Config; + awsCredentialsProvider: AwsCredentialProvider; + logger: Logger; + userAgentString: string; +} + +export class AcdpBaseService { + _userAgentString: string; + _deploymentTargets: AcdpDeploymentTarget[]; + _buildConfigSsmPrefix: string; + _awsCredentialsProvider: AwsCredentialProvider; + _logger: Logger; + + constructor(options: AcdpBaseServiceOptions) { + const { config, awsCredentialsProvider, logger, userAgentString } = options; + + const defaultDeploymentTarget = { + name: constants.ACDP_DEFAULT_DEPLOYMENT_TARGET, + awsAccount: config.getString("acdp.deploymentDefaults.accountId"), + awsRegion: config.getString("acdp.deploymentDefaults.region"), + codeBuildArn: config.getString( + "acdp.deploymentDefaults.codeBuildProjectArn", + ), + }; + this._deploymentTargets = [defaultDeploymentTarget]; + this._buildConfigSsmPrefix = config.getString( + "acdp.buildConfig.buildConfigStoreSsmPrefix", + ); + this._userAgentString = userAgentString; + this._awsCredentialsProvider = awsCredentialsProvider; + this._logger = logger; + } + + _getSSMClient(region?: string): SSMClient { + return new SSMClient({ + region: region, + customUserAgent: this._userAgentString, + credentialDefaultProvider: () => + this._awsCredentialsProvider.sdkCredentialProvider, + }); + } + + async _retrieveBuildEnvironmentVariables( + entity: Entity, + ): Promise { + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); + const parameterName = getSsmParameterNameForEntityBuildParameters( + this._buildConfigSsmPrefix, + entity, + ); + + const command = new GetParameterCommand({ + Name: parameterName, + WithDecryption: true, + }); + + const customErrorMessage = "Failed to retrieve build parameters from ssm."; + const ssmClient = this._getSSMClient(deploymentTarget.awsRegion); + const response = await awsApiCallWithErrorHandling( + () => ssmClient.send(command), + customErrorMessage, + this._logger, + ); + if (response.Parameter?.Value) { + const variables: EnvironmentVariable[] = JSON.parse( + response.Parameter.Value, + ); + return variables; + } + throw new Error(`Parameter '${parameterName}' not found or has no value`); + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.test.ts b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.test.ts index 23415f01..274ca974 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.test.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.test.ts @@ -1,12 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { mockClient } from "aws-sdk-client-mock"; -import { - mockedCatalogEntity, - mockedConfigData, - resetMocks, -} from "../__mocks__/common-mocks"; import { BatchGetBuildsCommand, BatchGetProjectsCommand, @@ -14,13 +8,23 @@ import { ListBuildsForProjectCommand, StartBuildCommand, } from "@aws-sdk/client-codebuild"; +import { mockClient } from "aws-sdk-client-mock"; import { GetParameterCommand, PutParameterCommand, SSMClient, } from "@aws-sdk/client-ssm"; + import { AcdpBuildAction, constants } from "backstage-plugin-acdp-common"; -import { MockedAcdpBuildService } from "./__mocks__/acdp-build-service.mock"; + +import { getSsmParameterNameForEntitySourceConfig } from "./utils"; +import { MockedAcdpBuildService } from "./mocks/acdp-build-service.mock"; +import { + mockSsmClientGetBuildParameters, + mockedCatalogEntity, + mockedConfigData, + resetUrlReaderMocks, +} from "../mocks"; const mockedCodeBuildClient = mockClient(CodeBuildClient); const mockedSsmClient = mockClient(SSMClient); @@ -33,7 +37,7 @@ beforeAll(async () => { beforeEach(() => { mockedCodeBuildClient.reset(); mockedSsmClient.reset(); - resetMocks(); + resetUrlReaderMocks(); }); function setupCommonBuildMocks() { @@ -48,29 +52,12 @@ function setupCommonBuildMocks() { Version: 1, }); - mockedSsmClient - .on(GetParameterCommand, { - Name: acdpBuildService.getSsmParameterNameForEntityBuildParameters( - mockedCatalogEntity, - ), - }) - .resolves({ - Parameter: { - Value: JSON.stringify([ - { name: "MODULE_STACK_NAME", value: "acdp-cms-sample" }, - { - name: "CFN_TEMPLATE_URL", - value: - "https://acdp-assets.s3.us-west-2.amazonaws.com/connected-mobility-solution-on-aws/vX.X.X/cms-sample/cms-sample.template", - }, - { name: "APP_UNIQUE_ID", value: "cms" }, - ]), - }, - }); + mockSsmClientGetBuildParameters(mockedSsmClient); mockedSsmClient .on(GetParameterCommand, { - Name: acdpBuildService.getSsmParameterNameForEntitySourceConfig( + Name: getSsmParameterNameForEntitySourceConfig( + mockedConfigData.acdp.buildConfig.buildConfigStoreSsmPrefix, mockedCatalogEntity, ), }) diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.ts b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.ts index c4fdcd58..7c855273 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-build-service.ts @@ -1,8 +1,21 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { Logger } from "winston"; + +import { UrlReader } from "@backstage/backend-common"; +import { Location } from "@backstage/catalog-client"; +import { + Entity, + parseLocationRef, + getEntitySourceLocation, + ANNOTATION_SOURCE_LOCATION, +} from "@backstage/catalog-model"; import { Config } from "@backstage/config"; -import { parse } from "@aws-sdk/util-arn-parser"; +import { InputError } from "@backstage/errors"; +import { ScmIntegrations } from "@backstage/integration"; +import { AwsCredentialProvider } from "@backstage/integration-aws-node"; + import { BatchGetBuildsCommand, BatchGetProjectsCommand, @@ -15,112 +28,56 @@ import { StartBuildCommand, StartBuildCommandOutput, } from "@aws-sdk/client-codebuild"; +import { PutParameterCommand, GetParameterCommand } from "@aws-sdk/client-ssm"; -import { AwsCredentialProvider } from "@backstage/integration-aws-node"; - -import { getLocationForEntity } from "../utils"; import { constants, - AcdpDeploymentTarget, AcdpBuildAction, BuildSourceConfig, } from "backstage-plugin-acdp-common"; -import { Location } from "@backstage/catalog-client"; -import { - Entity, - parseLocationRef, - getEntitySourceLocation, - getCompoundEntityRef, - stringifyEntityRef, - ANNOTATION_SOURCE_LOCATION, -} from "@backstage/catalog-model"; -import { InputError } from "@backstage/errors"; -import { UrlReader } from "@backstage/backend-common"; -import { ScmIntegrations } from "@backstage/integration"; -import { Logger } from "winston"; + +import { awsApiCallWithErrorHandling, getLocationForEntity } from "../utils"; import { - SSMClient, - PutParameterCommand, - GetParameterCommand, -} from "@aws-sdk/client-ssm"; -import * as path from "path"; + formatS3UrlToPath, + getCodeBuildSourceTypeForUrl, + parseCodeBuildArn, + removeUrlPrefix, + getDeploymentTargetForEntity, + getSsmParameterNameForEntityBuildParameters, + getSsmParameterNameForEntitySourceConfig, + updateEnvironmentVariablesForDeploymentTarget, +} from "./utils"; +import { AcdpBaseService } from "./acdp-base-service"; + +export interface AcdpBuildServiceOptions { + config: Config; + reader: UrlReader; + integrations: ScmIntegrations; + awsCredentialsProvider: AwsCredentialProvider; + logger: Logger; +} -export class AcdpBuildService { +export class AcdpBuildService extends AcdpBaseService { private reader: UrlReader; private integrations: ScmIntegrations; - private userAgentString: string; - private deploymentTargets: AcdpDeploymentTarget[]; - private buildParameterSsmPrefix: string; - private awsCredentialsProvider: AwsCredentialProvider; - private ssmClient: SSMClient; - private logger: Logger; - - constructor(options: { - config: Config; - reader: UrlReader; - integrations: ScmIntegrations; - awsCredentialsProvider: AwsCredentialProvider; - logger: Logger; - }) { - const { config, reader, integrations, awsCredentialsProvider, logger } = - options; - - const defaultDeploymentTarget = { - name: constants.ACDP_DEFAULT_DEPLOYMENT_TARGET, - awsAccount: config.getString("acdp.deploymentDefaults.accountId"), - awsRegion: config.getString("acdp.deploymentDefaults.region"), - codeBuildArn: config.getString( - "acdp.deploymentDefaults.codeBuildProjectArn", - ), - }; - this.deploymentTargets = [defaultDeploymentTarget]; - this.buildParameterSsmPrefix = config.getString( - "acdp.buildConfig.buildConfigStoreSsmPrefix", - ); - this.userAgentString = config.getString("acdp.metrics.userAgentString"); - this.reader = reader; - this.integrations = integrations; - this.awsCredentialsProvider = awsCredentialsProvider; - this.ssmClient = new SSMClient({ - customUserAgent: this.userAgentString, - credentialDefaultProvider: () => - this.awsCredentialsProvider.sdkCredentialProvider, + constructor(options: AcdpBuildServiceOptions) { + super({ + ...options, + userAgentString: options.config.getString("acdp.metrics.userAgentString"), }); - this.logger = logger; - } - - private getDeploymentTargetForEntity(entity: Entity) { - const annotations = entity.metadata.annotations!; - - const deploymentTargetName = - annotations[constants.ACDP_DEPLOYMENT_TARGET_ANNOTATION]; - - if (!deploymentTargetName) { - throw new InputError( - `No deployment target is set under annotation '${constants.ACDP_DEPLOYMENT_TARGET_ANNOTATION}'`, - ); - } - - const deploymentTarget = this.deploymentTargets.find( - (deploymentTarget) => deploymentTarget.name === deploymentTargetName, - ); - - if (!deploymentTarget) { - throw new InputError( - `No deployment target found with name '${deploymentTargetName}'`, - ); - } + const { reader, integrations } = options; - return deploymentTarget; + this.reader = reader; + this.integrations = integrations; } - private getCodeBuildClient(region: string) { + private getCodeBuildClient(region?: string): CodeBuildClient { return new CodeBuildClient({ region: region, - customUserAgent: this.userAgentString, + customUserAgent: this._userAgentString, credentialDefaultProvider: () => - this.awsCredentialsProvider.sdkCredentialProvider, + this._awsCredentialsProvider.sdkCredentialProvider, }); } @@ -172,49 +129,66 @@ export class AcdpBuildService { return (await buildspecBody.buffer()).toString(); } catch (err: any) { if (err.name === "NoSuchKey") { - this.logger.error("Buildspec not found"); + this._logger.error("Buildspec not found"); return undefined; - } else { - throw err; } + + throw err; } } public async getProject(entity: Entity): Promise { - const deploymentTarget = this.getDeploymentTargetForEntity(entity); - const codeBuildClient = this.getCodeBuildClient(deploymentTarget.awsRegion); - const { projectName } = this.parseCodeBuildArn( - deploymentTarget.codeBuildArn, + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, ); - - const projectQueryResult = await codeBuildClient.send( - new BatchGetProjectsCommand({ - names: [projectName], - }), + const codeBuildClient = this.getCodeBuildClient(deploymentTarget.awsRegion); + const { projectName } = parseCodeBuildArn(deploymentTarget.codeBuildArn); + + const projectQueryResult = await awsApiCallWithErrorHandling( + () => + codeBuildClient.send( + new BatchGetProjectsCommand({ + names: [projectName], + }), + ), + `Could not get projects with project name: ${projectName}`, + this._logger, ); return projectQueryResult.projects?.[0]; } public async getBuilds(entity: Entity): Promise { - const deploymentTarget = this.getDeploymentTargetForEntity(entity); - const codeBuildClient = this.getCodeBuildClient(deploymentTarget.awsRegion); - const { projectName } = this.parseCodeBuildArn( - deploymentTarget.codeBuildArn, + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, ); - - const buildIds = await codeBuildClient.send( - new ListBuildsForProjectCommand({ - projectName, - }), + const codeBuildClient = this.getCodeBuildClient(deploymentTarget.awsRegion); + const { projectName } = parseCodeBuildArn(deploymentTarget.codeBuildArn); + + const buildIds = await awsApiCallWithErrorHandling( + () => + codeBuildClient.send( + new ListBuildsForProjectCommand({ + projectName, + }), + ), + `Could not list builds with project name: ${projectName}`, + this._logger, ); let builds: Build[] = []; if (buildIds.ids) { - const output = await codeBuildClient.send( - new BatchGetBuildsCommand({ - ids: buildIds.ids, - }), + const output = await awsApiCallWithErrorHandling( + () => + codeBuildClient.send( + new BatchGetBuildsCommand({ + ids: buildIds.ids, + }), + ), + `Could not get builds with build ids: ${buildIds.ids}`, + this._logger, ); builds = output.builds ?? []; } @@ -237,12 +211,15 @@ export class AcdpBuildService { }) { const { entity, action } = options; - const deploymentTarget = this.getDeploymentTargetForEntity(entity); + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); const codeBuildClient = this.getCodeBuildClient(deploymentTarget.awsRegion); const buildspecBody = await this.getBuildspecForAction(action, entity); if (buildspecBody === undefined) { - this.logger.error("No buildspec available for action, can't run build"); + this._logger.error("No buildspec available for action, can't run build"); const output: StartBuildCommandOutput = { build: undefined, $metadata: {}, @@ -251,50 +228,46 @@ export class AcdpBuildService { } const storedEnvironmentVariables = - await this.retrieveBuildEnvironmentVariables(entity); + await this._retrieveBuildEnvironmentVariables(entity); const buildSourceConfig = await this.retrieveBuildSourceConfig(entity); - const environmentVariables = - this.updateEnvironmentVariablesForDeploymentTarget( - storedEnvironmentVariables, - deploymentTarget, - entity, - ); - - return await codeBuildClient.send( - new StartBuildCommand({ - projectName: deploymentTarget.codeBuildArn, - buildspecOverride: buildspecBody, - environmentVariablesOverride: environmentVariables, - sourceTypeOverride: buildSourceConfig.sourceType, - sourceLocationOverride: buildSourceConfig.sourceLocation, - sourceVersion: buildSourceConfig.sourceVersion, - }), + const environmentVariables = updateEnvironmentVariablesForDeploymentTarget( + deploymentTarget, + entity, + storedEnvironmentVariables, ); - } - private parseCodeBuildArn(arn: string): { - accountId: string; - region: string; - service: string; - resource: string; - projectName: string; - } { - const parsedArn = parse(arn); - const resourceParts = parsedArn.resource.split("/"); - const projectName = resourceParts[1].split(":")[0]; - - return { projectName, ...parsedArn }; + return await awsApiCallWithErrorHandling( + () => + codeBuildClient.send( + new StartBuildCommand({ + projectName: deploymentTarget.codeBuildArn, + buildspecOverride: buildspecBody, + environmentVariablesOverride: environmentVariables, + sourceTypeOverride: buildSourceConfig.sourceType, + sourceLocationOverride: buildSourceConfig.sourceLocation, + sourceVersion: buildSourceConfig.sourceVersion, + }), + ), + `Could not start build for project name: ${deploymentTarget.codeBuildArn}`, + this._logger, + ); } public async storeBuildEnvironmentVariables( entity: Entity, variables: EnvironmentVariable[], ) { + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); const serializedVariables = JSON.stringify(variables); - const parameterName = - this.getSsmParameterNameForEntityBuildParameters(entity); + const parameterName = getSsmParameterNameForEntityBuildParameters( + this._buildConfigSsmPrefix, + entity, + ); const command = new PutParameterCommand({ Name: parameterName, @@ -303,108 +276,27 @@ export class AcdpBuildService { Overwrite: true, }); - try { - await this.ssmClient.send(command); - } catch (error) { - this.logger.error("Failed to store build parameters in ssm", error); - throw new Error("Failed to store build parameters"); - } - } - - private async retrieveBuildEnvironmentVariables( - entity: Entity, - ): Promise { - const parameterName = - this.getSsmParameterNameForEntityBuildParameters(entity); - - const command = new GetParameterCommand({ - Name: parameterName, - WithDecryption: true, - }); - - try { - const response = await this.ssmClient.send(command); - if (response.Parameter?.Value) { - const variables: EnvironmentVariable[] = JSON.parse( - response.Parameter.Value, - ); - return variables; - } - throw new Error(`Parameter '${parameterName}' not found or has no value`); - } catch (error) { - this.logger.error("Failed to retrieve build parameters from ssm", error); - throw new Error("Failed to retrieve build parameters"); - } - } - - protected getSsmParameterNameForEntityBuildParameters(entity: Entity) { - const { kind, namespace, name } = getCompoundEntityRef(entity); - return path.posix.join( - this.buildParameterSsmPrefix, - kind.toLowerCase(), - namespace.toLowerCase(), - name.toLowerCase(), - constants.BUILD_PARAMETER_SSM_POSTFIX, - ); - } - - protected getSsmParameterNameForEntitySourceConfig(entity: Entity) { - const { kind, namespace, name } = getCompoundEntityRef(entity); - return path.posix.join( - this.buildParameterSsmPrefix, - kind.toLowerCase(), - namespace.toLowerCase(), - name.toLowerCase(), - constants.BUILD_SOURCE_CONFIG_SSM_POSTFIX, + const ssmClient = this._getSSMClient(deploymentTarget.awsRegion); + await awsApiCallWithErrorHandling( + () => ssmClient.send(command), + `Failed to store build environment variables in ssm with parameter name: ${parameterName}`, + this._logger, ); } - private updateEnvironmentVariablesForDeploymentTarget( - environmentVariables: EnvironmentVariable[], - deploymentTarget: AcdpDeploymentTarget, - entity: Entity, - ) { - if (!environmentVariables) environmentVariables = []; - - const overrideValues = [ - { - name: "AWS_ACCOUNT_ID", - value: deploymentTarget.awsAccount, - }, - { - name: "AWS_REGION", - value: deploymentTarget.awsRegion, - }, - { - name: constants.BACKSTAGE_ENTITY_UID_ENVIRONMENT_VARIABLE, - value: entity.metadata.uid, - }, - { - name: "BACKSTAGE_ENTITY_REF", - value: stringifyEntityRef(entity), - }, - ]; - - for (const variableOverride of overrideValues) { - const variableIndex = environmentVariables.findIndex( - (x) => x.name === variableOverride.name, - ); - if (variableIndex >= 0) { - environmentVariables[variableIndex].value = variableOverride.value; - } else { - environmentVariables.push(variableOverride); - } - } - - return environmentVariables; - } - public async storeBuildSourceConfig( entity: Entity, config: BuildSourceConfig, ) { + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); + const parameterName = getSsmParameterNameForEntitySourceConfig( + this._buildConfigSsmPrefix, + entity, + ); const serializedConfig = JSON.stringify(config); - const parameterName = this.getSsmParameterNameForEntitySourceConfig(entity); const command = new PutParameterCommand({ Name: parameterName, @@ -413,100 +305,69 @@ export class AcdpBuildService { Overwrite: true, }); - try { - await this.ssmClient.send(command); - } catch (error) { - this.logger.error("Failed to store build source config in ssm", error); - throw new Error("Failed to store build source config"); - } + const ssmClient = this._getSSMClient(deploymentTarget.awsRegion); + await awsApiCallWithErrorHandling( + () => ssmClient.send(command), + `Failed to store build source config in ssm with parameter name: ${parameterName}.`, + this._logger, + ); } private async retrieveBuildSourceConfig( entity: Entity, ): Promise { - const parameterName = this.getSsmParameterNameForEntitySourceConfig(entity); + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); + const parameterName = getSsmParameterNameForEntitySourceConfig( + this._buildConfigSsmPrefix, + entity, + ); const command = new GetParameterCommand({ Name: parameterName, WithDecryption: true, }); - try { - const response = await this.ssmClient.send(command); - if (response.Parameter?.Value) { - const storedConfig: BuildSourceConfig = JSON.parse( - response.Parameter.Value, - ); + const ssmClient = this._getSSMClient(deploymentTarget.awsRegion); + const response = await awsApiCallWithErrorHandling( + () => ssmClient.send(command), + `Failed to get build source config from ssm with parameter name: ${parameterName}`, + this._logger, + ); + if (response.Parameter?.Value) { + const storedConfig: BuildSourceConfig = JSON.parse( + response.Parameter.Value, + ); - let config: BuildSourceConfig = storedConfig; + let config: BuildSourceConfig = storedConfig; - if ( - storedConfig.useEntityAssets === true && - entity.metadata.annotations?.[ANNOTATION_SOURCE_LOCATION] !== - undefined - ) { - const catalogItemSourceLocation = removeUrlPrefix( - entity.metadata.annotations[ANNOTATION_SOURCE_LOCATION], - ); - const sourceType = getCodeBuildSourceTypeForUrl( + if ( + storedConfig.useEntityAssets === true && + entity.metadata.annotations?.[ANNOTATION_SOURCE_LOCATION] !== undefined + ) { + const catalogItemSourceLocation = removeUrlPrefix( + entity.metadata.annotations[ANNOTATION_SOURCE_LOCATION], + ); + const sourceType = getCodeBuildSourceTypeForUrl( + catalogItemSourceLocation, + ); + let assetPathCodeBuildLocation: string = catalogItemSourceLocation; + if (sourceType === SourceType.S3) + assetPathCodeBuildLocation = formatS3UrlToPath( catalogItemSourceLocation, ); - let assetPathCodeBuildLocation: string = catalogItemSourceLocation; - if (sourceType == SourceType.S3) - assetPathCodeBuildLocation = formatS3UrlToPath( - catalogItemSourceLocation, - ); - - config = { - useEntityAssets: true, - sourceType: sourceType, - sourceLocation: assetPathCodeBuildLocation, - }; - } - - return config; - } - throw new Error(`Parameter '${parameterName}' not found or has no value`); - } catch (error) { - this.logger.error( - "Failed to retrieve build source config from ssm", - error, - ); - throw new Error("Failed to retrieve build source config"); - } - } -} - -function removeUrlPrefix(input: string): string { - return input.replace(/^url:/, ""); -} -function getCodeBuildSourceTypeForUrl(url: string): SourceType { - const githubPattern = /^https?:\/\/(www\.)?github\.com\/.+\/.+$/; - // NOSONAR - const s3Pattern = - /^https?:\/\/s3[\.-](?:[a-z0-9-]+)\.amazonaws\.com\/.+|https?:\/\/[a-z0-9-]+\.s3[\.-](?:[a-z0-9-]+)\.amazonaws\.com\/.+/; - - if (githubPattern.test(url)) { - return SourceType.GITHUB; - } else if (s3Pattern.test(url)) { - return SourceType.S3; - } else { - return SourceType.NO_SOURCE; - } -} - -function formatS3UrlToPath(url: string): string { - const urlObject = new URL(url); - - let bucket: string; - let path: string = urlObject.pathname.substring(1); + config = { + useEntityAssets: true, + sourceType: sourceType, + sourceLocation: assetPathCodeBuildLocation, + }; + } - if (urlObject.hostname.endsWith("s3.amazonaws.com")) { - bucket = urlObject.hostname.split(".s3.amazonaws.com")[0]; - } else { - bucket = urlObject.hostname.split(".s3.")[0]; + return config; + } + throw new Error(`Parameter '${parameterName}' not found or has no value`); } - - return `${bucket}/${path}`; } diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.test.ts b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.test.ts new file mode 100644 index 00000000..de9d2d77 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.test.ts @@ -0,0 +1,130 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + CostExplorerClient, + GetCostAndUsageCommand, +} from "@aws-sdk/client-cost-explorer"; +import { mockClient } from "aws-sdk-client-mock"; +import { SSMClient } from "@aws-sdk/client-ssm"; +import { + GetApplicationCommand, + ServiceCatalogAppRegistryClient, +} from "@aws-sdk/client-service-catalog-appregistry"; + +import { getVoidLogger } from "@backstage/backend-common"; + +import { constants } from "backstage-plugin-acdp-common"; + +import { + mockedApplicationArn, + mockedApplicationTag, + mockedCurrentMonthCost, + mockConfig, + mockCredentialsProvider, + mockSsmClientGetBuildParameters, + mockedCatalogEntity, +} from "../mocks"; +import { + AcdpMetricsService, + AcdpMetricsServiceOptions, +} from "./acdp-metrics-service"; + +const mockedAppRegistryClient = mockClient(ServiceCatalogAppRegistryClient); +const mockedCostExplorerClient = mockClient(CostExplorerClient); +const mockedSsmClient = mockClient(SSMClient); + +let acdpMetricsService: AcdpMetricsService; +beforeAll(async () => { + const acdpMetricsServiceMockedOptions: AcdpMetricsServiceOptions = { + config: mockConfig, + awsCredentialsProvider: mockCredentialsProvider, + logger: getVoidLogger(), + }; + acdpMetricsService = new AcdpMetricsService(acdpMetricsServiceMockedOptions); +}); + +beforeEach(() => { + mockedSsmClient.reset(); + mockedAppRegistryClient.reset(); + mockedCostExplorerClient.reset(); +}); + +describe("AcdpMetricsService", () => { + describe("getApplicationByEntity", () => { + it("should return application", async () => { + mockSsmClientGetBuildParameters(mockedSsmClient); + + mockedAppRegistryClient.on(GetApplicationCommand).resolves({ + arn: mockedApplicationArn, + applicationTag: { + [constants.APP_REGISTRY_AWS_APPLICATION_TAG]: mockedApplicationTag, + }, + }); + + const application = + await acdpMetricsService.getApplicationByEntity(mockedCatalogEntity); + + expect(mockedSsmClient.calls()).toHaveLength(1); + expect(mockedAppRegistryClient.calls()).toHaveLength(1); + expect( + application?.applicationTag?.[ + constants.APP_REGISTRY_AWS_APPLICATION_TAG + ], + ).toEqual(mockedApplicationTag); + expect(application?.arn).toEqual(mockedApplicationArn); + }); + }); + + describe("getApplicationByArn", () => { + it("should return application", async () => { + mockSsmClientGetBuildParameters(mockedSsmClient); + + mockedAppRegistryClient.on(GetApplicationCommand).resolves({ + arn: mockedApplicationArn, + applicationTag: { + [constants.APP_REGISTRY_AWS_APPLICATION_TAG]: mockedApplicationTag, + }, + }); + + const application = + await acdpMetricsService.getApplicationByEntity(mockedCatalogEntity); + + expect(mockedSsmClient.calls()).toHaveLength(1); + expect(mockedAppRegistryClient.calls()).toHaveLength(1); + expect( + application?.applicationTag?.[ + constants.APP_REGISTRY_AWS_APPLICATION_TAG + ], + ).toEqual(mockedApplicationTag); + expect(application?.arn).toEqual(mockedApplicationArn); + }); + }); + + describe("getNetUnblendedCurrentMonthCost", () => { + it("should return correct cost with valid application tag", async () => { + const mockedOutput = { + ResultsByTime: [ + { + Total: { + NetUnblendedCost: { + Amount: mockedCurrentMonthCost, + }, + }, + }, + ], + }; + mockedCostExplorerClient + .on(GetCostAndUsageCommand) + .resolves(mockedOutput); + + const cost = await acdpMetricsService.getNetUnblendedCurrentMonthCost( + mockedCatalogEntity, + mockedApplicationTag, + ); + + expect(mockedCostExplorerClient.calls()).toHaveLength(1); + expect(cost).toEqual(mockedCurrentMonthCost); + }); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.ts b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.ts new file mode 100644 index 00000000..d6155ec3 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/acdp-metrics-service.ts @@ -0,0 +1,177 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Logger } from "winston"; + +import { Entity } from "@backstage/catalog-model"; +import { Config } from "@backstage/config"; +import { AwsCredentialProvider } from "@backstage/integration-aws-node"; + +import { + Application, + GetApplicationCommand, + ServiceCatalogAppRegistryClient, +} from "@aws-sdk/client-service-catalog-appregistry"; + +import { + GetCostAndUsageCommand, + CostExplorerClient, + GetCostAndUsageCommandInput, +} from "@aws-sdk/client-cost-explorer"; + +import { constants } from "backstage-plugin-acdp-common"; + +import { getDeploymentTargetForEntity, getRegionFromArn } from "./utils"; +import { AcdpBaseService } from "./acdp-base-service"; + +import { awsApiCallWithErrorHandling } from "../utils"; + +export interface AcdpMetricsServiceOptions { + config: Config; + awsCredentialsProvider: AwsCredentialProvider; + logger: Logger; +} + +export class AcdpMetricsService extends AcdpBaseService { + constructor(options: AcdpMetricsServiceOptions) { + super({ + ...options, + userAgentString: options.config.getString("acdp.metrics.userAgentString"), + }); + } + + private getAppRegistryClient( + region?: string, + ): ServiceCatalogAppRegistryClient { + return new ServiceCatalogAppRegistryClient({ + region: region, + customUserAgent: this._userAgentString, + credentialDefaultProvider: () => + this._awsCredentialsProvider.sdkCredentialProvider, + }); + } + + private getCostExplorerClient(region?: string): CostExplorerClient { + return new CostExplorerClient({ + region: region, + customUserAgent: this._userAgentString, + credentialDefaultProvider: () => + this._awsCredentialsProvider.sdkCredentialProvider, + }); + } + + public async getApplicationByEntity(entity: Entity): Promise { + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); + const appRegistryClient = this.getAppRegistryClient( + deploymentTarget.awsRegion, + ); + + const storedEnvironmentVariables = + await this._retrieveBuildEnvironmentVariables(entity); + + // ACDP build action sets a MODULE_STACK_NAME environment variable in the CodeBuild environment, and stores this variable in SSM. This is then used by the deploy buildspecs as the stack name. + // This can be used to determine the application name, but could be incompatible with third-party modules that don't follow this convention. + const moduleStackName = storedEnvironmentVariables.find( + (variable) => + variable.name === constants.MODULE_STACK_NAME_ENVIRONMENT_VARIABLE, + )?.value; + + const applicationName = `${moduleStackName}-${deploymentTarget.awsRegion}-${deploymentTarget.awsAccount}`; + + const getApplicationOutput = await awsApiCallWithErrorHandling( + () => + appRegistryClient.send( + new GetApplicationCommand({ + application: applicationName, // Name, ID, or Arn + }), + ), + `Could not get application from entity with application name: ${applicationName}.`, + this._logger, + ); + + const application: Application = { + arn: getApplicationOutput.arn, + applicationTag: getApplicationOutput.applicationTag, + }; + + return application; + } + + public async getApplicationByArn(arn: string): Promise { + const region = getRegionFromArn(arn); + const appRegistryClient = this.getAppRegistryClient(region); + + const getApplicationOutput = await awsApiCallWithErrorHandling( + () => + appRegistryClient.send( + new GetApplicationCommand({ + application: arn, // Name, ID, or Arn + }), + ), + `Could not get application with application arn: ${arn}.`, + this._logger, + ); + + const application: Application = { + arn: getApplicationOutput.arn, + applicationTag: getApplicationOutput.applicationTag, + }; + + return application; + } + + public async getNetUnblendedCurrentMonthCost( + entity: Entity, + awsApplicationTag: string, + ): Promise { + const deploymentTarget = getDeploymentTargetForEntity( + entity, + this._deploymentTargets, + ); + const costExplorerClient = this.getCostExplorerClient( + deploymentTarget.awsRegion, + ); + + const firstDayOfMonth = new Date(); + firstDayOfMonth.setDate(1); + const lastDayOfMonth = new Date( + firstDayOfMonth.getFullYear(), + firstDayOfMonth.getMonth() + 1, + 1, + ); // Get first day of next month + lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1); // Subtract one day to get last day of current month + + const getCostAndUsageCommandInput: GetCostAndUsageCommandInput = { + TimePeriod: { + Start: firstDayOfMonth.toISOString().split("T")[0], // Format: YYYY-MM-DD + End: lastDayOfMonth.toISOString().split("T")[0], + }, + Granularity: "MONTHLY", + Metrics: ["NetUnblendedCost"], + Filter: { + Tags: { + Key: constants.APP_REGISTRY_AWS_APPLICATION_TAG, + Values: [awsApplicationTag], + MatchOptions: ["EQUALS"], + }, + }, + }; + + const getCostAndUsageOutput = await awsApiCallWithErrorHandling( + () => + costExplorerClient.send( + new GetCostAndUsageCommand(getCostAndUsageCommandInput), + ), + `Could not get cost and usage for awsApplicationTag: ${awsApplicationTag}`, + this._logger, + ); + + return ( + getCostAndUsageOutput.ResultsByTime?.[0]?.Total?.NetUnblendedCost + .Amount ?? "" + ); + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/index.ts b/source/modules/backstage/plugins/acdp-backend/src/service/index.ts new file mode 100644 index 00000000..f143d45c --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./acdp-base-service"; +export * from "./acdp-build-service"; +export * from "./acdp-metrics-service"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-build-service.mock.ts b/source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-build-service.mock.ts new file mode 100644 index 00000000..4a108131 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-build-service.mock.ts @@ -0,0 +1,35 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Logger } from "winston"; + +import { UrlReader, getVoidLogger } from "@backstage/backend-common"; +import { Config } from "@backstage/config"; +import { ScmIntegrations } from "@backstage/integration"; +import { AwsCredentialProvider } from "@backstage/integration-aws-node"; + +import { AcdpBuildService } from "../acdp-build-service"; +import { + mockConfig, + mockCredentialsProvider, + mockIntegrations, + mockUrlReader, +} from "../../mocks"; + +export class MockedAcdpBuildService extends AcdpBuildService { + public constructor( + config?: Config, + reader?: UrlReader, + integrations?: ScmIntegrations, + awsCredentialsProvider?: AwsCredentialProvider, + logger?: Logger, + ) { + super({ + config: config ?? mockConfig, + reader: reader ?? mockUrlReader, + integrations: integrations ?? mockIntegrations, + awsCredentialsProvider: awsCredentialsProvider ?? mockCredentialsProvider, + logger: logger ?? getVoidLogger(), + }); + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-metrics-service.mock.ts b/source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-metrics-service.mock.ts new file mode 100644 index 00000000..2d869e8f --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/mocks/acdp-metrics-service.mock.ts @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Logger } from "winston"; + +import { Application } from "@aws-sdk/client-service-catalog-appregistry"; + +import { getVoidLogger } from "@backstage/backend-common"; +import { Entity } from "@backstage/catalog-model"; +import { Config } from "@backstage/config"; +import { AwsCredentialProvider } from "@backstage/integration-aws-node"; + +import { constants } from "backstage-plugin-acdp-common"; + +import { + mockedApplicationArn, + mockedApplicationTag, + mockedCurrentMonthCost, + mockConfig, + mockCredentialsProvider, + mockedCatalogEntity, +} from "../../mocks"; +import { AcdpMetricsService } from "../acdp-metrics-service"; + +export class MockedAcdpMetricsService extends AcdpMetricsService { + mockedApplication: Application; + mockedApplicationArn: string; + mockedApplicationTag: string; + mockedNetUnblendedCurrentMonthCost: string; + + public constructor( + config?: Config, + awsCredentialsProvider?: AwsCredentialProvider, + logger?: Logger, + ) { + super({ + config: config ?? mockConfig, + awsCredentialsProvider: awsCredentialsProvider ?? mockCredentialsProvider, + logger: logger ?? getVoidLogger(), + }); + + this.mockedApplication = { + arn: mockedApplicationArn, + applicationTag: { + [constants.APP_REGISTRY_AWS_APPLICATION_TAG]: mockedApplicationTag, + }, + }; + this.mockedApplicationArn = mockedApplicationArn; + this.mockedApplicationTag = mockedApplicationTag; + this.mockedNetUnblendedCurrentMonthCost = mockedCurrentMonthCost; + } + + private areEntitiesEqual(entity: Entity): boolean { + return JSON.stringify(entity) === JSON.stringify(mockedCatalogEntity); + } + + public async getApplicationByEntity(entity: Entity): Promise { + return this.areEntitiesEqual(entity) ? this.mockedApplication : {}; + } + + public async getApplicationByArn(arn: string): Promise { + return arn === mockedApplicationArn ? this.mockedApplication : {}; + } + + public async getNetUnblendedCurrentMonthCost( + entity: Entity, + awsApplicationTag: string, + ): Promise { + return awsApplicationTag === this.mockedApplicationTag && + this.areEntitiesEqual(entity) + ? this.mockedNetUnblendedCurrentMonthCost + : ""; + } +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/mocks/index.ts b/source/modules/backstage/plugins/acdp-backend/src/service/mocks/index.ts new file mode 100644 index 00000000..6c1d49d4 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/mocks/index.ts @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./acdp-build-service.mock"; +export * from "./acdp-metrics-service.mock"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/utils/index.ts b/source/modules/backstage/plugins/acdp-backend/src/service/utils/index.ts new file mode 100644 index 00000000..49659926 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/utils/index.ts @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./ssm-helper"; +export * from "./utils"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/utils/ssm-helper.ts b/source/modules/backstage/plugins/acdp-backend/src/service/utils/ssm-helper.ts new file mode 100644 index 00000000..604c649b --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/utils/ssm-helper.ts @@ -0,0 +1,36 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import * as path from "path"; + +import { Entity, getCompoundEntityRef } from "@backstage/catalog-model"; + +import { constants } from "backstage-plugin-acdp-common"; + +export function getSsmParameterNameForEntityBuildParameters( + prefix: string, + entity: Entity, +) { + const { kind, namespace, name } = getCompoundEntityRef(entity); + return path.posix.join( + prefix, + kind.toLowerCase(), + namespace.toLowerCase(), + name.toLowerCase(), + constants.BUILD_PARAMETER_SSM_POSTFIX, + ); +} + +export function getSsmParameterNameForEntitySourceConfig( + prefix: string, + entity: Entity, +) { + const { kind, namespace, name } = getCompoundEntityRef(entity); + return path.posix.join( + prefix, + kind.toLowerCase(), + namespace.toLowerCase(), + name.toLowerCase(), + constants.BUILD_SOURCE_CONFIG_SSM_POSTFIX, + ); +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/service/utils/utils.ts b/source/modules/backstage/plugins/acdp-backend/src/service/utils/utils.ts new file mode 100644 index 00000000..fca731c9 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/service/utils/utils.ts @@ -0,0 +1,135 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { EnvironmentVariable, SourceType } from "@aws-sdk/client-codebuild"; +import { validate, parse } from "@aws-sdk/util-arn-parser"; + +import { Entity, stringifyEntityRef } from "@backstage/catalog-model"; +import { InputError } from "@backstage/errors"; + +import { constants, AcdpDeploymentTarget } from "backstage-plugin-acdp-common"; + +export function getCodeBuildSourceTypeForUrl(url: string): SourceType { + const githubPattern = /^https?:\/\/(www\.)?github\.com\/.+$/; + const s3Pattern = + /^https?:\/\/([a-z0-9-]+\.)?s3[\.-]([a-z0-9-]+\.)?amazonaws\.com\/.+/; + + if (githubPattern.test(url)) { + return SourceType.GITHUB; + } else if (s3Pattern.test(url)) { + return SourceType.S3; + } + + return SourceType.NO_SOURCE; +} + +export function getDeploymentTargetForEntity( + entity: Entity, + deploymentTargets: AcdpDeploymentTarget[], +): AcdpDeploymentTarget { + const annotations = entity.metadata.annotations!; + + const deploymentTargetName = + annotations[constants.ACDP_DEPLOYMENT_TARGET_ANNOTATION]; + + if (!deploymentTargetName) { + throw new InputError( + `No deployment target is set under annotation '${constants.ACDP_DEPLOYMENT_TARGET_ANNOTATION}'`, + ); + } + + const deploymentTarget = deploymentTargets.find( + (acdpDeploymentTarget) => + acdpDeploymentTarget.name === deploymentTargetName, + ); + + if (!deploymentTarget) { + throw new InputError( + `No deployment target found with name '${deploymentTargetName}'`, + ); + } + + return deploymentTarget; +} + +export function formatS3UrlToPath(url: string): string { + const urlObject = new URL(url); + + let bucket: string; + const s3_path: string = urlObject.pathname.substring(1); + + if (urlObject.hostname.endsWith("s3.amazonaws.com")) { + bucket = urlObject.hostname.split(".s3.amazonaws.com")[0]; + } else { + bucket = urlObject.hostname.split(".s3.")[0]; + } + + return `${bucket}/${s3_path}`; +} + +export function getRegionFromArn(arn: string): string { + if (!validate(arn)) + throw new Error(`Value for arn was not a valid ARN: '${arn}'`); + + const parsedArn = parse(arn); + return parsedArn.region; +} + +export function parseCodeBuildArn(arn: string): { + accountId: string; + region: string; + service: string; + resource: string; + projectName: string; +} { + if (!validate(arn)) + throw new Error(`Value for codebuild arn was not a valid ARN: '${arn}'`); + + const parsedArn = parse(arn); + const resourceParts = parsedArn.resource.split("/"); + const projectName = resourceParts[1].split(":")[0]; + + return { projectName, ...parsedArn }; +} + +export function removeUrlPrefix(input: string): string { + return input.replace(/^url:/, ""); +} + +export function updateEnvironmentVariablesForDeploymentTarget( + deploymentTarget: AcdpDeploymentTarget, + entity: Entity, + environmentVariables: EnvironmentVariable[] = [], +) { + const overrideValues = [ + { + name: "AWS_ACCOUNT_ID", + value: deploymentTarget.awsAccount, + }, + { + name: "AWS_REGION", + value: deploymentTarget.awsRegion, + }, + { + name: constants.BACKSTAGE_ENTITY_UID_ENVIRONMENT_VARIABLE, + value: entity.metadata.uid, + }, + { + name: "BACKSTAGE_ENTITY_REF", + value: stringifyEntityRef(entity), + }, + ]; + + for (const variableOverride of overrideValues) { + const variableIndex = environmentVariables.findIndex( + (x) => x.name === variableOverride.name, + ); + if (variableIndex >= 0) { + environmentVariables[variableIndex].value = variableOverride.value; + } else { + environmentVariables.push(variableOverride); + } + } + + return environmentVariables; +} diff --git a/source/modules/backstage/plugins/acdp-backend/src/utils/aws-s3-helper.ts b/source/modules/backstage/plugins/acdp-backend/src/utils/aws-s3-helper.ts index c03f2abd..5ff634c2 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/utils/aws-s3-helper.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/utils/aws-s3-helper.ts @@ -1,6 +1,12 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { Logger } from "winston"; +import createLimiter from "p-limit"; +import recursiveReadDir from "recursive-readdir"; +import path from "path"; +import fs from "fs"; + import { PutObjectCommandInput, DeleteObjectCommand, @@ -8,16 +14,22 @@ import { ListObjectsV2CommandOutput, S3Client, } from "@aws-sdk/client-s3"; - import { Upload } from "@aws-sdk/lib-storage"; -import { Logger } from "winston"; -import createLimiter from "p-limit"; -import recursiveReadDir from "recursive-readdir"; -import path from "path"; -import fs from "fs"; import { Entity, DEFAULT_NAMESPACE } from "@backstage/catalog-model"; +import { awsApiCallWithErrorHandling } from "./error-handling-helper"; + +// Perform rate limited generic operations by passing a function and a list of arguments +const bulkStorageOperation = async ( + operation: (arg: T) => Promise, + args: T[], + { concurrencyLimit } = { concurrencyLimit: 25 }, +) => { + const limiter = createLimiter(concurrencyLimit); + await Promise.all(args.map((arg) => limiter(operation, arg))); +}; + export class AwsS3Helper { private readonly s3Client: S3Client; private readonly bucketName: string; @@ -42,12 +54,15 @@ export class AwsS3Helper { let allObjects: ListObjectsV2CommandOutput; // Iterate through every file in the root of the publisher. do { - allObjects = await this.s3Client.send( - new ListObjectsV2Command({ - Bucket: this.bucketName, - ContinuationToken: nextContinuation, - ...(keyPrefix ? { Prefix: keyPrefix } : {}), - }), + const command = new ListObjectsV2Command({ + Bucket: this.bucketName, + ContinuationToken: nextContinuation, + ...(keyPrefix ? { Prefix: keyPrefix } : {}), + }); + allObjects = await awsApiCallWithErrorHandling( + () => this.s3Client.send(command), + `Could not list objects from bucket name: ${this.bucketName}`, + this.logger, ); objects.push( ...(allObjects.Contents || []) @@ -63,11 +78,16 @@ export class AwsS3Helper { async deleteObjectsFromBucket(objectsToDelete: string[]) { await bulkStorageOperation( async (relativeFilePath) => { - return await this.s3Client.send( - new DeleteObjectCommand({ - Bucket: this.bucketName, - Key: relativeFilePath, - }), + return await awsApiCallWithErrorHandling( + () => + this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: relativeFilePath, + }), + ), + `Could not delete object from bucket name: ${this.bucketName} with key: ${relativeFilePath}`, + this.logger, ); }, objectsToDelete, @@ -117,22 +137,30 @@ export class AwsS3Helper { this.logger.info( `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${fileList.length}`, ); - } catch (e) { - const errorMessage = `Unable to upload file(s) to AWS S3. ${e}`; - this.logger.error(errorMessage); + } catch (error: any) { + const errorMessage = "Unable to upload file(s) to AWS S3."; + this.logger.error(`${errorMessage} Error: ${error}`); throw new Error(errorMessage); } } } -// Perform rate limited generic operations by passing a function and a list of arguments -const bulkStorageOperation = async ( - operation: (arg: T) => Promise, - args: T[], - { concurrencyLimit } = { concurrencyLimit: 25 }, -) => { - const limiter = createLimiter(concurrencyLimit); - await Promise.all(args.map((arg) => limiter(operation, arg))); +/** + * Takes a posix path and returns a lower-cased version of entity's triplet + * with the remaining path in posix. + * + * Path must not include a starting slash. + * + * @example + * lowerCaseEntityTriplet('default/Component/backstage') + * // return default/component/backstage + */ +const lowerCaseEntityTriplet = (posixPath: string): string => { + const [namespace, kind, name, ...rest] = posixPath.split(path.posix.sep); + const lowerNamespace = namespace.toLowerCase(); + const lowerKind = kind.toLowerCase(); + const lowerName = name.toLowerCase(); + return [lowerNamespace, lowerKind, lowerName, ...rest].join(path.posix.sep); }; export const getCloudPathForLocalPath = ( @@ -157,21 +185,3 @@ export const getCloudPathForLocalPath = ( return destinationWithRoot; }; - -/** - * Takes a posix path and returns a lower-cased version of entity's triplet - * with the remaining path in posix. - * - * Path must not include a starting slash. - * - * @example - * lowerCaseEntityTriplet('default/Component/backstage') - * // return default/component/backstage - */ -const lowerCaseEntityTriplet = (posixPath: string): string => { - const [namespace, kind, name, ...rest] = posixPath.split(path.posix.sep); - const lowerNamespace = namespace.toLowerCase(); - const lowerKind = kind.toLowerCase(); - const lowerName = name.toLowerCase(); - return [lowerNamespace, lowerKind, lowerName, ...rest].join(path.posix.sep); -}; diff --git a/source/modules/backstage/plugins/acdp-backend/src/utils/error-handling-helper.ts b/source/modules/backstage/plugins/acdp-backend/src/utils/error-handling-helper.ts new file mode 100644 index 00000000..b13d6e0a --- /dev/null +++ b/source/modules/backstage/plugins/acdp-backend/src/utils/error-handling-helper.ts @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Logger } from "winston"; + +// Log granular error message and error object. +// Throw generic error message with status code. +export async function awsApiCallWithErrorHandling( + apiCall: () => Promise, + customErrorMessage: string, + logger: Logger, +): Promise { + try { + return await apiCall(); + } catch (error: any) { + if (typeof error.message === "string" && typeof error.statusCode === "number") { + logger.error(`${customErrorMessage} Error: ${error}`); + throw new Error(`Error while calling AWS API. Status Code: ${error.statusCode}`); + } else { + logger.error(`${customErrorMessage} Unexpected Error: ${error}`); + throw new Error("Unexpected error while calling AWS API. Status code unknown."); + } + } +} \ No newline at end of file diff --git a/source/modules/backstage/plugins/acdp-backend/src/utils/index.ts b/source/modules/backstage/plugins/acdp-backend/src/utils/index.ts index a297cbf9..91ac36b9 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/utils/index.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/utils/index.ts @@ -4,3 +4,4 @@ export * from "./validators"; export * from "./aws-s3-helper"; export * from "./location-helper"; +export * from "./error-handling-helper"; diff --git a/source/modules/backstage/plugins/acdp-backend/src/utils/location-helper.ts b/source/modules/backstage/plugins/acdp-backend/src/utils/location-helper.ts index a8950390..3cf837db 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/utils/location-helper.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/utils/location-helper.ts @@ -10,25 +10,17 @@ import { InputError } from "@backstage/errors"; import * as path from "path"; -export const getLocationForEntity = ( - location: Location, +const resolvePath = ( baseUrl: string, - scmIntegration: ScmIntegrationRegistry, + assetPath: string, allowUnsafeAccess: boolean, -): Location => { - switch (location.type) { - case "url": - return location; - case "dir": - return transformDirLocation( - baseUrl, - location, - scmIntegration, - allowUnsafeAccess, - ); - default: - throw new Error(`Invalid reference location ${location.type}`); +) => { + if (allowUnsafeAccess) { + // skips relative path check for local filesystem access + return path.resolve(baseUrl, assetPath); } + + return resolveSafeChildPath(baseUrl, assetPath); }; const transformDirLocation = ( @@ -74,15 +66,23 @@ const transformDirLocation = ( } }; -const resolvePath = ( +export const getLocationForEntity = ( + location: Location, baseUrl: string, - assetPath: string, + scmIntegration: ScmIntegrationRegistry, allowUnsafeAccess: boolean, -) => { - if (allowUnsafeAccess) { - //skips relative path check for local filesystem access - return path.resolve(baseUrl, assetPath); - } else { - return resolveSafeChildPath(baseUrl, assetPath); +): Location => { + switch (location.type) { + case "url": + return location; + case "dir": + return transformDirLocation( + baseUrl, + location, + scmIntegration, + allowUnsafeAccess, + ); + default: + throw new Error(`Invalid reference location ${location.type}`); } }; diff --git a/source/modules/backstage/plugins/acdp-backend/src/utils/validators.ts b/source/modules/backstage/plugins/acdp-backend/src/utils/validators.ts index 73d45a0c..fe6e0743 100644 --- a/source/modules/backstage/plugins/acdp-backend/src/utils/validators.ts +++ b/source/modules/backstage/plugins/acdp-backend/src/utils/validators.ts @@ -9,10 +9,23 @@ export const startBuildInputSchema = z.object({ entityRef: z .string() .regex( - /^([A-Za-z0-9][-A-Za-z0-9]*):([A-Za-z0-9][-A-Za-z0-9]*|default)\/([A-Za-z0-9_][-A-Za-z0-9_]*)$/, + /^([A-Za-z0-9][-A-Za-z0-9]*):([A-Za-z0-9][-A-Za-z0-9]*|default)\/(\w[-A-Za-z0-9_]*)$/, "Invalid EntityRef", ), action: z.nativeEnum(AcdpBuildAction), }); export type StartBuildInput = z.infer; + +export function isValidEntityRef(entityRef: string): boolean { + const entityRefPattern = /^[a-zA-Z0-9-_]+:[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+$/; + + return entityRefPattern.test(entityRef); +} + +export function isValidApplicationArn(arn: string): boolean { + const applicationArnPattern = + /^arn:aws:servicecatalog:[a-z]+(-[a-z]+){1,2}-[0-9]{1}:[0-9]{12}:\/applications\/[a-zA-Z0-9-_]{1,256}$/; + + return applicationArnPattern.test(arn); +} diff --git a/source/modules/backstage/plugins/acdp-common/package.json b/source/modules/backstage/plugins/acdp-common/package.json index af05ffa2..b8f42138 100644 --- a/source/modules/backstage/plugins/acdp-common/package.json +++ b/source/modules/backstage/plugins/acdp-common/package.json @@ -1,7 +1,7 @@ { "name": "backstage-plugin-acdp-common", "description": "Common interfaces for ACDP plugins", - "version": "1.2.2", + "version": "2.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,19 +25,20 @@ }, "sideEffects": false, "scripts": { + "start": "backstage-cli package start", "build": "backstage-cli package build", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", + "test": "backstage-cli package test --coverage", + "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean" + "postpack": "backstage-cli package postpack" }, "dependencies": { "@backstage/catalog-model": "^1.5.0", "@aws-sdk/client-codebuild": "^3.621.0" }, "devDependencies": { - "@backstage/cli": "^0.26.10" + "@backstage/cli": "^0.26.11" }, "files": [ "dist" diff --git a/source/modules/backstage/plugins/acdp-common/src/constants.ts b/source/modules/backstage/plugins/acdp-common/src/constants.ts index 5784c3f1..c64ce43c 100644 --- a/source/modules/backstage/plugins/acdp-common/src/constants.ts +++ b/source/modules/backstage/plugins/acdp-common/src/constants.ts @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// Annotation Keys export const ACDP_DEPLOY_ON_CREATE_ANNOTATION = "aws.amazon.com/acdp-deploy-on-create"; export const ACDP_DEPLOYMENT_TARGET_ANNOTATION = @@ -11,14 +12,16 @@ export const ACDP_UPDATE_BUILDSPEC_ANNOTATION = "aws.amazon.com/acdp-update-buildspec"; export const ACDP_TEARDOWN_BUILDSPEC_ANNOTATION = "aws.amazon.com/acdp-teardown-buildspec"; +export const APP_REGISTRY_APPLICATION_ARN_ANNOTATION = + "aws.amazon.com/app-registry-application-arn"; export const BACKSTAGE_TECHDOCS_ANNOTATION = "backstage.io/techdocs-ref"; -export const ACDP_DEFAULT_DEPLOYMENT_TARGET = "default"; export const ACDP_ASSETS_REF = "aws.amazon.com/acdp-assets-ref"; export const ACDP_ASSETS_STORED = "aws.amazon.com/acdp-assets-stored"; -export const BACKSTAGE_ENTITY_UID_ENVIRONMENT_VARIABLE = "BACKSTAGE_ENTITY_UID"; +// Anotation Values +export const ACDP_DEFAULT_DEPLOYMENT_TARGET = "default"; export const ACDP_DEFAULT_DEPLOY_BUILDSPEC_LOCATION = "dir:./.acdp/deploy.buildspec.yaml"; export const ACDP_DEFAULT_UPDATE_BUILDSPEC_LOCATION = @@ -26,5 +29,13 @@ export const ACDP_DEFAULT_UPDATE_BUILDSPEC_LOCATION = export const ACDP_DEFAULT_TEARDOWN_BUILDSPEC_LOCATION = "dir:./.acdp/teardown.buildspec.yaml"; +// Environment Variable Keys +export const BACKSTAGE_ENTITY_UID_ENVIRONMENT_VARIABLE = "BACKSTAGE_ENTITY_UID"; +export const MODULE_STACK_NAME_ENVIRONMENT_VARIABLE = "MODULE_STACK_NAME"; + +// SSM Names export const BUILD_PARAMETER_SSM_POSTFIX = "build-parameters"; export const BUILD_SOURCE_CONFIG_SSM_POSTFIX = "source-config"; + +// Tag Keys +export const APP_REGISTRY_AWS_APPLICATION_TAG = "awsApplication"; diff --git a/source/modules/backstage/plugins/acdp-common/src/index.ts b/source/modules/backstage/plugins/acdp-common/src/index.ts index 8af8f327..5b3f56e2 100644 --- a/source/modules/backstage/plugins/acdp-common/src/index.ts +++ b/source/modules/backstage/plugins/acdp-common/src/index.ts @@ -2,4 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from "./interfaces/acdp-build"; +export * from "./interfaces/acdp-ops-metrics"; export * as constants from "./constants"; +export * from "./schemas/PartnerOfferingEntityV1beta1"; diff --git a/source/modules/backstage/plugins/acdp-common/src/interfaces/acdp-ops-metrics.ts b/source/modules/backstage/plugins/acdp-common/src/interfaces/acdp-ops-metrics.ts new file mode 100644 index 00000000..3d4e7171 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-common/src/interfaces/acdp-ops-metrics.ts @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// For now all we need is Arn, but later on we might need more information from the Application +export interface AcdpApplication { + arn?: string; + applicationTag?: { + [key: string]: string; + }; +} diff --git a/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOffering.v1beta1.schema.json b/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOffering.v1beta1.schema.json new file mode 100644 index 00000000..1ba3f116 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOffering.v1beta1.schema.json @@ -0,0 +1,90 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "PartnerOfferingV1Beta1", + "description": "A Partner Offering describes a service or application available as a partner solution.", + "examples": [ + { + "apiVersion": "aws.amazon.com/v1beta1", + "kind": "PartnerOffering", + "metadata": { + "name": "partner-offering-name", + "title": "Partner Offering Name", + "description": "This is a partner offering.", + "tags": [ + "service", + "automotive" + ] + }, + "spec": { + "owner": "automotive-team", + "type": "service" + } + } + ], + "allOf": [ + { + "$ref": "Entity" + }, + { + "type": "object", + "required": [ + "spec" + ], + "properties": { + "apiVersion": { + "enum": [ + "aws.amazon.com/v1beta1" + ] + }, + "kind": { + "enum": [ + "PartnerOffering" + ] + }, + "spec": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of software for the partner offering.", + "examples": [ + "service", + "application", + "module" + ], + "minLength": 1 + }, + "author": { + "type": "string", + "description": "The author of the partner offering", + "minLength": 1, + "examples": [ + "aws" + ] + }, + "authorPageUrl": { + "type": "string", + "description": "The URL to the info page for the author of the partner offering", + "minLength": 1, + "examples": [ + "https://aws.amazon.com/marketplace/seller-profile?id=abcd" + ] + }, + "url": { + "type": "string", + "description": "The URL to the source/frontpage of the partner offering", + "minLength": 1, + "examples": [ + "https://aws.amazon.com/marketplace/pp/prodview-abcd", + "https://github.com/aws-solutions/connected-mobility-solution-on-aws" + ] + } + } + } + } + } + ] +} diff --git a/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.test.ts b/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.test.ts new file mode 100644 index 00000000..6b0bb8bb --- /dev/null +++ b/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.test.ts @@ -0,0 +1,96 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { entityKindSchemaValidator } from "@backstage/catalog-model"; +import type { PartnerOfferingEntityV1beta1 } from "./PartnerOfferingEntityV1beta1"; +import schema from "./PartnerOffering.v1beta1.schema.json"; + +const validator = entityKindSchemaValidator(schema); + +describe("partnerOfferingEntityV1beta1Validator", () => { + let entity: PartnerOfferingEntityV1beta1; + + beforeEach(() => { + entity = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "test", + }, + spec: { + type: "website", + author: "team-b", + }, + }; + }); + + it("happy path: accepts valid data", async () => { + expect(validator(entity)).toBe(entity); + }); + + it("ignores unknown apiVersion", async () => { + (entity as any).apiVersion = "aws.amazon.com/v1beta0"; + expect(validator(entity)).toBe(false); + }); + + it("ignores unknown kind", async () => { + (entity as any).kind = "Wizard"; + expect(validator(entity)).toBe(false); + }); + + it("rejects missing type", async () => { + delete (entity as any).spec.type; + expect(() => validator(entity)).toThrow(/type/); + }); + + it("accepts any other type", async () => { + (entity as any).spec.type = "hallo"; + expect(validator(entity)).toBe(entity); + }); + + it("accepts missing parameters", async () => { + delete (entity as any).spec.parameters; + expect(validator(entity)).toBe(entity); + }); + + it("accepts missing outputs", async () => { + delete (entity as any).spec.outputs; + expect(validator(entity)).toBe(entity); + }); + + it("rejects empty type", async () => { + (entity as any).spec.type = ""; + expect(() => validator(entity)).toThrow(/type/); + }); + + it("accepts missing author", async () => { + delete (entity as any).spec.author; + expect(validator(entity)).toBe(entity); + }); + + it("rejects empty author", async () => { + (entity as any).spec.author = ""; + expect(() => validator(entity)).toThrow(/author/); + }); + + it("rejects wrong type author", async () => { + (entity as any).spec.author = 5; + expect(() => validator(entity)).toThrow(/author/); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.ts b/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.ts new file mode 100644 index 00000000..a8308edb --- /dev/null +++ b/source/modules/backstage/plugins/acdp-common/src/schemas/PartnerOfferingEntityV1beta1.ts @@ -0,0 +1,88 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Entity, + entityKindSchemaValidator, + KindValidator, +} from "@backstage/catalog-model"; +import schema from "./PartnerOffering.v1beta1.schema.json"; + +/** + * Backstage catalog PartnerOffering kind Entity. + * PartnerOfferings are used by the Partners page to display available Partner Offerings + * @public + */ +export interface PartnerOfferingEntityV1beta1 extends Entity { + /** + * The apiVersion string of the TaskSpec. + */ + apiVersion: "aws.amazon.com/v1beta1"; + /** + * The kind of the entity + */ + kind: "PartnerOffering"; + /** + * The specification of the PartnerOffering Entity + */ + spec: { + /** + * The type of software that the PartnerOffering is. For example service, service or application. + */ + type: string; + + /** + * The author of the PartnerOfferingEntity + */ + author?: string; + + /** + * The url to the author page for the PartnerOfferingEntity + */ + authorPageUrl?: string; + + /** + * The url to the source or marketplace page for the partner offering + */ + url?: string; + }; +} + +const validator = entityKindSchemaValidator(schema); + +/** + * Entity data validator for {@link PartnerOfferingEntityV1beta1}. + * + * @public + */ +export const partnerOfferingEntityV1beta1Validator: KindValidator = { + async check(data: Entity) { + return validator(data) === data; + }, +}; + +/** + * Typeguard for filtering entities and ensuring v1beta3 entities + * @public + */ +export const isPartnerOfferingEntityV1beta1 = ( + entity: Entity, +): entity is PartnerOfferingEntityV1beta1 => + entity.apiVersion === "aws.amazon.com/v1beta1" && + entity.kind === "PartnerOffering"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/.eslintrc.js b/source/modules/backstage/plugins/acdp-partner-offering-backend/.eslintrc.js new file mode 100644 index 00000000..709e25dd --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/.eslintrc.js @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +module.exports = require("@backstage/cli/config/eslint-factory")(__dirname); diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/README.md b/source/modules/backstage/plugins/acdp-partner-offering-backend/README.md new file mode 100644 index 00000000..26b6134e --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/README.md @@ -0,0 +1,14 @@ +# acdp-partner-offering + +Welcome to the acdp-partner-offering backend plugin! + +This plugin was created through the Backstage CLI + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn +start` in the root directory, and then navigating to [/acdpPartnerOfferingPlugin/health](http://localhost:7007/api/acdpPartnerOfferingPlugin/health). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/dev/index.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/dev/index.ts new file mode 100644 index 00000000..0bedec5d --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/dev/index.ts @@ -0,0 +1,12 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createBackend } from "@backstage/backend-defaults"; + +const backend = createBackend(); + +backend.add(import("@backstage/plugin-auth-backend")); +backend.add(import("@backstage/plugin-auth-backend-module-guest-provider")); +backend.add(import("../src")); + +backend.start(); diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/package.json b/source/modules/backstage/plugins/acdp-partner-offering-backend/package.json new file mode 100644 index 00000000..3555e7e7 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/package.json @@ -0,0 +1,52 @@ +{ + "name": "backstage-plugin-acdp-partner-offering-backend", + "version": "2.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test --coverage", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/backend-common": "^0.23.3", + "@backstage/backend-defaults": "^0.4.1", + "@backstage/backend-plugin-api": "^0.7.0", + "@backstage/catalog-model": "^1.5.0", + "@backstage/config": "^1.2.0", + "@backstage/plugin-catalog-common": "^1.0.25", + "@backstage/plugin-catalog-node": "^1.12.4", + "backstage-plugin-acdp-common": "*", + "@types/express": "*", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "node-fetch": "^2.6.7", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.26.11", + "@backstage/plugin-auth-backend": "^0.22.9", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.8", + "@types/supertest": "^2.0.12", + "msw": "^1.0.0", + "supertest": "^6.2.4" + }, + "files": [ + "dist" + ] +} diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/src/index.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/index.ts new file mode 100644 index 00000000..bae64b38 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/index.ts @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from "./processor"; +export { partnerOfferingEntityModel as default } from "./module"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/src/module.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/module.ts new file mode 100644 index 00000000..aa240800 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/module.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackendModule } from "@backstage/backend-plugin-api"; +import { catalogProcessingExtensionPoint } from "@backstage/plugin-catalog-node/alpha"; +import { PartnerOfferingEntitiesProcessor } from "./processor"; + +/** + * Registers support for the partner offering specific entity model (e.g. the Partner Offering + * kind) to the catalog backend plugin. + * + * @public + */ +export const partnerOfferingEntityModel = createBackendModule({ + pluginId: "catalog", + moduleId: "acdp-partner-offering-backend", + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addProcessor(new PartnerOfferingEntitiesProcessor()); + }, + }); + }, +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.test.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.test.ts new file mode 100644 index 00000000..ec6546ae --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.test.ts @@ -0,0 +1,69 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; +import { PartnerOfferingEntitiesProcessor } from "./PartnerOfferingEntitiesProcessor"; + +const mockLocation = { type: "a", target: "b" }; +const mockEntity: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "n" }, + spec: { + type: "service", + author: "o", + }, +}; + +describe("ScaffolderEntitiesProcessor", () => { + describe("validateEntityKind", () => { + it("validates the entity kind", async () => { + const processor = new PartnerOfferingEntitiesProcessor(); + + await expect(processor.validateEntityKind(mockEntity)).resolves.toBe( + true, + ); + await expect( + processor.validateEntityKind({ + ...mockEntity, + apiVersion: "aws.amazon.com/v1beta0", + }), + ).resolves.toBe(false); + await expect( + processor.validateEntityKind({ ...mockEntity, kind: "Component" }), + ).resolves.toBe(false); + }); + }); + + describe("postProcessEntity", () => { + it("generates relations for component entities", async () => { + const processor = new PartnerOfferingEntitiesProcessor(); + + const emit = jest.fn(); + + const entity = await processor.postProcessEntity( + mockEntity, + mockLocation, + emit, + ); + + expect(entity).toBeDefined(); + }); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.ts new file mode 100644 index 00000000..7f5a79ac --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/PartnerOfferingEntitiesProcessor.ts @@ -0,0 +1,57 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from "@backstage/catalog-model"; +import { + CatalogProcessor, + CatalogProcessorEmit, +} from "@backstage/plugin-catalog-node"; +import { LocationSpec } from "@backstage/plugin-catalog-common"; +import { partnerOfferingEntityV1beta1Validator } from "backstage-plugin-acdp-common"; + +/** + * Adds support for partner offering specific entity kinds to the catalog. + * + * @public + */ +export class PartnerOfferingEntitiesProcessor implements CatalogProcessor { + getProcessorName(): string { + return "PartnerOfferingEntitiesProcessor"; + } + + private readonly validators = [partnerOfferingEntityV1beta1Validator]; + + async validateEntityKind(entity: Entity): Promise { + for (const validator of this.validators) { + if (await validator.check(entity)) { + return true; + } + } + + return false; + } + + async postProcessEntity( + entity: Entity, + _location: LocationSpec, + _emit: CatalogProcessorEmit, + ): Promise { + return entity; + } +} diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/index.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/index.ts new file mode 100644 index 00000000..28c214bc --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/processor/index.ts @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { PartnerOfferingEntitiesProcessor } from "./PartnerOfferingEntitiesProcessor"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering-backend/src/setupTests.ts b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/setupTests.ts new file mode 100644 index 00000000..5c787755 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering-backend/src/setupTests.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export {}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/.eslintrc.js b/source/modules/backstage/plugins/acdp-partner-offering/.eslintrc.js new file mode 100644 index 00000000..709e25dd --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/.eslintrc.js @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +module.exports = require("@backstage/cli/config/eslint-factory")(__dirname); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/README.md b/source/modules/backstage/plugins/acdp-partner-offering/README.md new file mode 100644 index 00000000..42d58b51 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/README.md @@ -0,0 +1,15 @@ +# acdp-partner-offering + +Welcome to the acdp-partner-offering plugin! + +This plugin was created through the Backstage CLI + +## Getting started + +Your plugin has been added to the example app in this repository, +meaning you'll be able to access it by running `yarn start` in the root directory, +and then navigating to [/acdp-partner-offering](http://localhost:3000/acdp-partner-offering). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. diff --git a/source/modules/backstage/plugins/acdp-partner-offering/dev/index.tsx b/source/modules/backstage/plugins/acdp-partner-offering/dev/index.tsx new file mode 100644 index 00000000..d423a231 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/dev/index.tsx @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; +import { createDevApp } from "@backstage/dev-utils"; +import { + acdpPartnerOfferingPlugin, + AcdpPartnerOfferingPage, +} from "../src/plugin"; + +createDevApp() + .registerPlugin(acdpPartnerOfferingPlugin) + .addPage({ + element: , + title: "Root Page", + path: "/acdp-partner-offering", + }) + .render(); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/package.json b/source/modules/backstage/plugins/acdp-partner-offering/package.json new file mode 100644 index 00000000..787ab309 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/package.json @@ -0,0 +1,58 @@ +{ + "name": "backstage-plugin-acdp-partner-offering", + "version": "2.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "sideEffects": false, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test --coverage", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/catalog-model": "^1.5.0", + "@backstage/core-app-api": "^1.14.1", + "@backstage/core-components": "^0.14.9", + "@backstage/core-plugin-api": "^1.9.3", + "@backstage/plugin-catalog": "^1.21.1", + "@backstage/plugin-catalog-react": "^1.12.2", + "@backstage/theme": "^0.5.6", + "backstage-plugin-acdp-common": "*", + "@material-ui/core": "^4.9.13", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.60", + "lodash": "^4.17.21", + "msw": "^1.0.0", + "react-use": "^17.2.4", + "zen-observable": "^0.10.0" + }, + "peerDependencies": { + "react": "^18.0.2" + }, + "devDependencies": { + "@backstage/cli": "^0.26.11", + "@backstage/dev-utils": "^1.0.36", + "@backstage/plugin-permission-react": "^0.4.24", + "@backstage/test-utils": "^1.5.9", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.test.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.test.tsx new file mode 100644 index 00000000..0104d5f4 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.test.tsx @@ -0,0 +1,186 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from "react"; +import { fireEvent } from "@testing-library/react"; +import { CardHeader } from "./CardHeader"; +import { ThemeProvider } from "@material-ui/core/styles"; +import { lightTheme } from "@backstage/theme"; +import { + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from "@backstage/test-utils"; +import { starredEntitiesApiRef } from "@backstage/plugin-catalog-react"; +import { DefaultStarredEntitiesApi } from "@backstage/plugin-catalog"; +import Observable from "zen-observable"; +import { stringifyEntityRef } from "@backstage/catalog-model"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +describe("CardHeader", () => { + it("should select the correct theme from the theme provider from the header", async () => { + // Can't really test what we want here. + // But we can check that we call the getPage theme with the right type of partner offering at least. + const mockTheme = { + ...lightTheme, + getPageTheme: jest.fn(lightTheme.getPageTheme), + }; + + await renderInTestApp( + + + + + , + ); + + expect(mockTheme.getPageTheme).toHaveBeenCalledWith({ themeId: "service" }); + }); + + it("should render the type", async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText("service")).toBeInTheDocument(); + }); + + it("should enable favoriting of the entity", async () => { + const starredEntitiesApi = { + starredEntitie$: () => new Observable(() => {}), + toggleStarred: jest.fn(async () => {}), + }; + + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob" }, + spec: { + type: "service", + }, + }; + + const { getByRole } = await renderInTestApp( + + + , + ); + + const favorite = getByRole("button", { name: "Add to favorites" }); + + await fireEvent.click(favorite); + + expect(starredEntitiesApi.toggleStarred).toHaveBeenCalledWith( + stringifyEntityRef(mockPartnerOffering), + ); + }); + + it("should render the name of the entity", async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText("bob")).toBeInTheDocument(); + }); + + it("should render the title of the entity in favor of the name if it is provided", async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText("Iamtitle")).toBeInTheDocument(); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.tsx new file mode 100644 index 00000000..f33408ed --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardHeader.tsx @@ -0,0 +1,84 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from "react"; +import { Theme, makeStyles, useTheme } from "@material-ui/core/styles"; +import { ItemCardHeader } from "@backstage/core-components"; +import { FavoriteEntity } from "@backstage/plugin-catalog-react"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +const useStyles = makeStyles< + Theme, + { + cardFontColor: string; + cardBackgroundImage: string; + } +>(() => ({ + header: { + backgroundImage: ({ cardBackgroundImage }) => cardBackgroundImage, + color: ({ cardFontColor }) => cardFontColor, + }, + subtitleWrapper: { + display: "flex", + justifyContent: "space-between", + }, +})); + +/** + * Props for the CardHeader component + */ +export interface CardHeaderProps { + partnerOffering: PartnerOfferingEntityV1beta1; +} + +/** + * The Card Header with the background for the PartnerOfferingCard. + */ +export const CardHeader = (props: CardHeaderProps) => { + const { + partnerOffering: { + metadata: { title, name }, + spec: { type }, + }, + } = props; + const { getPageTheme } = useTheme(); + const themeForType = getPageTheme({ themeId: type }); + + const styles = useStyles({ + cardFontColor: themeForType.fontColor, + cardBackgroundImage: themeForType.backgroundImage, + }); + + const SubtitleComponent = ( +
+
{type}
+
+ +
+
+ ); + + return ( + + ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardLink.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardLink.tsx new file mode 100644 index 00000000..eea76fcf --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/CardLink.tsx @@ -0,0 +1,49 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconComponent } from "@backstage/core-plugin-api"; +import { Link } from "@backstage/core-components"; +import { makeStyles } from "@material-ui/core/styles"; +import React from "react"; + +interface CardLinkProps { + icon: IconComponent; + text: string; + url: string; +} + +const useStyles = makeStyles(() => ({ + linkText: { + display: "inline-flex", + alignItems: "center", + }, +})); + +export const CardLink = ({ icon: Icon, text, url }: CardLinkProps) => { + const styles = useStyles(); + + return ( +
+ + + {text || url} + +
+ ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.test.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.test.tsx new file mode 100644 index 00000000..32ece5af --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.test.tsx @@ -0,0 +1,384 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DefaultStarredEntitiesApi } from "@backstage/plugin-catalog"; +import { + entityRouteRef, + starredEntitiesApiRef, +} from "@backstage/plugin-catalog-react"; +import { + MockPermissionApi, + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from "@backstage/test-utils"; +import { PartnerOfferingCard } from "./PartnerOfferingCard"; +import React from "react"; +import { RELATION_OWNED_BY } from "@backstage/catalog-model"; +import { fireEvent } from "@testing-library/react"; +import { permissionApiRef } from "@backstage/plugin-permission-react"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +describe("PartnerOfferingCard", () => { + it("should render the card title", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob" }, + spec: { + type: "service", + }, + }; + + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText("bob")).toBeInTheDocument(); + }); + + it("should render the description as markdown", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob", description: "hello **test**" }, + spec: { + type: "service", + }, + }; + + const { getByText, getByTestId } = await renderInTestApp( + + + , + ); + + const description = getByText("hello"); + expect(description.querySelector("strong")).toBeInTheDocument(); + expect(getByTestId("partnerOffering-card-separator")).toBeInTheDocument(); + }); + + it("should render no description if none is provided through the partner offering", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob" }, + spec: { + type: "service", + }, + }; + + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText("No description")).toBeInTheDocument(); + }); + + it("should not render extra separators when tags or links are not present", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob" }, + spec: { + type: "service", + }, + }; + + const { queryByTestId } = await renderInTestApp( + + + , + ); + + expect(queryByTestId("partnerOffering-card-separator")).toBeInTheDocument(); + expect( + queryByTestId("partnerOffering-card-separator--tags"), + ).not.toBeInTheDocument(); + expect( + queryByTestId("partnerOffering-card-separator--links"), + ).not.toBeInTheDocument(); + }); + + it("should render the tags", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob", tags: ["cpp", "react"] }, + spec: { + type: "service", + }, + }; + + const { getByText, queryByTestId } = await renderInTestApp( + + + , + ); + + for (const tag of mockPartnerOffering.metadata.tags!) { + expect(getByText(tag)).toBeInTheDocument(); + } + expect( + queryByTestId("partnerOffering-card-separator"), + ).not.toBeInTheDocument(); + expect( + queryByTestId("partnerOffering-card-separator--tags"), + ).toBeInTheDocument(); + }); + + it("should not render links section when empty links are defined", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob", tags: [], links: [] }, + spec: { + type: "service", + }, + relations: [ + { + targetRef: "group:default/my-test-user", + type: RELATION_OWNED_BY, + }, + ], + }; + + const { queryByTestId, queryByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + "/catalog/:kind/:namespace/:name": entityRouteRef, + }, + }, + ); + + expect(queryByTestId("partnerOffering-card-separator")).toBeInTheDocument(); + expect( + queryByTestId("partnerOffering-card-separator--links"), + ).not.toBeInTheDocument(); + expect(queryByText("0")).not.toBeInTheDocument(); + }); + + it("should not render links section when empty additional links are defined", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob", tags: [], links: [] }, + spec: { + type: "service", + }, + relations: [ + { + targetRef: "group:default/my-test-user", + type: RELATION_OWNED_BY, + }, + ], + }; + + const { queryByTestId, queryByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + "/catalog/:kind/:namespace/:name": entityRouteRef, + }, + }, + ); + + expect(queryByTestId("partnerOffering-card-separator")).toBeInTheDocument(); + expect( + queryByTestId("partnerOffering-card-separator--links"), + ).not.toBeInTheDocument(); + expect(queryByText("0")).not.toBeInTheDocument(); + }); + + it("should render links section when links are defined", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "bob", + tags: [], + links: [{ url: "/some/url", title: "Learn More" }], + }, + spec: { + type: "service", + }, + relations: [ + { + targetRef: "group:default/my-test-user", + type: RELATION_OWNED_BY, + }, + ], + }; + + const { queryByTestId, getByRole } = await renderInTestApp( + + + , + { + mountedRoutes: { + "/catalog/:kind/:namespace/:name": entityRouteRef, + }, + }, + ); + + expect( + queryByTestId("partnerOffering-card-separator"), + ).not.toBeInTheDocument(); + expect( + queryByTestId("partnerOffering-card-separator--links"), + ).toBeInTheDocument(); + expect(getByRole("link", { name: "Learn More" })).toBeInTheDocument(); + }); + + it("should call the onSelected handler when clicking the choose button", async () => { + const mockPartnerOffering: PartnerOfferingEntityV1beta1 = { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "bob", tags: ["cpp", "react"] }, + spec: { + type: "service", + }, + }; + const mockOnSelected = jest.fn(); + + const { getByRole } = await renderInTestApp( + + + , + { + mountedRoutes: { + "/catalog/:kind/:namespace/:name": entityRouteRef, + }, + }, + ); + + expect(getByRole("button", { name: "Choose" })).toBeInTheDocument(); + + fireEvent.click(getByRole("button", { name: "Choose" })); + + expect(mockOnSelected).toHaveBeenCalledWith(mockPartnerOffering); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.tsx new file mode 100644 index 00000000..1d804dc4 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/PartnerOfferingCard.tsx @@ -0,0 +1,218 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Link, MarkdownContent, UserIcon } from "@backstage/core-components"; +import { + IconComponent, + useAnalytics, + useApp, +} from "@backstage/core-plugin-api"; +import Box from "@material-ui/core/Box"; +import Card from "@material-ui/core/Card"; +import CardActions from "@material-ui/core/CardActions"; +import CardContent from "@material-ui/core/CardContent"; +import Chip from "@material-ui/core/Chip"; +import Divider from "@material-ui/core/Divider"; +import Button from "@material-ui/core/Button"; +import Grid from "@material-ui/core/Grid"; +import { makeStyles, Theme } from "@material-ui/core/styles"; +import LanguageIcon from "@material-ui/icons/Language"; +import React, { useCallback } from "react"; +import { CardHeader } from "./CardHeader"; +import { CardLink } from "./CardLink"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +const useStyles = makeStyles((theme) => ({ + box: { + overflow: "hidden", + textOverflow: "ellipsis", + display: "-webkit-box", + "-webkit-line-clamp": 10, + "-webkit-box-orient": "vertical", + }, + markdown: { + /** to make the styles for React Markdown not leak into the description */ + "& :first-child": { + margin: 0, + }, + }, + label: { + color: theme.palette.text.secondary, + textTransform: "uppercase", + fontWeight: "bold", + letterSpacing: 0.5, + lineHeight: 1, + fontSize: "0.75rem", + }, + footer: { + display: "flex", + justifyContent: "space-between", + flex: 1, + alignItems: "center", + }, + ownedBy: { + display: "flex", + alignItems: "center", + flex: 1, + color: theme.palette.link, + }, +})); + +/** + * The Props for the {@link PartnerOfferingCard} component + * @alpha + */ +export interface PartnerOfferingCardProps { + partnerOffering: PartnerOfferingEntityV1beta1; + additionalLinks?: { + icon: IconComponent; + text: string; + url: string; + }[]; + + onSelected?: (partnerOffering: PartnerOfferingEntityV1beta1) => void; +} + +/** + * The `PartnerOfferingCard` component that is rendered in a list for each partner offering + * @alpha + */ +export const PartnerOfferingCard = (props: PartnerOfferingCardProps) => { + const { onSelected, partnerOffering: partnerOffering } = props; + const styles = useStyles(); + const analytics = useAnalytics(); + const app = useApp(); + const iconResolver = (key?: string): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; + const hasTags = !!partnerOffering.metadata.tags?.length; + const hasLinks = + !!props.additionalLinks?.length || !!partnerOffering.metadata.links?.length; + const displayDefaultDivider = !hasTags && !hasLinks; + + const handleChoose = useCallback(() => { + analytics.captureEvent("click", `PartnerOffering has been opened`); + onSelected?.(partnerOffering); + }, [analytics, onSelected, partnerOffering]); + + return ( + + + + + + + + + + {displayDefaultDivider && ( + + + + )} + {hasTags && ( + <> + + + + + + {partnerOffering.metadata.tags?.map((tag) => ( + + + + ))} + + + + )} + {hasLinks && ( + <> + + + + + + {props.additionalLinks?.map(({ icon, text, url }, index) => ( + + + + ))} + {partnerOffering.metadata.links?.map( + ({ url, icon, title }, index) => ( + + + + ), + )} + + + + )} + + + +
+
+ {(partnerOffering.spec.author?.length ?? 0) > 0 && ( + <> + + {(partnerOffering.spec.authorPageUrl?.length ?? 0) > 0 ? ( + + {partnerOffering.spec.author} + + ) : ( + + {partnerOffering.spec.author} + + )} + + )} +
+ +
+
+
+ ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/index.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/index.ts new file mode 100644 index 00000000..49dec2a3 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCard/index.ts @@ -0,0 +1,22 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { + PartnerOfferingCard, + type PartnerOfferingCardProps, +} from "./PartnerOfferingCard"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.test.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.test.tsx new file mode 100644 index 00000000..268e7260 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.test.tsx @@ -0,0 +1,156 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from "react"; +import { useEntityTypeFilter } from "@backstage/plugin-catalog-react"; +import { PartnerOfferingCategoryPicker } from "./PartnerOfferingCategoryPicker"; +import { renderInTestApp, TestApiProvider } from "@backstage/test-utils"; +import { alertApiRef } from "@backstage/core-plugin-api"; +import { fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +jest.mock("@backstage/plugin-catalog-react", () => ({ + useEntityTypeFilter: jest.fn(), +})); + +describe("PartnerOfferingCategoryPicker", () => { + const mockAlertApi = { post: jest.fn() }; + + beforeEach(() => { + mockAlertApi.post.mockClear(); + }); + + it("should post the error to errorApi if an errors is returned", async () => { + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + error: new Error("something broke"), + }); + + await renderInTestApp( + + + , + ); + + expect(mockAlertApi.post).toHaveBeenCalledWith({ + message: expect.stringContaining("something broke"), + severity: "error", + }); + }); + + it("should render loading if the hook is loading", async () => { + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + loading: true, + }); + + const { findByTestId } = await renderInTestApp( + + + , + ); + + expect(await findByTestId("progress")).toBeInTheDocument(); + }); + + it("should not render if there is no available types", async () => { + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: null, + }); + + const { queryByText } = await renderInTestApp( + + + , + ); + + expect(queryByText("Categories")).not.toBeInTheDocument(); + }); + + it("renders the autocomplete with the availableTypes", async () => { + const mockAvailableTypes = ["foo", "bar"]; + + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: mockAvailableTypes, + }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const openButton = getByRole("button", { name: "Open" }); + await userEvent.click(openButton); + + expect(getByRole("checkbox", { name: "Foo" })).toBeInTheDocument(); + expect(getByRole("checkbox", { name: "Bar" })).toBeInTheDocument(); + }); + + it("should call setSelectedTypes when one of the options are called", async () => { + const mockAvailableTypes = ["foo", "bar"]; + const mockSetSelectedTypes = jest.fn(); + + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: mockAvailableTypes, + setSelectedTypes: mockSetSelectedTypes, + }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const openButton = getByRole("button", { name: "Open" }); + await fireEvent(openButton, new MouseEvent("click", { bubbles: true })); + + const fooCheckbox = getByRole("checkbox", { name: "Foo" }); + await fireEvent(fooCheckbox, new MouseEvent("click", { bubbles: true })); + + expect(mockSetSelectedTypes).toHaveBeenCalledWith(["foo"]); + + await fireEvent(openButton, new MouseEvent("click", { bubbles: true })); + + const barCheckbox = getByRole("checkbox", { name: "Bar" }); + await fireEvent(barCheckbox, new MouseEvent("click", { bubbles: true })); + + expect(mockSetSelectedTypes).toHaveBeenCalledWith(["foo", "bar"]); + }); + + it("should render the selectedTypes already in the document", async () => { + const mockAvailableTypes = ["foo", "bar"]; + const mockSelectedTypes = ["foo"]; + + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: mockAvailableTypes, + selectedTypes: mockSelectedTypes, + }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const openButton = getByRole("button", { name: "Open" }); + await fireEvent(openButton, new MouseEvent("click", { bubbles: true })); + + const fooCheckbox = getByRole("checkbox", { name: "Foo" }); + expect(fooCheckbox).toBeChecked(); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.tsx new file mode 100644 index 00000000..21a334cf --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/PartnerOfferingCategoryPicker.tsx @@ -0,0 +1,110 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ReactNode } from "react"; +import capitalize from "lodash/capitalize"; +import { Progress } from "@backstage/core-components"; +import Box from "@material-ui/core/Box"; +import Checkbox from "@material-ui/core/Checkbox"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/core/styles"; +import CheckBoxIcon from "@material-ui/icons/CheckBox"; +import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank"; +import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; +import Autocomplete from "@material-ui/lab/Autocomplete"; +import { useEntityTypeFilter } from "@backstage/plugin-catalog-react"; +import { alertApiRef, useApi } from "@backstage/core-plugin-api"; + +const icon = ; +const checkedIcon = ; + +/** @alpha */ +export type PartnerOfferingReactCategoryPickerClassKey = "root" | "label"; + +const useStyles = makeStyles( + { + root: {}, + label: {}, + }, + { name: "PartnerOfferingReactCategoryPicker" }, +); + +/** + * The Category Picker that is rendered on the left side for picking + * categories and filtering the partner offerings list. + * @alpha + */ +export const PartnerOfferingCategoryPicker = () => { + const classes = useStyles(); + const alertApi = useApi(alertApiRef); + const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = + useEntityTypeFilter(); + + if (loading) return ; + + if (error) { + alertApi.post({ + message: `Failed to load entity types with error: ${error}`, + severity: "error", + }); + return null; + } + + if (!availableTypes) return null; + + return ( + + + Categories + + + PopperComponent={(popperProps) => ( +
{popperProps.children as ReactNode}
+ )} + multiple + id="categories-picker" + options={availableTypes} + value={selectedTypes} + onChange={(_: object, value: string[]) => setSelectedTypes(value)} + renderOption={(option, { selected }) => ( + + } + label={capitalize(option)} + /> + )} + size="small" + popupIcon={} + renderInput={(params) => } + /> +
+ ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/index.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/index.ts new file mode 100644 index 00000000..bca0d649 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingCategoryPicker/index.ts @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { PartnerOfferingCategoryPicker } from "./PartnerOfferingCategoryPicker"; +export type { PartnerOfferingReactCategoryPickerClassKey } from "./PartnerOfferingCategoryPicker"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.test.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.test.tsx new file mode 100644 index 00000000..d8735c92 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.test.tsx @@ -0,0 +1,176 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +jest.mock("../PartnerOfferingCard", () => ({ + PartnerOfferingCard: jest.fn(() => null), +})); + +import React from "react"; +import { PartnerOfferingGroup } from "./PartnerOfferingGroup"; +import { render } from "@testing-library/react"; +import { PartnerOfferingCard } from "../PartnerOfferingCard"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +describe("PartnerOfferingGroup", () => { + it("should render a card for each partner offering with the partner offering being passed as a prop", () => { + const mockOnSelected = jest.fn(); + const mockPartnerOfferings: { + partnerOffering: PartnerOfferingEntityV1beta1; + }[] = [ + { + partnerOffering: { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "test" }, + spec: { + type: "website", + }, + }, + }, + { + partnerOffering: { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "test2" }, + spec: { + type: "service", + }, + }, + }, + ]; + + render( + , + ); + + expect(PartnerOfferingCard).toHaveBeenCalledTimes(2); + + for (const { partnerOffering } of mockPartnerOfferings) { + expect(PartnerOfferingCard).toHaveBeenCalledWith( + expect.objectContaining({ + partnerOffering, + onSelected: mockOnSelected, + }), + {}, + ); + } + }); + + it("should use the passed in PartnerOfferingCard prop to render the partner offering card", () => { + const mockPartnerOfferingCardComponent = jest.fn(() => null); + const mockOnSelected = jest.fn(); + const mockPartnerOfferings: { + partnerOffering: PartnerOfferingEntityV1beta1; + }[] = [ + { + partnerOffering: { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "test" }, + spec: { + type: "website", + }, + }, + }, + { + partnerOffering: { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "test2" }, + spec: { + type: "service", + }, + }, + }, + ]; + + render( + , + ); + + expect(mockPartnerOfferingCardComponent).toHaveBeenCalledTimes(2); + + for (const { partnerOffering } of mockPartnerOfferings) { + expect(mockPartnerOfferingCardComponent).toHaveBeenCalledWith( + expect.objectContaining({ + onSelected: mockOnSelected, + partnerOffering, + }), + {}, + ); + } + }); + it("should render the title when there are partnerOfferings in the list", () => { + const mockPartnerOfferings: { + partnerOffering: PartnerOfferingEntityV1beta1; + }[] = [ + { + partnerOffering: { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "test" }, + spec: { type: "website" }, + }, + }, + ]; + + const { getByText } = render( + , + ); + + expect(getByText("Test")).toBeInTheDocument(); + }); + + it("should allow for passing through a user given title component", () => { + const TitleComponent =

Im a custom header

; + const mockPartnerOfferings: { + partnerOffering: PartnerOfferingEntityV1beta1; + }[] = [ + { + partnerOffering: { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { name: "test" }, + spec: { type: "website" }, + }, + }, + ]; + const { getByText } = render( + , + ); + + expect(getByText("Im a custom header")).toBeInTheDocument(); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.tsx new file mode 100644 index 00000000..a7cc15de --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/PartnerOfferingGroup.tsx @@ -0,0 +1,90 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from "react"; +import { + Content, + ContentHeader, + ItemCardGrid, +} from "@backstage/core-components"; +import { stringifyEntityRef } from "@backstage/catalog-model"; +import { + PartnerOfferingCardProps, + PartnerOfferingCard, +} from "../PartnerOfferingCard"; +import { IconComponent } from "@backstage/core-plugin-api"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +/** + * The props for the {@link PartnerOfferingGroup} component. + * @alpha + */ +export interface PartnerOfferingGroupProps { + partnerOfferings: { + partnerOffering: PartnerOfferingEntityV1beta1; + additionalLinks?: { + icon: IconComponent; + text: string; + url: string; + }[]; + }[]; + onSelected: (partnerOffering: PartnerOfferingEntityV1beta1) => void; + title: React.ReactNode; + components?: { + CardComponent?: React.ComponentType; + }; +} + +/** + * The `PartnerOfferingGroup` component is used to display a group of partner offerings with a title. + * @alpha + */ +export const PartnerOfferingGroup = (props: PartnerOfferingGroupProps) => { + const { + partnerOfferings: partnerOfferings, + title, + components: { CardComponent } = {}, + onSelected, + } = props; + const titleComponent = + typeof title === "string" ? : title; + + if (partnerOfferings.length === 0) { + return null; + } + + const Card = CardComponent || PartnerOfferingCard; + + return ( + + {titleComponent} + + {partnerOfferings.map( + ({ partnerOffering: partnerOffering, additionalLinks }) => ( + + ), + )} + + + ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/index.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/index.ts new file mode 100644 index 00000000..0dd8b86a --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroup/index.ts @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + PartnerOfferingGroup, + type PartnerOfferingGroupProps, +} from "./PartnerOfferingGroup"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.test.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.test.tsx new file mode 100644 index 00000000..f99ffe17 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.test.tsx @@ -0,0 +1,231 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +jest.mock("@backstage/plugin-catalog-react", () => ({ + useEntityList: jest.fn(), +})); + +jest.mock("../PartnerOfferingGroup/PartnerOfferingGroup", () => ({ + PartnerOfferingGroup: jest.fn(() => null), +})); + +import React from "react"; +import { useEntityList } from "@backstage/plugin-catalog-react"; +import { PartnerOfferingGroups } from "./PartnerOfferingGroups"; +import { renderInTestApp, TestApiProvider } from "@backstage/test-utils"; +import { errorApiRef } from "@backstage/core-plugin-api"; +import { PartnerOfferingGroup } from "../PartnerOfferingGroup"; + +describe("PartnerOfferingGroups", () => { + beforeEach(() => jest.clearAllMocks()); + + it("should return progress if the hook is loading", async () => { + (useEntityList as jest.Mock).mockReturnValue({ loading: true }); + + const { findByTestId } = await renderInTestApp( + + + , + ); + + expect(await findByTestId("progress")).toBeInTheDocument(); + }); + + it("should use the error api if there is an error with the retrieval of entitylist", async () => { + const mockError = new Error("tings went poop"); + (useEntityList as jest.Mock).mockReturnValue({ + error: mockError, + }); + const errorApi = { + post: jest.fn(), + }; + await renderInTestApp( + + + , + ); + + expect(errorApi.post).toHaveBeenCalledWith(mockError); + }); + + it("should return a no partner offerings message if entities is unset", async () => { + (useEntityList as jest.Mock).mockReturnValue({ + entities: null, + loading: false, + error: null, + }); + + const { findByText } = await renderInTestApp( + + + , + ); + + expect(await findByText(/No partner offerings found/)).toBeInTheDocument(); + }); + + it("should return a no partner offerings message if entities has no values in it", async () => { + (useEntityList as jest.Mock).mockReturnValue({ + entities: [], + loading: false, + error: null, + }); + + const { findByText } = await renderInTestApp( + + + , + ); + + expect(await findByText(/No partner offerings found/)).toBeInTheDocument(); + }); + + it("should call the partner offerings group with the components", async () => { + const mockEntities = [ + { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "t1", + }, + spec: {}, + }, + { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "t2", + }, + spec: {}, + }, + ]; + + (useEntityList as jest.Mock).mockReturnValue({ + entities: mockEntities, + loading: false, + error: null, + }); + + await renderInTestApp( + + true }]} + /> + , + ); + + expect(PartnerOfferingGroup).toHaveBeenCalledWith( + expect.objectContaining({ + partnerOfferings: mockEntities.map((partnerOffering) => + expect.objectContaining({ partnerOffering }), + ), + }), + {}, + ); + }); + + it("should apply the filter for each group", async () => { + const mockEntities = [ + { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "t1", + }, + spec: {}, + }, + { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "t2", + }, + spec: {}, + }, + ]; + + (useEntityList as jest.Mock).mockReturnValue({ + entities: mockEntities, + loading: false, + error: null, + }); + + await renderInTestApp( + + e.metadata.name === "t1" }]} + /> + , + ); + + expect(PartnerOfferingGroup).toHaveBeenCalledWith( + expect.objectContaining({ + partnerOfferings: [ + expect.objectContaining({ partnerOffering: mockEntities[0] }), + ], + }), + {}, + ); + }); + + it("should filter out partner offerings based on filter condition", async () => { + const mockEntities = [ + { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "t1", + }, + spec: {}, + }, + { + apiVersion: "aws.amazon.com/v1beta1", + kind: "PartnerOffering", + metadata: { + name: "t2", + }, + spec: {}, + }, + ]; + + (useEntityList as jest.Mock).mockReturnValue({ + entities: mockEntities, + loading: false, + error: null, + }); + + await renderInTestApp( + + true }]} + partnerOfferingFilter={(e) => e.metadata.name === "t1"} + /> + , + ); + + expect(PartnerOfferingGroup).toHaveBeenCalledWith( + expect.objectContaining({ + partnerOfferings: [ + expect.objectContaining({ partnerOffering: mockEntities[0] }), + ], + }), + {}, + ); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.tsx new file mode 100644 index 00000000..26ceaaaa --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/PartnerOfferingGroups.tsx @@ -0,0 +1,120 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useCallback } from "react"; + +import { useEntityList } from "@backstage/plugin-catalog-react"; +import { Progress } from "@backstage/core-components"; +import Typography from "@material-ui/core/Typography"; +import { errorApiRef, IconComponent, useApi } from "@backstage/core-plugin-api"; +import { PartnerOfferingGroup } from "../PartnerOfferingGroup"; +import { + PartnerOfferingEntityV1beta1, + isPartnerOfferingEntityV1beta1, +} from "backstage-plugin-acdp-common"; +import { PartnerOfferingGroupFilter } from "../types"; + +/** + * @alpha + */ +export interface PartnerOfferingGroupsProps { + groups: PartnerOfferingGroupFilter[]; + partnerOfferingFilter?: (entity: PartnerOfferingEntityV1beta1) => boolean; + PartnerOfferingCardComponent?: React.ComponentType<{ + partnerOffering: PartnerOfferingEntityV1beta1; + }>; + onPartnerOfferingSelected?: ( + partnerOffering: PartnerOfferingEntityV1beta1, + ) => void; + additionalLinksForEntity?: ( + partnerOffering: PartnerOfferingEntityV1beta1, + ) => { + icon: IconComponent; + text: string; + url: string; + }[]; +} + +/** + * @alpha + */ +export const PartnerOfferingGroups = (props: PartnerOfferingGroupsProps) => { + const { loading, error, entities } = useEntityList(); + const { + groups, + partnerOfferingFilter: partnerOfferingFilter, + PartnerOfferingCardComponent: PartnerOfferingCardComponent, + onPartnerOfferingSelected: onPartnerOfferingSelected, + } = props; + const errorApi = useApi(errorApiRef); + const onSelected = useCallback( + (partnerOffering: PartnerOfferingEntityV1beta1) => { + onPartnerOfferingSelected?.(partnerOffering); + }, + [onPartnerOfferingSelected], + ); + + if (loading) { + return ; + } + + if (error) { + errorApi.post(error); + return null; + } + + if (!entities || !entities.length) { + return ( + + No partner offerings found that match your filter. + + ); + } + + return ( + <> + {groups.map(({ title, filter }, index) => { + const partnerOfferings = entities + .filter(isPartnerOfferingEntityV1beta1) + .filter((e) => + partnerOfferingFilter ? partnerOfferingFilter(e) : true, + ) + .filter(filter) + .map((partnerOffering) => { + const additionalLinks = + props.additionalLinksForEntity?.(partnerOffering) ?? []; + + return { + partnerOffering, + additionalLinks, + }; + }); + + return ( + + ); + })} + + ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/index.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/index.ts new file mode 100644 index 00000000..6d24ccc6 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingGroups/index.ts @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from "./PartnerOfferingGroups"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingList.tsx b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingList.tsx new file mode 100644 index 00000000..610c01f1 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/PartnerOfferingList.tsx @@ -0,0 +1,111 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + Page, + Header, + Content, + ContentHeader, +} from "@backstage/core-components"; +import { + CatalogFilterLayout, + EntityKindPicker, + EntityListProvider, + EntitySearchBar, + EntityTagPicker, + UserListPicker, +} from "@backstage/plugin-catalog-react"; +import { PartnerOfferingCategoryPicker } from "./PartnerOfferingCategoryPicker"; +import { PartnerOfferingGroups } from "./PartnerOfferingGroups"; +import { PartnerOfferingGroupFilter } from "./types"; +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; +import React from "react"; + +export type PartnerOfferingListPageProps = { + PartnerOfferingCardComponent?: React.ComponentType<{ + partnerOffering: PartnerOfferingEntityV1beta1; + }>; + groups?: PartnerOfferingGroupFilter[]; + partnerOfferingFilter?: (entity: PartnerOfferingEntityV1beta1) => boolean; + contextMenu?: { + editor?: boolean; + actions?: boolean; + tasks?: boolean; + }; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; +}; + +const defaultGroup: PartnerOfferingGroupFilter = { + title: "Partner Offerings", + filter: () => true, +}; + +const createGroupsWithOther = ( + groups: PartnerOfferingGroupFilter[], +): PartnerOfferingGroupFilter[] => [ + ...groups, + { + title: "Other Partner Offerings", + filter: (e) => ![...groups].some(({ filter }) => filter(e)), + }, +]; + +export const PartnerOfferingListPage = ( + props: PartnerOfferingListPageProps, +) => { + const { + PartnerOfferingCardComponent: PartnerOfferingCardComponent, + groups: givenGroups = [], + partnerOfferingFilter: partnerOfferingFilter, + } = props; + + const groups = givenGroups.length + ? createGroupsWithOther(givenGroups) + : [defaultGroup]; + + const onPartnerOfferingSelected = ( + partnerOffering: PartnerOfferingEntityV1beta1, + ) => { + window.open(partnerOffering.spec.url, "_blank"); + }; + + return ( + + +
+ + + + + + + + + + + + + + ); +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/components/types.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/components/types.ts new file mode 100644 index 00000000..87dddba7 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/components/types.ts @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PartnerOfferingEntityV1beta1 } from "backstage-plugin-acdp-common"; + +/** @public */ +export type PartnerOfferingGroupFilter = { + title?: React.ReactNode; + filter: (entity: PartnerOfferingEntityV1beta1) => boolean; +}; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/index.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/index.ts new file mode 100644 index 00000000..1dbf7ec6 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { acdpPartnerOfferingPlugin, AcdpPartnerOfferingPage } from "./plugin"; diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/plugin.test.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/plugin.test.ts new file mode 100644 index 00000000..530d4bfa --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/plugin.test.ts @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { acdpPartnerOfferingPlugin } from "./plugin"; + +describe("acdp-partner-offering", () => { + it("should export plugin", () => { + expect(acdpPartnerOfferingPlugin).toBeDefined(); + }); +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/plugin.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/plugin.ts new file mode 100644 index 00000000..e3ed9714 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/plugin.ts @@ -0,0 +1,29 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + createPlugin, + createRoutableExtension, +} from "@backstage/core-plugin-api"; + +import { rootRouteRef } from "./routes"; + +export const acdpPartnerOfferingPlugin = createPlugin({ + id: "acdp-partner-offering", + routes: { + root: rootRouteRef, + }, +}); + +// Partner offerings is given a unique route in top-level FlatRoutes component in the Backstage App, requiring a routable extension +// https://backstage.io/docs/plugins/plugin-development#routing +export const AcdpPartnerOfferingPage = acdpPartnerOfferingPlugin.provide( + createRoutableExtension({ + name: "AcdpPartnerOfferingPage", + component: () => + import("./components/PartnerOfferingList").then( + (m) => m.PartnerOfferingListPage, + ), + mountPoint: rootRouteRef, + }), +); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/routes.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/routes.ts new file mode 100644 index 00000000..0fd0afbd --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/routes.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createRouteRef } from "@backstage/core-plugin-api"; + +export const rootRouteRef = createRouteRef({ + id: "acdp-partner-offering", +}); diff --git a/source/modules/backstage/plugins/acdp-partner-offering/src/setupTests.ts b/source/modules/backstage/plugins/acdp-partner-offering/src/setupTests.ts new file mode 100644 index 00000000..7ea7f359 --- /dev/null +++ b/source/modules/backstage/plugins/acdp-partner-offering/src/setupTests.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import "@testing-library/jest-dom"; diff --git a/source/modules/backstage/plugins/acdp/dev/index.tsx b/source/modules/backstage/plugins/acdp/dev/index.tsx index b443634b..5c94015a 100644 --- a/source/modules/backstage/plugins/acdp/dev/index.tsx +++ b/source/modules/backstage/plugins/acdp/dev/index.tsx @@ -3,13 +3,22 @@ import React from "react"; import { createDevApp } from "@backstage/dev-utils"; -import { acdpPlugin, EntityAcdpBuildProjectOverviewCard } from "../src/plugin"; +import { + acdpPlugin, + EntityAcdpBuildProjectOverviewCard, + EntityApplicationsDashboardLinkCard, +} from "../src/plugin"; createDevApp() .registerPlugin(acdpPlugin) .addPage({ element: , - title: "Root Page", - path: "/acdp", + title: "ACDP Build Project Overview Card", + path: "/acdp-build-project", + }) + .addPage({ + element: , + title: "Applications Dashboard Link Card", + path: "/acdp-applications-dashboard", }) .render(); diff --git a/source/modules/backstage/plugins/acdp/package.json b/source/modules/backstage/plugins/acdp/package.json index 6f0739ae..4087b65e 100644 --- a/source/modules/backstage/plugins/acdp/package.json +++ b/source/modules/backstage/plugins/acdp/package.json @@ -1,7 +1,7 @@ { "name": "backstage-plugin-acdp", "description": "ACDP plugin for Backstage", - "version": "1.2.2", + "version": "2.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,34 +26,39 @@ }, "dependencies": { "@aws-sdk/client-codebuild": "^3.621.0", - "@aws-sdk/util-arn-parser": "^3.568.0", + "@aws-sdk/client-service-catalog-appregistry": "^3.621.0", + "@aws-sdk/util-arn-parser": "^3.495.0", "@backstage/catalog-model": "^1.5.0", - "@backstage/core-components": "^0.14.8", + "@backstage/core-components": "^0.14.9", "@backstage/core-plugin-api": "^1.9.3", "@backstage/errors": "^1.2.4", - "@backstage/plugin-catalog-react": "^1.12.1", + "@backstage/plugin-catalog-react": "^1.12.2", + "@backstage/test-utils": "^1.5.9", "@backstage/theme": "^0.5.6", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", "@tanstack/react-query": "^4.36.1", - "date-fns": "^2.30.0", "backstage-plugin-acdp-common": "*", - "react": "^18.0.2" + "classnames": "^2.5.1", + "date-fns": "^2.30.0", + "react-use": "^17.5.1" }, "devDependencies": { - "@backstage/cli": "^0.26.10", - "@backstage/core-app-api": "^1.13.0", - "@backstage/dev-utils": "^1.0.34", - "@backstage/test-utils": "^1.5.7", + "@backstage/cli": "^0.26.11", + "@backstage/core-app-api": "^1.14.1", + "@backstage/dev-utils": "^1.0.36", "@testing-library/jest-dom": "^6.0.0", - "@types/react": "*", - "@types/react-dom": "*", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", + "@types/react": "*", + "@types/react-dom": "*", "msw": "^1.0.0", "prettier": "^3.1.0" }, + "peerDependencies": { + "react": "^18.0.2" + }, "files": [ "dist" ] diff --git a/source/modules/backstage/plugins/acdp/src/api/AcdpBaseApi.ts b/source/modules/backstage/plugins/acdp/src/api/AcdpBaseApi.ts new file mode 100644 index 00000000..163993d7 --- /dev/null +++ b/source/modules/backstage/plugins/acdp/src/api/AcdpBaseApi.ts @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { IdentityApi, ConfigApi } from "@backstage/core-plugin-api"; +import { ResponseError } from "@backstage/errors"; + +export interface AcdpBaseApiInput { + configApi: ConfigApi; + identityApi: IdentityApi; +} + +export class AcdpBaseApi { + private readonly configApi: ConfigApi; + private readonly identityApi: IdentityApi; + + public constructor(options: AcdpBaseApiInput) { + this.configApi = options.configApi; + this.identityApi = options.identityApi; + } + + async _fetch(input: string, init?: RequestInit): Promise { + const baseUrl = `${this.configApi.getString( + "backend.baseUrl", + )}/api/acdp-backend`; + + const { token: idToken } = await this.identityApi.getCredentials(); + + const headers: HeadersInit = new Headers(init?.headers); + if (idToken && !headers.has("authorization")) { + headers.set("authorization", `Bearer ${idToken}`); + } + + const request = new Request(`${baseUrl}${input}`, { + ...init, + headers, + }); + + return fetch(request).then(async (response) => { + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const text = await response.text(); + if (text !== undefined && text.length > 0) { + return JSON.parse(text); + } + + return undefined; + }); + } +} diff --git a/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.test.ts b/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.test.ts index c4cebfdb..e0e35b03 100644 --- a/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.test.ts +++ b/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.test.ts @@ -2,48 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 import { stringifyEntityRef } from "@backstage/catalog-model"; -import { AcdpBuildApi } from "."; -import { MockConfigApi } from "@backstage/test-utils"; -import { mockCodeBuildEntity } from "../mocks/mocksCodeBuild"; + import { AcdpBuildAction } from "backstage-plugin-acdp-common"; -const baseUrl = "https://example.com"; - -const acdpBuildApiClient = new AcdpBuildApi({ - configApi: new MockConfigApi({ - backend: { - baseUrl, - }, - }), - identityApi: { - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn().mockReturnValue({ token: "test" }), - getProfileInfo: jest.fn(), - signOut: jest.fn(), - }, -}); +import { AcdpBuildApi } from "."; +import { + mockCodeBuildEntity, + mockGlobalFetch, + mockAcdpBaseApiInput, + baseUrl, +} from "../mocks"; + +const acdpBuildApiClient = new AcdpBuildApi(mockAcdpBaseApiInput); let mockedFetch: jest.SpyInstance; beforeEach(() => { - mockedFetch = jest.spyOn(global, "fetch").mockImplementation((input) => { - const { status, ok } = (input.valueOf() as Request).url.includes( - "arn=bad-arn", - ) - ? { status: 404, ok: false } - : { status: 200, ok: true }; - return Promise.resolve({ - text: () => Promise.resolve(""), - status, - ok, - } as Response); - }); + mockedFetch = mockGlobalFetch(); }); afterEach(() => { jest.clearAllMocks(); }); -describe("test", () => { +describe("AcdpBuildApi", () => { it("should getProject", async () => { await acdpBuildApiClient.getProject({ entityRef: stringifyEntityRef(mockCodeBuildEntity), @@ -52,7 +33,7 @@ describe("test", () => { const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; expect(fetchCall.method).toEqual("GET"); expect(fetchCall.url).toEqual( - `${baseUrl}/api/acdp-backend/project?entityRef=component%3Aacdp%2Fcms-sample`, + `${baseUrl}/api/acdp-backend/project?entityRef=component%3Aacdp-build%2Fcms-sample`, ); }); @@ -64,7 +45,7 @@ describe("test", () => { const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; expect(fetchCall.method).toEqual("GET"); expect(fetchCall.url).toEqual( - `${baseUrl}/api/acdp-backend/builds?entityRef=component%3Aacdp%2Fcms-sample`, + `${baseUrl}/api/acdp-backend/builds?entityRef=component%3Aacdp-build%2Fcms-sample`, ); }); @@ -77,7 +58,7 @@ describe("test", () => { const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; expect(fetchCall.method).toEqual("POST"); - expect(fetchCall.url).toEqual(`${baseUrl}/api/acdp-backend/startBuild`); + expect(fetchCall.url).toEqual(`${baseUrl}/api/acdp-backend/start-build`); expect(await fetchCall.json()).toStrictEqual(startBuildInput); }); @@ -90,7 +71,7 @@ describe("test", () => { const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; expect(fetchCall.method).toEqual("POST"); - expect(fetchCall.url).toEqual(`${baseUrl}/api/acdp-backend/startBuild`); + expect(fetchCall.url).toEqual(`${baseUrl}/api/acdp-backend/start-build`); expect(await fetchCall.json()).toStrictEqual(startBuildInput); }); @@ -103,7 +84,7 @@ describe("test", () => { const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; expect(fetchCall.method).toEqual("POST"); - expect(fetchCall.url).toEqual(`${baseUrl}/api/acdp-backend/startBuild`); + expect(fetchCall.url).toEqual(`${baseUrl}/api/acdp-backend/start-build`); expect(await fetchCall.json()).toStrictEqual(startBuildInput); }); }); diff --git a/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.ts b/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.ts index 9ba7a328..da9e24c5 100644 --- a/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.ts +++ b/source/modules/backstage/plugins/acdp/src/api/AcdpBuildApi.ts @@ -1,18 +1,15 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { - IdentityApi, - ConfigApi, - createApiRef, -} from "@backstage/core-plugin-api"; -import { ResponseError } from "@backstage/errors"; +import { createApiRef } from "@backstage/core-plugin-api"; import { AcdpBuildAction, AcdpBuildProject, AcdpBuildProjectBuild, } from "backstage-plugin-acdp-common"; +import { AcdpBaseApi, AcdpBaseApiInput } from "./AcdpBaseApi"; + export const acdpBuildApiRef = createApiRef({ id: "plugin.acdpbuild.service", }); @@ -22,16 +19,9 @@ export interface StartBuildInput { action: AcdpBuildAction; } -export class AcdpBuildApi { - private readonly configApi: ConfigApi; - private readonly identityApi: IdentityApi; - - public constructor(options: { - configApi: ConfigApi; - identityApi: IdentityApi; - }) { - this.configApi = options.configApi; - this.identityApi = options.identityApi; +export class AcdpBuildApi extends AcdpBaseApi { + public constructor(options: AcdpBaseApiInput) { + super(options); } async getProject({ @@ -44,7 +34,7 @@ export class AcdpBuildApi { }); const urlSegment = `/project?${searchParams}`; - return await this.fetch(urlSegment); + return await this._fetch(urlSegment); } async listBuilds({ @@ -57,45 +47,14 @@ export class AcdpBuildApi { }); const urlSegment = `/builds?${searchParams}`; - return await this.fetch(urlSegment); + return await this._fetch(urlSegment); } async startBuild(input: StartBuildInput): Promise { - return await this.fetch("/startBuild", { + return await this._fetch("/start-build", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input), }); } - - private async fetch(input: string, init?: RequestInit): Promise { - const baseUrl = `${this.configApi.getString( - "backend.baseUrl", - )}/api/acdp-backend`; - - const { token: idToken } = await this.identityApi.getCredentials(); - - const headers: HeadersInit = new Headers(init?.headers); - if (idToken && !headers.has("authorization")) { - headers.set("authorization", `Bearer ${idToken}`); - } - - const request = new Request(`${baseUrl}${input}`, { - ...init, - headers, - }); - - return fetch(request).then(async (response) => { - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - const text = await response.text(); - if (text != undefined && text.length > 0) { - return JSON.parse(text); - } else { - return undefined; - } - }); - } } diff --git a/source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.test.ts b/source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.test.ts new file mode 100644 index 00000000..6c17807b --- /dev/null +++ b/source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.test.ts @@ -0,0 +1,62 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { stringifyEntityRef } from "@backstage/catalog-model"; + +import { AcdpMetricsApi } from "."; +import { + mockMetricsEntity, + mockGlobalFetch, + mockAcdpBaseApiInput, + baseUrl, +} from "../mocks"; + +const acdpMetricsApiClient = new AcdpMetricsApi(mockAcdpBaseApiInput); + +let mockedFetch: jest.SpyInstance; +beforeEach(() => { + mockedFetch = mockGlobalFetch(); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe("AcdpMetricsApi", () => { + it("should get Application by entity", async () => { + await acdpMetricsApiClient.getApplicationByEntity({ + entityRef: stringifyEntityRef(mockMetricsEntity), + }); + + const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; + expect(fetchCall.method).toEqual("GET"); + expect(fetchCall.url).toEqual( + `${baseUrl}/api/acdp-backend/application/by-entity?entityRef=component%3Aacdp-metrics%2Fcms-sample`, + ); + }); + + it("should get Application by arn", async () => { + const mockedApplicationArn = + "arn:aws:servicecatalog:us-east-2:111111111111:/applications/test-application-id"; + await acdpMetricsApiClient.getApplicationByArn(mockedApplicationArn); + + const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; + expect(fetchCall.method).toEqual("GET"); + expect(fetchCall.url).toEqual( + `${baseUrl}/api/acdp-backend/application/by-arn?arn=arn%3Aaws%3Aservicecatalog%3Aus-east-2%3A111111111111%3A%2Fapplications%2Ftest-application-id`, + ); + }); + + it("should get cost/current-month-net-unblended", async () => { + await acdpMetricsApiClient.getNetUnblendedCurrentMonthCost({ + entityRef: stringifyEntityRef(mockMetricsEntity), + awsApplicationTag: "mocked-aws-application-tag", + }); + + const fetchCall = mockedFetch.mock.calls[0][0].valueOf() as Request; + expect(fetchCall.method).toEqual("GET"); + expect(fetchCall.url).toEqual( + `${baseUrl}/api/acdp-backend/cost/current-month-net-unblended?entityRef=component%3Aacdp-metrics%2Fcms-sample&awsApplicationTag=mocked-aws-application-tag`, + ); + }); +}); diff --git a/source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.ts b/source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.ts new file mode 100644 index 00000000..053c56d9 --- /dev/null +++ b/source/modules/backstage/plugins/acdp/src/api/AcdpMetricsApi.ts @@ -0,0 +1,55 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createApiRef } from "@backstage/core-plugin-api"; +import { AcdpApplication } from "backstage-plugin-acdp-common"; + +import { AcdpBaseApi, AcdpBaseApiInput } from "./AcdpBaseApi"; + +export const acdpMetricsApiRef = createApiRef({ + id: "plugin.acdpmetrics.service", +}); + +export class AcdpMetricsApi extends AcdpBaseApi { + public constructor(options: AcdpBaseApiInput) { + super(options); + } + + async getApplicationByEntity({ + entityRef, + }: { + entityRef: string; + }): Promise { + const searchParams = new URLSearchParams({ + entityRef: entityRef, + }); + const urlSegment = `/application/by-entity?${searchParams}`; + + return await this._fetch(urlSegment); + } + + async getApplicationByArn(arn: string): Promise { + const searchParams = new URLSearchParams({ + arn: arn, + }); + const urlSegment = `/application/by-arn?${searchParams}`; + + return await this._fetch(urlSegment); + } + + async getNetUnblendedCurrentMonthCost({ + entityRef, + awsApplicationTag, + }: { + entityRef: string; + awsApplicationTag: string; + }): Promise { + const searchParams = new URLSearchParams({ + entityRef: entityRef, + awsApplicationTag: awsApplicationTag, + }); + const urlSegment = `/cost/current-month-net-unblended?${searchParams}`; + + return await this._fetch(urlSegment); + } +} diff --git a/source/modules/backstage/plugins/acdp/src/api/index.ts b/source/modules/backstage/plugins/acdp/src/api/index.ts index da511835..8a31b8f6 100644 --- a/source/modules/backstage/plugins/acdp/src/api/index.ts +++ b/source/modules/backstage/plugins/acdp/src/api/index.ts @@ -2,3 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 export * from "./AcdpBuildApi"; +export * from "./AcdpBaseApi"; +export * from "./AcdpMetricsApi"; diff --git a/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/AboutField.tsx b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/AboutField.tsx new file mode 100644 index 00000000..1c13f20d --- /dev/null +++ b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/AboutField.tsx @@ -0,0 +1,57 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; + +import { Grid, makeStyles, Typography } from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + links: { + margin: theme.spacing(2, 0), + display: "grid", + gridAutoFlow: "column", + gridAutoColumns: "min-content", + gridGap: theme.spacing(3), + }, + label: { + color: theme.palette.text.secondary, + textTransform: "uppercase", + fontSize: "10px", + fontWeight: "bold", + letterSpacing: 0.5, + overflow: "hidden", + whiteSpace: "nowrap", + }, + value: { + fontWeight: "bold", + overflow: "hidden", + lineHeight: "24px", + wordBreak: "break-word", + }, + description: { + wordBreak: "break-word", + }, +})); + +interface AboutFieldProps { + label: string; + gridSizes?: Record; + children?: React.ReactNode; +} + +export const AboutField = (props: AboutFieldProps) => { + const { label, gridSizes, children } = props; + + const classes = useStyles(); + + return ( + + + {children} + + ); +}; diff --git a/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildHistoryTable.tsx b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildHistoryTable.tsx index ea31de7d..14f888dc 100644 --- a/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildHistoryTable.tsx +++ b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildHistoryTable.tsx @@ -2,15 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 import React from "react"; +import { formatDistanceStrict } from "date-fns"; + import { Link } from "@material-ui/core"; import { Table, TableColumn } from "@backstage/core-components"; -import { formatDistanceStrict } from "date-fns"; - -import { BuildStatus } from "../BuildStatus"; import { AcdpBuildProjectBuild } from "backstage-plugin-acdp-common"; +import { BuildStatus } from "./BuildStatus"; + interface BuildHistoryTableProps { region: string; accountId: string; diff --git a/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/BuildStatus.tsx b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/BuildStatus.tsx new file mode 100644 index 00000000..75dc37d0 --- /dev/null +++ b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/BuildStatus.tsx @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; +import { StatusType } from "@aws-sdk/client-codebuild"; + +import { StatusRunning, StatusOK, StatusAborted, StatusError } from "./Status"; + +interface BuildStatusProps { + status?: string; +} + +export const BuildStatus = (props: BuildStatusProps) => { + switch (props.status) { + case StatusType.IN_PROGRESS: + return ( + <> + In progress + + ); + case StatusType.FAULT: + return ( + <> + Fault + + ); + case StatusType.TIMED_OUT: + return ( + <> + Timed out + + ); + case StatusType.FAILED: + return ( + <> + Failed + + ); + case StatusType.SUCCEEDED: + return ( + <> + Succeeded + + ); + case StatusType.STOPPED: + return ( + <> + Stopped + + ); + default: + return ( + <> + Unknown + + ); + } +}; diff --git a/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/Status.tsx b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/Status.tsx new file mode 100644 index 00000000..35c26dac --- /dev/null +++ b/source/modules/backstage/plugins/acdp/src/components/CodeBuildWidget/BuildStatus/Status.tsx @@ -0,0 +1,158 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { PropsWithChildren } from "react"; +import classNames from "classnames"; + +import { makeStyles, Theme } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; + +export type StatusClassKey = + | "status" + | "ok" + | "warning" + | "error" + | "pending" + | "running" + | "aborted"; + +const useStyles = makeStyles( + (theme) => ({ + status: { + fontWeight: theme.typography.fontWeightMedium as number, + "&::before": { + width: "0.7em", + height: "0.7em", + display: "inline-block", + marginRight: theme.spacing(1), + borderRadius: "50%", + content: '""', + }, + }, + ok: { + "&::before": { + backgroundColor: theme.palette.status.ok, + }, + }, + warning: { + "&::before": { + backgroundColor: theme.palette.status.warning, + }, + }, + error: { + "&::before": { + backgroundColor: theme.palette.status.error, + }, + }, + pending: { + "&::before": { + backgroundColor: theme.palette.status.pending, + }, + }, + running: { + "&::before": { + backgroundColor: theme.palette.status.running, + }, + }, + aborted: { + "&::before": { + backgroundColor: theme.palette.status.aborted, + }, + }, + }), + { name: "BackstageStatus" }, +); + +export function StatusOK(props: PropsWithChildren<{}>) { + const classes = useStyles(props); + return ( +