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
cumulusci/core/github.py
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import io
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import time
|
|
6
|
+
import webbrowser
|
|
7
|
+
from string import Template
|
|
8
|
+
from typing import Callable, Optional, Union
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
import github3
|
|
12
|
+
from github3 import GitHub, GitHubEnterprise, login
|
|
13
|
+
from github3.exceptions import (
|
|
14
|
+
AuthenticationFailed,
|
|
15
|
+
ConnectionError,
|
|
16
|
+
ResponseError,
|
|
17
|
+
TransportError,
|
|
18
|
+
)
|
|
19
|
+
from github3.git import Reference, Tag
|
|
20
|
+
from github3.pulls import ShortPullRequest
|
|
21
|
+
from github3.repos.commit import RepoCommit
|
|
22
|
+
from github3.repos.release import Release
|
|
23
|
+
from github3.repos.repo import Repository
|
|
24
|
+
from github3.session import GitHubSession
|
|
25
|
+
from requests.adapters import HTTPAdapter
|
|
26
|
+
from requests.exceptions import RetryError
|
|
27
|
+
from requests.models import Response
|
|
28
|
+
from requests.packages.urllib3.util.retry import Retry
|
|
29
|
+
from rich.console import Console
|
|
30
|
+
|
|
31
|
+
from cumulusci.core.exceptions import (
|
|
32
|
+
DependencyLookupError,
|
|
33
|
+
GithubApiError,
|
|
34
|
+
GithubApiNotFoundError,
|
|
35
|
+
GithubException,
|
|
36
|
+
ServiceNotConfigured,
|
|
37
|
+
)
|
|
38
|
+
from cumulusci.oauth.client import (
|
|
39
|
+
OAuth2ClientConfig,
|
|
40
|
+
OAuth2DeviceConfig,
|
|
41
|
+
get_device_code,
|
|
42
|
+
get_device_oauth_token,
|
|
43
|
+
)
|
|
44
|
+
from cumulusci.utils.git import parse_repo_url
|
|
45
|
+
from cumulusci.utils.http.requests_utils import safe_json_from_response
|
|
46
|
+
from cumulusci.utils.yaml.cumulusci_yml import cci_safe_load
|
|
47
|
+
|
|
48
|
+
OAUTH_DEVICE_APP = {
|
|
49
|
+
"client_id": "2a4bc3e5ce4f2c49a957",
|
|
50
|
+
"auth_uri": "https://github.com/login/device/code",
|
|
51
|
+
"token_uri": "https://github.com/login/oauth/access_token",
|
|
52
|
+
"scope": "repo gist",
|
|
53
|
+
}
|
|
54
|
+
SSO_WARNING = """Results may be incomplete. You have not granted your Personal Access token access to the following organizations:"""
|
|
55
|
+
UNAUTHORIZED_WARNING = """
|
|
56
|
+
Bad credentials. Verify that your personal access token is correct and that you are authorized to access this resource.
|
|
57
|
+
"""
|
|
58
|
+
SELF_SIGNED_WARNING = """
|
|
59
|
+
There was a problem verifying the SSL Certificate due to a certificate authority that isn't trusted or a self-signed certificate in the certificate chain. Try setting CUMULUSCI_SYSTEM_CERTS Environment Variable to 'True'. See https://cumulusci.readthedocs.io/en/stable/env-var-reference.html?#cumulusci-system-certs
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class GitHubRety(Retry):
|
|
64
|
+
def __init__(self, *args, **kwargs):
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
|
|
67
|
+
def increment(self, *args, **kwargs):
|
|
68
|
+
# Check for connnection and fail on SSLerror
|
|
69
|
+
# SSLCertVerificationError
|
|
70
|
+
if "error" in kwargs:
|
|
71
|
+
error = kwargs["error"]
|
|
72
|
+
error_str = "CERTIFICATE_VERIFY_FAILED"
|
|
73
|
+
if error_str in str(error):
|
|
74
|
+
raise error
|
|
75
|
+
# finally call increment
|
|
76
|
+
return super().increment(*args, **kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Prepare request retry policy to be attached to github sessions.
|
|
80
|
+
# 401 is a weird status code to retry, but sometimes it happens spuriously
|
|
81
|
+
# and https://github.community/t5/GitHub-API-Development-and/Random-401-errors-after-using-freshly-generated-installation/m-p/22905 suggests retrying
|
|
82
|
+
retries = GitHubRety(status_forcelist=(401, 502, 503, 504), backoff_factor=0.3)
|
|
83
|
+
adapter = HTTPAdapter(max_retries=retries)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_github_api(username=None, password=None):
|
|
87
|
+
"""Old API that only handles logging in as a user.
|
|
88
|
+
|
|
89
|
+
Here for backwards-compatibility during the transition.
|
|
90
|
+
"""
|
|
91
|
+
gh = login(username, password)
|
|
92
|
+
gh.session.mount("http://", adapter)
|
|
93
|
+
gh.session.mount("https://", adapter)
|
|
94
|
+
return gh
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
INSTALLATIONS = {}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _determine_github_client(host: str, client_params: dict) -> GitHub:
|
|
101
|
+
# also covers "api.github.com"
|
|
102
|
+
is_github: bool = host in (None, "None") or "github.com" in host
|
|
103
|
+
client_cls: GitHub = GitHub if is_github else GitHubEnterprise # type: ignore
|
|
104
|
+
params: dict = client_params
|
|
105
|
+
if not is_github:
|
|
106
|
+
params["url"] = "https://" + host # type: ignore
|
|
107
|
+
|
|
108
|
+
return client_cls(**params)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_github_api_for_repo(keychain, repo_url, session=None) -> GitHub:
|
|
112
|
+
owner, repo_name, host = parse_repo_url(repo_url)
|
|
113
|
+
gh: GitHub = _determine_github_client(
|
|
114
|
+
host,
|
|
115
|
+
{
|
|
116
|
+
"session": session
|
|
117
|
+
or GitHubSession(default_read_timeout=30, default_connect_timeout=30)
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Apply retry policy
|
|
122
|
+
gh.session.mount("http://", adapter)
|
|
123
|
+
gh.session.mount("https://", adapter)
|
|
124
|
+
|
|
125
|
+
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
|
|
126
|
+
APP_KEY = os.environ.get("GITHUB_APP_KEY", "").encode("utf-8")
|
|
127
|
+
APP_ID = os.environ.get("GITHUB_APP_ID")
|
|
128
|
+
if APP_ID and APP_KEY:
|
|
129
|
+
installation = INSTALLATIONS.get((owner, repo_name))
|
|
130
|
+
if installation is None:
|
|
131
|
+
gh.login_as_app(APP_KEY, APP_ID, expire_in=120)
|
|
132
|
+
try:
|
|
133
|
+
installation = gh.app_installation_for_repository(owner, repo_name)
|
|
134
|
+
except github3.exceptions.NotFoundError:
|
|
135
|
+
raise GithubException(
|
|
136
|
+
f"Could not access {owner}/{repo_name} using GitHub app. "
|
|
137
|
+
"Does the app need to be installed for this repository?"
|
|
138
|
+
)
|
|
139
|
+
INSTALLATIONS[(owner, repo_name)] = installation
|
|
140
|
+
gh.login_as_app_installation(APP_KEY, APP_ID, installation.id)
|
|
141
|
+
elif GITHUB_TOKEN:
|
|
142
|
+
gh.login(token=GITHUB_TOKEN)
|
|
143
|
+
else:
|
|
144
|
+
token = get_auth_from_service(host, keychain)
|
|
145
|
+
gh.login(token=token)
|
|
146
|
+
|
|
147
|
+
return gh
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_auth_from_service(host, keychain) -> tuple:
|
|
151
|
+
"""
|
|
152
|
+
Given a host extracted from a repo_url, returns the username and token for
|
|
153
|
+
the first service with a matching server_domain
|
|
154
|
+
"""
|
|
155
|
+
if host is None or host == "None" or "github.com" in host:
|
|
156
|
+
service_config = keychain.get_service("github")
|
|
157
|
+
else:
|
|
158
|
+
services = keychain.get_services_for_type("github_enterprise")
|
|
159
|
+
service_by_host = {service.server_domain: service for service in services}
|
|
160
|
+
|
|
161
|
+
# Check when connecting to server, but not when creating new service as this would always catch
|
|
162
|
+
if list(service_by_host.keys()).count(host) == 0:
|
|
163
|
+
raise ServiceNotConfigured(
|
|
164
|
+
f"No Github Enterprise service configured for domain {host}."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
service_config = service_by_host[host]
|
|
168
|
+
|
|
169
|
+
# Basic Auth no longer supported on github.com, so only returning token
|
|
170
|
+
# this requires GitHub Enterprise to use token auth and not Basic auth
|
|
171
|
+
# docs.github.com/en/rest/overview/other-authentication-methods#via-username-and-password
|
|
172
|
+
return service_config.token
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def validate_gh_enterprise(host: str, keychain) -> None:
|
|
176
|
+
services = keychain.get_services_for_type("github_enterprise")
|
|
177
|
+
if services:
|
|
178
|
+
hosts = [service.server_domain for service in services]
|
|
179
|
+
if hosts.count(host) > 1:
|
|
180
|
+
raise GithubException(
|
|
181
|
+
f"More than one Github Enterprise service configured for domain {host}."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_service(options: dict, keychain) -> dict:
|
|
186
|
+
username = options["username"]
|
|
187
|
+
token = options["token"]
|
|
188
|
+
# Github service doesn't have "server_domain",
|
|
189
|
+
server_domain = options.get("server_domain", None)
|
|
190
|
+
|
|
191
|
+
gh = _determine_github_client(server_domain, {"token": token})
|
|
192
|
+
if type(gh) == GitHubEnterprise:
|
|
193
|
+
validate_gh_enterprise(server_domain, keychain)
|
|
194
|
+
try:
|
|
195
|
+
authed_user = gh.me()
|
|
196
|
+
auth_login = authed_user.login
|
|
197
|
+
assert username == auth_login, f"{username}, {auth_login}"
|
|
198
|
+
except AssertionError as e:
|
|
199
|
+
raise GithubException(
|
|
200
|
+
f"Service username and token username do not match. ({str(e)})"
|
|
201
|
+
)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
warning_msg = format_github3_exception(e) or str(e)
|
|
204
|
+
raise GithubException(
|
|
205
|
+
f"Could not confirm access to the GitHub API: {warning_msg}"
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
member_orgs = {f"{org.id}": f"{org.login}" for org in gh.organizations()}
|
|
209
|
+
options["Organizations"] = ", ".join([k for k in member_orgs.values()])
|
|
210
|
+
|
|
211
|
+
# We're checking for a partial-response SSO header and /user/orgs
|
|
212
|
+
# doesn't include one, so we need /user/repos instead.
|
|
213
|
+
repo_generator = gh.repositories()
|
|
214
|
+
_ = next(repo_generator, None)
|
|
215
|
+
repo_response = repo_generator.last_response
|
|
216
|
+
options["scopes"] = ", ".join(sorted(get_oauth_scopes(repo_response)))
|
|
217
|
+
|
|
218
|
+
unauthorized_org_ids = get_sso_disabled_orgs(repo_response)
|
|
219
|
+
unauthorized_orgs = {
|
|
220
|
+
k: member_orgs[k] for k in unauthorized_org_ids if k in member_orgs
|
|
221
|
+
}
|
|
222
|
+
if unauthorized_orgs:
|
|
223
|
+
options["SSO Disabled"] = ", ".join([k for k in unauthorized_orgs.values()])
|
|
224
|
+
|
|
225
|
+
expiration_date = repo_response.headers.get(
|
|
226
|
+
"GitHub-Authentication-Token-Expiration"
|
|
227
|
+
)
|
|
228
|
+
if expiration_date:
|
|
229
|
+
options["expires"] = expiration_date
|
|
230
|
+
|
|
231
|
+
return options
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def get_pull_requests_with_base_branch(repo, base_branch_name, head=None, state=None):
|
|
235
|
+
"""Returns a list of pull requests with the given base branch"""
|
|
236
|
+
if head:
|
|
237
|
+
head = repo.owner.login + ":" + head
|
|
238
|
+
return list(repo.pull_requests(base=base_branch_name, head=head, state=state))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_pull_requests_by_head(repo, branch_name):
|
|
242
|
+
"""Returns all pull requests with head equal to the given branch name."""
|
|
243
|
+
if branch_name == repo.default_branch:
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
return list(repo.pull_requests(head=repo.owner.login + ":" + branch_name))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def create_pull_request(repo, branch_name, base=None, title=None):
|
|
250
|
+
"""Creates a pull request for the given branch"""
|
|
251
|
+
base = base or repo.default_branch
|
|
252
|
+
title = title or "Auto-Generated Pull Request"
|
|
253
|
+
pull_request = repo.create_pull(title, base, branch_name)
|
|
254
|
+
return pull_request
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def add_labels_to_pull_request(repo, pull_request, *labels):
|
|
258
|
+
"""Adds a label to a pull request via the issue object
|
|
259
|
+
Args:
|
|
260
|
+
* repo: Repository object
|
|
261
|
+
* pull_request: ShortPullRequest object that exists in repo
|
|
262
|
+
* labels: list(str) of labels to add to the pull request"""
|
|
263
|
+
issue = repo.issue(pull_request.number)
|
|
264
|
+
issue.add_labels(*labels)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def is_label_on_pull_request(repo, pull_request, label_name):
|
|
268
|
+
"""Returns True if the given label is on the pull request with the given
|
|
269
|
+
pull request number. False otherwise."""
|
|
270
|
+
labels = list(repo.issue(pull_request.number).labels())
|
|
271
|
+
return any(label_name == issue_label.name for issue_label in labels)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_pull_requests_by_commit(github, repo, commit_sha):
|
|
275
|
+
endpoint = (
|
|
276
|
+
github.session.base_url
|
|
277
|
+
+ f"/repos/{repo.owner.login}/{repo.name}/commits/{commit_sha}/pulls"
|
|
278
|
+
)
|
|
279
|
+
response = github.session.get(
|
|
280
|
+
endpoint, headers={"Accept": "application/vnd.github.groot-preview+json"}
|
|
281
|
+
)
|
|
282
|
+
json_list = safe_json_from_response(response)
|
|
283
|
+
|
|
284
|
+
# raises github3.exceptions.IncompleteResposne
|
|
285
|
+
# when these are not present
|
|
286
|
+
for json in json_list:
|
|
287
|
+
json["body_html"] = ""
|
|
288
|
+
json["body_text"] = ""
|
|
289
|
+
|
|
290
|
+
return [ShortPullRequest(json, github) for json in json_list]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def is_pull_request_merged(pull_request):
|
|
294
|
+
"""Takes a github3.pulls.ShortPullRequest object"""
|
|
295
|
+
return pull_request.merged_at is not None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def markdown_link_to_pr(change_note):
|
|
299
|
+
return f"{change_note.title} [[PR{change_note.number}]({change_note.html_url})]"
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def find_latest_release(repo, include_beta=None) -> Optional[Release]:
|
|
303
|
+
try:
|
|
304
|
+
if include_beta:
|
|
305
|
+
return get_latest_prerelease(repo)
|
|
306
|
+
else:
|
|
307
|
+
return repo.latest_release()
|
|
308
|
+
except (github3.exceptions.NotFoundError, StopIteration):
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_latest_prerelease(repo: Repository) -> Optional[Release]:
|
|
313
|
+
"""Calls GraphQL to retrieve the latest release, ordered chronologically."""
|
|
314
|
+
QUERY = Template(
|
|
315
|
+
"""
|
|
316
|
+
query {
|
|
317
|
+
repository(owner: "$owner", name: "$name") {
|
|
318
|
+
releases(last: 1, orderBy: {field: CREATED_AT, direction: ASC}) {
|
|
319
|
+
nodes {
|
|
320
|
+
tagName
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
"""
|
|
326
|
+
).substitute(dict(owner=repo.owner, name=repo.name))
|
|
327
|
+
|
|
328
|
+
session: GitHubSession = repo.session
|
|
329
|
+
# HACK: This is a kludgy workaround because GitHub Enterprise Server
|
|
330
|
+
# base_urls in github3.py end in `/api/v3`.
|
|
331
|
+
host = (
|
|
332
|
+
session.base_url[: -len("/v3")]
|
|
333
|
+
if session.base_url.endswith("/v3")
|
|
334
|
+
else session.base_url
|
|
335
|
+
)
|
|
336
|
+
url: str = f"{host}/graphql"
|
|
337
|
+
response: Response = session.request("POST", url, json={"query": QUERY})
|
|
338
|
+
response_dict: dict = response.json()
|
|
339
|
+
|
|
340
|
+
if release_tags := response_dict["data"]["repository"]["releases"]["nodes"]:
|
|
341
|
+
return repo.release_from_tag(release_tags[0]["tagName"])
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def find_previous_release(repo, prefix=None):
|
|
345
|
+
most_recent = None
|
|
346
|
+
for release in repo.releases():
|
|
347
|
+
if prefix and not release.tag_name.startswith(prefix):
|
|
348
|
+
continue
|
|
349
|
+
if not prefix and release.prerelease:
|
|
350
|
+
continue
|
|
351
|
+
# Return the second release
|
|
352
|
+
if most_recent is None:
|
|
353
|
+
most_recent = release
|
|
354
|
+
else:
|
|
355
|
+
return release
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
VERSION_ID_RE = re.compile(r"version_id: (\S+)")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def get_version_id_from_commit(repo, commit_sha, context):
|
|
362
|
+
commit = get_commit(repo, commit_sha)
|
|
363
|
+
|
|
364
|
+
for status in commit.status().statuses:
|
|
365
|
+
if status.state == "success" and status.context == context:
|
|
366
|
+
match = VERSION_ID_RE.search(status.description)
|
|
367
|
+
if match:
|
|
368
|
+
return match.group(1)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def get_commit(repo: Repository, commit_sha: str) -> Optional[RepoCommit]:
|
|
372
|
+
"""Given a SHA1 hash, retrieve a Commit object from the REST API."""
|
|
373
|
+
try:
|
|
374
|
+
commit = repo.commit(commit_sha)
|
|
375
|
+
except (github3.exceptions.NotFoundError, github3.exceptions.UnprocessableEntity):
|
|
376
|
+
# GitHub returns 422 for nonexistent commits in at least some circumstances.
|
|
377
|
+
raise DependencyLookupError(f"Could not find commit {commit_sha} on GitHub")
|
|
378
|
+
return commit
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def find_repo_feature_prefix(repo: Repository) -> str:
|
|
382
|
+
contents = repo.file_contents(
|
|
383
|
+
"cumulusci.yml",
|
|
384
|
+
ref=repo.branch(repo.default_branch).commit.sha,
|
|
385
|
+
)
|
|
386
|
+
head_cumulusci_yml = cci_safe_load(io.StringIO(contents.decoded.decode("utf-8")))
|
|
387
|
+
return (
|
|
388
|
+
head_cumulusci_yml.get("project", {})
|
|
389
|
+
.get("git", {})
|
|
390
|
+
.get("prefix_feature", "feature/")
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def find_repo_commit_status_context(
|
|
395
|
+
repo: Repository, context_name: str, default: str
|
|
396
|
+
) -> str:
|
|
397
|
+
contents = repo.file_contents(
|
|
398
|
+
"cumulusci.yml",
|
|
399
|
+
ref=repo.branch(repo.default_branch).commit.sha,
|
|
400
|
+
)
|
|
401
|
+
head_cumulusci_yml = cci_safe_load(io.StringIO(contents.decoded.decode("utf-8")))
|
|
402
|
+
return (
|
|
403
|
+
head_cumulusci_yml.get("project", {}).get("git", {}).get(context_name, default)
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def get_tag_by_name(repo: Repository, tag_name: str) -> Tag:
|
|
408
|
+
"""Fetches a tag by name from the given repository"""
|
|
409
|
+
ref: Reference = get_ref_for_tag(repo, tag_name)
|
|
410
|
+
try:
|
|
411
|
+
return repo.tag(ref.object.sha)
|
|
412
|
+
except github3.exceptions.NotFoundError:
|
|
413
|
+
msg = f"Could not find tag '{tag_name}' with SHA {ref.object.sha} on GitHub"
|
|
414
|
+
if ref.object.type != "tag":
|
|
415
|
+
msg += f"\n{tag_name} is not an annotated tag."
|
|
416
|
+
raise GithubApiNotFoundError(msg)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def get_ref_for_tag(repo: Repository, tag_name: str) -> Reference:
|
|
420
|
+
"""Gets a Reference object for the tag with the given name"""
|
|
421
|
+
try:
|
|
422
|
+
return repo.ref(f"tags/{tag_name}")
|
|
423
|
+
except github3.exceptions.NotFoundError:
|
|
424
|
+
raise GithubApiNotFoundError(
|
|
425
|
+
f"Could not find reference for 'tags/{tag_name}' on GitHub"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def get_version_id_from_tag(repo: Repository, tag_name: str) -> str:
|
|
430
|
+
"""Given the name of a tag, return the version_id in the tag's message.
|
|
431
|
+
|
|
432
|
+
@param tag_name: the name of the tag
|
|
433
|
+
@param repo: the repository of the package to look for a release in
|
|
434
|
+
@returns: the 04t id in the tag's messages
|
|
435
|
+
"""
|
|
436
|
+
tag = get_tag_by_name(repo, tag_name)
|
|
437
|
+
for line in tag.message.split("\n"):
|
|
438
|
+
if line.startswith("version_id:"):
|
|
439
|
+
version_id = line.split("version_id: ")[1]
|
|
440
|
+
if not version_id.startswith("04t"):
|
|
441
|
+
continue
|
|
442
|
+
return version_id
|
|
443
|
+
|
|
444
|
+
raise DependencyLookupError(f"Could not find version_id for tag {tag_name}")
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def format_github3_exception(
|
|
448
|
+
exc: Union[ResponseError, TransportError, ConnectionError]
|
|
449
|
+
) -> str:
|
|
450
|
+
"""Checks github3 exceptions for the most common GitHub authentication
|
|
451
|
+
issues, returning a user-friendly message if found.
|
|
452
|
+
|
|
453
|
+
@param exc: The exception to process
|
|
454
|
+
@returns: The formatted exception string
|
|
455
|
+
"""
|
|
456
|
+
user_warning = ""
|
|
457
|
+
|
|
458
|
+
too_many_str = "too many 401 error responses"
|
|
459
|
+
is_bad_auth_retry = (
|
|
460
|
+
type(exc) is TransportError
|
|
461
|
+
and type(exc.exception) is RetryError
|
|
462
|
+
and too_many_str in str(exc.exception)
|
|
463
|
+
)
|
|
464
|
+
is_auth_failure = type(exc) is AuthenticationFailed
|
|
465
|
+
|
|
466
|
+
if is_bad_auth_retry or is_auth_failure:
|
|
467
|
+
user_warning = UNAUTHORIZED_WARNING
|
|
468
|
+
|
|
469
|
+
if isinstance(exc, ResponseError):
|
|
470
|
+
scope_error_msg = check_github_scopes(exc)
|
|
471
|
+
sso_error_msg = check_github_sso_auth(exc)
|
|
472
|
+
user_warning = scope_error_msg + sso_error_msg
|
|
473
|
+
|
|
474
|
+
if isinstance(exc, ConnectionError):
|
|
475
|
+
if "self signed certificate" in str(exc.exception):
|
|
476
|
+
user_warning = SELF_SIGNED_WARNING
|
|
477
|
+
else:
|
|
478
|
+
return ""
|
|
479
|
+
|
|
480
|
+
return user_warning
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def warn_oauth_restricted(exc: ResponseError) -> str:
|
|
484
|
+
user_warning = ""
|
|
485
|
+
|
|
486
|
+
is_403 = exc.response.status_code == 403
|
|
487
|
+
org_restricted_oauth_warning = (
|
|
488
|
+
"organization has enabled OAuth App access restriction"
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
if is_403 and org_restricted_oauth_warning in str(exc):
|
|
492
|
+
user_warning = str(exc)
|
|
493
|
+
user_warning += "\nYou may also use a Personal Access Token as a workaround."
|
|
494
|
+
|
|
495
|
+
return user_warning
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def check_github_scopes(exc: ResponseError) -> str:
|
|
499
|
+
"""
|
|
500
|
+
Parse github3 ResponseError headers for the correct scopes and return a
|
|
501
|
+
warning if the user is missing.
|
|
502
|
+
|
|
503
|
+
@param exc: The exception to process
|
|
504
|
+
@returns: The formatted exception string
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
user_warning = ""
|
|
508
|
+
|
|
509
|
+
has_wrong_status_code = exc.response.status_code not in (403, 404)
|
|
510
|
+
if has_wrong_status_code:
|
|
511
|
+
return user_warning
|
|
512
|
+
|
|
513
|
+
token_scopes = get_oauth_scopes(exc.response)
|
|
514
|
+
|
|
515
|
+
# Gist resource won't return X-Accepted-OAuth-Scopes for some reason, so this
|
|
516
|
+
# string might be `None`; we discard the empty string if so.
|
|
517
|
+
accepted_scopes = exc.response.headers.get("X-Accepted-OAuth-Scopes") or ""
|
|
518
|
+
accepted_scopes = set(accepted_scopes.split(", "))
|
|
519
|
+
accepted_scopes.discard("")
|
|
520
|
+
|
|
521
|
+
request_url = urlparse(exc.response.url)
|
|
522
|
+
if not accepted_scopes and request_url.path == "/gists":
|
|
523
|
+
accepted_scopes = {"gist"}
|
|
524
|
+
|
|
525
|
+
missing_scopes = accepted_scopes.difference(token_scopes)
|
|
526
|
+
if missing_scopes:
|
|
527
|
+
user_warning = f"Your token may be missing the following scopes: {', '.join(missing_scopes)}\n"
|
|
528
|
+
# This assumes we're not on enterprise and 'api.github.com' == request_url.hostname
|
|
529
|
+
user_warning += (
|
|
530
|
+
"Visit Settings > Developer settings > Personal access tokens to add them."
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
return user_warning
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def check_github_sso_auth(exc: ResponseError) -> str:
|
|
537
|
+
"""
|
|
538
|
+
Check ResponseError header for SSO authorization and return a warning if
|
|
539
|
+
required
|
|
540
|
+
|
|
541
|
+
@param exc: The exception to process
|
|
542
|
+
@returns: The formatted exception string
|
|
543
|
+
"""
|
|
544
|
+
user_warning = ""
|
|
545
|
+
headers = exc.response.headers
|
|
546
|
+
|
|
547
|
+
if exc.response.status_code != 403 or "X-Github-Sso" not in headers:
|
|
548
|
+
return user_warning
|
|
549
|
+
|
|
550
|
+
sso_header = str(headers["X-Github-Sso"] or "")
|
|
551
|
+
if sso_header.startswith("required; url="):
|
|
552
|
+
# In this case the message from github is good enough, but we can help
|
|
553
|
+
# the user by opening a browser to authorize the token.
|
|
554
|
+
auth_url = sso_header.split("url=", maxsplit=1)[1]
|
|
555
|
+
user_warning = f"{exc.message}\n{auth_url}"
|
|
556
|
+
webbrowser.open(auth_url)
|
|
557
|
+
elif sso_header.startswith("partial-results"):
|
|
558
|
+
# In cases where we don't have complete results we get the
|
|
559
|
+
# partal-results header, so return the organization IDs. This may or
|
|
560
|
+
# may not be useful without help from us to lookup the org IDs.
|
|
561
|
+
unauthorized_org_ids = get_sso_disabled_orgs(exc.response)
|
|
562
|
+
user_warning = f"{SSO_WARNING} {unauthorized_org_ids}"
|
|
563
|
+
|
|
564
|
+
return user_warning
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def get_sso_disabled_orgs(response: Response) -> list:
|
|
568
|
+
"""
|
|
569
|
+
Given a response from Github, return a list of organization IDs without SSO
|
|
570
|
+
grants.
|
|
571
|
+
"""
|
|
572
|
+
disabled_orgs = []
|
|
573
|
+
sso_header = response.headers.get("X-Github-Sso")
|
|
574
|
+
partial_results_prefix = "partial-results; organizations="
|
|
575
|
+
|
|
576
|
+
if sso_header and partial_results_prefix in sso_header:
|
|
577
|
+
disabled_orgs = sso_header[len(partial_results_prefix) :].split(",")
|
|
578
|
+
|
|
579
|
+
return disabled_orgs
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def get_oauth_scopes(response: Response) -> set:
|
|
583
|
+
"""
|
|
584
|
+
Given a response from Github, return the set of OAuth scopes for its
|
|
585
|
+
request.
|
|
586
|
+
"""
|
|
587
|
+
authorized_scopes = set()
|
|
588
|
+
|
|
589
|
+
# If the token isn't authorized "X-OAuth-Scopes" header won't be present
|
|
590
|
+
x_oauth_scopes = response.headers.get("X-OAuth-Scopes")
|
|
591
|
+
if x_oauth_scopes:
|
|
592
|
+
authorized_scopes = set(x_oauth_scopes.split(", "))
|
|
593
|
+
|
|
594
|
+
return authorized_scopes
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def catch_common_github_auth_errors(func: Callable) -> Callable:
|
|
598
|
+
"""
|
|
599
|
+
A decorator catching the most common Github authentication errors.
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
@functools.wraps(func)
|
|
603
|
+
def inner(*args, **kwargs):
|
|
604
|
+
try:
|
|
605
|
+
return func(*args, **kwargs)
|
|
606
|
+
except (ConnectionError) as exc:
|
|
607
|
+
if error_msg := format_github3_exception(exc):
|
|
608
|
+
raise GithubApiError(error_msg) from exc
|
|
609
|
+
else:
|
|
610
|
+
raise
|
|
611
|
+
except (ResponseError, TransportError) as exc:
|
|
612
|
+
if error_msg := format_github3_exception(exc):
|
|
613
|
+
url = request_url_from_exc(exc)
|
|
614
|
+
error_msg = f"{url}\n{error_msg}".strip()
|
|
615
|
+
raise GithubApiError(error_msg) from exc
|
|
616
|
+
else:
|
|
617
|
+
raise
|
|
618
|
+
|
|
619
|
+
return inner
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def request_url_from_exc(exc: Union[ResponseError, TransportError]) -> str:
|
|
623
|
+
if isinstance(exc, TransportError):
|
|
624
|
+
return exc.exception.response.url
|
|
625
|
+
else:
|
|
626
|
+
return exc.response.url
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def get_oauth_device_flow_token():
|
|
630
|
+
"""Interactive github authorization"""
|
|
631
|
+
config = OAuth2ClientConfig(**OAUTH_DEVICE_APP)
|
|
632
|
+
device_code = OAuth2DeviceConfig(**get_device_code(config))
|
|
633
|
+
|
|
634
|
+
console = Console()
|
|
635
|
+
console.print(
|
|
636
|
+
f"[bold] Enter this one-time code: [red]{device_code.user_code}[/red][/bold]"
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
console.print(f"Opening {device_code.verification_uri} in your default browser...")
|
|
640
|
+
webbrowser.open(device_code.verification_uri)
|
|
641
|
+
time.sleep(2) # Give the user a second or two before we start polling
|
|
642
|
+
|
|
643
|
+
with console.status("Polling server for authorization..."):
|
|
644
|
+
device_token: dict = get_device_oauth_token(
|
|
645
|
+
client_config=config, device_config=device_code
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
access_token = device_token.get("access_token")
|
|
649
|
+
if access_token:
|
|
650
|
+
console.print(
|
|
651
|
+
f"[bold green]Successfully authorized OAuth token ({access_token[:7]}...)[/bold green]"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
return access_token
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
@catch_common_github_auth_errors
|
|
658
|
+
def create_gist(github, description, files):
|
|
659
|
+
"""Creates a gist with the given description and files.
|
|
660
|
+
|
|
661
|
+
github - an
|
|
662
|
+
description - str
|
|
663
|
+
files - A dict of files in the form of {filename:{'content': content},...}
|
|
664
|
+
"""
|
|
665
|
+
return github.create_gist(description, files, public=False)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# IMPORT ORDER MATTERS!
|
|
2
|
+
|
|
3
|
+
# inherit from BaseConfig
|
|
4
|
+
from cumulusci.core.keychain.base_project_keychain import (
|
|
5
|
+
BaseProjectKeychain,
|
|
6
|
+
DEFAULT_CONNECTED_APP,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from cumulusci.core.keychain.environment_project_keychain import (
|
|
10
|
+
EnvironmentProjectKeychain,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# inherit from BaseEncryptedProjectKeychain
|
|
14
|
+
from cumulusci.core.keychain.encrypted_file_project_keychain import (
|
|
15
|
+
EncryptedFileProjectKeychain,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
|
+
"BaseProjectKeychain",
|
|
20
|
+
"DEFAULT_CONNECTED_APP",
|
|
21
|
+
"BaseEncryptedProjectKeychain",
|
|
22
|
+
"EnvironmentProjectKeychain",
|
|
23
|
+
"EncryptedFileProjectKeychain",
|
|
24
|
+
)
|