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,945 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import typing as T
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from shutil import rmtree
|
|
8
|
+
|
|
9
|
+
from cumulusci.core.config import OrgConfig, ScratchOrgConfig, ServiceConfig
|
|
10
|
+
from cumulusci.core.config.base_config import BaseConfig
|
|
11
|
+
from cumulusci.core.config.sfdx_org_config import SfdxOrgConfig
|
|
12
|
+
from cumulusci.core.exceptions import (
|
|
13
|
+
ConfigError,
|
|
14
|
+
CumulusCIException,
|
|
15
|
+
CumulusCIUsageError,
|
|
16
|
+
KeychainKeyNotFound,
|
|
17
|
+
OrgCannotBeLoaded,
|
|
18
|
+
OrgNotFound,
|
|
19
|
+
ServiceCannotBeLoaded,
|
|
20
|
+
ServiceNotConfigured,
|
|
21
|
+
)
|
|
22
|
+
from cumulusci.core.keychain import BaseProjectKeychain
|
|
23
|
+
from cumulusci.core.keychain.base_project_keychain import DEFAULT_CONNECTED_APP_NAME
|
|
24
|
+
from cumulusci.core.keychain.serialization import (
|
|
25
|
+
load_config_from_json_or_pickle,
|
|
26
|
+
serialize_config_to_json_or_pickle,
|
|
27
|
+
)
|
|
28
|
+
from cumulusci.core.utils import import_class, import_global
|
|
29
|
+
from cumulusci.utils.encryption import _get_cipher, encrypt_and_b64
|
|
30
|
+
from cumulusci.utils.yaml.cumulusci_yml import ScratchOrg
|
|
31
|
+
|
|
32
|
+
DEFAULT_SERVICES_FILENAME = "DEFAULT_SERVICES.json"
|
|
33
|
+
|
|
34
|
+
# The file permissions that we want set on all
|
|
35
|
+
# .org and .service files. Equivalent to -rw-------
|
|
36
|
+
SERVICE_ORG_FILE_MODE = 0o600
|
|
37
|
+
OS_FILE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
|
38
|
+
if sys.platform.startswith("win"):
|
|
39
|
+
# O_BINARY only available on Windows
|
|
40
|
+
OS_FILE_FLAGS |= os.O_BINARY
|
|
41
|
+
|
|
42
|
+
scratch_org_class = os.environ.get("CUMULUSCI_SCRATCH_ORG_CLASS")
|
|
43
|
+
if scratch_org_class:
|
|
44
|
+
scratch_org_factory = import_global(scratch_org_class) # pragma: no cover
|
|
45
|
+
else:
|
|
46
|
+
scratch_org_factory = ScratchOrgConfig
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
EncryptedFileProjectKeychain
|
|
51
|
+
----------------------------
|
|
52
|
+
This class represents a project keychain that stores services and orgs in encrypted
|
|
53
|
+
files. These files have the extensions: '.org' and '.service'.
|
|
54
|
+
|
|
55
|
+
Service files (.service) are organized under ~/.cumulusci/services/ into sub-directories that
|
|
56
|
+
pertain to a particular service type (e.g. github, connected_app). These files hold
|
|
57
|
+
the encrypted 'attributes' of each service as they are listed in the universal
|
|
58
|
+
cumulusci.yml file. There are multiple DEFAULT_SERVICES.json files that map a particular
|
|
59
|
+
service type to the default alias for that taype.
|
|
60
|
+
|
|
61
|
+
The DEFAULT_SERVICES.json file that resides at ~/.cumulusci holds the global default
|
|
62
|
+
services mappings. Each local project directory (~/.cumulusci/project/) also has a
|
|
63
|
+
DEFAULT_SERVICES.json file for any services that have been configured to be project specific.
|
|
64
|
+
|
|
65
|
+
Org files (.org) live in both under ~/.cumulusci and in local project directories.
|
|
66
|
+
The .org files store an encrypted access_token and other org attributes. There is a
|
|
67
|
+
DEFAULT_ORG.txt file in each local project directory that stores the name of
|
|
68
|
+
the default org for that project.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class EncryptedFileProjectKeychain(BaseProjectKeychain):
|
|
73
|
+
encrypted = True
|
|
74
|
+
env_service_alias_prefix = "env"
|
|
75
|
+
env_service_var_prefix = "CUMULUSCI_SERVICE_"
|
|
76
|
+
env_org_var_prefix = "CUMULUSCI_ORG_"
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def global_config_dir(self):
|
|
80
|
+
try:
|
|
81
|
+
global_config_dir = (
|
|
82
|
+
self.project_config.universal_config_obj.cumulusci_config_dir
|
|
83
|
+
)
|
|
84
|
+
except AttributeError:
|
|
85
|
+
# Handle a global config passed as a project config
|
|
86
|
+
global_config_dir = self.project_config.cumulusci_config_dir
|
|
87
|
+
return global_config_dir
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def project_local_dir(self):
|
|
91
|
+
return self.project_config.project_local_dir
|
|
92
|
+
|
|
93
|
+
#######################################
|
|
94
|
+
# Encryption #
|
|
95
|
+
#######################################
|
|
96
|
+
|
|
97
|
+
# TODO: Move this class into encryption.py
|
|
98
|
+
def _decrypt_config(self, config_class, encrypted_config, extra=None, context=None):
|
|
99
|
+
if self.key:
|
|
100
|
+
if not encrypted_config:
|
|
101
|
+
if extra:
|
|
102
|
+
return config_class(None, *extra)
|
|
103
|
+
else:
|
|
104
|
+
return config_class()
|
|
105
|
+
encrypted_config = base64.b64decode(encrypted_config)
|
|
106
|
+
try:
|
|
107
|
+
iv = encrypted_config[:16]
|
|
108
|
+
cipher, iv = _get_cipher(self.key, iv=iv)
|
|
109
|
+
pickled = cipher.decryptor().update(encrypted_config[16:])
|
|
110
|
+
unpickled = load_config_from_json_or_pickle(pickled)
|
|
111
|
+
except ValueError as e:
|
|
112
|
+
message = "\n".join(
|
|
113
|
+
[
|
|
114
|
+
f"Unable to decrypt{' ' + context if context else ''}. \n"
|
|
115
|
+
"A changed CUMULUSCI_KEY or laptop password might be the cause.\n"
|
|
116
|
+
"Unfortunately, there is usually no way to recover an Org's Configuration \n"
|
|
117
|
+
"once this has happened.\n"
|
|
118
|
+
"Typically we advise users to delete the unusable file or rename it to .bak.\n"
|
|
119
|
+
"The org can be connected or imported again to replace the corrupted config.\n"
|
|
120
|
+
"(Specific error: " + str(e) + ")\n"
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
raise KeychainKeyNotFound(message) from e
|
|
124
|
+
|
|
125
|
+
config_dict = self.cleanup_Python_2_configs(unpickled)
|
|
126
|
+
|
|
127
|
+
args = [config_dict]
|
|
128
|
+
if extra:
|
|
129
|
+
args += extra
|
|
130
|
+
return self._construct_config(config_class, args)
|
|
131
|
+
|
|
132
|
+
def cleanup_Python_2_configs(self, unpickled):
|
|
133
|
+
# Convert bytes created in Python 2
|
|
134
|
+
config_dict = {}
|
|
135
|
+
|
|
136
|
+
# After a few months, we can clean up this code if nobody reports
|
|
137
|
+
# warnings.
|
|
138
|
+
message = (
|
|
139
|
+
"Unexpected bytes found in config.\n"
|
|
140
|
+
"Please inform the CumulusCI team.\n"
|
|
141
|
+
"Future versions of CumulusCI may break your config."
|
|
142
|
+
)
|
|
143
|
+
for k, v in unpickled.items():
|
|
144
|
+
if isinstance(k, bytes):
|
|
145
|
+
self.logger.warning(message)
|
|
146
|
+
k = k.decode("utf-8")
|
|
147
|
+
if isinstance(v, bytes):
|
|
148
|
+
self.logger.warning(message)
|
|
149
|
+
v = v.decode("utf-8")
|
|
150
|
+
config_dict[k] = v
|
|
151
|
+
|
|
152
|
+
return config_dict
|
|
153
|
+
|
|
154
|
+
def _construct_config(self, config_class, args):
|
|
155
|
+
config = args[0]
|
|
156
|
+
if config.get("scratch"):
|
|
157
|
+
config_class = scratch_org_factory
|
|
158
|
+
elif config.get("sfdx"):
|
|
159
|
+
config_class = SfdxOrgConfig
|
|
160
|
+
|
|
161
|
+
return config_class(*args)
|
|
162
|
+
|
|
163
|
+
def _get_config_bytes(self, config) -> bytes:
|
|
164
|
+
"""Depending on if a key is present return
|
|
165
|
+
the bytes that we want to store on the keychain."""
|
|
166
|
+
org_bytes = None
|
|
167
|
+
org_bytes = serialize_config_to_json_or_pickle(config.config, self.logger)
|
|
168
|
+
|
|
169
|
+
if self.key:
|
|
170
|
+
org_bytes = encrypt_and_b64(org_bytes, self.key)
|
|
171
|
+
|
|
172
|
+
assert org_bytes is not None, "org_bytes should have a value"
|
|
173
|
+
return org_bytes
|
|
174
|
+
|
|
175
|
+
def _validate_key(self):
|
|
176
|
+
# the key can be None when we detect issues using keyring
|
|
177
|
+
if not self.key:
|
|
178
|
+
return
|
|
179
|
+
if len(self.key) != 16:
|
|
180
|
+
raise ConfigError("The keychain key must be 16 characters long.")
|
|
181
|
+
|
|
182
|
+
#######################################
|
|
183
|
+
# Orgs #
|
|
184
|
+
#######################################
|
|
185
|
+
|
|
186
|
+
def get_default_org(self):
|
|
187
|
+
"""Retrieve the name and configuration of the default org"""
|
|
188
|
+
# first look for a file with the default org in it
|
|
189
|
+
default_org_path = self._default_org_path
|
|
190
|
+
if default_org_path and default_org_path.exists():
|
|
191
|
+
org_name = default_org_path.read_text().strip()
|
|
192
|
+
try:
|
|
193
|
+
org_config = self.get_org(org_name)
|
|
194
|
+
return org_name, org_config
|
|
195
|
+
except OrgNotFound: # org was deleted
|
|
196
|
+
default_org_path.unlink() # we don't really have a usable default anymore
|
|
197
|
+
|
|
198
|
+
# fallback to old way of doing it
|
|
199
|
+
org_name, org_config = super().get_default_org()
|
|
200
|
+
if org_name:
|
|
201
|
+
self.set_default_org(org_name) # upgrade to new way
|
|
202
|
+
return org_name, org_config
|
|
203
|
+
|
|
204
|
+
def set_default_org(self, name: str):
|
|
205
|
+
"""Set the default org for tasks and flows by name"""
|
|
206
|
+
super().set_default_org(name)
|
|
207
|
+
self._default_org_path.write_text(name)
|
|
208
|
+
|
|
209
|
+
def unset_default_org(self):
|
|
210
|
+
"""Unset the default orgs for tasks and flows"""
|
|
211
|
+
super().unset_default_org()
|
|
212
|
+
if self._default_org_path:
|
|
213
|
+
try:
|
|
214
|
+
self._default_org_path.unlink()
|
|
215
|
+
except FileNotFoundError:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def _default_org_path(self):
|
|
220
|
+
if self.project_local_dir:
|
|
221
|
+
return Path(self.project_local_dir) / "DEFAULT_ORG.txt"
|
|
222
|
+
|
|
223
|
+
def _load_orgs(self) -> None:
|
|
224
|
+
self._load_orgs_from_environment()
|
|
225
|
+
self._load_org_files(self.global_config_dir, GlobalOrg)
|
|
226
|
+
self._load_org_files(self.project_local_dir, LocalOrg)
|
|
227
|
+
|
|
228
|
+
def _load_orgs_from_environment(self):
|
|
229
|
+
for env_var_name, value in os.environ.items():
|
|
230
|
+
if env_var_name.startswith(self.env_org_var_prefix):
|
|
231
|
+
self._load_org_from_environment(env_var_name, value)
|
|
232
|
+
|
|
233
|
+
def _load_org_from_environment(self, env_var_name, value):
|
|
234
|
+
if not value:
|
|
235
|
+
raise OrgCannotBeLoaded(
|
|
236
|
+
f"Org env var {env_var_name} cannot be loaded because it is empty. Either set {env_var_name} to a json string or unset it from the environment."
|
|
237
|
+
)
|
|
238
|
+
try:
|
|
239
|
+
org_config = json.loads(value)
|
|
240
|
+
except Exception as e:
|
|
241
|
+
raise OrgCannotBeLoaded(
|
|
242
|
+
f"Could not parse {env_var_name} as JSON becase {e}"
|
|
243
|
+
)
|
|
244
|
+
org_name = env_var_name[len(self.env_org_var_prefix) :].lower()
|
|
245
|
+
if org_config.get("scratch"):
|
|
246
|
+
org_config = scratch_org_factory(
|
|
247
|
+
json.loads(value), org_name, keychain=self, global_org=False
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
org_config = OrgConfig(
|
|
251
|
+
org_config, org_name, keychain=self, global_org=False
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
self.set_org(org_config, global_org=False, save=False)
|
|
255
|
+
|
|
256
|
+
def _load_org_files(self, dirname: str, constructor=None):
|
|
257
|
+
"""Loads .org files in a given directory onto the keychain"""
|
|
258
|
+
if not dirname:
|
|
259
|
+
return
|
|
260
|
+
dir_path = Path(dirname)
|
|
261
|
+
for item in sorted(dir_path.iterdir()):
|
|
262
|
+
if item.suffix == ".org":
|
|
263
|
+
with open(item, "rb") as f:
|
|
264
|
+
config = f.read()
|
|
265
|
+
name = item.name.replace(".org", "")
|
|
266
|
+
if "orgs" not in self.config:
|
|
267
|
+
self.config["orgs"] = {}
|
|
268
|
+
self.config["orgs"][name] = (
|
|
269
|
+
constructor(config, filename=item) if constructor else config
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def _set_org(self, org_config, global_org, save=True):
|
|
273
|
+
if org_config.keychain:
|
|
274
|
+
assert org_config.keychain is self
|
|
275
|
+
assert org_config.global_org == global_org
|
|
276
|
+
org_config.keychain = self
|
|
277
|
+
org_config.global_org = global_org
|
|
278
|
+
|
|
279
|
+
org_name = org_config.name
|
|
280
|
+
|
|
281
|
+
org_bytes = self._get_config_bytes(org_config)
|
|
282
|
+
assert isinstance(org_bytes, bytes)
|
|
283
|
+
|
|
284
|
+
if global_org:
|
|
285
|
+
org_config = GlobalOrg(org_bytes)
|
|
286
|
+
else:
|
|
287
|
+
org_config = LocalOrg(org_bytes)
|
|
288
|
+
|
|
289
|
+
self.orgs[org_name] = org_config
|
|
290
|
+
|
|
291
|
+
# if keychain is explicitly set to
|
|
292
|
+
# EnvironmentProjectKeychain never save the org config.
|
|
293
|
+
keychain_class = os.environ.get("CUMULUSCI_KEYCHAIN_CLASS")
|
|
294
|
+
if keychain_class == "EnvironmentProjectKeychain":
|
|
295
|
+
message = (
|
|
296
|
+
"The keychain is currently set to EnvironmentProjectKeychain; "
|
|
297
|
+
"skipping save of org config to file. "
|
|
298
|
+
"If you would like orgs to be saved for re-use later, remove the "
|
|
299
|
+
"CUMULUSCI_KEYCHAIN_CLASS environment variable."
|
|
300
|
+
)
|
|
301
|
+
self.logger.warning(message)
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
if save:
|
|
305
|
+
self._save_org(
|
|
306
|
+
org_name,
|
|
307
|
+
org_config.data,
|
|
308
|
+
global_org,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def _save_org(self, name, org_bytes, global_org):
|
|
312
|
+
"""
|
|
313
|
+
@name - name of the org
|
|
314
|
+
@org_bytes - bytes-like objecte to write to disk
|
|
315
|
+
@global_org - whether or not this is a global org
|
|
316
|
+
"""
|
|
317
|
+
if global_org:
|
|
318
|
+
filename = Path(f"{self.global_config_dir}/{name}.org")
|
|
319
|
+
elif self.project_local_dir is None:
|
|
320
|
+
return
|
|
321
|
+
else:
|
|
322
|
+
filename = Path(f"{self.project_local_dir}/{name}.org")
|
|
323
|
+
|
|
324
|
+
fd = os.open(filename, OS_FILE_FLAGS, SERVICE_ORG_FILE_MODE)
|
|
325
|
+
with open(fd, "wb") as f:
|
|
326
|
+
f.write(org_bytes)
|
|
327
|
+
|
|
328
|
+
def _get_org(self, org_name: str) -> ScratchOrgConfig:
|
|
329
|
+
try:
|
|
330
|
+
config = self.orgs[org_name].data
|
|
331
|
+
global_org = self.orgs[org_name].global_org
|
|
332
|
+
except KeyError:
|
|
333
|
+
raise OrgNotFound(f"Org with name '{org_name}' does not exist.")
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
org = self._config_from_bytes(config, org_name)
|
|
337
|
+
except Exception as e:
|
|
338
|
+
try:
|
|
339
|
+
filename = self.orgs[org_name].filename
|
|
340
|
+
except Exception: # pragma: no cover
|
|
341
|
+
filename = None
|
|
342
|
+
if not filename:
|
|
343
|
+
raise e
|
|
344
|
+
raise OrgCannotBeLoaded(
|
|
345
|
+
f"Cannot parse config loaded from\n{filename}\n{e}\n"
|
|
346
|
+
)
|
|
347
|
+
org.global_org = global_org
|
|
348
|
+
|
|
349
|
+
if isinstance(org, ScratchOrgConfig):
|
|
350
|
+
self._merge_config_from_yml(org)
|
|
351
|
+
|
|
352
|
+
return org
|
|
353
|
+
|
|
354
|
+
def _merge_config_from_yml(self, scratch_config: ScratchOrgConfig):
|
|
355
|
+
"""Merges any values configurable via cumulusci.yml
|
|
356
|
+
into the scratch org config that is loaded from file."""
|
|
357
|
+
|
|
358
|
+
configurable_attributes = list(ScratchOrg.schema()["properties"].keys())
|
|
359
|
+
for attr in configurable_attributes:
|
|
360
|
+
try:
|
|
361
|
+
value_from_yml = self.project_config.config["orgs"]["scratch"][
|
|
362
|
+
scratch_config.name
|
|
363
|
+
][attr]
|
|
364
|
+
scratch_config.config[attr] = value_from_yml
|
|
365
|
+
except KeyError:
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
def _config_from_bytes(self, config, name):
|
|
369
|
+
if self.key:
|
|
370
|
+
org = self._decrypt_config(
|
|
371
|
+
OrgConfig,
|
|
372
|
+
config,
|
|
373
|
+
extra=[name, self],
|
|
374
|
+
context=f"org config ({name})",
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
config = load_config_from_json_or_pickle(config)
|
|
378
|
+
org = self._construct_config(OrgConfig, [config, name, self])
|
|
379
|
+
|
|
380
|
+
return org
|
|
381
|
+
|
|
382
|
+
def _remove_org(self, name, global_org):
|
|
383
|
+
scope = self.global_config_dir if global_org else self.project_local_dir
|
|
384
|
+
org_path = Path(f"{scope}/{name}.org")
|
|
385
|
+
if not org_path.exists():
|
|
386
|
+
if not global_org:
|
|
387
|
+
raise OrgNotFound(
|
|
388
|
+
f"Could not find org named {name} to delete. Deleting in project org mode. Is {name} a global org?"
|
|
389
|
+
)
|
|
390
|
+
raise OrgNotFound(
|
|
391
|
+
f"Could not find org named {name} to delete. Deleting in global org mode. Is {name} a project org instead of a global org?"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
org_path.unlink()
|
|
395
|
+
del self.orgs[name]
|
|
396
|
+
|
|
397
|
+
def cleanup_org_cache_dirs(self):
|
|
398
|
+
"""Cleanup directories that are not associated with a connected/live org."""
|
|
399
|
+
|
|
400
|
+
if not self.project_config or not self.project_config.cache_dir:
|
|
401
|
+
return
|
|
402
|
+
active_org_domains = set()
|
|
403
|
+
for org in self.list_orgs():
|
|
404
|
+
org_config = self.get_org(org)
|
|
405
|
+
domain = org_config.get_domain()
|
|
406
|
+
if domain:
|
|
407
|
+
active_org_domains.add(domain)
|
|
408
|
+
|
|
409
|
+
assert self.project_config.cache_dir, "Project cache dir does not exist."
|
|
410
|
+
assert self.global_config_dir, "Global config directory does not exist."
|
|
411
|
+
|
|
412
|
+
project_org_directories = (self.project_config.cache_dir / "orginfo").glob("*")
|
|
413
|
+
global_org_directories = (self.global_config_dir / "orginfo").glob("*")
|
|
414
|
+
|
|
415
|
+
for path in list(project_org_directories) + list(global_org_directories):
|
|
416
|
+
if path.is_dir() and path.name not in active_org_domains:
|
|
417
|
+
rmtree(path)
|
|
418
|
+
|
|
419
|
+
#######################################
|
|
420
|
+
# Services #
|
|
421
|
+
#######################################
|
|
422
|
+
|
|
423
|
+
def set_default_service(
|
|
424
|
+
self, service_type: str, alias: str, project: bool = False, save: bool = True
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Public API for setting a default service e.g. `cci service default`
|
|
427
|
+
|
|
428
|
+
@param service_type: the type of service
|
|
429
|
+
@param alias: the name of the service
|
|
430
|
+
@param project: Should this be a project default
|
|
431
|
+
@param save: save the defaults so they are loaded on subsequent executions
|
|
432
|
+
@raises ServiceNotConfigured if service_type or alias are invalid
|
|
433
|
+
"""
|
|
434
|
+
self._validate_service_type_and_alias(service_type, alias)
|
|
435
|
+
self._default_services[service_type] = alias
|
|
436
|
+
if save:
|
|
437
|
+
self._save_default_service(service_type, alias, project=project)
|
|
438
|
+
|
|
439
|
+
def rename_service(
|
|
440
|
+
self, service_type: str, current_alias: str, new_alias: str
|
|
441
|
+
) -> None:
|
|
442
|
+
"""Public API for renaming a service
|
|
443
|
+
|
|
444
|
+
@param service_type type of service being renamed
|
|
445
|
+
@param current_alias the current alias of the service
|
|
446
|
+
@param new_alias the new alias for the service
|
|
447
|
+
@throws: ServiceNotValid if no services of the given type are configured,
|
|
448
|
+
or if no service of the given type has the current_alias
|
|
449
|
+
"""
|
|
450
|
+
if (
|
|
451
|
+
service_type == "connected_app"
|
|
452
|
+
and current_alias == DEFAULT_CONNECTED_APP_NAME
|
|
453
|
+
):
|
|
454
|
+
raise CumulusCIException(
|
|
455
|
+
"You cannot rename the connected app service that is provided by CumulusCI."
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
self._validate_service_type_and_alias(service_type, current_alias)
|
|
459
|
+
if new_alias in self.services[service_type]:
|
|
460
|
+
raise CumulusCIUsageError(
|
|
461
|
+
f"A service of type {service_type} already exists with name: {new_alias}"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
self.services[service_type][new_alias] = self.services[service_type][
|
|
465
|
+
current_alias
|
|
466
|
+
]
|
|
467
|
+
del self.services[service_type][current_alias]
|
|
468
|
+
|
|
469
|
+
# rename the corresponding .service file
|
|
470
|
+
current_filepath = Path(
|
|
471
|
+
f"{self.global_config_dir}/services/{service_type}/{current_alias}.service"
|
|
472
|
+
)
|
|
473
|
+
new_filepath = Path(
|
|
474
|
+
f"{self.global_config_dir}/services/{service_type}/{new_alias}.service"
|
|
475
|
+
)
|
|
476
|
+
current_filepath.replace(new_filepath)
|
|
477
|
+
|
|
478
|
+
# look through all DEFAULT_SERVICE.json files and
|
|
479
|
+
# change current_alias to new_alias (if present)
|
|
480
|
+
self._rename_alias_in_default_service_file(
|
|
481
|
+
Path(self.global_config_dir, "DEFAULT_SERVICES.json"),
|
|
482
|
+
service_type,
|
|
483
|
+
current_alias,
|
|
484
|
+
new_alias,
|
|
485
|
+
)
|
|
486
|
+
for project_dir in self._iter_local_project_dirs():
|
|
487
|
+
self._rename_alias_in_default_service_file(
|
|
488
|
+
project_dir / "DEFAULT_SERVICES.json",
|
|
489
|
+
service_type,
|
|
490
|
+
current_alias,
|
|
491
|
+
new_alias,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
def remove_service(self, service_type: str, alias: str):
|
|
495
|
+
"""Removes the given service from the keychain. If the service
|
|
496
|
+
is the default service, and there is only one other service
|
|
497
|
+
of the same type, that service is set as the new default.
|
|
498
|
+
|
|
499
|
+
@param service_type type of the service
|
|
500
|
+
@param alias the name of the service
|
|
501
|
+
@raises ServiceNotConfigured if the service_type or alias are invalid
|
|
502
|
+
"""
|
|
503
|
+
if service_type == "connected_app" and alias == DEFAULT_CONNECTED_APP_NAME:
|
|
504
|
+
raise CumulusCIException(
|
|
505
|
+
f"Unable to remove connected app service: {DEFAULT_CONNECTED_APP_NAME}. "
|
|
506
|
+
"This connected app is provided by CumulusCI and cannot be removed."
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
self._validate_service_type_and_alias(service_type, alias)
|
|
510
|
+
# remove the loaded service from the keychain
|
|
511
|
+
del self.services[service_type][alias]
|
|
512
|
+
|
|
513
|
+
# delete the corresponding .service file
|
|
514
|
+
service_filepath = Path(
|
|
515
|
+
f"{self.global_config_dir}/services/{service_type}/{alias}.service"
|
|
516
|
+
)
|
|
517
|
+
service_filepath.unlink()
|
|
518
|
+
|
|
519
|
+
# remove any references from DEFAULT_SERVICES.json files
|
|
520
|
+
self._remove_reference_to_alias(
|
|
521
|
+
Path(self.global_config_dir, "DEFAULT_SERVICES.json"),
|
|
522
|
+
service_type,
|
|
523
|
+
alias,
|
|
524
|
+
)
|
|
525
|
+
for project_dir in self._iter_local_project_dirs():
|
|
526
|
+
self._remove_reference_to_alias(
|
|
527
|
+
project_dir / "DEFAULT_SERVICES.json",
|
|
528
|
+
service_type,
|
|
529
|
+
alias,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# if set, remove the service as the default
|
|
533
|
+
if alias == self._default_services[service_type]:
|
|
534
|
+
del self._default_services[service_type]
|
|
535
|
+
if len(self.services[service_type].keys()) == 1:
|
|
536
|
+
alias = self.list_services()[service_type][0]
|
|
537
|
+
self.set_default_service(service_type, alias, project=False)
|
|
538
|
+
|
|
539
|
+
def _remove_reference_to_alias(
|
|
540
|
+
self, default_services_filepath: Path, service_type: str, alias: str
|
|
541
|
+
) -> None:
|
|
542
|
+
"""Given the path to a DEFAULT_SERVICES.json file, removes any references
|
|
543
|
+
to the given alias if present."""
|
|
544
|
+
default_services = self._read_default_services(default_services_filepath)
|
|
545
|
+
|
|
546
|
+
if service_type in default_services and alias == default_services[service_type]:
|
|
547
|
+
del default_services[service_type]
|
|
548
|
+
|
|
549
|
+
self._write_default_services(default_services_filepath, default_services)
|
|
550
|
+
|
|
551
|
+
def _rename_alias_in_default_service_file(
|
|
552
|
+
self,
|
|
553
|
+
default_service_file_path: Path,
|
|
554
|
+
service_type: str,
|
|
555
|
+
current_alias: str,
|
|
556
|
+
new_alias: str,
|
|
557
|
+
) -> None:
|
|
558
|
+
"""Given the path to a DEFAULT_SERVICES.json file,
|
|
559
|
+
if current_alias is present for the given service_type,
|
|
560
|
+
then rename it to new_alias. Otherwise, do nothing.
|
|
561
|
+
"""
|
|
562
|
+
default_services = self._read_default_services(default_service_file_path)
|
|
563
|
+
|
|
564
|
+
if (
|
|
565
|
+
service_type not in default_services
|
|
566
|
+
or current_alias != default_services[service_type]
|
|
567
|
+
):
|
|
568
|
+
return
|
|
569
|
+
|
|
570
|
+
default_services[service_type] = new_alias
|
|
571
|
+
self._write_default_services(default_service_file_path, default_services)
|
|
572
|
+
|
|
573
|
+
def _load_services(self) -> None:
|
|
574
|
+
"""Load services (and migrate old ones if present)"""
|
|
575
|
+
self._load_services_from_environment()
|
|
576
|
+
|
|
577
|
+
if not (self.global_config_dir / "services").is_dir():
|
|
578
|
+
self._create_default_service_files()
|
|
579
|
+
self._create_services_dir_structure()
|
|
580
|
+
self._migrate_services()
|
|
581
|
+
|
|
582
|
+
self._load_service_files()
|
|
583
|
+
|
|
584
|
+
def _set_service(
|
|
585
|
+
self, service_type, alias, service_config, save=True, config_encrypted=False
|
|
586
|
+
):
|
|
587
|
+
if service_type not in self.services:
|
|
588
|
+
self.services[service_type] = {}
|
|
589
|
+
# set the first service of a given type as the global default
|
|
590
|
+
self._default_services[service_type] = alias
|
|
591
|
+
if save:
|
|
592
|
+
self._save_default_service(service_type, alias, project=False)
|
|
593
|
+
|
|
594
|
+
# If the config is already encrypted coming in
|
|
595
|
+
# (like when were setting services after loading from an encrypted file)
|
|
596
|
+
# then we don't need to do anything.
|
|
597
|
+
if self.key and config_encrypted:
|
|
598
|
+
serialized_config = service_config
|
|
599
|
+
else:
|
|
600
|
+
serialized_config = serialize_config_to_json_or_pickle(
|
|
601
|
+
service_config.config, self.logger
|
|
602
|
+
)
|
|
603
|
+
if self.key:
|
|
604
|
+
serialized_config = encrypt_and_b64(serialized_config, self.key)
|
|
605
|
+
|
|
606
|
+
self.services[service_type][alias] = serialized_config
|
|
607
|
+
|
|
608
|
+
if save:
|
|
609
|
+
self._save_encrypted_service(service_type, alias, serialized_config)
|
|
610
|
+
|
|
611
|
+
def _save_encrypted_service(self, service_type, alias, encrypted):
|
|
612
|
+
"""Write out the encrypted service to disk."""
|
|
613
|
+
service_path = Path(
|
|
614
|
+
f"{self.global_config_dir}/services/{service_type}/{alias}.service"
|
|
615
|
+
)
|
|
616
|
+
if not service_path.parent.is_dir():
|
|
617
|
+
service_path.parent.mkdir()
|
|
618
|
+
|
|
619
|
+
fd = os.open(service_path, OS_FILE_FLAGS, SERVICE_ORG_FILE_MODE)
|
|
620
|
+
with open(fd, "wb") as f:
|
|
621
|
+
f.write(encrypted)
|
|
622
|
+
|
|
623
|
+
def _get_service(self, service_type, alias):
|
|
624
|
+
if service_type == "connected_app" and alias == DEFAULT_CONNECTED_APP_NAME:
|
|
625
|
+
# CumulusCI's default connected app is not encrypted, just return it
|
|
626
|
+
return self.config["services"]["connected_app"][DEFAULT_CONNECTED_APP_NAME]
|
|
627
|
+
|
|
628
|
+
ConfigClass = ServiceConfig
|
|
629
|
+
if "class_path" in self.project_config.config["services"][service_type]:
|
|
630
|
+
class_path = self.project_config.config["services"][service_type][
|
|
631
|
+
"class_path"
|
|
632
|
+
]
|
|
633
|
+
try:
|
|
634
|
+
ServiceConfigClass = import_class(class_path)
|
|
635
|
+
|
|
636
|
+
if issubclass(ServiceConfigClass, BaseConfig):
|
|
637
|
+
ConfigClass = ServiceConfigClass
|
|
638
|
+
except (AttributeError, ModuleNotFoundError):
|
|
639
|
+
raise CumulusCIException(
|
|
640
|
+
f"Unrecognized class_path for service: {class_path}"
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
config = self.services[service_type][alias]
|
|
645
|
+
except KeyError:
|
|
646
|
+
raise ServiceNotConfigured(
|
|
647
|
+
f"No service of type {service_type} exists with the name: {alias}"
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
if self.key:
|
|
651
|
+
org = self._decrypt_config(
|
|
652
|
+
ConfigClass,
|
|
653
|
+
config,
|
|
654
|
+
extra=[alias, self],
|
|
655
|
+
context=f"service config ({service_type}:{alias})",
|
|
656
|
+
)
|
|
657
|
+
else:
|
|
658
|
+
config = load_config_from_json_or_pickle(config)
|
|
659
|
+
org = self._construct_config(ConfigClass, [config, alias, self])
|
|
660
|
+
|
|
661
|
+
return org
|
|
662
|
+
|
|
663
|
+
def _load_services_from_environment(self):
|
|
664
|
+
"""Load any services specified by environment variables"""
|
|
665
|
+
for env_var_name, value in os.environ.items():
|
|
666
|
+
if env_var_name.startswith(self.env_service_var_prefix):
|
|
667
|
+
self._load_service_from_environment(env_var_name, value)
|
|
668
|
+
|
|
669
|
+
def _load_service_from_environment(self, env_var_name, value):
|
|
670
|
+
"""Given a valid name/value pair, load the
|
|
671
|
+
service from the environment on to the keychain"""
|
|
672
|
+
if not value:
|
|
673
|
+
raise ServiceCannotBeLoaded(
|
|
674
|
+
f"Service env var {env_var_name} cannot be loaded because it is empty. Either set {env_var_name} to a json string or unset it from the environment."
|
|
675
|
+
)
|
|
676
|
+
try:
|
|
677
|
+
service_config = json.loads(value)
|
|
678
|
+
except Exception as e:
|
|
679
|
+
raise ServiceCannotBeLoaded(
|
|
680
|
+
f"Could not parse {env_var_name} as JSON because {e}"
|
|
681
|
+
)
|
|
682
|
+
service_config = ServiceConfig(json.loads(value))
|
|
683
|
+
service_type, service_name = self._get_env_service_type_and_name(env_var_name)
|
|
684
|
+
self.set_service(service_type, service_name, service_config, save=False)
|
|
685
|
+
|
|
686
|
+
def _get_env_service_type_and_name(self, env_service_name):
|
|
687
|
+
return (
|
|
688
|
+
self._get_env_service_type(env_service_name),
|
|
689
|
+
self._get_env_service_name(env_service_name),
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
def _get_env_service_type(self, env_service_name):
|
|
693
|
+
"""Parse the service type given the env var name"""
|
|
694
|
+
post_prefix = env_service_name[len(self.env_service_var_prefix) :].lower()
|
|
695
|
+
return post_prefix.split("__")[0]
|
|
696
|
+
|
|
697
|
+
def _get_env_service_name(self, env_service_name):
|
|
698
|
+
"""
|
|
699
|
+
Parse the service name given the env var name.
|
|
700
|
+
Services from the environment can be listed with or without a name:
|
|
701
|
+
* CUMULUSCI_SERVICE_service_type -> this gets a default name of "env"
|
|
702
|
+
* CUMULUSCI_SERVICE_service_type__name -> this gets the name "env-name"
|
|
703
|
+
"""
|
|
704
|
+
parts = env_service_name.split("__")
|
|
705
|
+
return f"env-{parts[-1]}" if len(parts) > 1 else "env"
|
|
706
|
+
|
|
707
|
+
def _load_service_files(self) -> None:
|
|
708
|
+
"""
|
|
709
|
+
Load configured services onto the keychain.
|
|
710
|
+
This method recursively goes through all subdirectories
|
|
711
|
+
in ~/.cumulusci/services looking for .service files to load.
|
|
712
|
+
"""
|
|
713
|
+
services_dir = Path(f"{self.global_config_dir}/services")
|
|
714
|
+
for item in services_dir.glob("**/*"):
|
|
715
|
+
if item.suffix == ".service":
|
|
716
|
+
with open(item) as f:
|
|
717
|
+
config = f.read()
|
|
718
|
+
name = item.name.replace(".service", "")
|
|
719
|
+
service_type = item.parent.name
|
|
720
|
+
|
|
721
|
+
self.set_service(
|
|
722
|
+
service_type, name, config, save=False, config_encrypted=True
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
def _load_default_services(self) -> None:
|
|
726
|
+
"""Init self._default_services on the keychain so that
|
|
727
|
+
calls to get_service() that do not pass an alias can
|
|
728
|
+
return the default service for the given type"""
|
|
729
|
+
|
|
730
|
+
# set CumulusCI's default connected app as the default first
|
|
731
|
+
# so it will be overwritten if the user is using a different connected app
|
|
732
|
+
self._default_services["connected_app"] = DEFAULT_CONNECTED_APP_NAME
|
|
733
|
+
|
|
734
|
+
global_default_services = Path(
|
|
735
|
+
f"{self.global_config_dir}/{DEFAULT_SERVICES_FILENAME}"
|
|
736
|
+
)
|
|
737
|
+
self._set_default_services_from_dir(global_default_services)
|
|
738
|
+
|
|
739
|
+
project_default_services = Path(
|
|
740
|
+
f"{self.project_local_dir}/{DEFAULT_SERVICES_FILENAME}"
|
|
741
|
+
)
|
|
742
|
+
# project defaults will overwrite global defaults
|
|
743
|
+
self._set_default_services_from_dir(project_default_services)
|
|
744
|
+
|
|
745
|
+
def _set_default_services_from_dir(self, default_services_file: Path) -> None:
|
|
746
|
+
"""Sets the keychain._default_services dictionary to the default
|
|
747
|
+
values in the given file.
|
|
748
|
+
|
|
749
|
+
@param default_services_file path to a DEFAULT_SERVICES.json file
|
|
750
|
+
"""
|
|
751
|
+
default_services = self._read_default_services(default_services_file)
|
|
752
|
+
for s_type, alias in default_services.items():
|
|
753
|
+
self._default_services[s_type] = alias
|
|
754
|
+
|
|
755
|
+
def _save_default_service(
|
|
756
|
+
self, service_type: str, alias: str, project: bool = False
|
|
757
|
+
) -> None:
|
|
758
|
+
"""Write out the contents of self._default_services to the
|
|
759
|
+
DEFAULT_SERVICES.json file based on the provided scope"""
|
|
760
|
+
dir_path = (
|
|
761
|
+
Path(self.project_local_dir) if project else Path(self.global_config_dir)
|
|
762
|
+
)
|
|
763
|
+
default_services_file = dir_path / DEFAULT_SERVICES_FILENAME
|
|
764
|
+
|
|
765
|
+
default_services = self._read_default_services(default_services_file)
|
|
766
|
+
default_services[service_type] = alias
|
|
767
|
+
self._write_default_services(default_services_file, default_services)
|
|
768
|
+
|
|
769
|
+
def _create_default_service_files(self) -> None:
|
|
770
|
+
"""
|
|
771
|
+
Generate the DEFAULT_SERVICES.json files at global and project scopes.
|
|
772
|
+
|
|
773
|
+
@param local_proj_path: should be the local_project_path for the project
|
|
774
|
+
"""
|
|
775
|
+
global_default_service_file = Path(
|
|
776
|
+
f"{self.global_config_dir}/{DEFAULT_SERVICES_FILENAME}"
|
|
777
|
+
)
|
|
778
|
+
if global_default_service_file.is_file():
|
|
779
|
+
return
|
|
780
|
+
|
|
781
|
+
self._write_default_services_for_dir(self.global_config_dir)
|
|
782
|
+
for local_proj_dir in self._iter_local_project_dirs():
|
|
783
|
+
self._write_default_services_for_dir(local_proj_dir)
|
|
784
|
+
|
|
785
|
+
def _write_default_services_for_dir(self, dir_path: str) -> None:
|
|
786
|
+
"""Look through the given dir and set all .service files
|
|
787
|
+
present as the defaults for their given types, and write these
|
|
788
|
+
out to a DEFAULT_SERVICES.json file in the directory. This occurs
|
|
789
|
+
once before .service files are migrated to the appropriate
|
|
790
|
+
services/ sub-directory, so we set the default to the alias
|
|
791
|
+
that will be assigned during migration.
|
|
792
|
+
|
|
793
|
+
@param dir_path: the directory to look through and write
|
|
794
|
+
the DEFAULT_SERVICES.json file in.
|
|
795
|
+
"""
|
|
796
|
+
dir_path = Path(dir_path)
|
|
797
|
+
default_services = {}
|
|
798
|
+
for item in dir_path.iterdir():
|
|
799
|
+
if item.suffix == ".service":
|
|
800
|
+
service_type = item.name.replace(".service", "")
|
|
801
|
+
alias = (
|
|
802
|
+
"global"
|
|
803
|
+
if item.parent.name == ".cumulusci"
|
|
804
|
+
else Path(self.project_local_dir).name
|
|
805
|
+
)
|
|
806
|
+
default_services[service_type] = f"{alias}"
|
|
807
|
+
|
|
808
|
+
self._write_default_services(
|
|
809
|
+
dir_path / DEFAULT_SERVICES_FILENAME, default_services
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
def _create_services_dir_structure(self) -> None:
|
|
813
|
+
"""
|
|
814
|
+
Ensure the 'services' directory structure exists.
|
|
815
|
+
The services dir has the following structure and lives
|
|
816
|
+
in the global_config_dir:
|
|
817
|
+
|
|
818
|
+
services
|
|
819
|
+
|-- github
|
|
820
|
+
| |-- alias1.service
|
|
821
|
+
| |-- alias2.service
|
|
822
|
+
| |-- ...
|
|
823
|
+
|-- devhub
|
|
824
|
+
| |-- alias1.service
|
|
825
|
+
| |-- alias2.service
|
|
826
|
+
| |-- ...
|
|
827
|
+
.
|
|
828
|
+
|
|
829
|
+
This also has the advantage that when a new service type
|
|
830
|
+
is added to cumulusci.yml a new directory for that service type
|
|
831
|
+
will be created.
|
|
832
|
+
"""
|
|
833
|
+
services_dir_path = Path(f"{self.global_config_dir}/services")
|
|
834
|
+
services_dir_path.mkdir(exist_ok=True)
|
|
835
|
+
|
|
836
|
+
configured_service_types = self.project_config.config["services"].keys()
|
|
837
|
+
for service_type in configured_service_types:
|
|
838
|
+
service_type_dir_path = Path(services_dir_path / service_type)
|
|
839
|
+
if not Path.is_dir(service_type_dir_path):
|
|
840
|
+
Path.mkdir(service_type_dir_path)
|
|
841
|
+
|
|
842
|
+
def _migrate_services(self):
|
|
843
|
+
"""Migrate .service files from the global_config_dir and
|
|
844
|
+
any project local directories."""
|
|
845
|
+
self._migrate_services_from_dir(self.global_config_dir)
|
|
846
|
+
for local_proj_dir in self._iter_local_project_dirs():
|
|
847
|
+
self._migrate_services_from_dir(local_proj_dir)
|
|
848
|
+
|
|
849
|
+
def _migrate_services_from_dir(self, dir_path: str) -> None:
|
|
850
|
+
"""Migrate all .service files from the given directory to
|
|
851
|
+
the appropriate service sub-directory and apply the default
|
|
852
|
+
alias. This is intended to be run against either the
|
|
853
|
+
global_config_dir, or a local project directory.
|
|
854
|
+
|
|
855
|
+
Default aliases are in the form `service_type__scope`.
|
|
856
|
+
Scope is either the name of the local project directory
|
|
857
|
+
or 'global'; depending on where the .service file is located."""
|
|
858
|
+
dir_path = Path(dir_path)
|
|
859
|
+
for item in dir_path.iterdir():
|
|
860
|
+
if item.suffix == ".service":
|
|
861
|
+
new_service_filepath = self._get_new_service_filepath(item)
|
|
862
|
+
if not new_service_filepath:
|
|
863
|
+
continue
|
|
864
|
+
if new_service_filepath.is_file():
|
|
865
|
+
self.logger.warning(
|
|
866
|
+
f"Skipping migration of {item.name} as a default alias already exists for this service type. "
|
|
867
|
+
)
|
|
868
|
+
continue
|
|
869
|
+
|
|
870
|
+
item.replace(new_service_filepath)
|
|
871
|
+
|
|
872
|
+
def _get_new_service_filepath(self, item: Path) -> Path:
|
|
873
|
+
"""Given an old .service filepath, determine the path
|
|
874
|
+
and filename of the new .service file.
|
|
875
|
+
|
|
876
|
+
@returns: the Path to the newfile, or None if the service
|
|
877
|
+
is of an unrecognized type.
|
|
878
|
+
"""
|
|
879
|
+
service_type = item.name.replace(".service", "")
|
|
880
|
+
configured_service_types = self.project_config.config["services"].keys()
|
|
881
|
+
if service_type not in configured_service_types:
|
|
882
|
+
self.logger.info(f"Skipping migration of unrecognized service: {item.name}")
|
|
883
|
+
return None
|
|
884
|
+
|
|
885
|
+
alias = (
|
|
886
|
+
"global"
|
|
887
|
+
if item.parent.name == ".cumulusci"
|
|
888
|
+
else Path(self.project_local_dir).name
|
|
889
|
+
)
|
|
890
|
+
new_filename = f"{alias}.service"
|
|
891
|
+
services_sub_dir = Path(f"{self.global_config_dir}/services/{service_type}")
|
|
892
|
+
return services_sub_dir / new_filename
|
|
893
|
+
|
|
894
|
+
def _read_default_services(self, file_path: Path) -> T.Dict[str, str]:
|
|
895
|
+
"""Reads the default services file at the given path
|
|
896
|
+
|
|
897
|
+
@param file_path path to DEFAULT_SERVICES.json
|
|
898
|
+
@returns dict of default services
|
|
899
|
+
@raises CumulusCIException if the file does not exist
|
|
900
|
+
"""
|
|
901
|
+
if not file_path.is_file() or file_path.name != DEFAULT_SERVICES_FILENAME:
|
|
902
|
+
return {}
|
|
903
|
+
else:
|
|
904
|
+
return json.loads(file_path.read_text(encoding="utf-8"))
|
|
905
|
+
|
|
906
|
+
def _write_default_services(
|
|
907
|
+
self, file_path: Path, default_services: T.Dict[str, str]
|
|
908
|
+
) -> None:
|
|
909
|
+
"""Writes default services out to the given file
|
|
910
|
+
|
|
911
|
+
@param file_path path to DEFAULT_SERVICES.json
|
|
912
|
+
@param dictionary mapping service types to the alias of the default service for that type
|
|
913
|
+
@raises CumulusCIException if the file does not exist
|
|
914
|
+
"""
|
|
915
|
+
if file_path.name != DEFAULT_SERVICES_FILENAME:
|
|
916
|
+
raise CumulusCIException(
|
|
917
|
+
f"No {DEFAULT_SERVICES_FILENAME} file found at: {file_path}"
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
921
|
+
f.write(json.dumps(default_services))
|
|
922
|
+
|
|
923
|
+
def _iter_local_project_dirs(self):
|
|
924
|
+
"""Iterate over all local project dirs in ~/.cumulusci"""
|
|
925
|
+
for item in Path(self.global_config_dir).iterdir():
|
|
926
|
+
if item.is_dir() and item.name not in ["logs", "services"]:
|
|
927
|
+
yield item
|
|
928
|
+
|
|
929
|
+
def _raise_service_not_configured(self, name):
|
|
930
|
+
raise ServiceNotConfigured(
|
|
931
|
+
f"'{name}' service configuration could not be found. "
|
|
932
|
+
f"Maybe you need to run: cci service connect {name}"
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
class GlobalOrg(T.NamedTuple):
|
|
937
|
+
data: bytes
|
|
938
|
+
global_org: bool = True
|
|
939
|
+
filename: str = None
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
class LocalOrg(T.NamedTuple):
|
|
943
|
+
data: bytes
|
|
944
|
+
global_org: bool = False
|
|
945
|
+
filename: str = None
|