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.

Files changed (744) hide show
  1. cumulusci/__about__.py +1 -0
  2. cumulusci/__init__.py +22 -0
  3. cumulusci/__main__.py +3 -0
  4. cumulusci/cli/__init__.py +0 -0
  5. cumulusci/cli/cci.py +244 -0
  6. cumulusci/cli/error.py +125 -0
  7. cumulusci/cli/flow.py +185 -0
  8. cumulusci/cli/logger.py +72 -0
  9. cumulusci/cli/org.py +692 -0
  10. cumulusci/cli/plan.py +181 -0
  11. cumulusci/cli/project.py +391 -0
  12. cumulusci/cli/robot.py +116 -0
  13. cumulusci/cli/runtime.py +190 -0
  14. cumulusci/cli/service.py +521 -0
  15. cumulusci/cli/task.py +295 -0
  16. cumulusci/cli/tests/__init__.py +0 -0
  17. cumulusci/cli/tests/test_cci.py +545 -0
  18. cumulusci/cli/tests/test_error.py +170 -0
  19. cumulusci/cli/tests/test_flow.py +276 -0
  20. cumulusci/cli/tests/test_logger.py +25 -0
  21. cumulusci/cli/tests/test_org.py +1438 -0
  22. cumulusci/cli/tests/test_plan.py +245 -0
  23. cumulusci/cli/tests/test_project.py +235 -0
  24. cumulusci/cli/tests/test_robot.py +177 -0
  25. cumulusci/cli/tests/test_runtime.py +197 -0
  26. cumulusci/cli/tests/test_service.py +853 -0
  27. cumulusci/cli/tests/test_task.py +266 -0
  28. cumulusci/cli/tests/test_ui.py +310 -0
  29. cumulusci/cli/tests/test_utils.py +122 -0
  30. cumulusci/cli/tests/utils.py +52 -0
  31. cumulusci/cli/ui.py +234 -0
  32. cumulusci/cli/utils.py +150 -0
  33. cumulusci/conftest.py +181 -0
  34. cumulusci/core/__init__.py +0 -0
  35. cumulusci/core/config/BaseConfig.py +5 -0
  36. cumulusci/core/config/BaseTaskFlowConfig.py +5 -0
  37. cumulusci/core/config/OrgConfig.py +5 -0
  38. cumulusci/core/config/ScratchOrgConfig.py +5 -0
  39. cumulusci/core/config/__init__.py +125 -0
  40. cumulusci/core/config/base_config.py +111 -0
  41. cumulusci/core/config/base_task_flow_config.py +82 -0
  42. cumulusci/core/config/marketing_cloud_service_config.py +83 -0
  43. cumulusci/core/config/oauth2_service_config.py +17 -0
  44. cumulusci/core/config/org_config.py +604 -0
  45. cumulusci/core/config/project_config.py +782 -0
  46. cumulusci/core/config/scratch_org_config.py +251 -0
  47. cumulusci/core/config/sfdx_org_config.py +220 -0
  48. cumulusci/core/config/tests/_test_config_backwards_compatibility.py +33 -0
  49. cumulusci/core/config/tests/test_config.py +1895 -0
  50. cumulusci/core/config/tests/test_config_expensive.py +839 -0
  51. cumulusci/core/config/tests/test_config_util.py +91 -0
  52. cumulusci/core/config/universal_config.py +88 -0
  53. cumulusci/core/config/util.py +18 -0
  54. cumulusci/core/datasets.py +303 -0
  55. cumulusci/core/debug.py +33 -0
  56. cumulusci/core/dependencies/__init__.py +55 -0
  57. cumulusci/core/dependencies/base.py +561 -0
  58. cumulusci/core/dependencies/dependencies.py +273 -0
  59. cumulusci/core/dependencies/github.py +177 -0
  60. cumulusci/core/dependencies/github_resolvers.py +244 -0
  61. cumulusci/core/dependencies/resolvers.py +580 -0
  62. cumulusci/core/dependencies/tests/__init__.py +0 -0
  63. cumulusci/core/dependencies/tests/conftest.py +385 -0
  64. cumulusci/core/dependencies/tests/test_dependencies.py +950 -0
  65. cumulusci/core/dependencies/tests/test_github.py +83 -0
  66. cumulusci/core/dependencies/tests/test_resolvers.py +1027 -0
  67. cumulusci/core/dependencies/utils.py +13 -0
  68. cumulusci/core/enums.py +11 -0
  69. cumulusci/core/exceptions.py +311 -0
  70. cumulusci/core/flowrunner.py +888 -0
  71. cumulusci/core/github.py +665 -0
  72. cumulusci/core/keychain/__init__.py +24 -0
  73. cumulusci/core/keychain/base_project_keychain.py +441 -0
  74. cumulusci/core/keychain/encrypted_file_project_keychain.py +945 -0
  75. cumulusci/core/keychain/environment_project_keychain.py +7 -0
  76. cumulusci/core/keychain/serialization.py +152 -0
  77. cumulusci/core/keychain/subprocess_keychain.py +24 -0
  78. cumulusci/core/keychain/tests/conftest.py +50 -0
  79. cumulusci/core/keychain/tests/test_base_project_keychain.py +299 -0
  80. cumulusci/core/keychain/tests/test_encrypted_file_project_keychain.py +1228 -0
  81. cumulusci/core/metadeploy/__init__.py +0 -0
  82. cumulusci/core/metadeploy/api.py +88 -0
  83. cumulusci/core/metadeploy/plans.py +25 -0
  84. cumulusci/core/metadeploy/tests/test_api.py +276 -0
  85. cumulusci/core/runtime.py +115 -0
  86. cumulusci/core/sfdx.py +162 -0
  87. cumulusci/core/source/__init__.py +16 -0
  88. cumulusci/core/source/github.py +50 -0
  89. cumulusci/core/source/local_folder.py +35 -0
  90. cumulusci/core/source_transforms/__init__.py +0 -0
  91. cumulusci/core/source_transforms/tests/test_transforms.py +1091 -0
  92. cumulusci/core/source_transforms/transforms.py +532 -0
  93. cumulusci/core/tasks.py +404 -0
  94. cumulusci/core/template_utils.py +59 -0
  95. cumulusci/core/tests/__init__.py +0 -0
  96. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml +215 -0
  97. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml +199 -0
  98. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_read_explicit_extract_declaration.yaml +3 -0
  99. cumulusci/core/tests/fake_remote_repo/cumulusci.yml +32 -0
  100. cumulusci/core/tests/fake_remote_repo/tasks/directory/example_2.py +6 -0
  101. cumulusci/core/tests/fake_remote_repo/tasks/example.py +43 -0
  102. cumulusci/core/tests/fake_remote_repo_2/cumulusci.yml +11 -0
  103. cumulusci/core/tests/fake_remote_repo_2/tasks/example_3.py +6 -0
  104. cumulusci/core/tests/test_datasets_e2e.py +386 -0
  105. cumulusci/core/tests/test_exceptions.py +11 -0
  106. cumulusci/core/tests/test_flowrunner.py +836 -0
  107. cumulusci/core/tests/test_github.py +942 -0
  108. cumulusci/core/tests/test_sfdx.py +138 -0
  109. cumulusci/core/tests/test_source.py +678 -0
  110. cumulusci/core/tests/test_tasks.py +262 -0
  111. cumulusci/core/tests/test_utils.py +141 -0
  112. cumulusci/core/tests/test_utils_merge_config.py +276 -0
  113. cumulusci/core/tests/test_versions.py +76 -0
  114. cumulusci/core/tests/untrusted_repo_child/cumulusci.yml +7 -0
  115. cumulusci/core/tests/untrusted_repo_child/tasks/untrusted_child.py +6 -0
  116. cumulusci/core/tests/untrusted_repo_parent/cumulusci.yml +26 -0
  117. cumulusci/core/tests/untrusted_repo_parent/tasks/untrusted_parent.py +6 -0
  118. cumulusci/core/tests/utils.py +116 -0
  119. cumulusci/core/tests/yaml/global.yaml +0 -0
  120. cumulusci/core/utils.py +402 -0
  121. cumulusci/core/versions.py +149 -0
  122. cumulusci/cumulusci.yml +1621 -0
  123. cumulusci/files/admin_profile.xml +20 -0
  124. cumulusci/files/delete_excludes.txt +424 -0
  125. cumulusci/files/templates/project/README.md +12 -0
  126. cumulusci/files/templates/project/cumulusci.yml +63 -0
  127. cumulusci/files/templates/project/dot-gitignore +60 -0
  128. cumulusci/files/templates/project/mapping.yml +45 -0
  129. cumulusci/files/templates/project/scratch_def.json +25 -0
  130. cumulusci/oauth/__init__.py +0 -0
  131. cumulusci/oauth/client.py +400 -0
  132. cumulusci/oauth/exceptions.py +9 -0
  133. cumulusci/oauth/salesforce.py +95 -0
  134. cumulusci/oauth/tests/__init__.py +0 -0
  135. cumulusci/oauth/tests/cassettes/test_get_device_code.yaml +22 -0
  136. cumulusci/oauth/tests/cassettes/test_get_device_oauth_token.yaml +74 -0
  137. cumulusci/oauth/tests/test_client.py +308 -0
  138. cumulusci/oauth/tests/test_salesforce.py +46 -0
  139. cumulusci/plugins/__init__.py +3 -0
  140. cumulusci/plugins/plugin_base.py +93 -0
  141. cumulusci/plugins/plugin_loader.py +59 -0
  142. cumulusci/robotframework/CumulusCI.py +340 -0
  143. cumulusci/robotframework/CumulusCI.robot +7 -0
  144. cumulusci/robotframework/Performance.py +165 -0
  145. cumulusci/robotframework/Salesforce.py +936 -0
  146. cumulusci/robotframework/Salesforce.robot +192 -0
  147. cumulusci/robotframework/SalesforceAPI.py +416 -0
  148. cumulusci/robotframework/SalesforcePlaywright.py +220 -0
  149. cumulusci/robotframework/SalesforcePlaywright.robot +40 -0
  150. cumulusci/robotframework/__init__.py +2 -0
  151. cumulusci/robotframework/base_library.py +39 -0
  152. cumulusci/robotframework/faker_mixin.py +89 -0
  153. cumulusci/robotframework/form_handlers.py +222 -0
  154. cumulusci/robotframework/javascript/cci_init.js +34 -0
  155. cumulusci/robotframework/javascript/cumulusci.js +4 -0
  156. cumulusci/robotframework/locator_manager.py +197 -0
  157. cumulusci/robotframework/locators_56.py +88 -0
  158. cumulusci/robotframework/locators_57.py +5 -0
  159. cumulusci/robotframework/pageobjects/BasePageObjects.py +433 -0
  160. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +246 -0
  161. cumulusci/robotframework/pageobjects/PageObjectLibrary.py +45 -0
  162. cumulusci/robotframework/pageobjects/PageObjects.py +351 -0
  163. cumulusci/robotframework/pageobjects/__init__.py +12 -0
  164. cumulusci/robotframework/pageobjects/baseobjects.py +120 -0
  165. cumulusci/robotframework/perftests/short/collection_perf.robot +105 -0
  166. cumulusci/robotframework/tests/CustomObjectTestPage.py +10 -0
  167. cumulusci/robotframework/tests/FooTestPage.py +8 -0
  168. cumulusci/robotframework/tests/cumulusci/base.robot +40 -0
  169. cumulusci/robotframework/tests/cumulusci/bulkdata.robot +38 -0
  170. cumulusci/robotframework/tests/cumulusci/communities.robot +57 -0
  171. cumulusci/robotframework/tests/cumulusci/datagen.robot +84 -0
  172. cumulusci/robotframework/tests/salesforce/TestLibraryA.py +24 -0
  173. cumulusci/robotframework/tests/salesforce/TestLibraryB.py +20 -0
  174. cumulusci/robotframework/tests/salesforce/TestListener.py +93 -0
  175. cumulusci/robotframework/tests/salesforce/api.robot +178 -0
  176. cumulusci/robotframework/tests/salesforce/browsers.robot +143 -0
  177. cumulusci/robotframework/tests/salesforce/classic.robot +51 -0
  178. cumulusci/robotframework/tests/salesforce/create_contact.robot +59 -0
  179. cumulusci/robotframework/tests/salesforce/faker.robot +68 -0
  180. cumulusci/robotframework/tests/salesforce/forms.robot +172 -0
  181. cumulusci/robotframework/tests/salesforce/label_locator.robot +244 -0
  182. cumulusci/robotframework/tests/salesforce/labels.html +33 -0
  183. cumulusci/robotframework/tests/salesforce/locators.robot +149 -0
  184. cumulusci/robotframework/tests/salesforce/pageobjects/base_pageobjects.robot +100 -0
  185. cumulusci/robotframework/tests/salesforce/pageobjects/example_page_object.py +25 -0
  186. cumulusci/robotframework/tests/salesforce/pageobjects/listing_page.robot +115 -0
  187. cumulusci/robotframework/tests/salesforce/pageobjects/objectmanager.robot +74 -0
  188. cumulusci/robotframework/tests/salesforce/pageobjects/pageobjects.robot +171 -0
  189. cumulusci/robotframework/tests/salesforce/performance.robot +109 -0
  190. cumulusci/robotframework/tests/salesforce/playwright/javascript_keywords.robot +33 -0
  191. cumulusci/robotframework/tests/salesforce/playwright/open_test_browser.robot +48 -0
  192. cumulusci/robotframework/tests/salesforce/playwright/playwright.robot +24 -0
  193. cumulusci/robotframework/tests/salesforce/playwright/ui.robot +32 -0
  194. cumulusci/robotframework/tests/salesforce/populate.robot +89 -0
  195. cumulusci/robotframework/tests/salesforce/test_testlistener.py +37 -0
  196. cumulusci/robotframework/tests/salesforce/ui.robot +361 -0
  197. cumulusci/robotframework/tests/test_cumulusci_library.py +304 -0
  198. cumulusci/robotframework/tests/test_locator_manager.py +158 -0
  199. cumulusci/robotframework/tests/test_pageobjects.py +291 -0
  200. cumulusci/robotframework/tests/test_performance.py +38 -0
  201. cumulusci/robotframework/tests/test_salesforce.py +79 -0
  202. cumulusci/robotframework/tests/test_salesforce_locators.py +73 -0
  203. cumulusci/robotframework/tests/test_template_util.py +53 -0
  204. cumulusci/robotframework/tests/test_utils.py +106 -0
  205. cumulusci/robotframework/utils.py +283 -0
  206. cumulusci/salesforce_api/__init__.py +0 -0
  207. cumulusci/salesforce_api/exceptions.py +23 -0
  208. cumulusci/salesforce_api/filterable_objects.py +96 -0
  209. cumulusci/salesforce_api/mc_soap_envelopes.py +89 -0
  210. cumulusci/salesforce_api/metadata.py +721 -0
  211. cumulusci/salesforce_api/org_schema.py +571 -0
  212. cumulusci/salesforce_api/org_schema_models.py +226 -0
  213. cumulusci/salesforce_api/package_install.py +265 -0
  214. cumulusci/salesforce_api/package_zip.py +301 -0
  215. cumulusci/salesforce_api/rest_deploy.py +148 -0
  216. cumulusci/salesforce_api/retrieve_profile_api.py +301 -0
  217. cumulusci/salesforce_api/soap_envelopes.py +177 -0
  218. cumulusci/salesforce_api/tests/__init__.py +0 -0
  219. cumulusci/salesforce_api/tests/metadata_test_strings.py +24 -0
  220. cumulusci/salesforce_api/tests/test_metadata.py +1015 -0
  221. cumulusci/salesforce_api/tests/test_package_install.py +219 -0
  222. cumulusci/salesforce_api/tests/test_package_zip.py +380 -0
  223. cumulusci/salesforce_api/tests/test_rest_deploy.py +264 -0
  224. cumulusci/salesforce_api/tests/test_retrieve_profile_api.py +337 -0
  225. cumulusci/salesforce_api/tests/test_utils.py +124 -0
  226. cumulusci/salesforce_api/utils.py +51 -0
  227. cumulusci/schema/cumulusci.jsonschema.json +782 -0
  228. cumulusci/tasks/__init__.py +0 -0
  229. cumulusci/tasks/apex/__init__.py +0 -0
  230. cumulusci/tasks/apex/anon.py +157 -0
  231. cumulusci/tasks/apex/batch.py +180 -0
  232. cumulusci/tasks/apex/testrunner.py +835 -0
  233. cumulusci/tasks/apex/tests/cassettes/ManualEditTestApexIntegrationTests.test_run_tests__integration_test.yaml +703 -0
  234. cumulusci/tasks/apex/tests/test_apex_tasks.py +1558 -0
  235. cumulusci/tasks/base_source_control_task.py +17 -0
  236. cumulusci/tasks/bulkdata/__init__.py +15 -0
  237. cumulusci/tasks/bulkdata/base_generate_data_task.py +96 -0
  238. cumulusci/tasks/bulkdata/dates.py +97 -0
  239. cumulusci/tasks/bulkdata/delete.py +156 -0
  240. cumulusci/tasks/bulkdata/extract.py +441 -0
  241. cumulusci/tasks/bulkdata/extract_dataset_utils/calculate_dependencies.py +117 -0
  242. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +123 -0
  243. cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py +49 -0
  244. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +283 -0
  245. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +142 -0
  246. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_synthesize_extract_declarations.py +575 -0
  247. cumulusci/tasks/bulkdata/factory_utils.py +134 -0
  248. cumulusci/tasks/bulkdata/generate.py +4 -0
  249. cumulusci/tasks/bulkdata/generate_and_load_data.py +232 -0
  250. cumulusci/tasks/bulkdata/generate_and_load_data_from_yaml.py +19 -0
  251. cumulusci/tasks/bulkdata/generate_from_yaml.py +183 -0
  252. cumulusci/tasks/bulkdata/generate_mapping.py +434 -0
  253. cumulusci/tasks/bulkdata/generate_mapping_utils/dependency_map.py +169 -0
  254. cumulusci/tasks/bulkdata/generate_mapping_utils/extract_mapping_file_generator.py +45 -0
  255. cumulusci/tasks/bulkdata/generate_mapping_utils/generate_mapping_from_declarations.py +121 -0
  256. cumulusci/tasks/bulkdata/generate_mapping_utils/load_mapping_file_generator.py +127 -0
  257. cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_generator_post_processes.py +53 -0
  258. cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_transforms.py +139 -0
  259. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_extract_mapping_from_declarations.py +135 -0
  260. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_load_mapping_from_declarations.py +330 -0
  261. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_generator_post_processes.py +60 -0
  262. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_transforms.py +188 -0
  263. cumulusci/tasks/bulkdata/load.py +1196 -0
  264. cumulusci/tasks/bulkdata/mapping_parser.py +811 -0
  265. cumulusci/tasks/bulkdata/query_transformers.py +264 -0
  266. cumulusci/tasks/bulkdata/select_utils.py +792 -0
  267. cumulusci/tasks/bulkdata/snowfakery.py +753 -0
  268. cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py +478 -0
  269. cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_run_until.py +141 -0
  270. cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_working_directory.py +53 -0
  271. cumulusci/tasks/bulkdata/snowfakery_utils/subtask_configurator.py +64 -0
  272. cumulusci/tasks/bulkdata/step.py +1242 -0
  273. cumulusci/tasks/bulkdata/tests/__init__.py +0 -0
  274. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_random_strategy.yaml +147 -0
  275. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_annoy_strategy.yaml +123 -0
  276. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy.yaml +313 -0
  277. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy_bulk.yaml +550 -0
  278. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_strategy.yaml +175 -0
  279. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_standard_strategy.yaml +147 -0
  280. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__multiple_needed.yaml +69 -0
  281. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__none_needed.yaml +22 -0
  282. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__one_needed.yaml +24 -0
  283. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_snowfakery_query_salesforce.yaml +25 -0
  284. cumulusci/tasks/bulkdata/tests/cassettes/TestUpdatesIntegrationTests.test_updates_task.yaml +80 -0
  285. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_simple_upsert__rest.yaml +270 -0
  286. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert__rest.yaml +267 -0
  287. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field__rest.yaml +369 -0
  288. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field_rest__duplicate_error.yaml +204 -0
  289. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_fields__bulk.yaml +675 -0
  290. cumulusci/tasks/bulkdata/tests/dummy_data_factory.py +36 -0
  291. cumulusci/tasks/bulkdata/tests/integration_test_utils.py +49 -0
  292. cumulusci/tasks/bulkdata/tests/mapping-oid.yml +87 -0
  293. cumulusci/tasks/bulkdata/tests/mapping_after.yml +38 -0
  294. cumulusci/tasks/bulkdata/tests/mapping_poly.yml +34 -0
  295. cumulusci/tasks/bulkdata/tests/mapping_poly_incomplete.yml +20 -0
  296. cumulusci/tasks/bulkdata/tests/mapping_poly_wrong.yml +21 -0
  297. cumulusci/tasks/bulkdata/tests/mapping_select.yml +20 -0
  298. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_strategy.yml +20 -0
  299. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_number.yml +21 -0
  300. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_strategy.yml +21 -0
  301. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__non_float.yml +21 -0
  302. cumulusci/tasks/bulkdata/tests/mapping_select_missing_priority_fields.yml +22 -0
  303. cumulusci/tasks/bulkdata/tests/mapping_select_no_priority_fields.yml +18 -0
  304. cumulusci/tasks/bulkdata/tests/mapping_simple.yml +27 -0
  305. cumulusci/tasks/bulkdata/tests/mapping_v1.yml +28 -0
  306. cumulusci/tasks/bulkdata/tests/mapping_v2.yml +21 -0
  307. cumulusci/tasks/bulkdata/tests/mapping_v3.yml +32 -0
  308. cumulusci/tasks/bulkdata/tests/mapping_vanilla_sf.yml +69 -0
  309. cumulusci/tasks/bulkdata/tests/mock_data_factory_without_mapping.py +12 -0
  310. cumulusci/tasks/bulkdata/tests/person_accounts.yml +23 -0
  311. cumulusci/tasks/bulkdata/tests/person_accounts_minimal.yml +15 -0
  312. cumulusci/tasks/bulkdata/tests/recordtypes.yml +8 -0
  313. cumulusci/tasks/bulkdata/tests/recordtypes_2.yml +6 -0
  314. cumulusci/tasks/bulkdata/tests/recordtypes_with_ispersontype.yml +8 -0
  315. cumulusci/tasks/bulkdata/tests/snowfakery/child/child2.yml +3 -0
  316. cumulusci/tasks/bulkdata/tests/snowfakery/child.yml +4 -0
  317. cumulusci/tasks/bulkdata/tests/snowfakery/gen_npsp_standard_objects.recipe.yml +89 -0
  318. cumulusci/tasks/bulkdata/tests/snowfakery/include_parent.yml +3 -0
  319. cumulusci/tasks/bulkdata/tests/snowfakery/npsp_standard_objects_macros.yml +34 -0
  320. cumulusci/tasks/bulkdata/tests/snowfakery/options.recipe.yml +6 -0
  321. cumulusci/tasks/bulkdata/tests/snowfakery/query_snowfakery.recipe.yml +16 -0
  322. cumulusci/tasks/bulkdata/tests/snowfakery/sf_standard_object_macros.yml +83 -0
  323. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.load.yml +2 -0
  324. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.recipe.yml +13 -0
  325. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_2.load.yml +5 -0
  326. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.load.yml +13 -0
  327. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.recipe.yml +12 -0
  328. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels_2.load.yml +13 -0
  329. cumulusci/tasks/bulkdata/tests/snowfakery/unique_values.recipe.yml +4 -0
  330. cumulusci/tasks/bulkdata/tests/snowfakery/upsert.recipe.yml +23 -0
  331. cumulusci/tasks/bulkdata/tests/snowfakery/upsert_2.recipe.yml +29 -0
  332. cumulusci/tasks/bulkdata/tests/snowfakery/upsert_before.yml +10 -0
  333. cumulusci/tasks/bulkdata/tests/test_base_generate_data_tasks.py +61 -0
  334. cumulusci/tasks/bulkdata/tests/test_dates.py +99 -0
  335. cumulusci/tasks/bulkdata/tests/test_delete.py +404 -0
  336. cumulusci/tasks/bulkdata/tests/test_extract.py +1311 -0
  337. cumulusci/tasks/bulkdata/tests/test_factory_utils.py +55 -0
  338. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +252 -0
  339. cumulusci/tasks/bulkdata/tests/test_generate_from_snowfakery_task.py +343 -0
  340. cumulusci/tasks/bulkdata/tests/test_generatemapping.py +1039 -0
  341. cumulusci/tasks/bulkdata/tests/test_load.py +3175 -0
  342. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +1658 -0
  343. cumulusci/tasks/bulkdata/tests/test_query_db__joins_self_lookups.yml +12 -0
  344. cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups.yml +26 -0
  345. cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups_select.yml +48 -0
  346. cumulusci/tasks/bulkdata/tests/test_select.py +171 -0
  347. cumulusci/tasks/bulkdata/tests/test_select_utils.py +1057 -0
  348. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +1153 -0
  349. cumulusci/tasks/bulkdata/tests/test_step.py +3957 -0
  350. cumulusci/tasks/bulkdata/tests/test_updates.py +513 -0
  351. cumulusci/tasks/bulkdata/tests/test_upsert.py +1015 -0
  352. cumulusci/tasks/bulkdata/tests/test_utils.py +158 -0
  353. cumulusci/tasks/bulkdata/tests/testdata.db +0 -0
  354. cumulusci/tasks/bulkdata/tests/update_describe.py +50 -0
  355. cumulusci/tasks/bulkdata/tests/update_person_accounts.yml +23 -0
  356. cumulusci/tasks/bulkdata/tests/utils.py +114 -0
  357. cumulusci/tasks/bulkdata/update_data.py +260 -0
  358. cumulusci/tasks/bulkdata/upsert_utils.py +130 -0
  359. cumulusci/tasks/bulkdata/utils.py +249 -0
  360. cumulusci/tasks/command.py +178 -0
  361. cumulusci/tasks/connectedapp.py +186 -0
  362. cumulusci/tasks/create_package_version.py +778 -0
  363. cumulusci/tasks/datadictionary.py +745 -0
  364. cumulusci/tasks/dx_convert_from.py +26 -0
  365. cumulusci/tasks/github/__init__.py +17 -0
  366. cumulusci/tasks/github/base.py +16 -0
  367. cumulusci/tasks/github/commit_status.py +13 -0
  368. cumulusci/tasks/github/merge.py +11 -0
  369. cumulusci/tasks/github/publish.py +11 -0
  370. cumulusci/tasks/github/pull_request.py +11 -0
  371. cumulusci/tasks/github/release.py +11 -0
  372. cumulusci/tasks/github/release_report.py +11 -0
  373. cumulusci/tasks/github/tag.py +11 -0
  374. cumulusci/tasks/github/tests/__init__.py +0 -0
  375. cumulusci/tasks/github/tests/test_util.py +202 -0
  376. cumulusci/tasks/github/tests/test_vcs_migration.py +44 -0
  377. cumulusci/tasks/github/tests/util_github_api.py +666 -0
  378. cumulusci/tasks/github/util.py +252 -0
  379. cumulusci/tasks/marketing_cloud/__init__.py +0 -0
  380. cumulusci/tasks/marketing_cloud/api.py +188 -0
  381. cumulusci/tasks/marketing_cloud/base.py +38 -0
  382. cumulusci/tasks/marketing_cloud/deploy.py +345 -0
  383. cumulusci/tasks/marketing_cloud/get_user_info.py +40 -0
  384. cumulusci/tasks/marketing_cloud/mc_constants.py +1 -0
  385. cumulusci/tasks/marketing_cloud/tests/__init__.py +0 -0
  386. cumulusci/tasks/marketing_cloud/tests/conftest.py +46 -0
  387. cumulusci/tasks/marketing_cloud/tests/expected-payload.json +110 -0
  388. cumulusci/tasks/marketing_cloud/tests/test_api.py +97 -0
  389. cumulusci/tasks/marketing_cloud/tests/test_api_soap_envelopes.py +145 -0
  390. cumulusci/tasks/marketing_cloud/tests/test_base.py +14 -0
  391. cumulusci/tasks/marketing_cloud/tests/test_deploy.py +400 -0
  392. cumulusci/tasks/marketing_cloud/tests/test_get_user_info.py +141 -0
  393. cumulusci/tasks/marketing_cloud/tests/validation-response.json +39 -0
  394. cumulusci/tasks/metadata/__init__.py +0 -0
  395. cumulusci/tasks/metadata/ee_src.py +94 -0
  396. cumulusci/tasks/metadata/managed_src.py +100 -0
  397. cumulusci/tasks/metadata/metadata_map.yml +868 -0
  398. cumulusci/tasks/metadata/modify.py +99 -0
  399. cumulusci/tasks/metadata/package.py +684 -0
  400. cumulusci/tasks/metadata/tests/__init__.py +0 -0
  401. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/.hidden/.keep +0 -0
  402. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/destructiveChanges.xml +9 -0
  403. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package.xml +9 -0
  404. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package_install_uninstall.xml +11 -0
  405. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/reports/namespace__TestFolder/TestReport.report +3 -0
  406. cumulusci/tasks/metadata/tests/sample_package.xml +9 -0
  407. cumulusci/tasks/metadata/tests/test_ee_src.py +112 -0
  408. cumulusci/tasks/metadata/tests/test_managed_src.py +111 -0
  409. cumulusci/tasks/metadata/tests/test_modify.py +123 -0
  410. cumulusci/tasks/metadata/tests/test_package.py +476 -0
  411. cumulusci/tasks/metadata_etl/__init__.py +29 -0
  412. cumulusci/tasks/metadata_etl/base.py +436 -0
  413. cumulusci/tasks/metadata_etl/duplicate_rules.py +24 -0
  414. cumulusci/tasks/metadata_etl/field_sets.py +70 -0
  415. cumulusci/tasks/metadata_etl/help_text.py +92 -0
  416. cumulusci/tasks/metadata_etl/layouts.py +550 -0
  417. cumulusci/tasks/metadata_etl/objects.py +68 -0
  418. cumulusci/tasks/metadata_etl/permissions.py +167 -0
  419. cumulusci/tasks/metadata_etl/picklists.py +221 -0
  420. cumulusci/tasks/metadata_etl/remote_site_settings.py +99 -0
  421. cumulusci/tasks/metadata_etl/sharing.py +138 -0
  422. cumulusci/tasks/metadata_etl/tests/test_base.py +512 -0
  423. cumulusci/tasks/metadata_etl/tests/test_duplicate_rules.py +22 -0
  424. cumulusci/tasks/metadata_etl/tests/test_field_sets.py +156 -0
  425. cumulusci/tasks/metadata_etl/tests/test_help_text.py +387 -0
  426. cumulusci/tasks/metadata_etl/tests/test_ip_ranges.py +85 -0
  427. cumulusci/tasks/metadata_etl/tests/test_layouts.py +858 -0
  428. cumulusci/tasks/metadata_etl/tests/test_objects.py +236 -0
  429. cumulusci/tasks/metadata_etl/tests/test_permissions.py +223 -0
  430. cumulusci/tasks/metadata_etl/tests/test_picklists.py +547 -0
  431. cumulusci/tasks/metadata_etl/tests/test_remote_site_settings.py +46 -0
  432. cumulusci/tasks/metadata_etl/tests/test_sharing.py +333 -0
  433. cumulusci/tasks/metadata_etl/tests/test_value_sets.py +298 -0
  434. cumulusci/tasks/metadata_etl/value_sets.py +106 -0
  435. cumulusci/tasks/metadeploy.py +393 -0
  436. cumulusci/tasks/metaxml.py +88 -0
  437. cumulusci/tasks/preflight/__init__.py +0 -0
  438. cumulusci/tasks/preflight/dataset_load.py +49 -0
  439. cumulusci/tasks/preflight/licenses.py +86 -0
  440. cumulusci/tasks/preflight/packages.py +14 -0
  441. cumulusci/tasks/preflight/permsets.py +23 -0
  442. cumulusci/tasks/preflight/recordtypes.py +16 -0
  443. cumulusci/tasks/preflight/retrieve_tasks.py +30 -0
  444. cumulusci/tasks/preflight/settings.py +77 -0
  445. cumulusci/tasks/preflight/sobjects.py +202 -0
  446. cumulusci/tasks/preflight/tests/test_dataset_load.py +85 -0
  447. cumulusci/tasks/preflight/tests/test_licenses.py +174 -0
  448. cumulusci/tasks/preflight/tests/test_packages.py +14 -0
  449. cumulusci/tasks/preflight/tests/test_permset_preflights.py +51 -0
  450. cumulusci/tasks/preflight/tests/test_recordtypes.py +30 -0
  451. cumulusci/tasks/preflight/tests/test_retrieve_tasks.py +62 -0
  452. cumulusci/tasks/preflight/tests/test_settings.py +130 -0
  453. cumulusci/tasks/preflight/tests/test_sobjects.py +231 -0
  454. cumulusci/tasks/push/README.md +59 -0
  455. cumulusci/tasks/push/__init__.py +0 -0
  456. cumulusci/tasks/push/push_api.py +659 -0
  457. cumulusci/tasks/push/pushfails.py +136 -0
  458. cumulusci/tasks/push/tasks.py +476 -0
  459. cumulusci/tasks/push/tests/conftest.py +263 -0
  460. cumulusci/tasks/push/tests/test_push_api.py +951 -0
  461. cumulusci/tasks/push/tests/test_push_tasks.py +659 -0
  462. cumulusci/tasks/release_notes/README.md +63 -0
  463. cumulusci/tasks/release_notes/__init__.py +0 -0
  464. cumulusci/tasks/release_notes/exceptions.py +5 -0
  465. cumulusci/tasks/release_notes/generator.py +137 -0
  466. cumulusci/tasks/release_notes/parser.py +232 -0
  467. cumulusci/tasks/release_notes/provider.py +44 -0
  468. cumulusci/tasks/release_notes/task.py +300 -0
  469. cumulusci/tasks/release_notes/tests/__init__.py +0 -0
  470. cumulusci/tasks/release_notes/tests/change_notes/full/example1.md +17 -0
  471. cumulusci/tasks/release_notes/tests/change_notes/multi/1.txt +1 -0
  472. cumulusci/tasks/release_notes/tests/change_notes/multi/2.txt +1 -0
  473. cumulusci/tasks/release_notes/tests/change_notes/multi/3.txt +1 -0
  474. cumulusci/tasks/release_notes/tests/change_notes/single/1.txt +1 -0
  475. cumulusci/tasks/release_notes/tests/test_generator.py +582 -0
  476. cumulusci/tasks/release_notes/tests/test_parser.py +867 -0
  477. cumulusci/tasks/release_notes/tests/test_provider.py +512 -0
  478. cumulusci/tasks/release_notes/tests/test_task.py +461 -0
  479. cumulusci/tasks/release_notes/tests/utils.py +153 -0
  480. cumulusci/tasks/robotframework/__init__.py +3 -0
  481. cumulusci/tasks/robotframework/debugger/DebugListener.py +100 -0
  482. cumulusci/tasks/robotframework/debugger/__init__.py +10 -0
  483. cumulusci/tasks/robotframework/debugger/model.py +87 -0
  484. cumulusci/tasks/robotframework/debugger/ui.py +259 -0
  485. cumulusci/tasks/robotframework/libdoc.py +269 -0
  486. cumulusci/tasks/robotframework/robotframework.py +392 -0
  487. cumulusci/tasks/robotframework/stylesheet.css +130 -0
  488. cumulusci/tasks/robotframework/template.html +109 -0
  489. cumulusci/tasks/robotframework/tests/TestLibrary.py +18 -0
  490. cumulusci/tasks/robotframework/tests/TestPageObjects.py +31 -0
  491. cumulusci/tasks/robotframework/tests/TestResource.robot +8 -0
  492. cumulusci/tasks/robotframework/tests/failing_tests.robot +16 -0
  493. cumulusci/tasks/robotframework/tests/performance.robot +23 -0
  494. cumulusci/tasks/robotframework/tests/test_browser_proxies.py +137 -0
  495. cumulusci/tasks/robotframework/tests/test_debugger.py +360 -0
  496. cumulusci/tasks/robotframework/tests/test_robot_parallel.py +141 -0
  497. cumulusci/tasks/robotframework/tests/test_robotframework.py +860 -0
  498. cumulusci/tasks/salesforce/BaseRetrieveMetadata.py +58 -0
  499. cumulusci/tasks/salesforce/BaseSalesforceApiTask.py +45 -0
  500. cumulusci/tasks/salesforce/BaseSalesforceMetadataApiTask.py +18 -0
  501. cumulusci/tasks/salesforce/BaseSalesforceTask.py +4 -0
  502. cumulusci/tasks/salesforce/BaseUninstallMetadata.py +41 -0
  503. cumulusci/tasks/salesforce/CreateCommunity.py +124 -0
  504. cumulusci/tasks/salesforce/CreatePackage.py +29 -0
  505. cumulusci/tasks/salesforce/Deploy.py +240 -0
  506. cumulusci/tasks/salesforce/DeployBundles.py +88 -0
  507. cumulusci/tasks/salesforce/DescribeMetadataTypes.py +26 -0
  508. cumulusci/tasks/salesforce/EnsureRecordTypes.py +202 -0
  509. cumulusci/tasks/salesforce/GetInstalledPackages.py +8 -0
  510. cumulusci/tasks/salesforce/ListCommunities.py +40 -0
  511. cumulusci/tasks/salesforce/ListCommunityTemplates.py +19 -0
  512. cumulusci/tasks/salesforce/PublishCommunity.py +62 -0
  513. cumulusci/tasks/salesforce/RetrievePackaged.py +41 -0
  514. cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py +82 -0
  515. cumulusci/tasks/salesforce/RetrieveUnpackaged.py +36 -0
  516. cumulusci/tasks/salesforce/SOQLQuery.py +39 -0
  517. cumulusci/tasks/salesforce/UninstallLocal.py +15 -0
  518. cumulusci/tasks/salesforce/UninstallLocalBundles.py +28 -0
  519. cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py +58 -0
  520. cumulusci/tasks/salesforce/UninstallPackage.py +32 -0
  521. cumulusci/tasks/salesforce/UninstallPackaged.py +56 -0
  522. cumulusci/tasks/salesforce/UpdateAdminProfile.py +8 -0
  523. cumulusci/tasks/salesforce/__init__.py +79 -0
  524. cumulusci/tasks/salesforce/activate_flow.py +74 -0
  525. cumulusci/tasks/salesforce/check_components.py +324 -0
  526. cumulusci/tasks/salesforce/composite.py +142 -0
  527. cumulusci/tasks/salesforce/create_permission_sets.py +35 -0
  528. cumulusci/tasks/salesforce/custom_settings.py +134 -0
  529. cumulusci/tasks/salesforce/custom_settings_wait.py +132 -0
  530. cumulusci/tasks/salesforce/enable_prediction.py +107 -0
  531. cumulusci/tasks/salesforce/insert_record.py +40 -0
  532. cumulusci/tasks/salesforce/install_package_version.py +242 -0
  533. cumulusci/tasks/salesforce/license_preflights.py +8 -0
  534. cumulusci/tasks/salesforce/network_member_group.py +178 -0
  535. cumulusci/tasks/salesforce/nonsourcetracking.py +228 -0
  536. cumulusci/tasks/salesforce/org_settings.py +193 -0
  537. cumulusci/tasks/salesforce/package_upload.py +328 -0
  538. cumulusci/tasks/salesforce/profiles.py +74 -0
  539. cumulusci/tasks/salesforce/promote_package_version.py +376 -0
  540. cumulusci/tasks/salesforce/retrieve_profile.py +195 -0
  541. cumulusci/tasks/salesforce/salesforce_files.py +244 -0
  542. cumulusci/tasks/salesforce/sourcetracking.py +507 -0
  543. cumulusci/tasks/salesforce/tests/__init__.py +3 -0
  544. cumulusci/tasks/salesforce/tests/test_CreateCommunity.py +278 -0
  545. cumulusci/tasks/salesforce/tests/test_CreatePackage.py +22 -0
  546. cumulusci/tasks/salesforce/tests/test_Deploy.py +470 -0
  547. cumulusci/tasks/salesforce/tests/test_DeployBundles.py +76 -0
  548. cumulusci/tasks/salesforce/tests/test_EnsureRecordTypes.py +345 -0
  549. cumulusci/tasks/salesforce/tests/test_ListCommunities.py +84 -0
  550. cumulusci/tasks/salesforce/tests/test_ListCommunityTemplates.py +49 -0
  551. cumulusci/tasks/salesforce/tests/test_PackageUpload.py +547 -0
  552. cumulusci/tasks/salesforce/tests/test_ProfileGrantAllAccess.py +699 -0
  553. cumulusci/tasks/salesforce/tests/test_PublishCommunity.py +181 -0
  554. cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +24 -0
  555. cumulusci/tasks/salesforce/tests/test_RetrieveReportsAndDashboards.py +56 -0
  556. cumulusci/tasks/salesforce/tests/test_RetrieveUnpackaged.py +21 -0
  557. cumulusci/tasks/salesforce/tests/test_SOQLQuery.py +30 -0
  558. cumulusci/tasks/salesforce/tests/test_UninstallLocal.py +15 -0
  559. cumulusci/tasks/salesforce/tests/test_UninstallLocalBundles.py +19 -0
  560. cumulusci/tasks/salesforce/tests/test_UninstallLocalNamespacedBundles.py +22 -0
  561. cumulusci/tasks/salesforce/tests/test_UninstallPackage.py +19 -0
  562. cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +66 -0
  563. cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +127 -0
  564. cumulusci/tasks/salesforce/tests/test_activate_flow.py +132 -0
  565. cumulusci/tasks/salesforce/tests/test_base_tasks.py +110 -0
  566. cumulusci/tasks/salesforce/tests/test_check_components.py +445 -0
  567. cumulusci/tasks/salesforce/tests/test_composite.py +250 -0
  568. cumulusci/tasks/salesforce/tests/test_create_permission_sets.py +41 -0
  569. cumulusci/tasks/salesforce/tests/test_custom_settings.py +227 -0
  570. cumulusci/tasks/salesforce/tests/test_custom_settings_wait.py +174 -0
  571. cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py +18 -0
  572. cumulusci/tasks/salesforce/tests/test_enable_prediction.py +240 -0
  573. cumulusci/tasks/salesforce/tests/test_insert_record.py +110 -0
  574. cumulusci/tasks/salesforce/tests/test_install_package_version.py +464 -0
  575. cumulusci/tasks/salesforce/tests/test_network_member_group.py +444 -0
  576. cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py +235 -0
  577. cumulusci/tasks/salesforce/tests/test_org_settings.py +407 -0
  578. cumulusci/tasks/salesforce/tests/test_profiles.py +202 -0
  579. cumulusci/tasks/salesforce/tests/test_retrieve_profile.py +287 -0
  580. cumulusci/tasks/salesforce/tests/test_salesforce_files.py +228 -0
  581. cumulusci/tasks/salesforce/tests/test_sourcetracking.py +350 -0
  582. cumulusci/tasks/salesforce/tests/test_trigger_handlers.py +300 -0
  583. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +509 -0
  584. cumulusci/tasks/salesforce/tests/util.py +79 -0
  585. cumulusci/tasks/salesforce/trigger_handlers.py +119 -0
  586. cumulusci/tasks/salesforce/uninstall_packaged_incremental.py +136 -0
  587. cumulusci/tasks/salesforce/update_dependencies.py +290 -0
  588. cumulusci/tasks/salesforce/update_profile.py +339 -0
  589. cumulusci/tasks/salesforce/users/permsets.py +227 -0
  590. cumulusci/tasks/salesforce/users/photos.py +162 -0
  591. cumulusci/tasks/salesforce/users/tests/photo.mock.txt +1 -0
  592. cumulusci/tasks/salesforce/users/tests/test_permsets.py +950 -0
  593. cumulusci/tasks/salesforce/users/tests/test_photos.py +373 -0
  594. cumulusci/tasks/sample_data/capture_sample_data.py +77 -0
  595. cumulusci/tasks/sample_data/load_sample_data.py +85 -0
  596. cumulusci/tasks/sample_data/test_capture_sample_data.py +117 -0
  597. cumulusci/tasks/sample_data/test_load_sample_data.py +121 -0
  598. cumulusci/tasks/sfdx.py +83 -0
  599. cumulusci/tasks/tests/__init__.py +1 -0
  600. cumulusci/tasks/tests/conftest.py +30 -0
  601. cumulusci/tasks/tests/test_command.py +129 -0
  602. cumulusci/tasks/tests/test_connectedapp.py +236 -0
  603. cumulusci/tasks/tests/test_create_package_version.py +847 -0
  604. cumulusci/tasks/tests/test_datadictionary.py +1575 -0
  605. cumulusci/tasks/tests/test_dx_convert_from.py +60 -0
  606. cumulusci/tasks/tests/test_metadeploy.py +624 -0
  607. cumulusci/tasks/tests/test_metaxml.py +99 -0
  608. cumulusci/tasks/tests/test_promote_package_version.py +488 -0
  609. cumulusci/tasks/tests/test_pushfails.py +96 -0
  610. cumulusci/tasks/tests/test_salesforce.py +72 -0
  611. cumulusci/tasks/tests/test_sfdx.py +105 -0
  612. cumulusci/tasks/tests/test_util.py +207 -0
  613. cumulusci/tasks/util.py +261 -0
  614. cumulusci/tasks/vcs/__init__.py +19 -0
  615. cumulusci/tasks/vcs/commit_status.py +58 -0
  616. cumulusci/tasks/vcs/create_commit_status.py +37 -0
  617. cumulusci/tasks/vcs/download_extract.py +199 -0
  618. cumulusci/tasks/vcs/merge.py +298 -0
  619. cumulusci/tasks/vcs/publish.py +207 -0
  620. cumulusci/tasks/vcs/pull_request.py +9 -0
  621. cumulusci/tasks/vcs/release.py +134 -0
  622. cumulusci/tasks/vcs/release_report.py +105 -0
  623. cumulusci/tasks/vcs/tag.py +31 -0
  624. cumulusci/tasks/vcs/tests/github/test_commit_status.py +196 -0
  625. cumulusci/tasks/vcs/tests/github/test_download_extract.py +896 -0
  626. cumulusci/tasks/vcs/tests/github/test_merge.py +1118 -0
  627. cumulusci/tasks/vcs/tests/github/test_publish.py +823 -0
  628. cumulusci/tasks/vcs/tests/github/test_pull_request.py +29 -0
  629. cumulusci/tasks/vcs/tests/github/test_release.py +390 -0
  630. cumulusci/tasks/vcs/tests/github/test_release_report.py +109 -0
  631. cumulusci/tasks/vcs/tests/github/test_tag.py +90 -0
  632. cumulusci/tasks/vlocity/exceptions.py +2 -0
  633. cumulusci/tasks/vlocity/tests/test_vlocity.py +283 -0
  634. cumulusci/tasks/vlocity/vlocity.py +342 -0
  635. cumulusci/tests/__init__.py +1 -0
  636. cumulusci/tests/cassettes/GET_sobjects_Account_PersonAccount_describe.yaml +18 -0
  637. cumulusci/tests/cassettes/TestIntegrationInfrastructure.test_integration_tests.yaml +19 -0
  638. cumulusci/tests/pytest_plugins/pytest_sf_orgconnect.py +307 -0
  639. cumulusci/tests/pytest_plugins/pytest_sf_vcr.py +275 -0
  640. cumulusci/tests/pytest_plugins/pytest_sf_vcr_serializer.py +160 -0
  641. cumulusci/tests/pytest_plugins/pytest_typeguard.py +5 -0
  642. cumulusci/tests/pytest_plugins/test_vcr_string_compressor.py +49 -0
  643. cumulusci/tests/pytest_plugins/vcr_string_compressor.py +97 -0
  644. cumulusci/tests/shared_cassettes/GET_sobjects_Account_describe.yaml +18 -0
  645. cumulusci/tests/shared_cassettes/GET_sobjects_Case_describe.yaml +18 -0
  646. cumulusci/tests/shared_cassettes/GET_sobjects_Contact_describe.yaml +4838 -0
  647. cumulusci/tests/shared_cassettes/GET_sobjects_Custom__c_describe.yaml +242 -0
  648. cumulusci/tests/shared_cassettes/GET_sobjects_Event_describe.yaml +19 -0
  649. cumulusci/tests/shared_cassettes/GET_sobjects_Global_describe.yaml +1338 -0
  650. cumulusci/tests/shared_cassettes/GET_sobjects_Lead_describe.yaml +18 -0
  651. cumulusci/tests/shared_cassettes/GET_sobjects_OpportunityContactRole_describe.yaml +34 -0
  652. cumulusci/tests/shared_cassettes/GET_sobjects_Opportunity_describe.yaml +1261 -0
  653. cumulusci/tests/shared_cassettes/GET_sobjects_Organization.yaml +49 -0
  654. cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfoList_xml.tpl +15 -0
  655. cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfo_xml.tpl +13 -0
  656. cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_insert_xml.tpl +24 -0
  657. cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_upsert_xml.tpl +25 -0
  658. cumulusci/tests/test_entry_points.py +20 -0
  659. cumulusci/tests/test_integration_infrastructure.py +131 -0
  660. cumulusci/tests/test_main.py +9 -0
  661. cumulusci/tests/test_schema.py +32 -0
  662. cumulusci/tests/test_utils.py +657 -0
  663. cumulusci/tests/test_vcr_serializer.py +134 -0
  664. cumulusci/tests/uncompressed_cassette.yaml +83 -0
  665. cumulusci/tests/util.py +344 -0
  666. cumulusci/utils/__init__.py +731 -0
  667. cumulusci/utils/classutils.py +9 -0
  668. cumulusci/utils/collections.py +32 -0
  669. cumulusci/utils/deprecation.py +11 -0
  670. cumulusci/utils/encryption.py +31 -0
  671. cumulusci/utils/fileutils.py +295 -0
  672. cumulusci/utils/git.py +142 -0
  673. cumulusci/utils/http/multi_request.py +214 -0
  674. cumulusci/utils/http/requests_utils.py +103 -0
  675. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +32 -0
  676. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_composite_parallel_salesforce.yaml +65 -0
  677. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_errors.yaml +24 -0
  678. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_reference_ids.yaml +49 -0
  679. cumulusci/utils/http/tests/test_multi_request.py +255 -0
  680. cumulusci/utils/iterators.py +21 -0
  681. cumulusci/utils/logging.py +128 -0
  682. cumulusci/utils/metaprogramming.py +10 -0
  683. cumulusci/utils/options.py +138 -0
  684. cumulusci/utils/parallel/queries_in_parallel/run_queries_in_parallel.py +29 -0
  685. cumulusci/utils/parallel/queries_in_parallel/tests/test_run_queries_in_parallel.py +50 -0
  686. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +238 -0
  687. cumulusci/utils/parallel/task_worker_queues/parallel_worker_queue.py +243 -0
  688. cumulusci/utils/parallel/task_worker_queues/tests/test_parallel_worker.py +353 -0
  689. cumulusci/utils/salesforce/count_sobjects.py +46 -0
  690. cumulusci/utils/salesforce/soql.py +17 -0
  691. cumulusci/utils/salesforce/tests/cassettes/ManualEdit_TestCountSObjects.test_count_sobjects__network_errors.yaml +23 -0
  692. cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects__errors.yaml +33 -0
  693. cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects_simple.yaml +29 -0
  694. cumulusci/utils/salesforce/tests/test_count_sobjects.py +29 -0
  695. cumulusci/utils/salesforce/tests/test_soql.py +30 -0
  696. cumulusci/utils/tests/cassettes/ManualEditTestDescribeOrg.test_minimal_schema.yaml +36 -0
  697. cumulusci/utils/tests/cassettes/ManualEdit_test_describe_to_sql.yaml +191 -0
  698. cumulusci/utils/tests/test_fileutils.py +284 -0
  699. cumulusci/utils/tests/test_git.py +85 -0
  700. cumulusci/utils/tests/test_logging.py +70 -0
  701. cumulusci/utils/tests/test_option_parsing.py +188 -0
  702. cumulusci/utils/tests/test_org_schema.py +691 -0
  703. cumulusci/utils/tests/test_org_schema_models.py +79 -0
  704. cumulusci/utils/tests/test_waiting.py +25 -0
  705. cumulusci/utils/version_strings.py +391 -0
  706. cumulusci/utils/waiting.py +42 -0
  707. cumulusci/utils/xml/__init__.py +91 -0
  708. cumulusci/utils/xml/metadata_tree.py +299 -0
  709. cumulusci/utils/xml/robot_xml.py +114 -0
  710. cumulusci/utils/xml/salesforce_encoding.py +100 -0
  711. cumulusci/utils/xml/test/test_metadata_tree.py +251 -0
  712. cumulusci/utils/xml/test/test_salesforce_encoding.py +173 -0
  713. cumulusci/utils/yaml/cumulusci_yml.py +401 -0
  714. cumulusci/utils/yaml/model_parser.py +156 -0
  715. cumulusci/utils/yaml/safer_loader.py +74 -0
  716. cumulusci/utils/yaml/tests/bad_cci.yml +5 -0
  717. cumulusci/utils/yaml/tests/cassettes/TestCumulusciYml.test_validate_url__with_errors.yaml +20 -0
  718. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +286 -0
  719. cumulusci/utils/yaml/tests/test_model_parser.py +175 -0
  720. cumulusci/utils/yaml/tests/test_safer_loader.py +88 -0
  721. cumulusci/utils/ziputils.py +61 -0
  722. cumulusci/vcs/base.py +143 -0
  723. cumulusci/vcs/bootstrap.py +272 -0
  724. cumulusci/vcs/github/__init__.py +24 -0
  725. cumulusci/vcs/github/adapter.py +689 -0
  726. cumulusci/vcs/github/release_notes/generator.py +219 -0
  727. cumulusci/vcs/github/release_notes/parser.py +151 -0
  728. cumulusci/vcs/github/release_notes/provider.py +143 -0
  729. cumulusci/vcs/github/service.py +569 -0
  730. cumulusci/vcs/github/tests/test_adapter.py +138 -0
  731. cumulusci/vcs/github/tests/test_service.py +408 -0
  732. cumulusci/vcs/models.py +586 -0
  733. cumulusci/vcs/tests/conftest.py +41 -0
  734. cumulusci/vcs/tests/dummy_service.py +241 -0
  735. cumulusci/vcs/tests/test_vcs_base.py +687 -0
  736. cumulusci/vcs/tests/test_vcs_bootstrap.py +727 -0
  737. cumulusci/vcs/utils/__init__.py +31 -0
  738. cumulusci/vcs/vcs_source.py +287 -0
  739. cumulusci_plus-5.0.0.dist-info/METADATA +145 -0
  740. cumulusci_plus-5.0.0.dist-info/RECORD +744 -0
  741. cumulusci_plus-5.0.0.dist-info/WHEEL +4 -0
  742. cumulusci_plus-5.0.0.dist-info/entry_points.txt +3 -0
  743. cumulusci_plus-5.0.0.dist-info/licenses/AUTHORS.rst +41 -0
  744. 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