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,888 @@
1
+ """ FlowRunner contains the logic for actually running a flow.
2
+
3
+ Flows are an integral part of CCI, they actually *do the thing*. We've been getting
4
+ along quite nicely with BaseFlow, which turns a flow definition into a callable
5
+ object that runs the flow in one fell swoop. We named it BaseFlow thinking that,
6
+ like tasks, specific flows might subclass it to extend behavior. In practice,
7
+ unlike BaseTask, subclasses ended up representing variations in how the flow
8
+ should actually be executed. We added callback hooks like pre_task and post_task
9
+ for host systems embedding cci, like web apps, to inspect the flow in progress.
10
+
11
+ BaseFlow suited us well.
12
+
13
+ FlowRunner is a v2 API for flows in CCI. There are two objects of interest:
14
+
15
+ - FlowCoordinator: takes a flow_config & runtime options to create a set of StepSpecs
16
+ - Meant to replace the public API of BaseFlow, including override hooks.
17
+ - Precomputes a flat list of steps, instead of running Flow recursively.
18
+ - TaskRunner: encapsulates the actual task running, result providing logic.
19
+
20
+ Upon initialization, FlowRunner:
21
+
22
+ - Creates a logger
23
+ - Validates that there are no cycles in the given flow_config
24
+ - Validates that the flow_config is using new-style-steps
25
+ - Collects a list of StepSpec objects that define what the flow will do.
26
+
27
+ Upon running the flow, FlowRunner:
28
+
29
+ - Refreshes the org credentials
30
+ - Runs each StepSpec in order
31
+ - * Logs the task or skip
32
+ - * Updates any ^^ task option values with return_values references
33
+ - * Creates a TaskRunner to run the task and get the result
34
+ - * Re-raise any fatal exceptions from the task, if not ignore_failure.
35
+ - * collects StepResults into the flow.
36
+
37
+ TaskRunner:
38
+
39
+ - Imports the actual task module.
40
+ - Constructs an instance of the BaseTask subclass.
41
+ - Runs/calls the task instance.
42
+ - Returns results or exception into an immutable StepResult
43
+
44
+ Option values/overrides can be passed in at a number of levels, in increasing order of priority:
45
+
46
+ - Task default (i.e. `.tasks__TASKNAME__options`)
47
+ - Flow definition task options (i.e. `.flows__FLOWNAME__steps__STEPNUM__options`)
48
+ - Flow definition subflow options (i.e. `.flows__FLOWNAME__steps__STEPNUM__options__TASKNAME`)
49
+ see `dev_org_namespaced` for an example
50
+ - Flow runtime (i.e. on the commandline)
51
+
52
+ """
53
+
54
+ import copy
55
+ import logging
56
+ from collections import defaultdict
57
+ from operator import attrgetter
58
+ from typing import (
59
+ TYPE_CHECKING,
60
+ Any,
61
+ DefaultDict,
62
+ Dict,
63
+ List,
64
+ NamedTuple,
65
+ Optional,
66
+ Tuple,
67
+ Type,
68
+ Union,
69
+ )
70
+
71
+ from jinja2.sandbox import ImmutableSandboxedEnvironment
72
+
73
+ from cumulusci.core.config import FlowConfig, TaskConfig
74
+ from cumulusci.core.config.org_config import OrgConfig
75
+ from cumulusci.core.config.project_config import BaseProjectConfig
76
+ from cumulusci.core.exceptions import (
77
+ FlowConfigError,
78
+ FlowInfiniteLoopError,
79
+ TaskImportError,
80
+ )
81
+ from cumulusci.utils.version_strings import LooseVersion
82
+
83
+ if TYPE_CHECKING:
84
+ from cumulusci.core.tasks import BaseTask
85
+
86
+
87
+ RETURN_VALUE_OPTION_PREFIX = "^^"
88
+
89
+ jinja2_env = ImmutableSandboxedEnvironment()
90
+
91
+
92
+ class StepVersion(LooseVersion):
93
+ """Like LooseVersion, but converts "/" into -1 to support comparisons"""
94
+
95
+ def parse(self, vstring: str):
96
+ super().parse(vstring)
97
+ self.version = tuple(-1 if x == "/" else x for x in self.version)
98
+
99
+
100
+ class StepSpec:
101
+ """simple namespace to describe what the flowrunner should do each step"""
102
+
103
+ __slots__ = (
104
+ "step_num",
105
+ "task_name",
106
+ "task_config",
107
+ "task_class",
108
+ "project_config",
109
+ "allow_failure",
110
+ "path",
111
+ "skip",
112
+ "when",
113
+ )
114
+
115
+ step_num: StepVersion
116
+ task_name: str
117
+ task_config: dict
118
+ task_class: Optional[
119
+ Type["BaseTask"]
120
+ ] # None means this step was skipped by setting task: None
121
+ project_config: BaseProjectConfig
122
+ allow_failure: bool
123
+ path: str
124
+ skip: bool
125
+ when: Optional[str]
126
+
127
+ def __init__(
128
+ self,
129
+ step_num: StepVersion,
130
+ task_name: str,
131
+ task_config: dict,
132
+ task_class: Optional[Type["BaseTask"]],
133
+ project_config: BaseProjectConfig,
134
+ allow_failure: bool = False,
135
+ from_flow: Optional[str] = None,
136
+ skip: bool = False,
137
+ when: Optional[str] = None,
138
+ ):
139
+ self.step_num = step_num
140
+ self.task_name = task_name
141
+ self.task_config = task_config
142
+ self.task_class = task_class
143
+ self.project_config = project_config
144
+ self.allow_failure = allow_failure
145
+ self.skip = skip
146
+ self.when = when
147
+
148
+ # Store the dotted path to this step.
149
+ # This is not guaranteed to be unique, because multiple steps
150
+ # in the same flow can reference the same task name with different options.
151
+ # It's here to support the ^^flow_name.task_name.attr_name syntax
152
+ # for referencing previous task return values in options.
153
+ if from_flow:
154
+ self.path = ".".join([from_flow, task_name])
155
+ else:
156
+ self.path = task_name
157
+
158
+ def __repr__(self):
159
+ skipstr = ""
160
+ if self.skip:
161
+ skipstr = "!SKIP! "
162
+ return (
163
+ f"<{skipstr}StepSpec {self.step_num}:{self.task_name} {self.task_config}>"
164
+ )
165
+
166
+
167
+ class StepResult(NamedTuple):
168
+ step_num: StepVersion
169
+ task_name: str
170
+ path: str
171
+ result: Any
172
+ return_values: Any
173
+ exception: Optional[Exception]
174
+
175
+
176
+ class FlowCallback:
177
+ """A subclass of FlowCallback allows code running a flow
178
+ to inject callback methods to run during the flow. Anything you
179
+ would like the FlowCallback to have access to can be passed to the
180
+ constructor. This is typically used to pass a Django model or model id
181
+ when running a flow inside of a web app.
182
+
183
+ Example subclass of FlowCallback:
184
+
185
+ class CustomFlowCallback(FlowCallback):
186
+ def __init__(self, model):
187
+ self.model = model
188
+
189
+ def post_task(self, step, result):
190
+ # do something to record state on self.model
191
+
192
+ Once a subclass is defined, you can instantiate it, and
193
+ pass it as the value for the 'callbacks' keyword argument
194
+ when instantiating a FlowCoordinator.
195
+
196
+ Example running a flow with custom callbacks:
197
+
198
+ custom_callbacks = CustomFlowCallbacks(model_instance)
199
+ flow_coordinator = FlowCoordinator(
200
+ project_config,
201
+ flow_config,
202
+ name=flow_name,
203
+ options=options,
204
+ callbacks=custom_callbacks,
205
+ )
206
+ flow_coordinator.run(org_config)
207
+
208
+
209
+ """
210
+
211
+ def pre_flow(self, coordinator: "FlowCoordinator"):
212
+ """This is passed an instance of FlowCoordinator,
213
+ that pertains to the flow which is about to run."""
214
+ pass
215
+
216
+ def post_flow(self, coordinator: "FlowCoordinator"):
217
+ """This is passed an instance of FlowCoordinator,
218
+ that pertains to the flow just finished running.
219
+ This step executes whether or not the flow completed
220
+ successfully."""
221
+ pass
222
+
223
+ def pre_task(self, step: StepSpec):
224
+ """This is passed an instance StepSpec, that
225
+ pertains to the task which is about to run."""
226
+ pass
227
+
228
+ def post_task(self, step: StepSpec, result: StepResult):
229
+ """This method is called after a task has executed.
230
+
231
+ :param step: Instance of StepSpec that relates to the task which executed
232
+ :param result: Instance of the StepResult class that was run. Attributes of
233
+ interest include, `result.result`, `result.return_values`, and `result.exception`
234
+ """
235
+ pass
236
+
237
+
238
+ class TaskRunner:
239
+ """TaskRunner encapsulates the job of instantiating and running a task."""
240
+
241
+ step: StepSpec
242
+ org_config: Optional[OrgConfig]
243
+ flow: Optional["FlowCoordinator"]
244
+
245
+ def __init__(
246
+ self,
247
+ step: StepSpec,
248
+ org_config: Optional[OrgConfig],
249
+ flow: Optional["FlowCoordinator"] = None,
250
+ ):
251
+ self.step = step
252
+ self.org_config = org_config
253
+ self.flow = flow
254
+
255
+ @classmethod
256
+ def from_flow(cls, flow: "FlowCoordinator", step: StepSpec) -> "TaskRunner":
257
+ return cls(step, flow.org_config, flow=flow)
258
+
259
+ def run_step(self, **options) -> StepResult:
260
+ """
261
+ Run a step.
262
+
263
+ :return: StepResult
264
+ """
265
+
266
+ # Resolve ^^task_name.return_value style option syntax
267
+ task_config = self.step.task_config.copy()
268
+ task_config["options"] = task_config.get("options", {}).copy()
269
+ assert self.flow
270
+ self.flow.resolve_return_value_options(task_config["options"])
271
+
272
+ task_config["options"].update(options)
273
+
274
+ assert self.step.task_class
275
+
276
+ task = self.step.task_class(
277
+ self.step.project_config,
278
+ TaskConfig(task_config),
279
+ org_config=self.org_config,
280
+ name=self.step.task_name,
281
+ stepnum=self.step.step_num,
282
+ flow=self.flow,
283
+ )
284
+ self._log_options(task)
285
+ exc = None
286
+ try:
287
+ task()
288
+ except Exception as e:
289
+ self.flow.logger.error(f"Exception in task {self.step.path}")
290
+ exc = e
291
+ return StepResult(
292
+ self.step.step_num,
293
+ self.step.task_name,
294
+ self.step.path,
295
+ task.result,
296
+ task.return_values,
297
+ exc,
298
+ )
299
+
300
+ def _log_options(self, task: "BaseTask"):
301
+ if not task.task_options:
302
+ task.logger.info("No task options present")
303
+ return
304
+ task.logger.info("Options:")
305
+ for key, info in task.task_options.items():
306
+ value = task.options.get(key)
307
+ if value is not None:
308
+ if type(value) is not list:
309
+ value = self._obfuscate_if_sensitive(value, info)
310
+ task.logger.info(f" {key}: {value}")
311
+ else:
312
+ task.logger.info(f" {key}:")
313
+ for v in value:
314
+ v = self._obfuscate_if_sensitive(v, info)
315
+ task.logger.info(f" - {v}")
316
+
317
+ def _obfuscate_if_sensitive(self, value: str, info: dict) -> str:
318
+ if info.get("sensitive"):
319
+ value = 8 * "*"
320
+ return value
321
+
322
+
323
+ class FlowCoordinator:
324
+ org_config: Optional[OrgConfig]
325
+ steps: List[StepSpec]
326
+ callbacks: FlowCallback
327
+ logger: logging.Logger
328
+ skip: List[str]
329
+ flow_config: FlowConfig
330
+ runtime_options: dict
331
+ name: Optional[str]
332
+ results: List[StepResult]
333
+
334
+ def __init__(
335
+ self,
336
+ project_config: BaseProjectConfig,
337
+ flow_config: FlowConfig,
338
+ name: Optional[str] = None,
339
+ options: Optional[dict] = None,
340
+ skip: Optional[List[str]] = None,
341
+ callbacks: Optional[FlowCallback] = None,
342
+ ):
343
+ self.project_config = project_config
344
+ self.flow_config = flow_config
345
+ self.name = name
346
+ self.org_config = None
347
+
348
+ if not callbacks:
349
+ callbacks = FlowCallback()
350
+ self.callbacks = callbacks
351
+
352
+ self.runtime_options = options or {}
353
+
354
+ self.skip = skip or []
355
+ self.results = []
356
+
357
+ self.logger = self._init_logger()
358
+ self.steps = self._init_steps()
359
+
360
+ @classmethod
361
+ def from_steps(
362
+ cls,
363
+ project_config: BaseProjectConfig,
364
+ steps: List[StepSpec],
365
+ name: Optional[str] = None,
366
+ callbacks: Optional[FlowCallback] = None,
367
+ ):
368
+ instance = cls(
369
+ project_config,
370
+ flow_config=FlowConfig({"steps": {}}),
371
+ name=name,
372
+ callbacks=callbacks,
373
+ )
374
+ instance.steps = steps
375
+ return instance
376
+
377
+ def _rule(self, fill="=", length=60, new_line=False):
378
+ self.logger.info(f"{fill * length}")
379
+ if new_line:
380
+ self.logger.info("")
381
+
382
+ def get_summary(self, verbose=False):
383
+ """Returns an output string that contains the description of the flow
384
+ and its steps."""
385
+ lines = []
386
+ if "description" in self.flow_config.config:
387
+ lines.append(f"Description: {self.flow_config.config['description']}")
388
+
389
+ step_lines = self.get_flow_steps(verbose=verbose)
390
+ if step_lines:
391
+ lines.append("\nFlow Steps")
392
+ lines.extend(step_lines)
393
+
394
+ return "\n".join(lines)
395
+
396
+ def get_flow_steps(
397
+ self, for_docs: bool = False, verbose: bool = False
398
+ ) -> List[str]:
399
+ """Returns a list of flow steps (tasks and sub-flows) for the given flow.
400
+ For docs, indicates whether or not we want to use the string for use in a code-block
401
+ of an rst file. If True, will omit output of source information."""
402
+ lines = []
403
+ previous_parts = []
404
+ previous_source = None
405
+ for step in self.steps:
406
+ parts = step.path.split(".")
407
+ steps = str(step.step_num).split("/")
408
+ if len(parts) > len(steps):
409
+ # Sub-step generated during freeze process; skip it
410
+ continue
411
+ task_name = parts.pop()
412
+
413
+ i = -1
414
+ new_source = (
415
+ f" [from {step.project_config.source}]"
416
+ if step.project_config.source is not previous_source
417
+ else ""
418
+ )
419
+ options_info = ""
420
+ for i, flow_name in enumerate(parts):
421
+ if not any(":" in part for part in step.path.split(".")[i + 1 :]):
422
+ source = new_source
423
+ else:
424
+ source = ""
425
+ if len(previous_parts) < i + 1 or previous_parts[i] != flow_name:
426
+ if for_docs:
427
+ source = ""
428
+
429
+ lines.append(f"{' ' * i}{steps[i]}) flow: {flow_name}{source}")
430
+ if source:
431
+ new_source = ""
432
+
433
+ padding = " " * (i + 1) + " " * len(str(steps[i + 1]))
434
+ when = f"{padding} when: {step.when}" if step.when is not None else ""
435
+
436
+ if for_docs:
437
+ new_source = ""
438
+
439
+ if step.task_config.get("options"):
440
+ if verbose:
441
+ options = step.task_config.get("options")
442
+ options_info = f"{padding} options:"
443
+
444
+ for option, value in options.items():
445
+ options_info += f"\n{padding} {option}: {value}"
446
+
447
+ lines.append(
448
+ f"{' ' * (i + 1)}{steps[i + 1]}) task: {task_name}{new_source}"
449
+ )
450
+
451
+ if when:
452
+ lines.append(when)
453
+
454
+ if options_info:
455
+ lines.append(options_info)
456
+
457
+ previous_parts = parts
458
+ previous_source = step.project_config.source
459
+
460
+ return lines
461
+
462
+ def run(self, org_config: OrgConfig):
463
+ self.org_config = org_config
464
+ line = f"Initializing flow: {self.__class__.__name__}"
465
+ if self.name:
466
+ line = f"{line} ({self.name})"
467
+ self._rule()
468
+ self.logger.info(line)
469
+ self.logger.info(self.flow_config.description)
470
+ self._rule(new_line=True)
471
+
472
+ self._init_org()
473
+ self._rule(fill="-")
474
+ self.logger.info("Organization:")
475
+ self.logger.info(f" Username: {org_config.username}")
476
+ self.logger.info(f" Org Id: {org_config.org_id}")
477
+ self.logger.info(f" Instance: {org_config.instance_name}")
478
+ self._rule(fill="-", new_line=True)
479
+
480
+ # Give pre_flow callback a chance to alter the steps
481
+ # based on the state of the org before we display the steps.
482
+ self.callbacks.pre_flow(self)
483
+
484
+ self._rule(fill="-")
485
+ self.logger.info("Steps:")
486
+ for line in self.get_summary().splitlines():
487
+ self.logger.info(line)
488
+ self._rule(fill="-", new_line=True)
489
+
490
+ self.logger.info("Starting execution")
491
+ self._rule(new_line=True)
492
+
493
+ try:
494
+ for step in self.steps:
495
+ self._run_step(step)
496
+ flow_name = f"'{self.name}' " if self.name else ""
497
+ self.logger.info(
498
+ f"Completed flow {flow_name}on org {org_config.name} successfully!"
499
+ )
500
+ finally:
501
+ self.callbacks.post_flow(self)
502
+
503
+ def _run_step(self, step: StepSpec):
504
+ if step.skip:
505
+ self._rule(fill="*")
506
+ self.logger.info(f"Skipping task: {step.task_name}")
507
+ self._rule(fill="*", new_line=True)
508
+ return
509
+
510
+ if step.when:
511
+ jinja2_context = {
512
+ "project_config": step.project_config,
513
+ "org_config": self.org_config,
514
+ }
515
+ expr = jinja2_env.compile_expression(step.when)
516
+ value = expr(**jinja2_context)
517
+ if not value:
518
+ self.logger.info(
519
+ f"Skipping task {step.task_name} (skipped unless {step.when})"
520
+ )
521
+ return
522
+
523
+ self._rule(fill="-")
524
+ self.logger.info(f"Running task: {step.task_name}")
525
+ self._rule(fill="-", new_line=True)
526
+
527
+ self.callbacks.pre_task(step)
528
+ result = TaskRunner.from_flow(self, step).run_step()
529
+ self.callbacks.post_task(step, result)
530
+
531
+ self.results.append(
532
+ result
533
+ ) # add even a failed result to the result set for the post flow
534
+
535
+ if result.exception and not step.allow_failure:
536
+ raise result.exception # PY3: raise an exception type we control *from* this exception instead?
537
+
538
+ def _init_logger(self) -> logging.Logger:
539
+ """
540
+ Returns a logging.Logger-like object to use for the duration of the flow. Tasks will receive this logger
541
+ and getChild(class_name) to get a child logger.
542
+
543
+ :return: logging.Logger
544
+ """
545
+ return logging.getLogger("cumulusci.flows").getChild(self.__class__.__name__)
546
+
547
+ def _init_steps(self) -> List[StepSpec]:
548
+ """
549
+ Given the flow config and everything else, create a list of steps to run, sorted by step number.
550
+
551
+ :return: List[StepSpec]
552
+ """
553
+ self._check_old_yaml_format()
554
+ self._check_infinite_flows(self.flow_config)
555
+
556
+ steps = []
557
+
558
+ for number, step_config in self.flow_config.steps.items():
559
+ specs = self._visit_step(number, step_config, self.project_config)
560
+ steps.extend(specs)
561
+
562
+ return sorted(steps, key=attrgetter("step_num"))
563
+
564
+ def _visit_step(
565
+ self,
566
+ number: Union[str, int],
567
+ step_config: dict,
568
+ project_config: BaseProjectConfig,
569
+ visited_steps: Optional[List[StepSpec]] = None,
570
+ parent_options: Optional[dict] = None,
571
+ parent_ui_options: Optional[dict] = None,
572
+ from_flow: Optional[str] = None,
573
+ ) -> List[StepSpec]:
574
+ """
575
+ for each step (as defined in the flow YAML), _visit_step is called with only
576
+ the first two parameters. this takes care of validating the step, collating the
577
+ option overrides, and if it is a task, creating a StepSpec for it.
578
+
579
+ If it is a flow, we recursively call _visit_step with the rest of the parameters of context.
580
+
581
+ :param number: StepVersion representation of the current step number
582
+ :param step_config: the current step's config (dict from YAML)
583
+ :param visited_steps: used when called recursively for nested steps, becomes the return value
584
+ :param parent_options: used when called recursively for nested steps, options from parent flow
585
+ :param parent_ui_options: used when called recursively for nested steps, UI options from parent flow
586
+ :param from_flow: used when called recursively for nested steps, name of parent flow
587
+ :return: List[StepSpec] a list of all resolved steps including/under the one passed in
588
+ """
589
+ step_number = StepVersion(str(number))
590
+
591
+ if visited_steps is None:
592
+ visited_steps = []
593
+ if parent_options is None:
594
+ parent_options = {}
595
+ if parent_ui_options is None:
596
+ parent_ui_options = {}
597
+
598
+ # This should never happen because of cleanup
599
+ # in core/utils/cleanup_old_flow_step_replace_syntax()
600
+ assert step_config.keys() != {"task", "flow"}
601
+
602
+ # Skips
603
+ # - either in YAML (with the None string)
604
+ # - or by providing a skip list to the FlowRunner at initialization.
605
+ if (
606
+ ("flow" in step_config and step_config["flow"] == "None")
607
+ or ("task" in step_config and step_config["task"] == "None")
608
+ or ("task" in step_config and step_config["task"] in self.skip)
609
+ ):
610
+ visited_steps.append(
611
+ StepSpec(
612
+ step_num=step_number,
613
+ task_name=step_config.get("task", step_config.get("flow")),
614
+ task_config=step_config.get("options", {}),
615
+ task_class=None,
616
+ project_config=project_config,
617
+ from_flow=from_flow,
618
+ skip=True, # someday we could use different vals for why skipped
619
+ )
620
+ )
621
+ return visited_steps
622
+
623
+ if "task" in step_config:
624
+ name = step_config["task"]
625
+
626
+ # get the base task_config from the project config, as a dict for easier manipulation.
627
+ # will raise if the task doesn't exist / is invalid
628
+ task_config = project_config.get_task(name)
629
+ task_config_dict: dict = copy.deepcopy(task_config.config)
630
+ if "options" not in task_config_dict:
631
+ task_config_dict["options"] = {}
632
+
633
+ # merge the options together, from task_config all the way down through parent_options
634
+ # parent_options should have higher priority than step_config options
635
+ step_overrides = copy.deepcopy(step_config.get("options", {}))
636
+ parent_task_options = parent_options.get(name, {})
637
+ step_overrides.update(parent_task_options)
638
+ task_config_dict["options"].update(step_overrides)
639
+
640
+ # merge UI options from task config and parent flow
641
+ if "ui_options" not in task_config_dict:
642
+ task_config_dict["ui_options"] = {}
643
+ # parent_ui_options should have higher priority than step_config ui_options
644
+ step_ui_overrides = copy.deepcopy(step_config.get("ui_options", {}))
645
+ step_ui_overrides.update(parent_ui_options.get(name, {}))
646
+ task_config_dict["ui_options"].update(step_ui_overrides)
647
+
648
+ # merge checks from task config and flow step
649
+ if "checks" not in task_config_dict:
650
+ task_config_dict["checks"] = []
651
+ task_config_dict["checks"].extend(step_config.get("checks", []))
652
+
653
+ # merge runtime options
654
+ if name in self.runtime_options:
655
+ task_config_dict["options"].update(self.runtime_options[name])
656
+
657
+ # get implementation class. raise/fail if it doesn't exist, because why continue
658
+ try:
659
+ task_class = task_config.get_class()
660
+ except (ImportError, AttributeError, TaskImportError) as e:
661
+ raise FlowConfigError(f"Task named {name} has bad classpath, {e}")
662
+
663
+ visited_steps.append(
664
+ StepSpec(
665
+ step_num=step_number,
666
+ task_name=name,
667
+ task_config=task_config_dict,
668
+ task_class=task_class,
669
+ project_config=task_config.project_config,
670
+ allow_failure=step_config.get("ignore_failure", False),
671
+ from_flow=from_flow,
672
+ when=step_config.get("when"),
673
+ )
674
+ )
675
+ return visited_steps
676
+
677
+ if "flow" in step_config:
678
+ name = step_config["flow"]
679
+ if from_flow:
680
+ path = ".".join([from_flow, name])
681
+ else:
682
+ path = name
683
+ step_options = step_config.get("options", {})
684
+ step_ui_options = step_config.get("ui_options", {})
685
+ flow_config = project_config.get_flow(name)
686
+ for sub_number, sub_stepconf in flow_config.steps.items():
687
+ # append the flow number to the child number, since its a LooseVersion.
688
+ # e.g. if we're in step 2.3 which references a flow with steps 1-5, it
689
+ # simply ends up as five steps: 2.3.1, 2.3.2, 2.3.3, 2.3.4, 2.3.5
690
+ # TODO: how does this work with nested flowveride? what does defining step 2.3.2 later do?
691
+ num = f"{number}/{sub_number}"
692
+ self._visit_step(
693
+ number=num,
694
+ step_config=sub_stepconf,
695
+ project_config=flow_config.project_config,
696
+ visited_steps=visited_steps,
697
+ parent_options=step_options,
698
+ parent_ui_options=step_ui_options,
699
+ from_flow=path,
700
+ )
701
+ return visited_steps
702
+
703
+ def _check_old_yaml_format(self):
704
+ if self.flow_config.steps is None:
705
+ if "tasks" in self.flow_config.config:
706
+ raise FlowConfigError(
707
+ 'Old flow syntax detected. Please change from "tasks" to "steps" in the flow definition.'
708
+ )
709
+ else:
710
+ raise FlowConfigError("No steps found in the flow definition")
711
+
712
+ def _check_infinite_flows(self, flow_config, flow_stack=None):
713
+ """
714
+ Recursively loop through the flow_config and check if there are any cycles.
715
+
716
+ :param flow_config: FlowConfig to traverse to find cycles/infinite loops
717
+ :param flow_stack: list of flow signatures already visited
718
+ :return: None
719
+ """
720
+ if not flow_stack:
721
+ flow_stack = []
722
+ project_config = flow_config.project_config
723
+
724
+ for step in flow_config.steps.values():
725
+ if "flow" in step:
726
+ next_flow_name = step["flow"]
727
+ if next_flow_name == "None":
728
+ continue
729
+
730
+ next_flow_config = project_config.get_flow(next_flow_name)
731
+ signature = (
732
+ hash(next_flow_config.project_config.source),
733
+ next_flow_config.name,
734
+ )
735
+
736
+ if signature in flow_stack:
737
+ raise FlowInfiniteLoopError(
738
+ f"Infinite flows detected with flow {next_flow_name}"
739
+ )
740
+ flow_stack.append(signature)
741
+ self._check_infinite_flows(next_flow_config, flow_stack)
742
+ flow_stack.pop()
743
+
744
+ def _init_org(self):
745
+ """Test and refresh credentials to the org specified."""
746
+ self.logger.info(
747
+ f"Verifying and refreshing credentials for the specified org: {self.org_config.name}."
748
+ )
749
+
750
+ # attempt to refresh the token, this can throw...
751
+ with self.org_config.save_if_changed():
752
+ self.org_config.refresh_oauth_token(self.project_config.keychain)
753
+
754
+ def resolve_return_value_options(self, options):
755
+ """Handle dynamic option value lookups in the format ^^task_name.attr"""
756
+ for key, value in options.items():
757
+ if isinstance(value, str) and value.startswith(RETURN_VALUE_OPTION_PREFIX):
758
+ path, name = value[len(RETURN_VALUE_OPTION_PREFIX) :].rsplit(".", 1)
759
+ result = self._find_result_by_path(path)
760
+ options[key] = result.return_values.get(name)
761
+
762
+ def _find_result_by_path(self, path):
763
+ for result in self.results:
764
+ if result.path[-len(path) :] == path:
765
+ return result
766
+ raise NameError(f"Path not found: {path}")
767
+
768
+
769
+ class PreflightFlowCoordinator(FlowCoordinator):
770
+ """Coordinates running preflight checks instead of the actual flow steps."""
771
+
772
+ preflight_results: DefaultDict[Optional[str], List[dict]]
773
+ _task_caches: Dict[BaseProjectConfig, "TaskCache"]
774
+
775
+ def run(self, org_config: OrgConfig):
776
+ self.org_config = org_config
777
+ self.callbacks.pre_flow(self)
778
+
779
+ self._init_org()
780
+ self._rule(fill="-")
781
+ self.logger.info("Organization:")
782
+ self.logger.info(f" Username: {org_config.username}")
783
+ self.logger.info(f" Org Id: {org_config.org_id}")
784
+ self._rule(fill="-", new_line=True)
785
+
786
+ self.logger.info("Running preflight checks...")
787
+ self._rule(new_line=True)
788
+
789
+ self.preflight_results = defaultdict(list)
790
+ # Expose for test access
791
+ self._task_caches = {self.project_config: TaskCache(self, self.project_config)}
792
+ try:
793
+ # flow-level checks
794
+ jinja2_context = {
795
+ "tasks": self._task_caches[self.project_config],
796
+ "project_config": self.project_config,
797
+ "org_config": self.org_config,
798
+ }
799
+ for check in self.flow_config.checks or []:
800
+ result = self.evaluate_check(check, jinja2_context)
801
+ if result:
802
+ self.preflight_results[None].append(result)
803
+
804
+ # Step-level checks
805
+ for step in self.steps:
806
+ jinja2_context["project_config"] = step.project_config
807
+ # Create a cache for this project config, if not present
808
+ # and not equal to the root project config.
809
+ # This accommodates cross-project preflight checks.
810
+ if step.project_config not in self._task_caches:
811
+ self._task_caches[step.project_config] = TaskCache(
812
+ self, step.project_config
813
+ )
814
+ jinja2_context["tasks"] = self._task_caches[step.project_config]
815
+ for check in step.task_config.get("checks", []):
816
+ result = self.evaluate_check(check, jinja2_context)
817
+ if result:
818
+ self.preflight_results[str(step.step_num)].append(result)
819
+ finally:
820
+ self.callbacks.post_flow(self)
821
+
822
+ def evaluate_check(
823
+ self, check: dict, jinja2_context: Dict[str, Any]
824
+ ) -> Optional[dict]:
825
+ self.logger.info(f"Evaluating check: {check['when']}")
826
+ expr = jinja2_env.compile_expression(check["when"])
827
+ value = bool(expr(**jinja2_context))
828
+ self.logger.info(f"Check result: {value}")
829
+ if value:
830
+ return {"status": check["action"], "message": check.get("message")}
831
+
832
+
833
+ class TaskCache:
834
+ """Provides access to named tasks and caches their results.
835
+
836
+ This is intended for use in a jinja2 expression context
837
+ so that multiple expressions evaluated in the same context
838
+ can avoid running a task more than once with the same options.
839
+ """
840
+
841
+ project_config: BaseProjectConfig
842
+ results: Dict[Tuple[str, Tuple[Any]], Any]
843
+
844
+ def __init__(self, flow: FlowCoordinator, project_config: BaseProjectConfig):
845
+ self.flow = flow
846
+ # Cross-project flows may include preflight checks
847
+ # that depend on their local context.
848
+ self.project_config = project_config
849
+ self.results = {}
850
+
851
+ def __getattr__(self, task_name: str):
852
+ return CachedTaskRunner(self, task_name)
853
+
854
+
855
+ class CachedTaskRunner:
856
+ """Runs a task and caches the result in a TaskCache"""
857
+
858
+ cache: TaskCache
859
+ task_name: str
860
+
861
+ def __init__(self, cache: TaskCache, task_name: str):
862
+ self.cache = cache
863
+ self.task_name = task_name
864
+
865
+ def __call__(self, **options: dict) -> Any:
866
+ cache_key = (self.task_name, tuple(sorted(options.items())))
867
+ if cache_key in self.cache.results:
868
+ return self.cache.results[cache_key].return_values
869
+
870
+ task_config = self.cache.project_config.tasks[self.task_name]
871
+ task_class = TaskConfig(
872
+ {**task_config, "project_config": self.cache.project_config}
873
+ ).get_class()
874
+ step = StepSpec(
875
+ step_num=StepVersion("1"),
876
+ task_name=self.task_name,
877
+ task_config=task_config,
878
+ task_class=task_class,
879
+ project_config=self.cache.project_config,
880
+ )
881
+ self.cache.flow.callbacks.pre_task(step)
882
+ result = TaskRunner(step, self.cache.flow.org_config, self.cache.flow).run_step(
883
+ **options
884
+ )
885
+ self.cache.flow.callbacks.post_task(step, result)
886
+
887
+ self.cache.results[cache_key] = result
888
+ return result.return_values