cumulusci-plus 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cumulusci-plus might be problematic. Click here for more details.
- cumulusci/__about__.py +1 -0
- cumulusci/__init__.py +22 -0
- cumulusci/__main__.py +3 -0
- cumulusci/cli/__init__.py +0 -0
- cumulusci/cli/cci.py +244 -0
- cumulusci/cli/error.py +125 -0
- cumulusci/cli/flow.py +185 -0
- cumulusci/cli/logger.py +72 -0
- cumulusci/cli/org.py +692 -0
- cumulusci/cli/plan.py +181 -0
- cumulusci/cli/project.py +391 -0
- cumulusci/cli/robot.py +116 -0
- cumulusci/cli/runtime.py +190 -0
- cumulusci/cli/service.py +521 -0
- cumulusci/cli/task.py +295 -0
- cumulusci/cli/tests/__init__.py +0 -0
- cumulusci/cli/tests/test_cci.py +545 -0
- cumulusci/cli/tests/test_error.py +170 -0
- cumulusci/cli/tests/test_flow.py +276 -0
- cumulusci/cli/tests/test_logger.py +25 -0
- cumulusci/cli/tests/test_org.py +1438 -0
- cumulusci/cli/tests/test_plan.py +245 -0
- cumulusci/cli/tests/test_project.py +235 -0
- cumulusci/cli/tests/test_robot.py +177 -0
- cumulusci/cli/tests/test_runtime.py +197 -0
- cumulusci/cli/tests/test_service.py +853 -0
- cumulusci/cli/tests/test_task.py +266 -0
- cumulusci/cli/tests/test_ui.py +310 -0
- cumulusci/cli/tests/test_utils.py +122 -0
- cumulusci/cli/tests/utils.py +52 -0
- cumulusci/cli/ui.py +234 -0
- cumulusci/cli/utils.py +150 -0
- cumulusci/conftest.py +181 -0
- cumulusci/core/__init__.py +0 -0
- cumulusci/core/config/BaseConfig.py +5 -0
- cumulusci/core/config/BaseTaskFlowConfig.py +5 -0
- cumulusci/core/config/OrgConfig.py +5 -0
- cumulusci/core/config/ScratchOrgConfig.py +5 -0
- cumulusci/core/config/__init__.py +125 -0
- cumulusci/core/config/base_config.py +111 -0
- cumulusci/core/config/base_task_flow_config.py +82 -0
- cumulusci/core/config/marketing_cloud_service_config.py +83 -0
- cumulusci/core/config/oauth2_service_config.py +17 -0
- cumulusci/core/config/org_config.py +604 -0
- cumulusci/core/config/project_config.py +782 -0
- cumulusci/core/config/scratch_org_config.py +251 -0
- cumulusci/core/config/sfdx_org_config.py +220 -0
- cumulusci/core/config/tests/_test_config_backwards_compatibility.py +33 -0
- cumulusci/core/config/tests/test_config.py +1895 -0
- cumulusci/core/config/tests/test_config_expensive.py +839 -0
- cumulusci/core/config/tests/test_config_util.py +91 -0
- cumulusci/core/config/universal_config.py +88 -0
- cumulusci/core/config/util.py +18 -0
- cumulusci/core/datasets.py +303 -0
- cumulusci/core/debug.py +33 -0
- cumulusci/core/dependencies/__init__.py +55 -0
- cumulusci/core/dependencies/base.py +561 -0
- cumulusci/core/dependencies/dependencies.py +273 -0
- cumulusci/core/dependencies/github.py +177 -0
- cumulusci/core/dependencies/github_resolvers.py +244 -0
- cumulusci/core/dependencies/resolvers.py +580 -0
- cumulusci/core/dependencies/tests/__init__.py +0 -0
- cumulusci/core/dependencies/tests/conftest.py +385 -0
- cumulusci/core/dependencies/tests/test_dependencies.py +950 -0
- cumulusci/core/dependencies/tests/test_github.py +83 -0
- cumulusci/core/dependencies/tests/test_resolvers.py +1027 -0
- cumulusci/core/dependencies/utils.py +13 -0
- cumulusci/core/enums.py +11 -0
- cumulusci/core/exceptions.py +311 -0
- cumulusci/core/flowrunner.py +888 -0
- cumulusci/core/github.py +665 -0
- cumulusci/core/keychain/__init__.py +24 -0
- cumulusci/core/keychain/base_project_keychain.py +441 -0
- cumulusci/core/keychain/encrypted_file_project_keychain.py +945 -0
- cumulusci/core/keychain/environment_project_keychain.py +7 -0
- cumulusci/core/keychain/serialization.py +152 -0
- cumulusci/core/keychain/subprocess_keychain.py +24 -0
- cumulusci/core/keychain/tests/conftest.py +50 -0
- cumulusci/core/keychain/tests/test_base_project_keychain.py +299 -0
- cumulusci/core/keychain/tests/test_encrypted_file_project_keychain.py +1228 -0
- cumulusci/core/metadeploy/__init__.py +0 -0
- cumulusci/core/metadeploy/api.py +88 -0
- cumulusci/core/metadeploy/plans.py +25 -0
- cumulusci/core/metadeploy/tests/test_api.py +276 -0
- cumulusci/core/runtime.py +115 -0
- cumulusci/core/sfdx.py +162 -0
- cumulusci/core/source/__init__.py +16 -0
- cumulusci/core/source/github.py +50 -0
- cumulusci/core/source/local_folder.py +35 -0
- cumulusci/core/source_transforms/__init__.py +0 -0
- cumulusci/core/source_transforms/tests/test_transforms.py +1091 -0
- cumulusci/core/source_transforms/transforms.py +532 -0
- cumulusci/core/tasks.py +404 -0
- cumulusci/core/template_utils.py +59 -0
- cumulusci/core/tests/__init__.py +0 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml +215 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml +199 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_read_explicit_extract_declaration.yaml +3 -0
- cumulusci/core/tests/fake_remote_repo/cumulusci.yml +32 -0
- cumulusci/core/tests/fake_remote_repo/tasks/directory/example_2.py +6 -0
- cumulusci/core/tests/fake_remote_repo/tasks/example.py +43 -0
- cumulusci/core/tests/fake_remote_repo_2/cumulusci.yml +11 -0
- cumulusci/core/tests/fake_remote_repo_2/tasks/example_3.py +6 -0
- cumulusci/core/tests/test_datasets_e2e.py +386 -0
- cumulusci/core/tests/test_exceptions.py +11 -0
- cumulusci/core/tests/test_flowrunner.py +836 -0
- cumulusci/core/tests/test_github.py +942 -0
- cumulusci/core/tests/test_sfdx.py +138 -0
- cumulusci/core/tests/test_source.py +678 -0
- cumulusci/core/tests/test_tasks.py +262 -0
- cumulusci/core/tests/test_utils.py +141 -0
- cumulusci/core/tests/test_utils_merge_config.py +276 -0
- cumulusci/core/tests/test_versions.py +76 -0
- cumulusci/core/tests/untrusted_repo_child/cumulusci.yml +7 -0
- cumulusci/core/tests/untrusted_repo_child/tasks/untrusted_child.py +6 -0
- cumulusci/core/tests/untrusted_repo_parent/cumulusci.yml +26 -0
- cumulusci/core/tests/untrusted_repo_parent/tasks/untrusted_parent.py +6 -0
- cumulusci/core/tests/utils.py +116 -0
- cumulusci/core/tests/yaml/global.yaml +0 -0
- cumulusci/core/utils.py +402 -0
- cumulusci/core/versions.py +149 -0
- cumulusci/cumulusci.yml +1621 -0
- cumulusci/files/admin_profile.xml +20 -0
- cumulusci/files/delete_excludes.txt +424 -0
- cumulusci/files/templates/project/README.md +12 -0
- cumulusci/files/templates/project/cumulusci.yml +63 -0
- cumulusci/files/templates/project/dot-gitignore +60 -0
- cumulusci/files/templates/project/mapping.yml +45 -0
- cumulusci/files/templates/project/scratch_def.json +25 -0
- cumulusci/oauth/__init__.py +0 -0
- cumulusci/oauth/client.py +400 -0
- cumulusci/oauth/exceptions.py +9 -0
- cumulusci/oauth/salesforce.py +95 -0
- cumulusci/oauth/tests/__init__.py +0 -0
- cumulusci/oauth/tests/cassettes/test_get_device_code.yaml +22 -0
- cumulusci/oauth/tests/cassettes/test_get_device_oauth_token.yaml +74 -0
- cumulusci/oauth/tests/test_client.py +308 -0
- cumulusci/oauth/tests/test_salesforce.py +46 -0
- cumulusci/plugins/__init__.py +3 -0
- cumulusci/plugins/plugin_base.py +93 -0
- cumulusci/plugins/plugin_loader.py +59 -0
- cumulusci/robotframework/CumulusCI.py +340 -0
- cumulusci/robotframework/CumulusCI.robot +7 -0
- cumulusci/robotframework/Performance.py +165 -0
- cumulusci/robotframework/Salesforce.py +936 -0
- cumulusci/robotframework/Salesforce.robot +192 -0
- cumulusci/robotframework/SalesforceAPI.py +416 -0
- cumulusci/robotframework/SalesforcePlaywright.py +220 -0
- cumulusci/robotframework/SalesforcePlaywright.robot +40 -0
- cumulusci/robotframework/__init__.py +2 -0
- cumulusci/robotframework/base_library.py +39 -0
- cumulusci/robotframework/faker_mixin.py +89 -0
- cumulusci/robotframework/form_handlers.py +222 -0
- cumulusci/robotframework/javascript/cci_init.js +34 -0
- cumulusci/robotframework/javascript/cumulusci.js +4 -0
- cumulusci/robotframework/locator_manager.py +197 -0
- cumulusci/robotframework/locators_56.py +88 -0
- cumulusci/robotframework/locators_57.py +5 -0
- cumulusci/robotframework/pageobjects/BasePageObjects.py +433 -0
- cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +246 -0
- cumulusci/robotframework/pageobjects/PageObjectLibrary.py +45 -0
- cumulusci/robotframework/pageobjects/PageObjects.py +351 -0
- cumulusci/robotframework/pageobjects/__init__.py +12 -0
- cumulusci/robotframework/pageobjects/baseobjects.py +120 -0
- cumulusci/robotframework/perftests/short/collection_perf.robot +105 -0
- cumulusci/robotframework/tests/CustomObjectTestPage.py +10 -0
- cumulusci/robotframework/tests/FooTestPage.py +8 -0
- cumulusci/robotframework/tests/cumulusci/base.robot +40 -0
- cumulusci/robotframework/tests/cumulusci/bulkdata.robot +38 -0
- cumulusci/robotframework/tests/cumulusci/communities.robot +57 -0
- cumulusci/robotframework/tests/cumulusci/datagen.robot +84 -0
- cumulusci/robotframework/tests/salesforce/TestLibraryA.py +24 -0
- cumulusci/robotframework/tests/salesforce/TestLibraryB.py +20 -0
- cumulusci/robotframework/tests/salesforce/TestListener.py +93 -0
- cumulusci/robotframework/tests/salesforce/api.robot +178 -0
- cumulusci/robotframework/tests/salesforce/browsers.robot +143 -0
- cumulusci/robotframework/tests/salesforce/classic.robot +51 -0
- cumulusci/robotframework/tests/salesforce/create_contact.robot +59 -0
- cumulusci/robotframework/tests/salesforce/faker.robot +68 -0
- cumulusci/robotframework/tests/salesforce/forms.robot +172 -0
- cumulusci/robotframework/tests/salesforce/label_locator.robot +244 -0
- cumulusci/robotframework/tests/salesforce/labels.html +33 -0
- cumulusci/robotframework/tests/salesforce/locators.robot +149 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/base_pageobjects.robot +100 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/example_page_object.py +25 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/listing_page.robot +115 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/objectmanager.robot +74 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/pageobjects.robot +171 -0
- cumulusci/robotframework/tests/salesforce/performance.robot +109 -0
- cumulusci/robotframework/tests/salesforce/playwright/javascript_keywords.robot +33 -0
- cumulusci/robotframework/tests/salesforce/playwright/open_test_browser.robot +48 -0
- cumulusci/robotframework/tests/salesforce/playwright/playwright.robot +24 -0
- cumulusci/robotframework/tests/salesforce/playwright/ui.robot +32 -0
- cumulusci/robotframework/tests/salesforce/populate.robot +89 -0
- cumulusci/robotframework/tests/salesforce/test_testlistener.py +37 -0
- cumulusci/robotframework/tests/salesforce/ui.robot +361 -0
- cumulusci/robotframework/tests/test_cumulusci_library.py +304 -0
- cumulusci/robotframework/tests/test_locator_manager.py +158 -0
- cumulusci/robotframework/tests/test_pageobjects.py +291 -0
- cumulusci/robotframework/tests/test_performance.py +38 -0
- cumulusci/robotframework/tests/test_salesforce.py +79 -0
- cumulusci/robotframework/tests/test_salesforce_locators.py +73 -0
- cumulusci/robotframework/tests/test_template_util.py +53 -0
- cumulusci/robotframework/tests/test_utils.py +106 -0
- cumulusci/robotframework/utils.py +283 -0
- cumulusci/salesforce_api/__init__.py +0 -0
- cumulusci/salesforce_api/exceptions.py +23 -0
- cumulusci/salesforce_api/filterable_objects.py +96 -0
- cumulusci/salesforce_api/mc_soap_envelopes.py +89 -0
- cumulusci/salesforce_api/metadata.py +721 -0
- cumulusci/salesforce_api/org_schema.py +571 -0
- cumulusci/salesforce_api/org_schema_models.py +226 -0
- cumulusci/salesforce_api/package_install.py +265 -0
- cumulusci/salesforce_api/package_zip.py +301 -0
- cumulusci/salesforce_api/rest_deploy.py +148 -0
- cumulusci/salesforce_api/retrieve_profile_api.py +301 -0
- cumulusci/salesforce_api/soap_envelopes.py +177 -0
- cumulusci/salesforce_api/tests/__init__.py +0 -0
- cumulusci/salesforce_api/tests/metadata_test_strings.py +24 -0
- cumulusci/salesforce_api/tests/test_metadata.py +1015 -0
- cumulusci/salesforce_api/tests/test_package_install.py +219 -0
- cumulusci/salesforce_api/tests/test_package_zip.py +380 -0
- cumulusci/salesforce_api/tests/test_rest_deploy.py +264 -0
- cumulusci/salesforce_api/tests/test_retrieve_profile_api.py +337 -0
- cumulusci/salesforce_api/tests/test_utils.py +124 -0
- cumulusci/salesforce_api/utils.py +51 -0
- cumulusci/schema/cumulusci.jsonschema.json +782 -0
- cumulusci/tasks/__init__.py +0 -0
- cumulusci/tasks/apex/__init__.py +0 -0
- cumulusci/tasks/apex/anon.py +157 -0
- cumulusci/tasks/apex/batch.py +180 -0
- cumulusci/tasks/apex/testrunner.py +835 -0
- cumulusci/tasks/apex/tests/cassettes/ManualEditTestApexIntegrationTests.test_run_tests__integration_test.yaml +703 -0
- cumulusci/tasks/apex/tests/test_apex_tasks.py +1558 -0
- cumulusci/tasks/base_source_control_task.py +17 -0
- cumulusci/tasks/bulkdata/__init__.py +15 -0
- cumulusci/tasks/bulkdata/base_generate_data_task.py +96 -0
- cumulusci/tasks/bulkdata/dates.py +97 -0
- cumulusci/tasks/bulkdata/delete.py +156 -0
- cumulusci/tasks/bulkdata/extract.py +441 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/calculate_dependencies.py +117 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +123 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py +49 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +283 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +142 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_synthesize_extract_declarations.py +575 -0
- cumulusci/tasks/bulkdata/factory_utils.py +134 -0
- cumulusci/tasks/bulkdata/generate.py +4 -0
- cumulusci/tasks/bulkdata/generate_and_load_data.py +232 -0
- cumulusci/tasks/bulkdata/generate_and_load_data_from_yaml.py +19 -0
- cumulusci/tasks/bulkdata/generate_from_yaml.py +183 -0
- cumulusci/tasks/bulkdata/generate_mapping.py +434 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/dependency_map.py +169 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/extract_mapping_file_generator.py +45 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/generate_mapping_from_declarations.py +121 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/load_mapping_file_generator.py +127 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_generator_post_processes.py +53 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_transforms.py +139 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_extract_mapping_from_declarations.py +135 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_load_mapping_from_declarations.py +330 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_generator_post_processes.py +60 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_transforms.py +188 -0
- cumulusci/tasks/bulkdata/load.py +1196 -0
- cumulusci/tasks/bulkdata/mapping_parser.py +811 -0
- cumulusci/tasks/bulkdata/query_transformers.py +264 -0
- cumulusci/tasks/bulkdata/select_utils.py +792 -0
- cumulusci/tasks/bulkdata/snowfakery.py +753 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py +478 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_run_until.py +141 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_working_directory.py +53 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/subtask_configurator.py +64 -0
- cumulusci/tasks/bulkdata/step.py +1242 -0
- cumulusci/tasks/bulkdata/tests/__init__.py +0 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_random_strategy.yaml +147 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_annoy_strategy.yaml +123 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy.yaml +313 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy_bulk.yaml +550 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_strategy.yaml +175 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_standard_strategy.yaml +147 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__multiple_needed.yaml +69 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__none_needed.yaml +22 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__one_needed.yaml +24 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_snowfakery_query_salesforce.yaml +25 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpdatesIntegrationTests.test_updates_task.yaml +80 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_simple_upsert__rest.yaml +270 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert__rest.yaml +267 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field__rest.yaml +369 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field_rest__duplicate_error.yaml +204 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_fields__bulk.yaml +675 -0
- cumulusci/tasks/bulkdata/tests/dummy_data_factory.py +36 -0
- cumulusci/tasks/bulkdata/tests/integration_test_utils.py +49 -0
- cumulusci/tasks/bulkdata/tests/mapping-oid.yml +87 -0
- cumulusci/tasks/bulkdata/tests/mapping_after.yml +38 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly.yml +34 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly_incomplete.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly_wrong.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_strategy.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_number.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_strategy.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__non_float.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_missing_priority_fields.yml +22 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_no_priority_fields.yml +18 -0
- cumulusci/tasks/bulkdata/tests/mapping_simple.yml +27 -0
- cumulusci/tasks/bulkdata/tests/mapping_v1.yml +28 -0
- cumulusci/tasks/bulkdata/tests/mapping_v2.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_v3.yml +32 -0
- cumulusci/tasks/bulkdata/tests/mapping_vanilla_sf.yml +69 -0
- cumulusci/tasks/bulkdata/tests/mock_data_factory_without_mapping.py +12 -0
- cumulusci/tasks/bulkdata/tests/person_accounts.yml +23 -0
- cumulusci/tasks/bulkdata/tests/person_accounts_minimal.yml +15 -0
- cumulusci/tasks/bulkdata/tests/recordtypes.yml +8 -0
- cumulusci/tasks/bulkdata/tests/recordtypes_2.yml +6 -0
- cumulusci/tasks/bulkdata/tests/recordtypes_with_ispersontype.yml +8 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/child/child2.yml +3 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/child.yml +4 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/gen_npsp_standard_objects.recipe.yml +89 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/include_parent.yml +3 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/npsp_standard_objects_macros.yml +34 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/options.recipe.yml +6 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/query_snowfakery.recipe.yml +16 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/sf_standard_object_macros.yml +83 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.load.yml +2 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.recipe.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_2.load.yml +5 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.load.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.recipe.yml +12 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels_2.load.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/unique_values.recipe.yml +4 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert.recipe.yml +23 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert_2.recipe.yml +29 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert_before.yml +10 -0
- cumulusci/tasks/bulkdata/tests/test_base_generate_data_tasks.py +61 -0
- cumulusci/tasks/bulkdata/tests/test_dates.py +99 -0
- cumulusci/tasks/bulkdata/tests/test_delete.py +404 -0
- cumulusci/tasks/bulkdata/tests/test_extract.py +1311 -0
- cumulusci/tasks/bulkdata/tests/test_factory_utils.py +55 -0
- cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +252 -0
- cumulusci/tasks/bulkdata/tests/test_generate_from_snowfakery_task.py +343 -0
- cumulusci/tasks/bulkdata/tests/test_generatemapping.py +1039 -0
- cumulusci/tasks/bulkdata/tests/test_load.py +3175 -0
- cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +1658 -0
- cumulusci/tasks/bulkdata/tests/test_query_db__joins_self_lookups.yml +12 -0
- cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups.yml +26 -0
- cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups_select.yml +48 -0
- cumulusci/tasks/bulkdata/tests/test_select.py +171 -0
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +1057 -0
- cumulusci/tasks/bulkdata/tests/test_snowfakery.py +1153 -0
- cumulusci/tasks/bulkdata/tests/test_step.py +3957 -0
- cumulusci/tasks/bulkdata/tests/test_updates.py +513 -0
- cumulusci/tasks/bulkdata/tests/test_upsert.py +1015 -0
- cumulusci/tasks/bulkdata/tests/test_utils.py +158 -0
- cumulusci/tasks/bulkdata/tests/testdata.db +0 -0
- cumulusci/tasks/bulkdata/tests/update_describe.py +50 -0
- cumulusci/tasks/bulkdata/tests/update_person_accounts.yml +23 -0
- cumulusci/tasks/bulkdata/tests/utils.py +114 -0
- cumulusci/tasks/bulkdata/update_data.py +260 -0
- cumulusci/tasks/bulkdata/upsert_utils.py +130 -0
- cumulusci/tasks/bulkdata/utils.py +249 -0
- cumulusci/tasks/command.py +178 -0
- cumulusci/tasks/connectedapp.py +186 -0
- cumulusci/tasks/create_package_version.py +778 -0
- cumulusci/tasks/datadictionary.py +745 -0
- cumulusci/tasks/dx_convert_from.py +26 -0
- cumulusci/tasks/github/__init__.py +17 -0
- cumulusci/tasks/github/base.py +16 -0
- cumulusci/tasks/github/commit_status.py +13 -0
- cumulusci/tasks/github/merge.py +11 -0
- cumulusci/tasks/github/publish.py +11 -0
- cumulusci/tasks/github/pull_request.py +11 -0
- cumulusci/tasks/github/release.py +11 -0
- cumulusci/tasks/github/release_report.py +11 -0
- cumulusci/tasks/github/tag.py +11 -0
- cumulusci/tasks/github/tests/__init__.py +0 -0
- cumulusci/tasks/github/tests/test_util.py +202 -0
- cumulusci/tasks/github/tests/test_vcs_migration.py +44 -0
- cumulusci/tasks/github/tests/util_github_api.py +666 -0
- cumulusci/tasks/github/util.py +252 -0
- cumulusci/tasks/marketing_cloud/__init__.py +0 -0
- cumulusci/tasks/marketing_cloud/api.py +188 -0
- cumulusci/tasks/marketing_cloud/base.py +38 -0
- cumulusci/tasks/marketing_cloud/deploy.py +345 -0
- cumulusci/tasks/marketing_cloud/get_user_info.py +40 -0
- cumulusci/tasks/marketing_cloud/mc_constants.py +1 -0
- cumulusci/tasks/marketing_cloud/tests/__init__.py +0 -0
- cumulusci/tasks/marketing_cloud/tests/conftest.py +46 -0
- cumulusci/tasks/marketing_cloud/tests/expected-payload.json +110 -0
- cumulusci/tasks/marketing_cloud/tests/test_api.py +97 -0
- cumulusci/tasks/marketing_cloud/tests/test_api_soap_envelopes.py +145 -0
- cumulusci/tasks/marketing_cloud/tests/test_base.py +14 -0
- cumulusci/tasks/marketing_cloud/tests/test_deploy.py +400 -0
- cumulusci/tasks/marketing_cloud/tests/test_get_user_info.py +141 -0
- cumulusci/tasks/marketing_cloud/tests/validation-response.json +39 -0
- cumulusci/tasks/metadata/__init__.py +0 -0
- cumulusci/tasks/metadata/ee_src.py +94 -0
- cumulusci/tasks/metadata/managed_src.py +100 -0
- cumulusci/tasks/metadata/metadata_map.yml +868 -0
- cumulusci/tasks/metadata/modify.py +99 -0
- cumulusci/tasks/metadata/package.py +684 -0
- cumulusci/tasks/metadata/tests/__init__.py +0 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/.hidden/.keep +0 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/destructiveChanges.xml +9 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package.xml +9 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package_install_uninstall.xml +11 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/reports/namespace__TestFolder/TestReport.report +3 -0
- cumulusci/tasks/metadata/tests/sample_package.xml +9 -0
- cumulusci/tasks/metadata/tests/test_ee_src.py +112 -0
- cumulusci/tasks/metadata/tests/test_managed_src.py +111 -0
- cumulusci/tasks/metadata/tests/test_modify.py +123 -0
- cumulusci/tasks/metadata/tests/test_package.py +476 -0
- cumulusci/tasks/metadata_etl/__init__.py +29 -0
- cumulusci/tasks/metadata_etl/base.py +436 -0
- cumulusci/tasks/metadata_etl/duplicate_rules.py +24 -0
- cumulusci/tasks/metadata_etl/field_sets.py +70 -0
- cumulusci/tasks/metadata_etl/help_text.py +92 -0
- cumulusci/tasks/metadata_etl/layouts.py +550 -0
- cumulusci/tasks/metadata_etl/objects.py +68 -0
- cumulusci/tasks/metadata_etl/permissions.py +167 -0
- cumulusci/tasks/metadata_etl/picklists.py +221 -0
- cumulusci/tasks/metadata_etl/remote_site_settings.py +99 -0
- cumulusci/tasks/metadata_etl/sharing.py +138 -0
- cumulusci/tasks/metadata_etl/tests/test_base.py +512 -0
- cumulusci/tasks/metadata_etl/tests/test_duplicate_rules.py +22 -0
- cumulusci/tasks/metadata_etl/tests/test_field_sets.py +156 -0
- cumulusci/tasks/metadata_etl/tests/test_help_text.py +387 -0
- cumulusci/tasks/metadata_etl/tests/test_ip_ranges.py +85 -0
- cumulusci/tasks/metadata_etl/tests/test_layouts.py +858 -0
- cumulusci/tasks/metadata_etl/tests/test_objects.py +236 -0
- cumulusci/tasks/metadata_etl/tests/test_permissions.py +223 -0
- cumulusci/tasks/metadata_etl/tests/test_picklists.py +547 -0
- cumulusci/tasks/metadata_etl/tests/test_remote_site_settings.py +46 -0
- cumulusci/tasks/metadata_etl/tests/test_sharing.py +333 -0
- cumulusci/tasks/metadata_etl/tests/test_value_sets.py +298 -0
- cumulusci/tasks/metadata_etl/value_sets.py +106 -0
- cumulusci/tasks/metadeploy.py +393 -0
- cumulusci/tasks/metaxml.py +88 -0
- cumulusci/tasks/preflight/__init__.py +0 -0
- cumulusci/tasks/preflight/dataset_load.py +49 -0
- cumulusci/tasks/preflight/licenses.py +86 -0
- cumulusci/tasks/preflight/packages.py +14 -0
- cumulusci/tasks/preflight/permsets.py +23 -0
- cumulusci/tasks/preflight/recordtypes.py +16 -0
- cumulusci/tasks/preflight/retrieve_tasks.py +30 -0
- cumulusci/tasks/preflight/settings.py +77 -0
- cumulusci/tasks/preflight/sobjects.py +202 -0
- cumulusci/tasks/preflight/tests/test_dataset_load.py +85 -0
- cumulusci/tasks/preflight/tests/test_licenses.py +174 -0
- cumulusci/tasks/preflight/tests/test_packages.py +14 -0
- cumulusci/tasks/preflight/tests/test_permset_preflights.py +51 -0
- cumulusci/tasks/preflight/tests/test_recordtypes.py +30 -0
- cumulusci/tasks/preflight/tests/test_retrieve_tasks.py +62 -0
- cumulusci/tasks/preflight/tests/test_settings.py +130 -0
- cumulusci/tasks/preflight/tests/test_sobjects.py +231 -0
- cumulusci/tasks/push/README.md +59 -0
- cumulusci/tasks/push/__init__.py +0 -0
- cumulusci/tasks/push/push_api.py +659 -0
- cumulusci/tasks/push/pushfails.py +136 -0
- cumulusci/tasks/push/tasks.py +476 -0
- cumulusci/tasks/push/tests/conftest.py +263 -0
- cumulusci/tasks/push/tests/test_push_api.py +951 -0
- cumulusci/tasks/push/tests/test_push_tasks.py +659 -0
- cumulusci/tasks/release_notes/README.md +63 -0
- cumulusci/tasks/release_notes/__init__.py +0 -0
- cumulusci/tasks/release_notes/exceptions.py +5 -0
- cumulusci/tasks/release_notes/generator.py +137 -0
- cumulusci/tasks/release_notes/parser.py +232 -0
- cumulusci/tasks/release_notes/provider.py +44 -0
- cumulusci/tasks/release_notes/task.py +300 -0
- cumulusci/tasks/release_notes/tests/__init__.py +0 -0
- cumulusci/tasks/release_notes/tests/change_notes/full/example1.md +17 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/1.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/2.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/3.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/single/1.txt +1 -0
- cumulusci/tasks/release_notes/tests/test_generator.py +582 -0
- cumulusci/tasks/release_notes/tests/test_parser.py +867 -0
- cumulusci/tasks/release_notes/tests/test_provider.py +512 -0
- cumulusci/tasks/release_notes/tests/test_task.py +461 -0
- cumulusci/tasks/release_notes/tests/utils.py +153 -0
- cumulusci/tasks/robotframework/__init__.py +3 -0
- cumulusci/tasks/robotframework/debugger/DebugListener.py +100 -0
- cumulusci/tasks/robotframework/debugger/__init__.py +10 -0
- cumulusci/tasks/robotframework/debugger/model.py +87 -0
- cumulusci/tasks/robotframework/debugger/ui.py +259 -0
- cumulusci/tasks/robotframework/libdoc.py +269 -0
- cumulusci/tasks/robotframework/robotframework.py +392 -0
- cumulusci/tasks/robotframework/stylesheet.css +130 -0
- cumulusci/tasks/robotframework/template.html +109 -0
- cumulusci/tasks/robotframework/tests/TestLibrary.py +18 -0
- cumulusci/tasks/robotframework/tests/TestPageObjects.py +31 -0
- cumulusci/tasks/robotframework/tests/TestResource.robot +8 -0
- cumulusci/tasks/robotframework/tests/failing_tests.robot +16 -0
- cumulusci/tasks/robotframework/tests/performance.robot +23 -0
- cumulusci/tasks/robotframework/tests/test_browser_proxies.py +137 -0
- cumulusci/tasks/robotframework/tests/test_debugger.py +360 -0
- cumulusci/tasks/robotframework/tests/test_robot_parallel.py +141 -0
- cumulusci/tasks/robotframework/tests/test_robotframework.py +860 -0
- cumulusci/tasks/salesforce/BaseRetrieveMetadata.py +58 -0
- cumulusci/tasks/salesforce/BaseSalesforceApiTask.py +45 -0
- cumulusci/tasks/salesforce/BaseSalesforceMetadataApiTask.py +18 -0
- cumulusci/tasks/salesforce/BaseSalesforceTask.py +4 -0
- cumulusci/tasks/salesforce/BaseUninstallMetadata.py +41 -0
- cumulusci/tasks/salesforce/CreateCommunity.py +124 -0
- cumulusci/tasks/salesforce/CreatePackage.py +29 -0
- cumulusci/tasks/salesforce/Deploy.py +240 -0
- cumulusci/tasks/salesforce/DeployBundles.py +88 -0
- cumulusci/tasks/salesforce/DescribeMetadataTypes.py +26 -0
- cumulusci/tasks/salesforce/EnsureRecordTypes.py +202 -0
- cumulusci/tasks/salesforce/GetInstalledPackages.py +8 -0
- cumulusci/tasks/salesforce/ListCommunities.py +40 -0
- cumulusci/tasks/salesforce/ListCommunityTemplates.py +19 -0
- cumulusci/tasks/salesforce/PublishCommunity.py +62 -0
- cumulusci/tasks/salesforce/RetrievePackaged.py +41 -0
- cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py +82 -0
- cumulusci/tasks/salesforce/RetrieveUnpackaged.py +36 -0
- cumulusci/tasks/salesforce/SOQLQuery.py +39 -0
- cumulusci/tasks/salesforce/UninstallLocal.py +15 -0
- cumulusci/tasks/salesforce/UninstallLocalBundles.py +28 -0
- cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py +58 -0
- cumulusci/tasks/salesforce/UninstallPackage.py +32 -0
- cumulusci/tasks/salesforce/UninstallPackaged.py +56 -0
- cumulusci/tasks/salesforce/UpdateAdminProfile.py +8 -0
- cumulusci/tasks/salesforce/__init__.py +79 -0
- cumulusci/tasks/salesforce/activate_flow.py +74 -0
- cumulusci/tasks/salesforce/check_components.py +324 -0
- cumulusci/tasks/salesforce/composite.py +142 -0
- cumulusci/tasks/salesforce/create_permission_sets.py +35 -0
- cumulusci/tasks/salesforce/custom_settings.py +134 -0
- cumulusci/tasks/salesforce/custom_settings_wait.py +132 -0
- cumulusci/tasks/salesforce/enable_prediction.py +107 -0
- cumulusci/tasks/salesforce/insert_record.py +40 -0
- cumulusci/tasks/salesforce/install_package_version.py +242 -0
- cumulusci/tasks/salesforce/license_preflights.py +8 -0
- cumulusci/tasks/salesforce/network_member_group.py +178 -0
- cumulusci/tasks/salesforce/nonsourcetracking.py +228 -0
- cumulusci/tasks/salesforce/org_settings.py +193 -0
- cumulusci/tasks/salesforce/package_upload.py +328 -0
- cumulusci/tasks/salesforce/profiles.py +74 -0
- cumulusci/tasks/salesforce/promote_package_version.py +376 -0
- cumulusci/tasks/salesforce/retrieve_profile.py +195 -0
- cumulusci/tasks/salesforce/salesforce_files.py +244 -0
- cumulusci/tasks/salesforce/sourcetracking.py +507 -0
- cumulusci/tasks/salesforce/tests/__init__.py +3 -0
- cumulusci/tasks/salesforce/tests/test_CreateCommunity.py +278 -0
- cumulusci/tasks/salesforce/tests/test_CreatePackage.py +22 -0
- cumulusci/tasks/salesforce/tests/test_Deploy.py +470 -0
- cumulusci/tasks/salesforce/tests/test_DeployBundles.py +76 -0
- cumulusci/tasks/salesforce/tests/test_EnsureRecordTypes.py +345 -0
- cumulusci/tasks/salesforce/tests/test_ListCommunities.py +84 -0
- cumulusci/tasks/salesforce/tests/test_ListCommunityTemplates.py +49 -0
- cumulusci/tasks/salesforce/tests/test_PackageUpload.py +547 -0
- cumulusci/tasks/salesforce/tests/test_ProfileGrantAllAccess.py +699 -0
- cumulusci/tasks/salesforce/tests/test_PublishCommunity.py +181 -0
- cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +24 -0
- cumulusci/tasks/salesforce/tests/test_RetrieveReportsAndDashboards.py +56 -0
- cumulusci/tasks/salesforce/tests/test_RetrieveUnpackaged.py +21 -0
- cumulusci/tasks/salesforce/tests/test_SOQLQuery.py +30 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocal.py +15 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocalBundles.py +19 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocalNamespacedBundles.py +22 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackage.py +19 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +66 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +127 -0
- cumulusci/tasks/salesforce/tests/test_activate_flow.py +132 -0
- cumulusci/tasks/salesforce/tests/test_base_tasks.py +110 -0
- cumulusci/tasks/salesforce/tests/test_check_components.py +445 -0
- cumulusci/tasks/salesforce/tests/test_composite.py +250 -0
- cumulusci/tasks/salesforce/tests/test_create_permission_sets.py +41 -0
- cumulusci/tasks/salesforce/tests/test_custom_settings.py +227 -0
- cumulusci/tasks/salesforce/tests/test_custom_settings_wait.py +174 -0
- cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py +18 -0
- cumulusci/tasks/salesforce/tests/test_enable_prediction.py +240 -0
- cumulusci/tasks/salesforce/tests/test_insert_record.py +110 -0
- cumulusci/tasks/salesforce/tests/test_install_package_version.py +464 -0
- cumulusci/tasks/salesforce/tests/test_network_member_group.py +444 -0
- cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py +235 -0
- cumulusci/tasks/salesforce/tests/test_org_settings.py +407 -0
- cumulusci/tasks/salesforce/tests/test_profiles.py +202 -0
- cumulusci/tasks/salesforce/tests/test_retrieve_profile.py +287 -0
- cumulusci/tasks/salesforce/tests/test_salesforce_files.py +228 -0
- cumulusci/tasks/salesforce/tests/test_sourcetracking.py +350 -0
- cumulusci/tasks/salesforce/tests/test_trigger_handlers.py +300 -0
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +509 -0
- cumulusci/tasks/salesforce/tests/util.py +79 -0
- cumulusci/tasks/salesforce/trigger_handlers.py +119 -0
- cumulusci/tasks/salesforce/uninstall_packaged_incremental.py +136 -0
- cumulusci/tasks/salesforce/update_dependencies.py +290 -0
- cumulusci/tasks/salesforce/update_profile.py +339 -0
- cumulusci/tasks/salesforce/users/permsets.py +227 -0
- cumulusci/tasks/salesforce/users/photos.py +162 -0
- cumulusci/tasks/salesforce/users/tests/photo.mock.txt +1 -0
- cumulusci/tasks/salesforce/users/tests/test_permsets.py +950 -0
- cumulusci/tasks/salesforce/users/tests/test_photos.py +373 -0
- cumulusci/tasks/sample_data/capture_sample_data.py +77 -0
- cumulusci/tasks/sample_data/load_sample_data.py +85 -0
- cumulusci/tasks/sample_data/test_capture_sample_data.py +117 -0
- cumulusci/tasks/sample_data/test_load_sample_data.py +121 -0
- cumulusci/tasks/sfdx.py +83 -0
- cumulusci/tasks/tests/__init__.py +1 -0
- cumulusci/tasks/tests/conftest.py +30 -0
- cumulusci/tasks/tests/test_command.py +129 -0
- cumulusci/tasks/tests/test_connectedapp.py +236 -0
- cumulusci/tasks/tests/test_create_package_version.py +847 -0
- cumulusci/tasks/tests/test_datadictionary.py +1575 -0
- cumulusci/tasks/tests/test_dx_convert_from.py +60 -0
- cumulusci/tasks/tests/test_metadeploy.py +624 -0
- cumulusci/tasks/tests/test_metaxml.py +99 -0
- cumulusci/tasks/tests/test_promote_package_version.py +488 -0
- cumulusci/tasks/tests/test_pushfails.py +96 -0
- cumulusci/tasks/tests/test_salesforce.py +72 -0
- cumulusci/tasks/tests/test_sfdx.py +105 -0
- cumulusci/tasks/tests/test_util.py +207 -0
- cumulusci/tasks/util.py +261 -0
- cumulusci/tasks/vcs/__init__.py +19 -0
- cumulusci/tasks/vcs/commit_status.py +58 -0
- cumulusci/tasks/vcs/create_commit_status.py +37 -0
- cumulusci/tasks/vcs/download_extract.py +199 -0
- cumulusci/tasks/vcs/merge.py +298 -0
- cumulusci/tasks/vcs/publish.py +207 -0
- cumulusci/tasks/vcs/pull_request.py +9 -0
- cumulusci/tasks/vcs/release.py +134 -0
- cumulusci/tasks/vcs/release_report.py +105 -0
- cumulusci/tasks/vcs/tag.py +31 -0
- cumulusci/tasks/vcs/tests/github/test_commit_status.py +196 -0
- cumulusci/tasks/vcs/tests/github/test_download_extract.py +896 -0
- cumulusci/tasks/vcs/tests/github/test_merge.py +1118 -0
- cumulusci/tasks/vcs/tests/github/test_publish.py +823 -0
- cumulusci/tasks/vcs/tests/github/test_pull_request.py +29 -0
- cumulusci/tasks/vcs/tests/github/test_release.py +390 -0
- cumulusci/tasks/vcs/tests/github/test_release_report.py +109 -0
- cumulusci/tasks/vcs/tests/github/test_tag.py +90 -0
- cumulusci/tasks/vlocity/exceptions.py +2 -0
- cumulusci/tasks/vlocity/tests/test_vlocity.py +283 -0
- cumulusci/tasks/vlocity/vlocity.py +342 -0
- cumulusci/tests/__init__.py +1 -0
- cumulusci/tests/cassettes/GET_sobjects_Account_PersonAccount_describe.yaml +18 -0
- cumulusci/tests/cassettes/TestIntegrationInfrastructure.test_integration_tests.yaml +19 -0
- cumulusci/tests/pytest_plugins/pytest_sf_orgconnect.py +307 -0
- cumulusci/tests/pytest_plugins/pytest_sf_vcr.py +275 -0
- cumulusci/tests/pytest_plugins/pytest_sf_vcr_serializer.py +160 -0
- cumulusci/tests/pytest_plugins/pytest_typeguard.py +5 -0
- cumulusci/tests/pytest_plugins/test_vcr_string_compressor.py +49 -0
- cumulusci/tests/pytest_plugins/vcr_string_compressor.py +97 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Account_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Case_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Contact_describe.yaml +4838 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Custom__c_describe.yaml +242 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Event_describe.yaml +19 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Global_describe.yaml +1338 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Lead_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_OpportunityContactRole_describe.yaml +34 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Opportunity_describe.yaml +1261 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Organization.yaml +49 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfoList_xml.tpl +15 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfo_xml.tpl +13 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_insert_xml.tpl +24 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_upsert_xml.tpl +25 -0
- cumulusci/tests/test_entry_points.py +20 -0
- cumulusci/tests/test_integration_infrastructure.py +131 -0
- cumulusci/tests/test_main.py +9 -0
- cumulusci/tests/test_schema.py +32 -0
- cumulusci/tests/test_utils.py +657 -0
- cumulusci/tests/test_vcr_serializer.py +134 -0
- cumulusci/tests/uncompressed_cassette.yaml +83 -0
- cumulusci/tests/util.py +344 -0
- cumulusci/utils/__init__.py +731 -0
- cumulusci/utils/classutils.py +9 -0
- cumulusci/utils/collections.py +32 -0
- cumulusci/utils/deprecation.py +11 -0
- cumulusci/utils/encryption.py +31 -0
- cumulusci/utils/fileutils.py +295 -0
- cumulusci/utils/git.py +142 -0
- cumulusci/utils/http/multi_request.py +214 -0
- cumulusci/utils/http/requests_utils.py +103 -0
- cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +32 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_composite_parallel_salesforce.yaml +65 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_errors.yaml +24 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_reference_ids.yaml +49 -0
- cumulusci/utils/http/tests/test_multi_request.py +255 -0
- cumulusci/utils/iterators.py +21 -0
- cumulusci/utils/logging.py +128 -0
- cumulusci/utils/metaprogramming.py +10 -0
- cumulusci/utils/options.py +138 -0
- cumulusci/utils/parallel/queries_in_parallel/run_queries_in_parallel.py +29 -0
- cumulusci/utils/parallel/queries_in_parallel/tests/test_run_queries_in_parallel.py +50 -0
- cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +238 -0
- cumulusci/utils/parallel/task_worker_queues/parallel_worker_queue.py +243 -0
- cumulusci/utils/parallel/task_worker_queues/tests/test_parallel_worker.py +353 -0
- cumulusci/utils/salesforce/count_sobjects.py +46 -0
- cumulusci/utils/salesforce/soql.py +17 -0
- cumulusci/utils/salesforce/tests/cassettes/ManualEdit_TestCountSObjects.test_count_sobjects__network_errors.yaml +23 -0
- cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects__errors.yaml +33 -0
- cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects_simple.yaml +29 -0
- cumulusci/utils/salesforce/tests/test_count_sobjects.py +29 -0
- cumulusci/utils/salesforce/tests/test_soql.py +30 -0
- cumulusci/utils/tests/cassettes/ManualEditTestDescribeOrg.test_minimal_schema.yaml +36 -0
- cumulusci/utils/tests/cassettes/ManualEdit_test_describe_to_sql.yaml +191 -0
- cumulusci/utils/tests/test_fileutils.py +284 -0
- cumulusci/utils/tests/test_git.py +85 -0
- cumulusci/utils/tests/test_logging.py +70 -0
- cumulusci/utils/tests/test_option_parsing.py +188 -0
- cumulusci/utils/tests/test_org_schema.py +691 -0
- cumulusci/utils/tests/test_org_schema_models.py +79 -0
- cumulusci/utils/tests/test_waiting.py +25 -0
- cumulusci/utils/version_strings.py +391 -0
- cumulusci/utils/waiting.py +42 -0
- cumulusci/utils/xml/__init__.py +91 -0
- cumulusci/utils/xml/metadata_tree.py +299 -0
- cumulusci/utils/xml/robot_xml.py +114 -0
- cumulusci/utils/xml/salesforce_encoding.py +100 -0
- cumulusci/utils/xml/test/test_metadata_tree.py +251 -0
- cumulusci/utils/xml/test/test_salesforce_encoding.py +173 -0
- cumulusci/utils/yaml/cumulusci_yml.py +401 -0
- cumulusci/utils/yaml/model_parser.py +156 -0
- cumulusci/utils/yaml/safer_loader.py +74 -0
- cumulusci/utils/yaml/tests/bad_cci.yml +5 -0
- cumulusci/utils/yaml/tests/cassettes/TestCumulusciYml.test_validate_url__with_errors.yaml +20 -0
- cumulusci/utils/yaml/tests/test_cumulusci_yml.py +286 -0
- cumulusci/utils/yaml/tests/test_model_parser.py +175 -0
- cumulusci/utils/yaml/tests/test_safer_loader.py +88 -0
- cumulusci/utils/ziputils.py +61 -0
- cumulusci/vcs/base.py +143 -0
- cumulusci/vcs/bootstrap.py +272 -0
- cumulusci/vcs/github/__init__.py +24 -0
- cumulusci/vcs/github/adapter.py +689 -0
- cumulusci/vcs/github/release_notes/generator.py +219 -0
- cumulusci/vcs/github/release_notes/parser.py +151 -0
- cumulusci/vcs/github/release_notes/provider.py +143 -0
- cumulusci/vcs/github/service.py +569 -0
- cumulusci/vcs/github/tests/test_adapter.py +138 -0
- cumulusci/vcs/github/tests/test_service.py +408 -0
- cumulusci/vcs/models.py +586 -0
- cumulusci/vcs/tests/conftest.py +41 -0
- cumulusci/vcs/tests/dummy_service.py +241 -0
- cumulusci/vcs/tests/test_vcs_base.py +687 -0
- cumulusci/vcs/tests/test_vcs_bootstrap.py +727 -0
- cumulusci/vcs/utils/__init__.py +31 -0
- cumulusci/vcs/vcs_source.py +287 -0
- cumulusci_plus-5.0.0.dist-info/METADATA +145 -0
- cumulusci_plus-5.0.0.dist-info/RECORD +744 -0
- cumulusci_plus-5.0.0.dist-info/WHEEL +4 -0
- cumulusci_plus-5.0.0.dist-info/entry_points.txt +3 -0
- cumulusci_plus-5.0.0.dist-info/licenses/AUTHORS.rst +41 -0
- cumulusci_plus-5.0.0.dist-info/licenses/LICENSE +30 -0
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
""" FlowRunner contains the logic for actually running a flow.
|
|
2
|
+
|
|
3
|
+
Flows are an integral part of CCI, they actually *do the thing*. We've been getting
|
|
4
|
+
along quite nicely with BaseFlow, which turns a flow definition into a callable
|
|
5
|
+
object that runs the flow in one fell swoop. We named it BaseFlow thinking that,
|
|
6
|
+
like tasks, specific flows might subclass it to extend behavior. In practice,
|
|
7
|
+
unlike BaseTask, subclasses ended up representing variations in how the flow
|
|
8
|
+
should actually be executed. We added callback hooks like pre_task and post_task
|
|
9
|
+
for host systems embedding cci, like web apps, to inspect the flow in progress.
|
|
10
|
+
|
|
11
|
+
BaseFlow suited us well.
|
|
12
|
+
|
|
13
|
+
FlowRunner is a v2 API for flows in CCI. There are two objects of interest:
|
|
14
|
+
|
|
15
|
+
- FlowCoordinator: takes a flow_config & runtime options to create a set of StepSpecs
|
|
16
|
+
- Meant to replace the public API of BaseFlow, including override hooks.
|
|
17
|
+
- Precomputes a flat list of steps, instead of running Flow recursively.
|
|
18
|
+
- TaskRunner: encapsulates the actual task running, result providing logic.
|
|
19
|
+
|
|
20
|
+
Upon initialization, FlowRunner:
|
|
21
|
+
|
|
22
|
+
- Creates a logger
|
|
23
|
+
- Validates that there are no cycles in the given flow_config
|
|
24
|
+
- Validates that the flow_config is using new-style-steps
|
|
25
|
+
- Collects a list of StepSpec objects that define what the flow will do.
|
|
26
|
+
|
|
27
|
+
Upon running the flow, FlowRunner:
|
|
28
|
+
|
|
29
|
+
- Refreshes the org credentials
|
|
30
|
+
- Runs each StepSpec in order
|
|
31
|
+
- * Logs the task or skip
|
|
32
|
+
- * Updates any ^^ task option values with return_values references
|
|
33
|
+
- * Creates a TaskRunner to run the task and get the result
|
|
34
|
+
- * Re-raise any fatal exceptions from the task, if not ignore_failure.
|
|
35
|
+
- * collects StepResults into the flow.
|
|
36
|
+
|
|
37
|
+
TaskRunner:
|
|
38
|
+
|
|
39
|
+
- Imports the actual task module.
|
|
40
|
+
- Constructs an instance of the BaseTask subclass.
|
|
41
|
+
- Runs/calls the task instance.
|
|
42
|
+
- Returns results or exception into an immutable StepResult
|
|
43
|
+
|
|
44
|
+
Option values/overrides can be passed in at a number of levels, in increasing order of priority:
|
|
45
|
+
|
|
46
|
+
- Task default (i.e. `.tasks__TASKNAME__options`)
|
|
47
|
+
- Flow definition task options (i.e. `.flows__FLOWNAME__steps__STEPNUM__options`)
|
|
48
|
+
- Flow definition subflow options (i.e. `.flows__FLOWNAME__steps__STEPNUM__options__TASKNAME`)
|
|
49
|
+
see `dev_org_namespaced` for an example
|
|
50
|
+
- Flow runtime (i.e. on the commandline)
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
import copy
|
|
55
|
+
import logging
|
|
56
|
+
from collections import defaultdict
|
|
57
|
+
from operator import attrgetter
|
|
58
|
+
from typing import (
|
|
59
|
+
TYPE_CHECKING,
|
|
60
|
+
Any,
|
|
61
|
+
DefaultDict,
|
|
62
|
+
Dict,
|
|
63
|
+
List,
|
|
64
|
+
NamedTuple,
|
|
65
|
+
Optional,
|
|
66
|
+
Tuple,
|
|
67
|
+
Type,
|
|
68
|
+
Union,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
|
72
|
+
|
|
73
|
+
from cumulusci.core.config import FlowConfig, TaskConfig
|
|
74
|
+
from cumulusci.core.config.org_config import OrgConfig
|
|
75
|
+
from cumulusci.core.config.project_config import BaseProjectConfig
|
|
76
|
+
from cumulusci.core.exceptions import (
|
|
77
|
+
FlowConfigError,
|
|
78
|
+
FlowInfiniteLoopError,
|
|
79
|
+
TaskImportError,
|
|
80
|
+
)
|
|
81
|
+
from cumulusci.utils.version_strings import LooseVersion
|
|
82
|
+
|
|
83
|
+
if TYPE_CHECKING:
|
|
84
|
+
from cumulusci.core.tasks import BaseTask
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
RETURN_VALUE_OPTION_PREFIX = "^^"
|
|
88
|
+
|
|
89
|
+
jinja2_env = ImmutableSandboxedEnvironment()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class StepVersion(LooseVersion):
|
|
93
|
+
"""Like LooseVersion, but converts "/" into -1 to support comparisons"""
|
|
94
|
+
|
|
95
|
+
def parse(self, vstring: str):
|
|
96
|
+
super().parse(vstring)
|
|
97
|
+
self.version = tuple(-1 if x == "/" else x for x in self.version)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class StepSpec:
|
|
101
|
+
"""simple namespace to describe what the flowrunner should do each step"""
|
|
102
|
+
|
|
103
|
+
__slots__ = (
|
|
104
|
+
"step_num",
|
|
105
|
+
"task_name",
|
|
106
|
+
"task_config",
|
|
107
|
+
"task_class",
|
|
108
|
+
"project_config",
|
|
109
|
+
"allow_failure",
|
|
110
|
+
"path",
|
|
111
|
+
"skip",
|
|
112
|
+
"when",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
step_num: StepVersion
|
|
116
|
+
task_name: str
|
|
117
|
+
task_config: dict
|
|
118
|
+
task_class: Optional[
|
|
119
|
+
Type["BaseTask"]
|
|
120
|
+
] # None means this step was skipped by setting task: None
|
|
121
|
+
project_config: BaseProjectConfig
|
|
122
|
+
allow_failure: bool
|
|
123
|
+
path: str
|
|
124
|
+
skip: bool
|
|
125
|
+
when: Optional[str]
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
step_num: StepVersion,
|
|
130
|
+
task_name: str,
|
|
131
|
+
task_config: dict,
|
|
132
|
+
task_class: Optional[Type["BaseTask"]],
|
|
133
|
+
project_config: BaseProjectConfig,
|
|
134
|
+
allow_failure: bool = False,
|
|
135
|
+
from_flow: Optional[str] = None,
|
|
136
|
+
skip: bool = False,
|
|
137
|
+
when: Optional[str] = None,
|
|
138
|
+
):
|
|
139
|
+
self.step_num = step_num
|
|
140
|
+
self.task_name = task_name
|
|
141
|
+
self.task_config = task_config
|
|
142
|
+
self.task_class = task_class
|
|
143
|
+
self.project_config = project_config
|
|
144
|
+
self.allow_failure = allow_failure
|
|
145
|
+
self.skip = skip
|
|
146
|
+
self.when = when
|
|
147
|
+
|
|
148
|
+
# Store the dotted path to this step.
|
|
149
|
+
# This is not guaranteed to be unique, because multiple steps
|
|
150
|
+
# in the same flow can reference the same task name with different options.
|
|
151
|
+
# It's here to support the ^^flow_name.task_name.attr_name syntax
|
|
152
|
+
# for referencing previous task return values in options.
|
|
153
|
+
if from_flow:
|
|
154
|
+
self.path = ".".join([from_flow, task_name])
|
|
155
|
+
else:
|
|
156
|
+
self.path = task_name
|
|
157
|
+
|
|
158
|
+
def __repr__(self):
|
|
159
|
+
skipstr = ""
|
|
160
|
+
if self.skip:
|
|
161
|
+
skipstr = "!SKIP! "
|
|
162
|
+
return (
|
|
163
|
+
f"<{skipstr}StepSpec {self.step_num}:{self.task_name} {self.task_config}>"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class StepResult(NamedTuple):
|
|
168
|
+
step_num: StepVersion
|
|
169
|
+
task_name: str
|
|
170
|
+
path: str
|
|
171
|
+
result: Any
|
|
172
|
+
return_values: Any
|
|
173
|
+
exception: Optional[Exception]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class FlowCallback:
|
|
177
|
+
"""A subclass of FlowCallback allows code running a flow
|
|
178
|
+
to inject callback methods to run during the flow. Anything you
|
|
179
|
+
would like the FlowCallback to have access to can be passed to the
|
|
180
|
+
constructor. This is typically used to pass a Django model or model id
|
|
181
|
+
when running a flow inside of a web app.
|
|
182
|
+
|
|
183
|
+
Example subclass of FlowCallback:
|
|
184
|
+
|
|
185
|
+
class CustomFlowCallback(FlowCallback):
|
|
186
|
+
def __init__(self, model):
|
|
187
|
+
self.model = model
|
|
188
|
+
|
|
189
|
+
def post_task(self, step, result):
|
|
190
|
+
# do something to record state on self.model
|
|
191
|
+
|
|
192
|
+
Once a subclass is defined, you can instantiate it, and
|
|
193
|
+
pass it as the value for the 'callbacks' keyword argument
|
|
194
|
+
when instantiating a FlowCoordinator.
|
|
195
|
+
|
|
196
|
+
Example running a flow with custom callbacks:
|
|
197
|
+
|
|
198
|
+
custom_callbacks = CustomFlowCallbacks(model_instance)
|
|
199
|
+
flow_coordinator = FlowCoordinator(
|
|
200
|
+
project_config,
|
|
201
|
+
flow_config,
|
|
202
|
+
name=flow_name,
|
|
203
|
+
options=options,
|
|
204
|
+
callbacks=custom_callbacks,
|
|
205
|
+
)
|
|
206
|
+
flow_coordinator.run(org_config)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def pre_flow(self, coordinator: "FlowCoordinator"):
|
|
212
|
+
"""This is passed an instance of FlowCoordinator,
|
|
213
|
+
that pertains to the flow which is about to run."""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
def post_flow(self, coordinator: "FlowCoordinator"):
|
|
217
|
+
"""This is passed an instance of FlowCoordinator,
|
|
218
|
+
that pertains to the flow just finished running.
|
|
219
|
+
This step executes whether or not the flow completed
|
|
220
|
+
successfully."""
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
def pre_task(self, step: StepSpec):
|
|
224
|
+
"""This is passed an instance StepSpec, that
|
|
225
|
+
pertains to the task which is about to run."""
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
def post_task(self, step: StepSpec, result: StepResult):
|
|
229
|
+
"""This method is called after a task has executed.
|
|
230
|
+
|
|
231
|
+
:param step: Instance of StepSpec that relates to the task which executed
|
|
232
|
+
:param result: Instance of the StepResult class that was run. Attributes of
|
|
233
|
+
interest include, `result.result`, `result.return_values`, and `result.exception`
|
|
234
|
+
"""
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TaskRunner:
|
|
239
|
+
"""TaskRunner encapsulates the job of instantiating and running a task."""
|
|
240
|
+
|
|
241
|
+
step: StepSpec
|
|
242
|
+
org_config: Optional[OrgConfig]
|
|
243
|
+
flow: Optional["FlowCoordinator"]
|
|
244
|
+
|
|
245
|
+
def __init__(
|
|
246
|
+
self,
|
|
247
|
+
step: StepSpec,
|
|
248
|
+
org_config: Optional[OrgConfig],
|
|
249
|
+
flow: Optional["FlowCoordinator"] = None,
|
|
250
|
+
):
|
|
251
|
+
self.step = step
|
|
252
|
+
self.org_config = org_config
|
|
253
|
+
self.flow = flow
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def from_flow(cls, flow: "FlowCoordinator", step: StepSpec) -> "TaskRunner":
|
|
257
|
+
return cls(step, flow.org_config, flow=flow)
|
|
258
|
+
|
|
259
|
+
def run_step(self, **options) -> StepResult:
|
|
260
|
+
"""
|
|
261
|
+
Run a step.
|
|
262
|
+
|
|
263
|
+
:return: StepResult
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
# Resolve ^^task_name.return_value style option syntax
|
|
267
|
+
task_config = self.step.task_config.copy()
|
|
268
|
+
task_config["options"] = task_config.get("options", {}).copy()
|
|
269
|
+
assert self.flow
|
|
270
|
+
self.flow.resolve_return_value_options(task_config["options"])
|
|
271
|
+
|
|
272
|
+
task_config["options"].update(options)
|
|
273
|
+
|
|
274
|
+
assert self.step.task_class
|
|
275
|
+
|
|
276
|
+
task = self.step.task_class(
|
|
277
|
+
self.step.project_config,
|
|
278
|
+
TaskConfig(task_config),
|
|
279
|
+
org_config=self.org_config,
|
|
280
|
+
name=self.step.task_name,
|
|
281
|
+
stepnum=self.step.step_num,
|
|
282
|
+
flow=self.flow,
|
|
283
|
+
)
|
|
284
|
+
self._log_options(task)
|
|
285
|
+
exc = None
|
|
286
|
+
try:
|
|
287
|
+
task()
|
|
288
|
+
except Exception as e:
|
|
289
|
+
self.flow.logger.error(f"Exception in task {self.step.path}")
|
|
290
|
+
exc = e
|
|
291
|
+
return StepResult(
|
|
292
|
+
self.step.step_num,
|
|
293
|
+
self.step.task_name,
|
|
294
|
+
self.step.path,
|
|
295
|
+
task.result,
|
|
296
|
+
task.return_values,
|
|
297
|
+
exc,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def _log_options(self, task: "BaseTask"):
|
|
301
|
+
if not task.task_options:
|
|
302
|
+
task.logger.info("No task options present")
|
|
303
|
+
return
|
|
304
|
+
task.logger.info("Options:")
|
|
305
|
+
for key, info in task.task_options.items():
|
|
306
|
+
value = task.options.get(key)
|
|
307
|
+
if value is not None:
|
|
308
|
+
if type(value) is not list:
|
|
309
|
+
value = self._obfuscate_if_sensitive(value, info)
|
|
310
|
+
task.logger.info(f" {key}: {value}")
|
|
311
|
+
else:
|
|
312
|
+
task.logger.info(f" {key}:")
|
|
313
|
+
for v in value:
|
|
314
|
+
v = self._obfuscate_if_sensitive(v, info)
|
|
315
|
+
task.logger.info(f" - {v}")
|
|
316
|
+
|
|
317
|
+
def _obfuscate_if_sensitive(self, value: str, info: dict) -> str:
|
|
318
|
+
if info.get("sensitive"):
|
|
319
|
+
value = 8 * "*"
|
|
320
|
+
return value
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class FlowCoordinator:
|
|
324
|
+
org_config: Optional[OrgConfig]
|
|
325
|
+
steps: List[StepSpec]
|
|
326
|
+
callbacks: FlowCallback
|
|
327
|
+
logger: logging.Logger
|
|
328
|
+
skip: List[str]
|
|
329
|
+
flow_config: FlowConfig
|
|
330
|
+
runtime_options: dict
|
|
331
|
+
name: Optional[str]
|
|
332
|
+
results: List[StepResult]
|
|
333
|
+
|
|
334
|
+
def __init__(
|
|
335
|
+
self,
|
|
336
|
+
project_config: BaseProjectConfig,
|
|
337
|
+
flow_config: FlowConfig,
|
|
338
|
+
name: Optional[str] = None,
|
|
339
|
+
options: Optional[dict] = None,
|
|
340
|
+
skip: Optional[List[str]] = None,
|
|
341
|
+
callbacks: Optional[FlowCallback] = None,
|
|
342
|
+
):
|
|
343
|
+
self.project_config = project_config
|
|
344
|
+
self.flow_config = flow_config
|
|
345
|
+
self.name = name
|
|
346
|
+
self.org_config = None
|
|
347
|
+
|
|
348
|
+
if not callbacks:
|
|
349
|
+
callbacks = FlowCallback()
|
|
350
|
+
self.callbacks = callbacks
|
|
351
|
+
|
|
352
|
+
self.runtime_options = options or {}
|
|
353
|
+
|
|
354
|
+
self.skip = skip or []
|
|
355
|
+
self.results = []
|
|
356
|
+
|
|
357
|
+
self.logger = self._init_logger()
|
|
358
|
+
self.steps = self._init_steps()
|
|
359
|
+
|
|
360
|
+
@classmethod
|
|
361
|
+
def from_steps(
|
|
362
|
+
cls,
|
|
363
|
+
project_config: BaseProjectConfig,
|
|
364
|
+
steps: List[StepSpec],
|
|
365
|
+
name: Optional[str] = None,
|
|
366
|
+
callbacks: Optional[FlowCallback] = None,
|
|
367
|
+
):
|
|
368
|
+
instance = cls(
|
|
369
|
+
project_config,
|
|
370
|
+
flow_config=FlowConfig({"steps": {}}),
|
|
371
|
+
name=name,
|
|
372
|
+
callbacks=callbacks,
|
|
373
|
+
)
|
|
374
|
+
instance.steps = steps
|
|
375
|
+
return instance
|
|
376
|
+
|
|
377
|
+
def _rule(self, fill="=", length=60, new_line=False):
|
|
378
|
+
self.logger.info(f"{fill * length}")
|
|
379
|
+
if new_line:
|
|
380
|
+
self.logger.info("")
|
|
381
|
+
|
|
382
|
+
def get_summary(self, verbose=False):
|
|
383
|
+
"""Returns an output string that contains the description of the flow
|
|
384
|
+
and its steps."""
|
|
385
|
+
lines = []
|
|
386
|
+
if "description" in self.flow_config.config:
|
|
387
|
+
lines.append(f"Description: {self.flow_config.config['description']}")
|
|
388
|
+
|
|
389
|
+
step_lines = self.get_flow_steps(verbose=verbose)
|
|
390
|
+
if step_lines:
|
|
391
|
+
lines.append("\nFlow Steps")
|
|
392
|
+
lines.extend(step_lines)
|
|
393
|
+
|
|
394
|
+
return "\n".join(lines)
|
|
395
|
+
|
|
396
|
+
def get_flow_steps(
|
|
397
|
+
self, for_docs: bool = False, verbose: bool = False
|
|
398
|
+
) -> List[str]:
|
|
399
|
+
"""Returns a list of flow steps (tasks and sub-flows) for the given flow.
|
|
400
|
+
For docs, indicates whether or not we want to use the string for use in a code-block
|
|
401
|
+
of an rst file. If True, will omit output of source information."""
|
|
402
|
+
lines = []
|
|
403
|
+
previous_parts = []
|
|
404
|
+
previous_source = None
|
|
405
|
+
for step in self.steps:
|
|
406
|
+
parts = step.path.split(".")
|
|
407
|
+
steps = str(step.step_num).split("/")
|
|
408
|
+
if len(parts) > len(steps):
|
|
409
|
+
# Sub-step generated during freeze process; skip it
|
|
410
|
+
continue
|
|
411
|
+
task_name = parts.pop()
|
|
412
|
+
|
|
413
|
+
i = -1
|
|
414
|
+
new_source = (
|
|
415
|
+
f" [from {step.project_config.source}]"
|
|
416
|
+
if step.project_config.source is not previous_source
|
|
417
|
+
else ""
|
|
418
|
+
)
|
|
419
|
+
options_info = ""
|
|
420
|
+
for i, flow_name in enumerate(parts):
|
|
421
|
+
if not any(":" in part for part in step.path.split(".")[i + 1 :]):
|
|
422
|
+
source = new_source
|
|
423
|
+
else:
|
|
424
|
+
source = ""
|
|
425
|
+
if len(previous_parts) < i + 1 or previous_parts[i] != flow_name:
|
|
426
|
+
if for_docs:
|
|
427
|
+
source = ""
|
|
428
|
+
|
|
429
|
+
lines.append(f"{' ' * i}{steps[i]}) flow: {flow_name}{source}")
|
|
430
|
+
if source:
|
|
431
|
+
new_source = ""
|
|
432
|
+
|
|
433
|
+
padding = " " * (i + 1) + " " * len(str(steps[i + 1]))
|
|
434
|
+
when = f"{padding} when: {step.when}" if step.when is not None else ""
|
|
435
|
+
|
|
436
|
+
if for_docs:
|
|
437
|
+
new_source = ""
|
|
438
|
+
|
|
439
|
+
if step.task_config.get("options"):
|
|
440
|
+
if verbose:
|
|
441
|
+
options = step.task_config.get("options")
|
|
442
|
+
options_info = f"{padding} options:"
|
|
443
|
+
|
|
444
|
+
for option, value in options.items():
|
|
445
|
+
options_info += f"\n{padding} {option}: {value}"
|
|
446
|
+
|
|
447
|
+
lines.append(
|
|
448
|
+
f"{' ' * (i + 1)}{steps[i + 1]}) task: {task_name}{new_source}"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if when:
|
|
452
|
+
lines.append(when)
|
|
453
|
+
|
|
454
|
+
if options_info:
|
|
455
|
+
lines.append(options_info)
|
|
456
|
+
|
|
457
|
+
previous_parts = parts
|
|
458
|
+
previous_source = step.project_config.source
|
|
459
|
+
|
|
460
|
+
return lines
|
|
461
|
+
|
|
462
|
+
def run(self, org_config: OrgConfig):
|
|
463
|
+
self.org_config = org_config
|
|
464
|
+
line = f"Initializing flow: {self.__class__.__name__}"
|
|
465
|
+
if self.name:
|
|
466
|
+
line = f"{line} ({self.name})"
|
|
467
|
+
self._rule()
|
|
468
|
+
self.logger.info(line)
|
|
469
|
+
self.logger.info(self.flow_config.description)
|
|
470
|
+
self._rule(new_line=True)
|
|
471
|
+
|
|
472
|
+
self._init_org()
|
|
473
|
+
self._rule(fill="-")
|
|
474
|
+
self.logger.info("Organization:")
|
|
475
|
+
self.logger.info(f" Username: {org_config.username}")
|
|
476
|
+
self.logger.info(f" Org Id: {org_config.org_id}")
|
|
477
|
+
self.logger.info(f" Instance: {org_config.instance_name}")
|
|
478
|
+
self._rule(fill="-", new_line=True)
|
|
479
|
+
|
|
480
|
+
# Give pre_flow callback a chance to alter the steps
|
|
481
|
+
# based on the state of the org before we display the steps.
|
|
482
|
+
self.callbacks.pre_flow(self)
|
|
483
|
+
|
|
484
|
+
self._rule(fill="-")
|
|
485
|
+
self.logger.info("Steps:")
|
|
486
|
+
for line in self.get_summary().splitlines():
|
|
487
|
+
self.logger.info(line)
|
|
488
|
+
self._rule(fill="-", new_line=True)
|
|
489
|
+
|
|
490
|
+
self.logger.info("Starting execution")
|
|
491
|
+
self._rule(new_line=True)
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
for step in self.steps:
|
|
495
|
+
self._run_step(step)
|
|
496
|
+
flow_name = f"'{self.name}' " if self.name else ""
|
|
497
|
+
self.logger.info(
|
|
498
|
+
f"Completed flow {flow_name}on org {org_config.name} successfully!"
|
|
499
|
+
)
|
|
500
|
+
finally:
|
|
501
|
+
self.callbacks.post_flow(self)
|
|
502
|
+
|
|
503
|
+
def _run_step(self, step: StepSpec):
|
|
504
|
+
if step.skip:
|
|
505
|
+
self._rule(fill="*")
|
|
506
|
+
self.logger.info(f"Skipping task: {step.task_name}")
|
|
507
|
+
self._rule(fill="*", new_line=True)
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
if step.when:
|
|
511
|
+
jinja2_context = {
|
|
512
|
+
"project_config": step.project_config,
|
|
513
|
+
"org_config": self.org_config,
|
|
514
|
+
}
|
|
515
|
+
expr = jinja2_env.compile_expression(step.when)
|
|
516
|
+
value = expr(**jinja2_context)
|
|
517
|
+
if not value:
|
|
518
|
+
self.logger.info(
|
|
519
|
+
f"Skipping task {step.task_name} (skipped unless {step.when})"
|
|
520
|
+
)
|
|
521
|
+
return
|
|
522
|
+
|
|
523
|
+
self._rule(fill="-")
|
|
524
|
+
self.logger.info(f"Running task: {step.task_name}")
|
|
525
|
+
self._rule(fill="-", new_line=True)
|
|
526
|
+
|
|
527
|
+
self.callbacks.pre_task(step)
|
|
528
|
+
result = TaskRunner.from_flow(self, step).run_step()
|
|
529
|
+
self.callbacks.post_task(step, result)
|
|
530
|
+
|
|
531
|
+
self.results.append(
|
|
532
|
+
result
|
|
533
|
+
) # add even a failed result to the result set for the post flow
|
|
534
|
+
|
|
535
|
+
if result.exception and not step.allow_failure:
|
|
536
|
+
raise result.exception # PY3: raise an exception type we control *from* this exception instead?
|
|
537
|
+
|
|
538
|
+
def _init_logger(self) -> logging.Logger:
|
|
539
|
+
"""
|
|
540
|
+
Returns a logging.Logger-like object to use for the duration of the flow. Tasks will receive this logger
|
|
541
|
+
and getChild(class_name) to get a child logger.
|
|
542
|
+
|
|
543
|
+
:return: logging.Logger
|
|
544
|
+
"""
|
|
545
|
+
return logging.getLogger("cumulusci.flows").getChild(self.__class__.__name__)
|
|
546
|
+
|
|
547
|
+
def _init_steps(self) -> List[StepSpec]:
|
|
548
|
+
"""
|
|
549
|
+
Given the flow config and everything else, create a list of steps to run, sorted by step number.
|
|
550
|
+
|
|
551
|
+
:return: List[StepSpec]
|
|
552
|
+
"""
|
|
553
|
+
self._check_old_yaml_format()
|
|
554
|
+
self._check_infinite_flows(self.flow_config)
|
|
555
|
+
|
|
556
|
+
steps = []
|
|
557
|
+
|
|
558
|
+
for number, step_config in self.flow_config.steps.items():
|
|
559
|
+
specs = self._visit_step(number, step_config, self.project_config)
|
|
560
|
+
steps.extend(specs)
|
|
561
|
+
|
|
562
|
+
return sorted(steps, key=attrgetter("step_num"))
|
|
563
|
+
|
|
564
|
+
def _visit_step(
|
|
565
|
+
self,
|
|
566
|
+
number: Union[str, int],
|
|
567
|
+
step_config: dict,
|
|
568
|
+
project_config: BaseProjectConfig,
|
|
569
|
+
visited_steps: Optional[List[StepSpec]] = None,
|
|
570
|
+
parent_options: Optional[dict] = None,
|
|
571
|
+
parent_ui_options: Optional[dict] = None,
|
|
572
|
+
from_flow: Optional[str] = None,
|
|
573
|
+
) -> List[StepSpec]:
|
|
574
|
+
"""
|
|
575
|
+
for each step (as defined in the flow YAML), _visit_step is called with only
|
|
576
|
+
the first two parameters. this takes care of validating the step, collating the
|
|
577
|
+
option overrides, and if it is a task, creating a StepSpec for it.
|
|
578
|
+
|
|
579
|
+
If it is a flow, we recursively call _visit_step with the rest of the parameters of context.
|
|
580
|
+
|
|
581
|
+
:param number: StepVersion representation of the current step number
|
|
582
|
+
:param step_config: the current step's config (dict from YAML)
|
|
583
|
+
:param visited_steps: used when called recursively for nested steps, becomes the return value
|
|
584
|
+
:param parent_options: used when called recursively for nested steps, options from parent flow
|
|
585
|
+
:param parent_ui_options: used when called recursively for nested steps, UI options from parent flow
|
|
586
|
+
:param from_flow: used when called recursively for nested steps, name of parent flow
|
|
587
|
+
:return: List[StepSpec] a list of all resolved steps including/under the one passed in
|
|
588
|
+
"""
|
|
589
|
+
step_number = StepVersion(str(number))
|
|
590
|
+
|
|
591
|
+
if visited_steps is None:
|
|
592
|
+
visited_steps = []
|
|
593
|
+
if parent_options is None:
|
|
594
|
+
parent_options = {}
|
|
595
|
+
if parent_ui_options is None:
|
|
596
|
+
parent_ui_options = {}
|
|
597
|
+
|
|
598
|
+
# This should never happen because of cleanup
|
|
599
|
+
# in core/utils/cleanup_old_flow_step_replace_syntax()
|
|
600
|
+
assert step_config.keys() != {"task", "flow"}
|
|
601
|
+
|
|
602
|
+
# Skips
|
|
603
|
+
# - either in YAML (with the None string)
|
|
604
|
+
# - or by providing a skip list to the FlowRunner at initialization.
|
|
605
|
+
if (
|
|
606
|
+
("flow" in step_config and step_config["flow"] == "None")
|
|
607
|
+
or ("task" in step_config and step_config["task"] == "None")
|
|
608
|
+
or ("task" in step_config and step_config["task"] in self.skip)
|
|
609
|
+
):
|
|
610
|
+
visited_steps.append(
|
|
611
|
+
StepSpec(
|
|
612
|
+
step_num=step_number,
|
|
613
|
+
task_name=step_config.get("task", step_config.get("flow")),
|
|
614
|
+
task_config=step_config.get("options", {}),
|
|
615
|
+
task_class=None,
|
|
616
|
+
project_config=project_config,
|
|
617
|
+
from_flow=from_flow,
|
|
618
|
+
skip=True, # someday we could use different vals for why skipped
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
return visited_steps
|
|
622
|
+
|
|
623
|
+
if "task" in step_config:
|
|
624
|
+
name = step_config["task"]
|
|
625
|
+
|
|
626
|
+
# get the base task_config from the project config, as a dict for easier manipulation.
|
|
627
|
+
# will raise if the task doesn't exist / is invalid
|
|
628
|
+
task_config = project_config.get_task(name)
|
|
629
|
+
task_config_dict: dict = copy.deepcopy(task_config.config)
|
|
630
|
+
if "options" not in task_config_dict:
|
|
631
|
+
task_config_dict["options"] = {}
|
|
632
|
+
|
|
633
|
+
# merge the options together, from task_config all the way down through parent_options
|
|
634
|
+
# parent_options should have higher priority than step_config options
|
|
635
|
+
step_overrides = copy.deepcopy(step_config.get("options", {}))
|
|
636
|
+
parent_task_options = parent_options.get(name, {})
|
|
637
|
+
step_overrides.update(parent_task_options)
|
|
638
|
+
task_config_dict["options"].update(step_overrides)
|
|
639
|
+
|
|
640
|
+
# merge UI options from task config and parent flow
|
|
641
|
+
if "ui_options" not in task_config_dict:
|
|
642
|
+
task_config_dict["ui_options"] = {}
|
|
643
|
+
# parent_ui_options should have higher priority than step_config ui_options
|
|
644
|
+
step_ui_overrides = copy.deepcopy(step_config.get("ui_options", {}))
|
|
645
|
+
step_ui_overrides.update(parent_ui_options.get(name, {}))
|
|
646
|
+
task_config_dict["ui_options"].update(step_ui_overrides)
|
|
647
|
+
|
|
648
|
+
# merge checks from task config and flow step
|
|
649
|
+
if "checks" not in task_config_dict:
|
|
650
|
+
task_config_dict["checks"] = []
|
|
651
|
+
task_config_dict["checks"].extend(step_config.get("checks", []))
|
|
652
|
+
|
|
653
|
+
# merge runtime options
|
|
654
|
+
if name in self.runtime_options:
|
|
655
|
+
task_config_dict["options"].update(self.runtime_options[name])
|
|
656
|
+
|
|
657
|
+
# get implementation class. raise/fail if it doesn't exist, because why continue
|
|
658
|
+
try:
|
|
659
|
+
task_class = task_config.get_class()
|
|
660
|
+
except (ImportError, AttributeError, TaskImportError) as e:
|
|
661
|
+
raise FlowConfigError(f"Task named {name} has bad classpath, {e}")
|
|
662
|
+
|
|
663
|
+
visited_steps.append(
|
|
664
|
+
StepSpec(
|
|
665
|
+
step_num=step_number,
|
|
666
|
+
task_name=name,
|
|
667
|
+
task_config=task_config_dict,
|
|
668
|
+
task_class=task_class,
|
|
669
|
+
project_config=task_config.project_config,
|
|
670
|
+
allow_failure=step_config.get("ignore_failure", False),
|
|
671
|
+
from_flow=from_flow,
|
|
672
|
+
when=step_config.get("when"),
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
return visited_steps
|
|
676
|
+
|
|
677
|
+
if "flow" in step_config:
|
|
678
|
+
name = step_config["flow"]
|
|
679
|
+
if from_flow:
|
|
680
|
+
path = ".".join([from_flow, name])
|
|
681
|
+
else:
|
|
682
|
+
path = name
|
|
683
|
+
step_options = step_config.get("options", {})
|
|
684
|
+
step_ui_options = step_config.get("ui_options", {})
|
|
685
|
+
flow_config = project_config.get_flow(name)
|
|
686
|
+
for sub_number, sub_stepconf in flow_config.steps.items():
|
|
687
|
+
# append the flow number to the child number, since its a LooseVersion.
|
|
688
|
+
# e.g. if we're in step 2.3 which references a flow with steps 1-5, it
|
|
689
|
+
# simply ends up as five steps: 2.3.1, 2.3.2, 2.3.3, 2.3.4, 2.3.5
|
|
690
|
+
# TODO: how does this work with nested flowveride? what does defining step 2.3.2 later do?
|
|
691
|
+
num = f"{number}/{sub_number}"
|
|
692
|
+
self._visit_step(
|
|
693
|
+
number=num,
|
|
694
|
+
step_config=sub_stepconf,
|
|
695
|
+
project_config=flow_config.project_config,
|
|
696
|
+
visited_steps=visited_steps,
|
|
697
|
+
parent_options=step_options,
|
|
698
|
+
parent_ui_options=step_ui_options,
|
|
699
|
+
from_flow=path,
|
|
700
|
+
)
|
|
701
|
+
return visited_steps
|
|
702
|
+
|
|
703
|
+
def _check_old_yaml_format(self):
|
|
704
|
+
if self.flow_config.steps is None:
|
|
705
|
+
if "tasks" in self.flow_config.config:
|
|
706
|
+
raise FlowConfigError(
|
|
707
|
+
'Old flow syntax detected. Please change from "tasks" to "steps" in the flow definition.'
|
|
708
|
+
)
|
|
709
|
+
else:
|
|
710
|
+
raise FlowConfigError("No steps found in the flow definition")
|
|
711
|
+
|
|
712
|
+
def _check_infinite_flows(self, flow_config, flow_stack=None):
|
|
713
|
+
"""
|
|
714
|
+
Recursively loop through the flow_config and check if there are any cycles.
|
|
715
|
+
|
|
716
|
+
:param flow_config: FlowConfig to traverse to find cycles/infinite loops
|
|
717
|
+
:param flow_stack: list of flow signatures already visited
|
|
718
|
+
:return: None
|
|
719
|
+
"""
|
|
720
|
+
if not flow_stack:
|
|
721
|
+
flow_stack = []
|
|
722
|
+
project_config = flow_config.project_config
|
|
723
|
+
|
|
724
|
+
for step in flow_config.steps.values():
|
|
725
|
+
if "flow" in step:
|
|
726
|
+
next_flow_name = step["flow"]
|
|
727
|
+
if next_flow_name == "None":
|
|
728
|
+
continue
|
|
729
|
+
|
|
730
|
+
next_flow_config = project_config.get_flow(next_flow_name)
|
|
731
|
+
signature = (
|
|
732
|
+
hash(next_flow_config.project_config.source),
|
|
733
|
+
next_flow_config.name,
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
if signature in flow_stack:
|
|
737
|
+
raise FlowInfiniteLoopError(
|
|
738
|
+
f"Infinite flows detected with flow {next_flow_name}"
|
|
739
|
+
)
|
|
740
|
+
flow_stack.append(signature)
|
|
741
|
+
self._check_infinite_flows(next_flow_config, flow_stack)
|
|
742
|
+
flow_stack.pop()
|
|
743
|
+
|
|
744
|
+
def _init_org(self):
|
|
745
|
+
"""Test and refresh credentials to the org specified."""
|
|
746
|
+
self.logger.info(
|
|
747
|
+
f"Verifying and refreshing credentials for the specified org: {self.org_config.name}."
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
# attempt to refresh the token, this can throw...
|
|
751
|
+
with self.org_config.save_if_changed():
|
|
752
|
+
self.org_config.refresh_oauth_token(self.project_config.keychain)
|
|
753
|
+
|
|
754
|
+
def resolve_return_value_options(self, options):
|
|
755
|
+
"""Handle dynamic option value lookups in the format ^^task_name.attr"""
|
|
756
|
+
for key, value in options.items():
|
|
757
|
+
if isinstance(value, str) and value.startswith(RETURN_VALUE_OPTION_PREFIX):
|
|
758
|
+
path, name = value[len(RETURN_VALUE_OPTION_PREFIX) :].rsplit(".", 1)
|
|
759
|
+
result = self._find_result_by_path(path)
|
|
760
|
+
options[key] = result.return_values.get(name)
|
|
761
|
+
|
|
762
|
+
def _find_result_by_path(self, path):
|
|
763
|
+
for result in self.results:
|
|
764
|
+
if result.path[-len(path) :] == path:
|
|
765
|
+
return result
|
|
766
|
+
raise NameError(f"Path not found: {path}")
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
class PreflightFlowCoordinator(FlowCoordinator):
|
|
770
|
+
"""Coordinates running preflight checks instead of the actual flow steps."""
|
|
771
|
+
|
|
772
|
+
preflight_results: DefaultDict[Optional[str], List[dict]]
|
|
773
|
+
_task_caches: Dict[BaseProjectConfig, "TaskCache"]
|
|
774
|
+
|
|
775
|
+
def run(self, org_config: OrgConfig):
|
|
776
|
+
self.org_config = org_config
|
|
777
|
+
self.callbacks.pre_flow(self)
|
|
778
|
+
|
|
779
|
+
self._init_org()
|
|
780
|
+
self._rule(fill="-")
|
|
781
|
+
self.logger.info("Organization:")
|
|
782
|
+
self.logger.info(f" Username: {org_config.username}")
|
|
783
|
+
self.logger.info(f" Org Id: {org_config.org_id}")
|
|
784
|
+
self._rule(fill="-", new_line=True)
|
|
785
|
+
|
|
786
|
+
self.logger.info("Running preflight checks...")
|
|
787
|
+
self._rule(new_line=True)
|
|
788
|
+
|
|
789
|
+
self.preflight_results = defaultdict(list)
|
|
790
|
+
# Expose for test access
|
|
791
|
+
self._task_caches = {self.project_config: TaskCache(self, self.project_config)}
|
|
792
|
+
try:
|
|
793
|
+
# flow-level checks
|
|
794
|
+
jinja2_context = {
|
|
795
|
+
"tasks": self._task_caches[self.project_config],
|
|
796
|
+
"project_config": self.project_config,
|
|
797
|
+
"org_config": self.org_config,
|
|
798
|
+
}
|
|
799
|
+
for check in self.flow_config.checks or []:
|
|
800
|
+
result = self.evaluate_check(check, jinja2_context)
|
|
801
|
+
if result:
|
|
802
|
+
self.preflight_results[None].append(result)
|
|
803
|
+
|
|
804
|
+
# Step-level checks
|
|
805
|
+
for step in self.steps:
|
|
806
|
+
jinja2_context["project_config"] = step.project_config
|
|
807
|
+
# Create a cache for this project config, if not present
|
|
808
|
+
# and not equal to the root project config.
|
|
809
|
+
# This accommodates cross-project preflight checks.
|
|
810
|
+
if step.project_config not in self._task_caches:
|
|
811
|
+
self._task_caches[step.project_config] = TaskCache(
|
|
812
|
+
self, step.project_config
|
|
813
|
+
)
|
|
814
|
+
jinja2_context["tasks"] = self._task_caches[step.project_config]
|
|
815
|
+
for check in step.task_config.get("checks", []):
|
|
816
|
+
result = self.evaluate_check(check, jinja2_context)
|
|
817
|
+
if result:
|
|
818
|
+
self.preflight_results[str(step.step_num)].append(result)
|
|
819
|
+
finally:
|
|
820
|
+
self.callbacks.post_flow(self)
|
|
821
|
+
|
|
822
|
+
def evaluate_check(
|
|
823
|
+
self, check: dict, jinja2_context: Dict[str, Any]
|
|
824
|
+
) -> Optional[dict]:
|
|
825
|
+
self.logger.info(f"Evaluating check: {check['when']}")
|
|
826
|
+
expr = jinja2_env.compile_expression(check["when"])
|
|
827
|
+
value = bool(expr(**jinja2_context))
|
|
828
|
+
self.logger.info(f"Check result: {value}")
|
|
829
|
+
if value:
|
|
830
|
+
return {"status": check["action"], "message": check.get("message")}
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
class TaskCache:
|
|
834
|
+
"""Provides access to named tasks and caches their results.
|
|
835
|
+
|
|
836
|
+
This is intended for use in a jinja2 expression context
|
|
837
|
+
so that multiple expressions evaluated in the same context
|
|
838
|
+
can avoid running a task more than once with the same options.
|
|
839
|
+
"""
|
|
840
|
+
|
|
841
|
+
project_config: BaseProjectConfig
|
|
842
|
+
results: Dict[Tuple[str, Tuple[Any]], Any]
|
|
843
|
+
|
|
844
|
+
def __init__(self, flow: FlowCoordinator, project_config: BaseProjectConfig):
|
|
845
|
+
self.flow = flow
|
|
846
|
+
# Cross-project flows may include preflight checks
|
|
847
|
+
# that depend on their local context.
|
|
848
|
+
self.project_config = project_config
|
|
849
|
+
self.results = {}
|
|
850
|
+
|
|
851
|
+
def __getattr__(self, task_name: str):
|
|
852
|
+
return CachedTaskRunner(self, task_name)
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
class CachedTaskRunner:
|
|
856
|
+
"""Runs a task and caches the result in a TaskCache"""
|
|
857
|
+
|
|
858
|
+
cache: TaskCache
|
|
859
|
+
task_name: str
|
|
860
|
+
|
|
861
|
+
def __init__(self, cache: TaskCache, task_name: str):
|
|
862
|
+
self.cache = cache
|
|
863
|
+
self.task_name = task_name
|
|
864
|
+
|
|
865
|
+
def __call__(self, **options: dict) -> Any:
|
|
866
|
+
cache_key = (self.task_name, tuple(sorted(options.items())))
|
|
867
|
+
if cache_key in self.cache.results:
|
|
868
|
+
return self.cache.results[cache_key].return_values
|
|
869
|
+
|
|
870
|
+
task_config = self.cache.project_config.tasks[self.task_name]
|
|
871
|
+
task_class = TaskConfig(
|
|
872
|
+
{**task_config, "project_config": self.cache.project_config}
|
|
873
|
+
).get_class()
|
|
874
|
+
step = StepSpec(
|
|
875
|
+
step_num=StepVersion("1"),
|
|
876
|
+
task_name=self.task_name,
|
|
877
|
+
task_config=task_config,
|
|
878
|
+
task_class=task_class,
|
|
879
|
+
project_config=self.cache.project_config,
|
|
880
|
+
)
|
|
881
|
+
self.cache.flow.callbacks.pre_task(step)
|
|
882
|
+
result = TaskRunner(step, self.cache.flow.org_config, self.cache.flow).run_step(
|
|
883
|
+
**options
|
|
884
|
+
)
|
|
885
|
+
self.cache.flow.callbacks.post_task(step, result)
|
|
886
|
+
|
|
887
|
+
self.cache.results[cache_key] = result
|
|
888
|
+
return result.return_values
|