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