qontract-reconcile 0.10.1rc1201__py3-none-any.whl → 0.10.2.dev1__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.
Files changed (140) hide show
  1. qontract_reconcile-0.10.2.dev1.dist-info/METADATA +500 -0
  2. {qontract_reconcile-0.10.1rc1201.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/RECORD +14 -132
  3. {qontract_reconcile-0.10.1rc1201.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/WHEEL +1 -2
  4. {qontract_reconcile-0.10.1rc1201.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/entry_points.txt +1 -0
  5. reconcile/aws_account_manager/README.md +5 -0
  6. reconcile/change_owners/README.md +34 -0
  7. reconcile/external_resources/manager.py +12 -1
  8. reconcile/external_resources/model.py +11 -0
  9. reconcile/glitchtip/README.md +150 -0
  10. reconcile/gql_definitions/introspection.json +51176 -0
  11. reconcile/run_integration.py +293 -0
  12. reconcile/utils/binary.py +2 -2
  13. reconcile/utils/mr/README.md +198 -0
  14. reconcile/utils/oc_map.py +2 -2
  15. tools/qontract_cli.py +0 -0
  16. qontract_reconcile-0.10.1rc1201.dist-info/METADATA +0 -64
  17. qontract_reconcile-0.10.1rc1201.dist-info/top_level.txt +0 -3
  18. reconcile/test/__init__.py +0 -0
  19. reconcile/test/conftest.py +0 -157
  20. reconcile/test/fixtures.py +0 -24
  21. reconcile/test/saas_auto_promotions_manager/__init__.py +0 -0
  22. reconcile/test/saas_auto_promotions_manager/conftest.py +0 -170
  23. reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py +0 -0
  24. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py +0 -0
  25. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -115
  26. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -19
  27. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_desired_state.py +0 -66
  28. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -86
  29. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +0 -352
  30. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +0 -494
  31. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/__init__.py +0 -0
  32. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -25
  33. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -37
  34. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -81
  35. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -61
  36. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_json_path_selector.py +0 -74
  37. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -52
  38. reconcile/test/saas_auto_promotions_manager/utils/__init__.py +0 -0
  39. reconcile/test/test_acs_notifiers.py +0 -393
  40. reconcile/test/test_acs_policies.py +0 -497
  41. reconcile/test/test_acs_rbac.py +0 -865
  42. reconcile/test/test_aggregated_list.py +0 -237
  43. reconcile/test/test_amtool.py +0 -37
  44. reconcile/test/test_aws_ami_cleanup.py +0 -230
  45. reconcile/test/test_aws_ami_share.py +0 -68
  46. reconcile/test/test_aws_cloudwatch_log_retention.py +0 -434
  47. reconcile/test/test_aws_iam_keys.py +0 -70
  48. reconcile/test/test_aws_iam_password_reset.py +0 -35
  49. reconcile/test/test_aws_support_cases_sos.py +0 -23
  50. reconcile/test/test_checkpoint.py +0 -178
  51. reconcile/test/test_cli.py +0 -41
  52. reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
  53. reconcile/test/test_dashdotdb_dora.py +0 -245
  54. reconcile/test/test_database_access_manager.py +0 -660
  55. reconcile/test/test_deadmanssnitch.py +0 -290
  56. reconcile/test/test_gabi_authorized_users.py +0 -72
  57. reconcile/test/test_gcr_mirror.py +0 -14
  58. reconcile/test/test_github_org.py +0 -156
  59. reconcile/test/test_github_repo_invites.py +0 -119
  60. reconcile/test/test_gitlab_housekeeping.py +0 -333
  61. reconcile/test/test_gitlab_labeler.py +0 -126
  62. reconcile/test/test_gitlab_members.py +0 -219
  63. reconcile/test/test_gitlab_permissions.py +0 -164
  64. reconcile/test/test_instrumented_wrappers.py +0 -18
  65. reconcile/test/test_integrations_manager.py +0 -1252
  66. reconcile/test/test_jenkins_worker_fleets.py +0 -57
  67. reconcile/test/test_jira_permissions_validator.py +0 -519
  68. reconcile/test/test_jump_host.py +0 -114
  69. reconcile/test/test_ldap_users.py +0 -125
  70. reconcile/test/test_make.py +0 -28
  71. reconcile/test/test_ocm_additional_routers.py +0 -133
  72. reconcile/test/test_ocm_clusters.py +0 -798
  73. reconcile/test/test_ocm_clusters_manifest_updates.py +0 -87
  74. reconcile/test/test_ocm_machine_pools.py +0 -1103
  75. reconcile/test/test_ocm_update_recommended_version.py +0 -145
  76. reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -125
  77. reconcile/test/test_openshift_base.py +0 -1269
  78. reconcile/test/test_openshift_cluster_bots.py +0 -240
  79. reconcile/test/test_openshift_namespace_labels.py +0 -344
  80. reconcile/test/test_openshift_namespaces.py +0 -256
  81. reconcile/test/test_openshift_resource.py +0 -443
  82. reconcile/test/test_openshift_resources_base.py +0 -478
  83. reconcile/test/test_openshift_saas_deploy.py +0 -188
  84. reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -308
  85. reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +0 -65
  86. reconcile/test/test_openshift_serviceaccount_tokens.py +0 -282
  87. reconcile/test/test_openshift_tekton_resources.py +0 -265
  88. reconcile/test/test_openshift_upgrade_watcher.py +0 -223
  89. reconcile/test/test_prometheus_rules_tester.py +0 -151
  90. reconcile/test/test_quay_membership.py +0 -86
  91. reconcile/test/test_quay_mirror.py +0 -172
  92. reconcile/test/test_quay_mirror_org.py +0 -82
  93. reconcile/test/test_quay_repos.py +0 -59
  94. reconcile/test/test_queries.py +0 -53
  95. reconcile/test/test_repo_owners.py +0 -47
  96. reconcile/test/test_requests_sender.py +0 -139
  97. reconcile/test/test_saasherder.py +0 -1611
  98. reconcile/test/test_saasherder_allowed_secret_paths.py +0 -125
  99. reconcile/test/test_secret_reader.py +0 -153
  100. reconcile/test/test_slack_base.py +0 -183
  101. reconcile/test/test_slack_usergroups.py +0 -785
  102. reconcile/test/test_sql_query.py +0 -316
  103. reconcile/test/test_status_board.py +0 -258
  104. reconcile/test/test_terraform_aws_route53.py +0 -29
  105. reconcile/test/test_terraform_cloudflare_dns.py +0 -117
  106. reconcile/test/test_terraform_cloudflare_resources.py +0 -408
  107. reconcile/test/test_terraform_cloudflare_users.py +0 -747
  108. reconcile/test/test_terraform_repo.py +0 -440
  109. reconcile/test/test_terraform_resources.py +0 -519
  110. reconcile/test/test_terraform_tgw_attachments.py +0 -1295
  111. reconcile/test/test_terraform_users.py +0 -152
  112. reconcile/test/test_terraform_vpc_peerings.py +0 -576
  113. reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1434
  114. reconcile/test/test_three_way_diff_strategy.py +0 -131
  115. reconcile/test/test_utils_jinja2.py +0 -130
  116. reconcile/test/test_vault_replication.py +0 -534
  117. reconcile/test/test_vault_utils.py +0 -47
  118. reconcile/test/test_version_bump.py +0 -18
  119. reconcile/test/test_vpc_peerings_validator.py +0 -194
  120. reconcile/test/test_wrong_region.py +0 -78
  121. release/__init__.py +0 -0
  122. release/test_version.py +0 -50
  123. release/version.py +0 -104
  124. tools/cli_commands/test/__init__.py +0 -0
  125. tools/cli_commands/test/conftest.py +0 -332
  126. tools/cli_commands/test/test_aws_cost_report.py +0 -258
  127. tools/cli_commands/test/test_cost_management_api.py +0 -326
  128. tools/cli_commands/test/test_gpg_encrypt.py +0 -235
  129. tools/cli_commands/test/test_openshift_cost_optimization_report.py +0 -255
  130. tools/cli_commands/test/test_openshift_cost_report.py +0 -295
  131. tools/cli_commands/test/test_util.py +0 -70
  132. tools/test/__init__.py +0 -0
  133. tools/test/conftest.py +0 -77
  134. tools/test/test_app_interface_metrics_exporter.py +0 -48
  135. tools/test/test_erv2.py +0 -80
  136. tools/test/test_get_container_images.py +0 -230
  137. tools/test/test_qontract_cli.py +0 -197
  138. tools/test/test_saas_promotion_state.py +0 -187
  139. tools/test/test_sd_app_sre_alert_report.py +0 -74
  140. tools/test/test_sre_checkpoints.py +0 -79
@@ -1,1611 +0,0 @@
1
- from collections.abc import (
2
- Callable,
3
- Iterable,
4
- MutableMapping,
5
- )
6
- from datetime import UTC, datetime, timedelta
7
- from typing import Any
8
- from unittest import TestCase
9
- from unittest.mock import (
10
- MagicMock,
11
- patch,
12
- )
13
-
14
- import pytest
15
- import yaml
16
- from github import (
17
- Github,
18
- GithubException,
19
- )
20
- from pydantic import BaseModel
21
-
22
- from reconcile.gql_definitions.common.saas_files import (
23
- SaasResourceTemplateTargetImageV1,
24
- SaasResourceTemplateTargetPromotionV1,
25
- SaasResourceTemplateTargetV2_SaasSecretParametersV1,
26
- )
27
- from reconcile.typed_queries.saas_files import (
28
- SaasFile,
29
- SaasResourceTemplate,
30
- )
31
- from reconcile.utils.jjb_client import JJB
32
- from reconcile.utils.openshift_resource import ResourceInventory
33
- from reconcile.utils.promotion_state import PromotionData
34
- from reconcile.utils.saasherder import SaasHerder
35
- from reconcile.utils.saasherder.interfaces import SaasFile as SaasFileInterface
36
- from reconcile.utils.saasherder.models import (
37
- Channel,
38
- Promotion,
39
- TriggerSpecContainerImage,
40
- TriggerSpecMovingCommit,
41
- TriggerSpecUpstreamJob,
42
- )
43
- from reconcile.utils.secret_reader import SecretReaderBase
44
-
45
- from .fixtures import Fixtures
46
-
47
-
48
- class MockJJB:
49
- def __init__(self, data: dict[str, list[dict]]) -> None:
50
- self.jobs = data
51
-
52
- def get_all_jobs(self, job_types: Iterable[str]) -> dict[str, list[dict]]:
53
- return self.jobs
54
-
55
- @staticmethod
56
- def get_repo_url(job: dict[str, Any]) -> str:
57
- return JJB.get_repo_url(job)
58
-
59
- @staticmethod
60
- def get_ref(job: dict[str, Any]) -> str:
61
- return JJB.get_ref(job)
62
-
63
-
64
- class MockSecretReader(SecretReaderBase):
65
- """
66
- Read secrets from a config file
67
- """
68
-
69
- def _read(
70
- self, path: str, field: str, format: str | None, version: int | None
71
- ) -> str:
72
- return "secret"
73
-
74
- def _read_all(
75
- self, path: str, field: str, format: str | None, version: int | None
76
- ) -> dict[str, str]:
77
- return {"param": "secret"}
78
-
79
-
80
- @pytest.fixture()
81
- def inject_gql_class_factory(
82
- request: pytest.FixtureRequest,
83
- gql_class_factory: Callable[..., SaasFile],
84
- ) -> None:
85
- def _gql_class_factory(
86
- self: Any,
87
- klass: type[BaseModel],
88
- data: MutableMapping[str, Any] | None = None,
89
- ) -> BaseModel:
90
- return gql_class_factory(klass, data)
91
-
92
- request.cls.gql_class_factory = _gql_class_factory
93
-
94
-
95
- @pytest.mark.usefixtures("inject_gql_class_factory")
96
- class TestSaasFileValid(TestCase):
97
- def setUp(self) -> None:
98
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
99
- SaasFile, Fixtures("saasherder").get_anymarkup("saas.gql.yml")
100
- )
101
- jjb_mock_data = {
102
- "ci": [
103
- {
104
- "name": "job",
105
- "properties": [
106
- {
107
- "github": {
108
- "url": "https://github.com/app-sre/test-saas-deployments"
109
- }
110
- }
111
- ],
112
- "scm": [{"git": {"branches": ["main"]}}],
113
- },
114
- {
115
- "name": "job",
116
- "properties": [
117
- {
118
- "github": {
119
- "url": "https://github.com/app-sre/test-saas-deployments"
120
- }
121
- }
122
- ],
123
- "scm": [{"git": {"branches": ["master"]}}],
124
- },
125
- ]
126
- }
127
- self.jjb = MockJJB(jjb_mock_data)
128
-
129
- def test_check_saas_file_env_combo_unique(self) -> None:
130
- saasherder = SaasHerder(
131
- [self.saas_file],
132
- secret_reader=MockSecretReader(),
133
- thread_pool_size=1,
134
- integration="",
135
- integration_version="",
136
- hash_length=7,
137
- repo_url="https://repo-url.com",
138
- validate=True,
139
- )
140
- self.assertTrue(saasherder.valid)
141
-
142
- def test_check_saas_file_env_combo_not_unique(self) -> None:
143
- self.saas_file.name = "long-name-which-is-too-long-to-produce-unique-combo"
144
- saasherder = SaasHerder(
145
- [self.saas_file],
146
- secret_reader=MockSecretReader(),
147
- thread_pool_size=1,
148
- integration="",
149
- integration_version="",
150
- hash_length=7,
151
- repo_url="https://repo-url.com",
152
- validate=True,
153
- )
154
-
155
- self.assertFalse(saasherder.valid)
156
-
157
- def test_saas_file_auto_promotion_used_with_commit_sha(self) -> None:
158
- self.saas_file.resource_templates[0].targets[
159
- 1
160
- ].ref = "1234567890123456789012345678901234567890"
161
- self.saas_file.resource_templates[0].targets[
162
- 1
163
- ].promotion = SaasResourceTemplateTargetPromotionV1(
164
- auto=True,
165
- publish=None,
166
- subscribe=None,
167
- redeployOnPublisherConfigChange=None,
168
- promotion_data=None,
169
- soakDays=0,
170
- schedule="* * * * *",
171
- )
172
- saasherder = SaasHerder(
173
- [self.saas_file],
174
- secret_reader=MockSecretReader(),
175
- thread_pool_size=1,
176
- integration="",
177
- integration_version="",
178
- hash_length=7,
179
- repo_url="https://repo-url.com",
180
- validate=True,
181
- )
182
-
183
- self.assertTrue(saasherder.valid)
184
-
185
- def test_saas_file_auto_promotion_not_used_with_commit_sha(self) -> None:
186
- self.saas_file.resource_templates[0].targets[1].ref = "main"
187
- self.saas_file.resource_templates[0].targets[
188
- 1
189
- ].promotion = SaasResourceTemplateTargetPromotionV1(
190
- auto=True,
191
- publish=None,
192
- subscribe=None,
193
- promotion_data=None,
194
- redeployOnPublisherConfigChange=None,
195
- soakDays=0,
196
- schedule="* * * * *",
197
- )
198
- saasherder = SaasHerder(
199
- [self.saas_file],
200
- secret_reader=MockSecretReader(),
201
- thread_pool_size=1,
202
- integration="",
203
- integration_version="",
204
- hash_length=7,
205
- repo_url="https://repo-url.com",
206
- validate=True,
207
- )
208
-
209
- self.assertFalse(saasherder.valid)
210
-
211
- def test_check_saas_file_upstream_not_used_with_commit_sha(self) -> None:
212
- saasherder = SaasHerder(
213
- [self.saas_file],
214
- secret_reader=MockSecretReader(),
215
- thread_pool_size=1,
216
- integration="",
217
- integration_version="",
218
- hash_length=7,
219
- repo_url="https://repo-url.com",
220
- validate=True,
221
- )
222
-
223
- self.assertTrue(saasherder.valid)
224
-
225
- def test_check_saas_file_upstream_used_with_commit_sha(self) -> None:
226
- self.saas_file.resource_templates[0].targets[
227
- 0
228
- ].ref = "2637b6c41bda7731b1bcaaf18b4a50d7c5e63e30"
229
- saasherder = SaasHerder(
230
- [self.saas_file],
231
- secret_reader=MockSecretReader(),
232
- thread_pool_size=1,
233
- integration="",
234
- integration_version="",
235
- hash_length=7,
236
- repo_url="https://repo-url.com",
237
- validate=True,
238
- )
239
-
240
- self.assertFalse(saasherder.valid)
241
-
242
- def test_dangling_target_config_hash(self) -> None:
243
- self.saas_file.resource_templates[0].targets[1].promotion.promotion_data[
244
- 0
245
- ].channel = "does-not-exist"
246
- saasherder = SaasHerder(
247
- [self.saas_file],
248
- secret_reader=MockSecretReader(),
249
- thread_pool_size=1,
250
- integration="",
251
- integration_version="",
252
- hash_length=7,
253
- repo_url="https://repo-url.com",
254
- validate=True,
255
- )
256
-
257
- self.assertFalse(saasherder.valid)
258
-
259
- def test_check_saas_file_upstream_used_with_image(self) -> None:
260
- self.saas_file.resource_templates[0].targets[
261
- 0
262
- ].image = SaasResourceTemplateTargetImageV1(**{
263
- "name": "image",
264
- "org": {"name": "org", "instance": {"url": "url"}},
265
- })
266
- saasherder = SaasHerder(
267
- [self.saas_file],
268
- secret_reader=MockSecretReader(),
269
- thread_pool_size=1,
270
- integration="",
271
- integration_version="",
272
- hash_length=7,
273
- repo_url="https://repo-url.com",
274
- validate=True,
275
- )
276
-
277
- self.assertFalse(saasherder.valid)
278
-
279
- def test_check_saas_file_image_used_with_commit_sha(self) -> None:
280
- self.saas_file.resource_templates[0].targets[
281
- 0
282
- ].ref = "2637b6c41bda7731b1bcaaf18b4a50d7c5e63e30"
283
- self.saas_file.resource_templates[0].targets[
284
- 0
285
- ].image = SaasResourceTemplateTargetImageV1(**{
286
- "name": "image",
287
- "org": {"name": "org", "instance": {"url": "url"}},
288
- })
289
- saasherder = SaasHerder(
290
- [self.saas_file],
291
- secret_reader=MockSecretReader(),
292
- thread_pool_size=1,
293
- integration="",
294
- integration_version="",
295
- hash_length=7,
296
- repo_url="https://repo-url.com",
297
- validate=True,
298
- )
299
-
300
- self.assertFalse(saasherder.valid)
301
-
302
- def test_validate_image_tag_not_equals_ref_valid(self) -> None:
303
- self.saas_file.resource_templates[0].targets[0].parameters = {
304
- "IMAGE_TAG": "2637b6c"
305
- }
306
- saasherder = SaasHerder(
307
- [self.saas_file],
308
- secret_reader=MockSecretReader(),
309
- thread_pool_size=1,
310
- integration="",
311
- integration_version="",
312
- hash_length=7,
313
- repo_url="https://repo-url.com",
314
- validate=True,
315
- )
316
-
317
- self.assertTrue(saasherder.valid)
318
-
319
- def test_validate_image_tag_not_equals_ref_invalid(self) -> None:
320
- self.saas_file.resource_templates[0].targets[
321
- 0
322
- ].ref = "2637b6c41bda7731b1bcaaf18b4a50d7c5e63e30"
323
- self.saas_file.resource_templates[0].targets[0].parameters = {
324
- "IMAGE_TAG": "2637b6c"
325
- }
326
- saasherder = SaasHerder(
327
- [self.saas_file],
328
- secret_reader=MockSecretReader(),
329
- thread_pool_size=1,
330
- integration="",
331
- integration_version="",
332
- hash_length=7,
333
- repo_url="https://repo-url.com",
334
- validate=True,
335
- )
336
-
337
- self.assertFalse(saasherder.valid)
338
-
339
- def test_validate_upstream_jobs_valid(self) -> None:
340
- saasherder = SaasHerder(
341
- [self.saas_file],
342
- secret_reader=MockSecretReader(),
343
- thread_pool_size=1,
344
- integration="",
345
- integration_version="",
346
- hash_length=7,
347
- repo_url="https://repo-url.com",
348
- validate=True,
349
- )
350
- saasherder.validate_upstream_jobs(self.jjb) # type: ignore
351
- self.assertTrue(saasherder.valid)
352
-
353
- def test_validate_upstream_jobs_invalid(self) -> None:
354
- saasherder = SaasHerder(
355
- [self.saas_file],
356
- secret_reader=MockSecretReader(),
357
- thread_pool_size=1,
358
- integration="",
359
- integration_version="",
360
- hash_length=7,
361
- repo_url="https://repo-url.com",
362
- validate=True,
363
- )
364
- jjb = MockJJB({"ci": []})
365
- saasherder.validate_upstream_jobs(jjb) # type: ignore
366
- self.assertFalse(saasherder.valid)
367
-
368
- def test_check_saas_file_promotion_same_source(self) -> None:
369
- raw_rts = [
370
- {
371
- "name": "rt_publisher",
372
- "url": "repo_publisher",
373
- "path": "path",
374
- "targets": [
375
- {
376
- "namespace": {
377
- "name": "ns",
378
- "app": {"name": "app"},
379
- "environment": {
380
- "name": "env1",
381
- },
382
- "cluster": {
383
- "name": "appsres03ue1",
384
- "serverUrl": "https://url",
385
- "internal": True,
386
- },
387
- },
388
- "parameters": "{}",
389
- "ref": "0000000000000",
390
- "promotion": {
391
- "publish": ["channel-1"],
392
- },
393
- }
394
- ],
395
- },
396
- {
397
- "name": "rt_subscriber",
398
- "url": "this-repo-will-not-match-the-publisher",
399
- "path": "path",
400
- "targets": [
401
- {
402
- "namespace": {
403
- "name": "ns2",
404
- "app": {"name": "app"},
405
- "environment": {
406
- "name": "env1",
407
- },
408
- "cluster": {
409
- "name": "appsres03ue1",
410
- "serverUrl": "https://url",
411
- "internal": True,
412
- },
413
- },
414
- "parameters": "{}",
415
- "ref": "0000000000000",
416
- "promotion": {
417
- "auto": "True",
418
- "subscribe": ["channel-1"],
419
- },
420
- }
421
- ],
422
- },
423
- ]
424
- rts = [
425
- self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
426
- SaasResourceTemplate, rt
427
- )
428
- for rt in raw_rts
429
- ]
430
- self.saas_file.resource_templates = rts
431
- saasherder = SaasHerder(
432
- [self.saas_file],
433
- secret_reader=MockSecretReader(),
434
- thread_pool_size=1,
435
- integration="",
436
- integration_version="",
437
- hash_length=7,
438
- repo_url="https://repo-url.com",
439
- validate=True,
440
- )
441
- self.assertFalse(saasherder.valid)
442
-
443
-
444
- @pytest.mark.usefixtures("inject_gql_class_factory")
445
- class TestGetMovingCommitsDiffSaasFile(TestCase):
446
- def setUp(self) -> None:
447
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
448
- SaasFile, Fixtures("saasherder").get_anymarkup("saas-trigger.gql.yml")
449
- )
450
-
451
- self.initiate_gh_patcher = patch.object(
452
- SaasHerder, "_initiate_github", autospec=True
453
- )
454
- self.get_commit_sha_patcher = patch.object(
455
- SaasHerder, "_get_commit_sha", autospec=True
456
- )
457
- self.initiate_gh = self.initiate_gh_patcher.start()
458
- self.get_commit_sha = self.get_commit_sha_patcher.start()
459
- self.maxDiff = None
460
-
461
- def tearDown(self) -> None:
462
- for p in (
463
- self.initiate_gh_patcher,
464
- self.get_commit_sha_patcher,
465
- ):
466
- p.stop()
467
-
468
- def test_get_moving_commits_diff_saas_file_all_fine(self) -> None:
469
- saasherder = SaasHerder(
470
- [self.saas_file],
471
- secret_reader=MockSecretReader(),
472
- thread_pool_size=1,
473
- integration="",
474
- integration_version="",
475
- hash_length=7,
476
- repo_url="https://repo-url.com",
477
- )
478
- saasherder.state = MagicMock()
479
- saasherder.state.get.return_value = "asha"
480
- self.get_commit_sha.return_value = "abcd4242"
481
- # 2nd target is the one that will be promoted
482
- expected = [
483
- TriggerSpecMovingCommit(
484
- saas_file_name=self.saas_file.name,
485
- env_name="App-SRE",
486
- timeout=None,
487
- pipelines_provider=self.saas_file.pipelines_provider,
488
- resource_template_name="test-saas-deployments",
489
- cluster_name="appsres03ue1",
490
- namespace_name="test-moving-commit-trigger",
491
- state_content="abcd4242",
492
- ref="main",
493
- reason=None,
494
- )
495
- ]
496
-
497
- self.assertEqual(
498
- saasherder.get_moving_commits_diff_saas_file(self.saas_file, True),
499
- expected,
500
- )
501
-
502
- def test_get_moving_commits_diff_saas_file_all_fine_include_trigger_trace(
503
- self,
504
- ) -> None:
505
- saasherder = SaasHerder(
506
- [self.saas_file],
507
- secret_reader=MockSecretReader(),
508
- thread_pool_size=1,
509
- integration="",
510
- integration_version="",
511
- hash_length=7,
512
- repo_url="https://repo-url.com",
513
- include_trigger_trace=True,
514
- )
515
-
516
- saasherder.state = MagicMock()
517
- saasherder.state.get.return_value = "asha"
518
- self.get_commit_sha.return_value = "abcd4242"
519
- expected = [
520
- TriggerSpecMovingCommit(
521
- saas_file_name=self.saas_file.name,
522
- env_name="App-SRE",
523
- timeout=None,
524
- pipelines_provider=self.saas_file.pipelines_provider,
525
- resource_template_name="test-saas-deployments",
526
- cluster_name="appsres03ue1",
527
- namespace_name="test-moving-commit-trigger",
528
- state_content="abcd4242",
529
- ref="main",
530
- reason="https://github.com/app-sre/test-saas-deployments/commit/abcd4242",
531
- ),
532
- ]
533
-
534
- self.assertEqual(
535
- saasherder.get_moving_commits_diff_saas_file(self.saas_file, True),
536
- expected,
537
- )
538
-
539
- def test_get_moving_commits_diff_saas_file_bad_sha1(self) -> None:
540
- saasherder = SaasHerder(
541
- [self.saas_file],
542
- secret_reader=MockSecretReader(),
543
- thread_pool_size=1,
544
- integration="",
545
- integration_version="",
546
- hash_length=7,
547
- repo_url="https://repo-url.com",
548
- )
549
- saasherder.state = MagicMock()
550
- saasherder.state.get.return_value = "asha"
551
- self.get_commit_sha.side_effect = GithubException(
552
- 401, "somedata", {"aheader": "avalue"}
553
- )
554
- # At least we don't crash!
555
- self.assertEqual(
556
- saasherder.get_moving_commits_diff_saas_file(self.saas_file, True), []
557
- )
558
-
559
-
560
- @pytest.mark.usefixtures("inject_gql_class_factory")
561
- class TestGetUpstreamJobsDiffSaasFile(TestCase):
562
- def setUp(self) -> None:
563
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
564
- SaasFile, Fixtures("saasherder").get_anymarkup("saas-trigger.gql.yml")
565
- )
566
- self.maxDiff = None
567
-
568
- def test_get_upstream_jobs_diff_saas_file_all_fine(self) -> None:
569
- state_content = {"number": 2, "result": "SUCCESS", "commit_sha": "abcd4242"}
570
- current_state = {"ci": {"job": [state_content]}}
571
- saasherder = SaasHerder(
572
- [self.saas_file],
573
- secret_reader=MockSecretReader(),
574
- thread_pool_size=1,
575
- integration="",
576
- integration_version="",
577
- hash_length=7,
578
- repo_url="https://repo-url.com",
579
- )
580
- saasherder.state = MagicMock()
581
- saasherder.state.get.return_value = {
582
- "number": 1,
583
- "result": "SUCCESS",
584
- "commit_sha": "4242efg",
585
- }
586
- expected = [
587
- TriggerSpecUpstreamJob(
588
- saas_file_name=self.saas_file.name,
589
- env_name="App-SRE-stage",
590
- timeout=None,
591
- pipelines_provider=self.saas_file.pipelines_provider,
592
- resource_template_name="test-saas-deployments",
593
- cluster_name="appsres03ue1",
594
- namespace_name="test-upstream-job-trigger",
595
- instance_name="ci",
596
- job_name="job",
597
- state_content=state_content,
598
- reason=None,
599
- )
600
- ]
601
-
602
- self.assertEqual(
603
- saasherder.get_upstream_jobs_diff_saas_file(
604
- self.saas_file, True, current_state
605
- ),
606
- expected,
607
- )
608
-
609
- def test_get_upstream_jobs_diff_saas_file_all_fine_include_trigger_trace(
610
- self,
611
- ) -> None:
612
- state_content = {"number": 2, "result": "SUCCESS", "commit_sha": "abcd4242"}
613
- current_state = {"ci": {"job": [state_content]}}
614
- saasherder = SaasHerder(
615
- [self.saas_file],
616
- secret_reader=MockSecretReader(),
617
- thread_pool_size=1,
618
- integration="",
619
- integration_version="",
620
- hash_length=7,
621
- repo_url="https://repo-url.com",
622
- include_trigger_trace=True,
623
- )
624
- saasherder.state = MagicMock()
625
- saasherder.state.get.return_value = {
626
- "number": 1,
627
- "result": "SUCCESS",
628
- "commit_sha": "4242efg",
629
- }
630
- expected = [
631
- TriggerSpecUpstreamJob(
632
- saas_file_name=self.saas_file.name,
633
- env_name="App-SRE-stage",
634
- timeout=None,
635
- pipelines_provider=self.saas_file.pipelines_provider,
636
- resource_template_name="test-saas-deployments",
637
- cluster_name="appsres03ue1",
638
- namespace_name="test-upstream-job-trigger",
639
- instance_name="ci",
640
- job_name="job",
641
- state_content=state_content,
642
- reason="https://github.com/app-sre/test-saas-deployments/commit/abcd4242 via https://jenkins.com/job/job/2",
643
- )
644
- ]
645
-
646
- self.assertEqual(
647
- saasherder.get_upstream_jobs_diff_saas_file(
648
- self.saas_file, True, current_state
649
- ),
650
- expected,
651
- )
652
-
653
-
654
- @pytest.mark.usefixtures("inject_gql_class_factory")
655
- class TestGetContainerImagesDiffSaasFile(TestCase):
656
- def setUp(self) -> None:
657
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
658
- SaasFile, Fixtures("saasherder").get_anymarkup("saas-trigger.gql.yml")
659
- )
660
-
661
- self.initiate_gh_patcher = patch.object(
662
- SaasHerder, "_initiate_github", autospec=True
663
- )
664
- self.get_commit_sha_patcher = patch.object(
665
- SaasHerder, "_get_commit_sha", autospec=True
666
- )
667
- self.get_image_patcher = patch.object(SaasHerder, "_get_image", autospec=True)
668
- self.initiate_gh = self.initiate_gh_patcher.start()
669
- self.get_commit_sha = self.get_commit_sha_patcher.start()
670
- self.get_image = self.get_image_patcher.start()
671
- self.maxDiff = None
672
-
673
- def tearDown(self) -> None:
674
- for p in (
675
- self.initiate_gh_patcher,
676
- self.get_commit_sha_patcher,
677
- self.get_image_patcher,
678
- ):
679
- p.stop()
680
-
681
- def test_get_container_images_diff_saas_file_all_fine(self) -> None:
682
- saasherder = SaasHerder(
683
- [self.saas_file],
684
- secret_reader=MockSecretReader(),
685
- thread_pool_size=1,
686
- integration="",
687
- integration_version="",
688
- hash_length=7,
689
- repo_url="https://repo-url.com",
690
- )
691
- saasherder.state = MagicMock()
692
- saasherder.state.get.return_value = "asha"
693
- self.get_commit_sha.return_value = "abcd4242"
694
- self.get_image.return_value = MagicMock()
695
- expected = [
696
- TriggerSpecContainerImage(
697
- saas_file_name=self.saas_file.name,
698
- env_name="App-SRE-stage",
699
- timeout=None,
700
- pipelines_provider=self.saas_file.pipelines_provider,
701
- resource_template_name="test-saas-deployments",
702
- cluster_name="appsres03ue1",
703
- namespace_name="test-image-trigger",
704
- image="quay.io/centos/centos",
705
- state_content="abcd424",
706
- reason=None,
707
- )
708
- ]
709
-
710
- self.assertEqual(
711
- saasherder.get_container_images_diff_saas_file(self.saas_file, True),
712
- expected,
713
- )
714
-
715
- def test_get_container_images_diff_saas_file_all_fine_include_trigger_trace(
716
- self,
717
- ) -> None:
718
- saasherder = SaasHerder(
719
- [self.saas_file],
720
- secret_reader=MockSecretReader(),
721
- thread_pool_size=1,
722
- integration="",
723
- integration_version="",
724
- hash_length=7,
725
- repo_url="https://repo-url.com",
726
- include_trigger_trace=True,
727
- )
728
- saasherder.state = MagicMock()
729
- saasherder.state.get.return_value = "asha"
730
- self.get_commit_sha.return_value = "abcd4242"
731
- self.get_image.return_value = MagicMock()
732
- expected = [
733
- TriggerSpecContainerImage(
734
- saas_file_name=self.saas_file.name,
735
- env_name="App-SRE-stage",
736
- timeout=None,
737
- pipelines_provider=self.saas_file.pipelines_provider,
738
- resource_template_name="test-saas-deployments",
739
- cluster_name="appsres03ue1",
740
- namespace_name="test-image-trigger",
741
- image="quay.io/centos/centos",
742
- state_content="abcd424",
743
- reason="https://github.com/app-sre/test-saas-deployments/commit/abcd4242 build quay.io/centos/centos:abcd424",
744
- )
745
- ]
746
-
747
- self.assertEqual(
748
- saasherder.get_container_images_diff_saas_file(self.saas_file, True),
749
- expected,
750
- )
751
-
752
-
753
- @pytest.mark.usefixtures("inject_gql_class_factory")
754
- class TestGetArchiveInfo(TestCase):
755
- def setUp(self) -> None:
756
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
757
- SaasFile, Fixtures("saasherder").get_anymarkup("saas-trigger.gql.yml")
758
- )
759
- self.initiate_gh_patcher = patch.object(
760
- SaasHerder, "_initiate_github", autospec=True
761
- )
762
- self.initiate_gh = self.initiate_gh_patcher.start()
763
- self.maxDiff = None
764
-
765
- def tearDown(self) -> None:
766
- for p in (self.initiate_gh_patcher,):
767
- p.stop()
768
-
769
- def test_get_gitlab_archive_info(self) -> None:
770
- trigger_reason = "https://gitlab.com/app-sre/test-saas-deployments/commit/abcd4242 via https://jenkins.com/job/job/2"
771
- saasherder = SaasHerder(
772
- [self.saas_file],
773
- secret_reader=MockSecretReader(),
774
- thread_pool_size=1,
775
- integration="",
776
- integration_version="",
777
- hash_length=7,
778
- repo_url="https://repo-url.com",
779
- include_trigger_trace=True,
780
- )
781
- file_name = "app-sre-test-saas-deployments-abcd4242.tar.gz"
782
- archive_url = f"https://gitlab.com/app-sre/test-saas-deployments/-/archive/abcd4242/{file_name}"
783
- self.assertEqual(
784
- saasherder.get_archive_info(self.saas_file, trigger_reason),
785
- (file_name, archive_url),
786
- )
787
-
788
- def test_get_github_archive_info(self) -> None:
789
- trigger_reason = "https://github.com/app-sre/test-saas-deployments/commit/abcd4242 build quay.io/centos/centos:abcd424"
790
- saasherder = SaasHerder(
791
- [self.saas_file],
792
- secret_reader=MockSecretReader(),
793
- thread_pool_size=1,
794
- integration="",
795
- integration_version="",
796
- hash_length=7,
797
- repo_url="https://repo-url.com",
798
- include_trigger_trace=True,
799
- )
800
- file_name = "app-sre-test-saas-deployments-abcd4242.tar.gz"
801
- archive_url = "https://api.github.com/repos/app-sre/test-saas-deployments/tarball/abcd4242"
802
- self.initiate_gh.return_value.get_repo.return_value.get_archive_link.return_value = archive_url
803
- self.assertEqual(
804
- saasherder.get_archive_info(self.saas_file, trigger_reason),
805
- (file_name, archive_url),
806
- )
807
-
808
-
809
- @pytest.mark.usefixtures("inject_gql_class_factory")
810
- class TestPopulateDesiredState(TestCase):
811
- def setUp(self) -> None:
812
- self.fxts = Fixtures("saasherder_populate_desired")
813
- raw_saas_file = self.fxts.get_anymarkup("saas_remote_openshift_template.yaml")
814
- del raw_saas_file["_placeholders"]
815
- saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
816
- SaasFile, raw_saas_file
817
- )
818
- self.saasherder = SaasHerder(
819
- [saas_file],
820
- secret_reader=MockSecretReader(),
821
- thread_pool_size=1,
822
- integration="",
823
- integration_version="",
824
- hash_length=7,
825
- repo_url="https://repo-url.com",
826
- )
827
-
828
- # Mock GitHub interactions.
829
- self.initiate_gh_patcher = patch.object(
830
- SaasHerder,
831
- "_initiate_github",
832
- autospec=True,
833
- return_value=None,
834
- )
835
- self.get_file_contents_patcher = patch.object(
836
- SaasHerder,
837
- "_get_file_contents",
838
- wraps=self.fake_get_file_contents,
839
- )
840
- self.initiate_gh_patcher.start()
841
- self.get_file_contents_patcher.start()
842
-
843
- # Mock image checking.
844
- self.get_check_images_patcher = patch.object(
845
- SaasHerder,
846
- "_check_images",
847
- autospec=True,
848
- return_value=None,
849
- )
850
- self.get_check_images_patcher.start()
851
-
852
- def fake_get_file_contents(
853
- self, url: str, path: str, ref: str, github: Github
854
- ) -> tuple[Any, str]:
855
- self.assertEqual("https://github.com/rhobs/configuration", url)
856
-
857
- content = self.fxts.get(ref + (path.replace("/", "_")))
858
- return yaml.safe_load(content), ref
859
-
860
- def tearDown(self) -> None:
861
- for p in (
862
- self.initiate_gh_patcher,
863
- self.get_file_contents_patcher,
864
- self.get_check_images_patcher,
865
- ):
866
- p.stop()
867
-
868
- def test_populate_desired_state_cases(self) -> None:
869
- ri = ResourceInventory()
870
- for resource_type in (
871
- "Deployment",
872
- "Service",
873
- "ConfigMap",
874
- ):
875
- ri.initialize_resource_type("stage-1", "yolo-stage", resource_type)
876
- ri.initialize_resource_type("prod-1", "yolo", resource_type)
877
- self.saasherder.populate_desired_state(ri)
878
-
879
- cnt = 0
880
- for cluster, namespace, resource_type, data in ri:
881
- for _, d_item in data["desired"].items():
882
- expected = yaml.safe_load(
883
- self.fxts.get(
884
- f"expected_{cluster}_{namespace}_{resource_type}.json",
885
- )
886
- )
887
- self.assertEqual(expected, d_item.body)
888
- cnt += 1
889
-
890
- self.assertEqual(5, cnt, "expected 5 resources, found less")
891
- self.assertEqual(self.saasherder.promotions, [None, None, None, None])
892
-
893
-
894
- @pytest.mark.usefixtures("inject_gql_class_factory")
895
- class TestCollectRepoUrls(TestCase):
896
- def setUp(self) -> None:
897
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
898
- SaasFile, Fixtures("saasherder").get_anymarkup("saas.gql.yml")
899
- )
900
-
901
- def test_collect_repo_urls(self) -> None:
902
- repo_url = "https://github.com/app-sre/test-saas-deployments"
903
- saasherder = SaasHerder(
904
- [self.saas_file],
905
- secret_reader=MockSecretReader(),
906
- thread_pool_size=1,
907
- integration="",
908
- integration_version="",
909
- hash_length=7,
910
- repo_url="https://repo-url.com",
911
- )
912
- self.assertEqual({repo_url}, saasherder.repo_urls)
913
-
914
-
915
- @pytest.mark.usefixtures("inject_gql_class_factory")
916
- class TestCollectImagePatterns(TestCase):
917
- def setUp(self) -> None:
918
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
919
- SaasFile, Fixtures("saasherder").get_anymarkup("saas.gql.yml")
920
- )
921
-
922
- def test_collect_image_patterns(self) -> None:
923
- image_pattern = "quay.io/centos/centos:centos8"
924
- saasherder = SaasHerder(
925
- [self.saas_file],
926
- secret_reader=MockSecretReader(),
927
- thread_pool_size=1,
928
- integration="",
929
- integration_version="",
930
- hash_length=7,
931
- repo_url="https://repo-url.com",
932
- )
933
- self.assertEqual({image_pattern}, saasherder.image_patterns)
934
-
935
-
936
- @pytest.mark.usefixtures("inject_gql_class_factory")
937
- class TestGetSaasFileAttribute(TestCase):
938
- def setUp(self) -> None:
939
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
940
- SaasFile, Fixtures("saasherder").get_anymarkup("saas.gql.yml")
941
- )
942
-
943
- def test_no_such_attribute(self) -> None:
944
- saasherder = SaasHerder(
945
- [self.saas_file],
946
- secret_reader=MockSecretReader(),
947
- thread_pool_size=1,
948
- integration="",
949
- integration_version="",
950
- hash_length=7,
951
- repo_url="https://repo-url.com",
952
- )
953
- att = saasherder._get_saas_file_feature_enabled("no_such_attribute")
954
- self.assertEqual(att, None)
955
-
956
- def test_attribute_none(self) -> None:
957
- saasherder = SaasHerder(
958
- [self.saas_file],
959
- secret_reader=MockSecretReader(),
960
- thread_pool_size=1,
961
- integration="",
962
- integration_version="",
963
- hash_length=7,
964
- repo_url="https://repo-url.com",
965
- )
966
- att = saasherder._get_saas_file_feature_enabled("takeover")
967
- self.assertEqual(att, None)
968
-
969
- def test_attribute_not_none(self) -> None:
970
- saasherder = SaasHerder(
971
- [self.saas_file],
972
- secret_reader=MockSecretReader(),
973
- thread_pool_size=1,
974
- integration="",
975
- integration_version="",
976
- hash_length=7,
977
- repo_url="https://repo-url.com",
978
- )
979
- att = saasherder._get_saas_file_feature_enabled("publish_job_logs")
980
- self.assertEqual(att, True)
981
-
982
- def test_attribute_none_with_default(self) -> None:
983
- saasherder = SaasHerder(
984
- [self.saas_file],
985
- secret_reader=MockSecretReader(),
986
- thread_pool_size=1,
987
- integration="",
988
- integration_version="",
989
- hash_length=7,
990
- repo_url="https://repo-url.com",
991
- )
992
- att = saasherder._get_saas_file_feature_enabled("no_such_att", default=True)
993
- self.assertEqual(att, True)
994
-
995
- def test_attribute_not_none_with_default(self) -> None:
996
- saasherder = SaasHerder(
997
- [self.saas_file],
998
- secret_reader=MockSecretReader(),
999
- thread_pool_size=1,
1000
- integration="",
1001
- integration_version="",
1002
- hash_length=7,
1003
- repo_url="https://repo-url.com",
1004
- )
1005
- att = saasherder._get_saas_file_feature_enabled(
1006
- "publish_job_logs", default=False
1007
- )
1008
- self.assertEqual(att, True)
1009
-
1010
- def test_attribute_multiple_saas_files_return_false(self) -> None:
1011
- saasherder = SaasHerder(
1012
- [self.saas_file, self.saas_file],
1013
- secret_reader=MockSecretReader(),
1014
- thread_pool_size=1,
1015
- integration="",
1016
- integration_version="",
1017
- hash_length=7,
1018
- repo_url="https://repo-url.com",
1019
- )
1020
- self.assertFalse(saasherder._get_saas_file_feature_enabled("publish_job_logs"))
1021
-
1022
- def test_attribute_multiple_saas_files_with_default_return_false(self) -> None:
1023
- saasherder = SaasHerder(
1024
- [self.saas_file, self.saas_file],
1025
- secret_reader=MockSecretReader(),
1026
- thread_pool_size=1,
1027
- integration="",
1028
- integration_version="",
1029
- hash_length=7,
1030
- repo_url="https://repo-url.com",
1031
- )
1032
- att = saasherder._get_saas_file_feature_enabled("attrib", default=True)
1033
- self.assertFalse(att)
1034
-
1035
-
1036
- @pytest.mark.usefixtures("inject_gql_class_factory")
1037
- class TestConfigHashPromotionsValidation(TestCase):
1038
- """TestCase to test SaasHerder promotions validation. SaasHerder is
1039
- initialized with ResourceInventory population. Like is done in
1040
- openshift-saas-deploy"""
1041
-
1042
- cluster: str
1043
- namespace: str
1044
- fxt: Any
1045
- template: Any
1046
-
1047
- @classmethod
1048
- def setUpClass(cls) -> None:
1049
- cls.fxt = Fixtures("saasherder")
1050
- cls.cluster = "test-cluster"
1051
- cls.template = cls.fxt.get_anymarkup("template_1.yml")
1052
-
1053
- def setUp(self) -> None:
1054
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
1055
- SaasFile, Fixtures("saasherder").get_anymarkup("saas-multi-channel.gql.yml")
1056
- )
1057
- self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
1058
- self.state_mock = self.state_patcher.start().return_value
1059
-
1060
- self.ig_patcher = patch.object(SaasHerder, "_initiate_github", autospec=True)
1061
- self.ig_patcher.start()
1062
-
1063
- self.image_auth_patcher = patch.object(SaasHerder, "_initiate_image_auth")
1064
- self.image_auth_patcher.start()
1065
-
1066
- self.gfc_patcher = patch.object(SaasHerder, "_get_file_contents", autospec=True)
1067
- gfc_mock = self.gfc_patcher.start()
1068
- gfc_mock.return_value = (self.template, "ahash")
1069
-
1070
- self.deploy_current_state_fxt = self.fxt.get_anymarkup("saas_deploy.state.json")
1071
-
1072
- self.post_deploy_current_state_fxt = self.fxt.get_anymarkup(
1073
- "saas_post_deploy.state.json"
1074
- )
1075
-
1076
- self.saasherder = SaasHerder(
1077
- [self.saas_file],
1078
- secret_reader=MockSecretReader(),
1079
- thread_pool_size=1,
1080
- state=self.state_mock,
1081
- integration="",
1082
- integration_version="",
1083
- hash_length=24,
1084
- repo_url="https://repo-url.com",
1085
- all_saas_files=[self.saas_file],
1086
- )
1087
-
1088
- # IMPORTANT: Populating desired state modify self.saas_files within
1089
- # saasherder object.
1090
- self.ri = ResourceInventory()
1091
- for ns in ["test-ns-publisher", "test-ns-subscriber"]:
1092
- for kind in ["Service", "Deployment"]:
1093
- self.ri.initialize_resource_type(self.cluster, ns, kind)
1094
-
1095
- self.saasherder.populate_desired_state(self.ri)
1096
- if self.ri.has_error_registered():
1097
- raise Exception("Errors registered in Resourceinventory")
1098
-
1099
- def tearDown(self) -> None:
1100
- self.state_patcher.stop()
1101
- self.ig_patcher.stop()
1102
- self.gfc_patcher.stop()
1103
-
1104
- def test_promotion_state_config_hash_match_validates(self) -> None:
1105
- """A promotion is valid if the parent target config_hash set in
1106
- the state is equal to the one set in the subscriber target
1107
- promotion data. This is the happy path.
1108
- """
1109
- publisher_state = {
1110
- "success": True,
1111
- "saas_file": self.saas_file.name,
1112
- "target_config_hash": "ed2af38cf21f268c",
1113
- "has_succeeded_once": True,
1114
- }
1115
- self.state_mock.get.return_value = publisher_state
1116
- result = self.saasherder.validate_promotions()
1117
- self.assertTrue(result)
1118
-
1119
- def test_promotion_state_config_hash_not_match_no_validates(self) -> None:
1120
- """Promotion is not valid if the parent target config hash set in
1121
- the state does not match with the one set in the subscriber target
1122
- promotion_data. This could happen if the parent target has run again
1123
- with the same ref before the subscriber target promotion MR is merged.
1124
- """
1125
- publisher_states = [
1126
- {
1127
- "success": True,
1128
- "saas_file": self.saas_file.name,
1129
- "target_config_hash": "ed2af38cf21f268c",
1130
- "has_succeeded_once": True,
1131
- },
1132
- {
1133
- "success": True,
1134
- "saas_file": self.saas_file.name,
1135
- "target_config_hash": "ed2af38cf21f268c",
1136
- "has_succeeded_once": True,
1137
- },
1138
- {
1139
- "success": True,
1140
- "saas_file": self.saas_file.name,
1141
- "target_config_hash": "will_not_match",
1142
- "has_succeeded_once": True,
1143
- },
1144
- ]
1145
- self.state_mock.get.side_effect = publisher_states
1146
- result = self.saasherder.validate_promotions()
1147
- self.assertFalse(result)
1148
-
1149
- def test_promotion_without_state_config_hash_validates(self) -> None:
1150
- """Existent states won't have promotion data. If there is an ongoing
1151
- promotion, this ensures it will happen.
1152
- """
1153
- publisher_state = {
1154
- "success": True,
1155
- }
1156
- self.state_mock.get.return_value = publisher_state
1157
- result = self.saasherder.validate_promotions()
1158
- self.assertTrue(result)
1159
-
1160
- def test_promotion_without_promotion_data_validates(self) -> None:
1161
- """A manual promotion might be required, subsribed targets without
1162
- promotion_data should validate if the parent target job has succed
1163
- with the same ref.
1164
- """
1165
- publisher_state = {
1166
- "success": True,
1167
- "saas_file": self.saas_file.name,
1168
- "target_config_hash": "whatever",
1169
- "has_succeeded_once": True,
1170
- }
1171
-
1172
- self.assertEqual(len(self.saasherder.promotions), 4)
1173
- self.assertIsNotNone(self.saasherder.promotions[3])
1174
- # Remove promotion_data on the promoted target
1175
- self.saasherder.promotions[3].promotion_data = None # type: ignore
1176
-
1177
- self.state_mock.get.return_value = publisher_state
1178
- result = self.saasherder.validate_promotions()
1179
- self.assertTrue(result)
1180
-
1181
- def test_promotion_state_re_deployment_failed(self) -> None:
1182
- """A promotion is valid if it has ever succeeded for that ref.
1183
- Re-deployment results should be neglected for validation.
1184
- """
1185
- publisher_state = {
1186
- # Latest state is failed ...
1187
- "success": False,
1188
- "saas_file": self.saas_file.name,
1189
- "target_config_hash": "ed2af38cf21f268c",
1190
- # ... however, the deployment succeeded sometime before once.
1191
- "has_succeeded_once": True,
1192
- }
1193
- self.state_mock.get.return_value = publisher_state
1194
- result = self.saasherder.validate_promotions()
1195
- self.assertTrue(result)
1196
-
1197
- def test_promotion_state_never_successfully_deployed(self) -> None:
1198
- """A promotion is invalid, if it never succeeded before."""
1199
- publisher_state = {
1200
- # Latest state is failed ...
1201
- "success": False,
1202
- "saas_file": self.saas_file.name,
1203
- "target_config_hash": "ed2af38cf21f268c",
1204
- # ... and it never succeeded once before.
1205
- "has_succeeded_once": False,
1206
- }
1207
- self.state_mock.get.return_value = publisher_state
1208
- result = self.saasherder.validate_promotions()
1209
- self.assertFalse(result)
1210
-
1211
- def test_promotion_state_success_backwards_compatibility_success(self) -> None:
1212
- """Not all states have the has_succeeded_once attribute yet.
1213
- If it doesnt exist, we should always fall back to latest success state.
1214
- """
1215
- publisher_state = {
1216
- "success": True,
1217
- "saas_file": self.saas_file.name,
1218
- "target_config_hash": "ed2af38cf21f268c",
1219
- }
1220
- self.state_mock.get.return_value = publisher_state
1221
- result = self.saasherder.validate_promotions()
1222
- self.assertTrue(result)
1223
-
1224
- def test_promotion_state_success_backwards_compatibility_fail(self) -> None:
1225
- """Not all states have the has_succeeded_once attribute yet.
1226
- If it doesnt exist, we should always fall back to latest success state.
1227
- """
1228
- publisher_state = {
1229
- "success": False,
1230
- "saas_file": self.saas_file.name,
1231
- "target_config_hash": "ed2af38cf21f268c",
1232
- }
1233
- self.state_mock.get.return_value = publisher_state
1234
- result = self.saasherder.validate_promotions()
1235
- self.assertFalse(result)
1236
-
1237
-
1238
- @pytest.mark.usefixtures("inject_gql_class_factory")
1239
- class TestSoakDays(TestCase):
1240
- """TestCase to test SaasHerder soakDays gate. SaasHerder is
1241
- initialized with ResourceInventory population. Like is done in
1242
- openshift-saas-deploy"""
1243
-
1244
- cluster: str
1245
- namespace: str
1246
- fxt: Any
1247
- template: Any
1248
-
1249
- @classmethod
1250
- def setUpClass(cls) -> None:
1251
- cls.fxt = Fixtures("saasherder")
1252
- cls.cluster = "test-cluster"
1253
- cls.template = cls.fxt.get_anymarkup("template_1.yml")
1254
-
1255
- def setUp(self) -> None:
1256
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
1257
- SaasFile, Fixtures("saasherder").get_anymarkup("saas-soak-days.gql.yml")
1258
- )
1259
- self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
1260
- self.state_mock = self.state_patcher.start().return_value
1261
-
1262
- self.ig_patcher = patch.object(SaasHerder, "_initiate_github", autospec=True)
1263
- self.ig_patcher.start()
1264
-
1265
- self.image_auth_patcher = patch.object(SaasHerder, "_initiate_image_auth")
1266
- self.image_auth_patcher.start()
1267
-
1268
- self.gfc_patcher = patch.object(SaasHerder, "_get_file_contents", autospec=True)
1269
- gfc_mock = self.gfc_patcher.start()
1270
- gfc_mock.return_value = (self.template, "ahash")
1271
-
1272
- self.deploy_current_state_fxt = self.fxt.get_anymarkup("saas_deploy.state.json")
1273
-
1274
- self.post_deploy_current_state_fxt = self.fxt.get_anymarkup(
1275
- "saas_post_deploy.state.json"
1276
- )
1277
-
1278
- self.saasherder = SaasHerder(
1279
- [self.saas_file],
1280
- secret_reader=MockSecretReader(),
1281
- thread_pool_size=1,
1282
- state=self.state_mock,
1283
- integration="",
1284
- integration_version="",
1285
- hash_length=24,
1286
- repo_url="https://repo-url.com",
1287
- all_saas_files=[self.saas_file],
1288
- )
1289
-
1290
- # IMPORTANT: Populating desired state modify self.saas_files within
1291
- # saasherder object.
1292
- self.ri = ResourceInventory()
1293
- for ns in ["test-ns-publisher", "test-ns-subscriber"]:
1294
- for kind in ["Service", "Deployment"]:
1295
- self.ri.initialize_resource_type(self.cluster, ns, kind)
1296
-
1297
- self.saasherder.populate_desired_state(self.ri)
1298
- if self.ri.has_error_registered():
1299
- raise Exception("Errors registered in Resourceinventory")
1300
-
1301
- def tearDown(self) -> None:
1302
- self.state_patcher.stop()
1303
- self.ig_patcher.stop()
1304
- self.gfc_patcher.stop()
1305
-
1306
- def test_soak_days_passed(self) -> None:
1307
- """A promotion is valid if the parent targets accumulated soak_days
1308
- passed. We have a soakDays setting of 2 days.
1309
- """
1310
- publisher_states = [
1311
- {
1312
- "success": True,
1313
- "saas_file": self.saas_file.name,
1314
- "target_config_hash": "ed2af38cf21f268c",
1315
- # the deployment happened 1 hour ago
1316
- "check_in": str(datetime.now(UTC) - timedelta(hours=1)),
1317
- },
1318
- {
1319
- "success": True,
1320
- "saas_file": self.saas_file.name,
1321
- "target_config_hash": "ed2af38cf21f268c",
1322
- # the deployment happened 47 hours ago
1323
- "check_in": str(datetime.now(UTC) - timedelta(hours=47)),
1324
- },
1325
- ]
1326
- self.state_mock.get.side_effect = publisher_states
1327
- result = self.saasherder.validate_promotions()
1328
- self.assertTrue(result)
1329
-
1330
- def test_soak_days_not_passed(self) -> None:
1331
- """A promotion is valid if the parent target accumulated soak_days
1332
- passed. We have a soakDays setting of 2 days.
1333
- """
1334
- publisher_states = [
1335
- {
1336
- "success": True,
1337
- "saas_file": self.saas_file.name,
1338
- "target_config_hash": "ed2af38cf21f268c",
1339
- # the deployment happened 12 hours ago
1340
- "check_in": str(datetime.now(UTC) - timedelta(hours=12)),
1341
- },
1342
- {
1343
- "success": True,
1344
- "saas_file": self.saas_file.name,
1345
- "target_config_hash": "ed2af38cf21f268c",
1346
- # the deployment happened 1 hour ago
1347
- "check_in": str(datetime.now(UTC) - timedelta(hours=1)),
1348
- },
1349
- ]
1350
- self.state_mock.get.side_effect = publisher_states
1351
- result = self.saasherder.validate_promotions()
1352
- self.assertFalse(result)
1353
-
1354
-
1355
- @pytest.mark.usefixtures("inject_gql_class_factory")
1356
- class TestConfigHashTrigger(TestCase):
1357
- """TestCase to test Openshift SAAS deploy configs trigger. SaasHerder is
1358
- initialized WITHOUT ResourceInventory population. Like is done in the
1359
- config changes trigger"""
1360
-
1361
- cluster: str
1362
- namespace: str
1363
- fxt: Any
1364
- template: Any
1365
-
1366
- @classmethod
1367
- def setUpClass(cls) -> None:
1368
- cls.fxt = Fixtures("saasherder")
1369
- cls.cluster = "test-cluster"
1370
-
1371
- def setUp(self) -> None:
1372
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
1373
- SaasFile, Fixtures("saasherder").get_anymarkup("saas.gql.yml")
1374
- )
1375
- self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
1376
- self.state_mock = self.state_patcher.start().return_value
1377
-
1378
- self.deploy_current_state_fxt = self.fxt.get_anymarkup("saas_deploy.state.json")
1379
-
1380
- self.post_deploy_current_state_fxt = self.fxt.get_anymarkup(
1381
- "saas_post_deploy.state.json"
1382
- )
1383
-
1384
- self.state_mock.get.side_effect = [
1385
- self.deploy_current_state_fxt,
1386
- self.post_deploy_current_state_fxt,
1387
- ]
1388
-
1389
- self.saasherder = SaasHerder(
1390
- [self.saas_file],
1391
- secret_reader=MockSecretReader(),
1392
- thread_pool_size=1,
1393
- state=self.state_mock,
1394
- integration="",
1395
- integration_version="",
1396
- hash_length=24,
1397
- repo_url="https://repo-url.com",
1398
- all_saas_files=[self.saas_file],
1399
- )
1400
-
1401
- def tearDown(self) -> None:
1402
- self.state_patcher.stop()
1403
-
1404
- def test_same_configs_do_not_trigger(self) -> None:
1405
- """Ensures that if the same config is found, no job is triggered
1406
- current Config is fetched from the state
1407
- """
1408
- trigger_specs = self.saasherder.get_configs_diff_saas_file(self.saas_file)
1409
- self.assertListEqual(trigger_specs, [])
1410
-
1411
- def test_config_hash_change_do_trigger(self) -> None:
1412
- """Ensures a new job is triggered if the parent config hash changes"""
1413
- self.saasherder.saas_files[0].resource_templates[0].targets[ # type: ignore
1414
- 1
1415
- ].promotion.promotion_data[0].data[0].target_config_hash = "Changed"
1416
- trigger_specs = self.saasherder.get_configs_diff_saas_file(self.saas_file)
1417
- self.assertEqual(len(trigger_specs), 1)
1418
-
1419
- def test_non_existent_config_triggers(self) -> None:
1420
- self.state_mock.get.side_effect = [self.deploy_current_state_fxt, None]
1421
- trigger_specs = self.saasherder.get_configs_diff_saas_file(self.saas_file)
1422
- self.assertEqual(len(trigger_specs), 1)
1423
-
1424
-
1425
- class TestRemoveNoneAttributes(TestCase):
1426
- def testSimpleDict(self) -> None:
1427
- input = {"a": 1, "b": {}, "d": None, "e": {"aa": "aa", "bb": None}}
1428
- expected = {"a": 1, "b": {}, "e": {"aa": "aa"}}
1429
- res = SaasHerder.remove_none_values(input)
1430
- self.assertEqual(res, expected)
1431
-
1432
- def testNoneValue(self) -> None:
1433
- input = None
1434
- expected: dict[Any, Any] = {}
1435
- res = SaasHerder.remove_none_values(input)
1436
- self.assertEqual(res, expected)
1437
-
1438
-
1439
- @pytest.mark.usefixtures("inject_gql_class_factory")
1440
- class TestPromotionBlockedHoxfixVersions(TestCase):
1441
- def setUp(self) -> None:
1442
- self.saas_file = self.gql_class_factory( # type: ignore[attr-defined] # it's set in the fixture
1443
- SaasFile,
1444
- Fixtures("saasherder").get_anymarkup("saas.gql.yml"),
1445
- )
1446
- self.state_patcher = patch("reconcile.utils.state.State", autospec=True)
1447
- self.state_mock = self.state_patcher.start().return_value
1448
- self.saasherder = SaasHerder(
1449
- [self.saas_file],
1450
- secret_reader=MockSecretReader(),
1451
- thread_pool_size=1,
1452
- state=self.state_mock,
1453
- integration="",
1454
- integration_version="",
1455
- hash_length=7,
1456
- repo_url="https://repo-url.com",
1457
- )
1458
- self.promotion_state_patcher = patch(
1459
- "reconcile.utils.promotion_state.PromotionState", autospec=True
1460
- )
1461
- self.promotion_state_mock = self.promotion_state_patcher.start().return_value
1462
- self.saasherder._promotion_state = self.promotion_state_mock
1463
-
1464
- def tearDown(self) -> None:
1465
- self.state_patcher.stop()
1466
- self.promotion_state_patcher.stop()
1467
-
1468
- def test_blocked_hotfix_version_promotion_validity(self) -> None:
1469
- code_component_url = "https://github.com/app-sre/test-saas-deployments"
1470
- hotfix_version = "1234567890123456789012345678901234567890"
1471
- # code_component = self.saas_file.app.code_components[0]
1472
- channel = Channel(
1473
- name="",
1474
- publisher_uids=[""],
1475
- )
1476
- promotion = Promotion(
1477
- url=code_component_url,
1478
- commit_sha=hotfix_version,
1479
- saas_file=self.saas_file.name,
1480
- target_config_hash="",
1481
- saas_target_uid="",
1482
- soak_days=0,
1483
- subscribe=[channel],
1484
- )
1485
- self.saasherder.promotions = [promotion]
1486
- self.promotion_state_mock.get_promotion_data.return_value = PromotionData(
1487
- success=False
1488
- )
1489
- self.assertFalse(self.saasherder.validate_promotions())
1490
-
1491
- self.saasherder.hotfix_versions[code_component_url] = {hotfix_version}
1492
- self.assertTrue(self.saasherder.validate_promotions())
1493
-
1494
- self.saasherder.blocked_versions[code_component_url] = {hotfix_version}
1495
- self.assertFalse(self.saasherder.validate_promotions())
1496
-
1497
-
1498
- def test_render_templated_parameters(
1499
- gql_class_factory: Callable[..., SaasFileInterface],
1500
- ) -> None:
1501
- saas_file = gql_class_factory(
1502
- SaasFile,
1503
- Fixtures("saasherder").get_anymarkup("saas-templated-params.gql.yml"),
1504
- )
1505
- SaasHerder.resolve_templated_parameters([saas_file])
1506
- assert saas_file.resource_templates[0].targets[0].parameters == {
1507
- "no-template": "v1",
1508
- "ignore-go-template": "{{ .GO_PARAM }}-go",
1509
- "template-param-1": "test-namespace-ns",
1510
- "template-param-2": "appsres03ue1-cluster",
1511
- }
1512
- assert saas_file.resource_templates[0].targets[0].secret_parameters == [
1513
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1514
- name="no-template",
1515
- secret={
1516
- "path": "path/to/secret",
1517
- "field": "secret_key",
1518
- "version": 1,
1519
- "format": None,
1520
- },
1521
- ),
1522
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1523
- name="ignore-go-template",
1524
- secret={
1525
- "path": "path/{{ .GO_PARAM }}/secret",
1526
- "field": "{{ .GO_PARAM }}-secret_key",
1527
- "version": 1,
1528
- "format": None,
1529
- },
1530
- ),
1531
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1532
- name="template-param-1",
1533
- secret={
1534
- "path": "path/appsres03ue1/test-namespace/secret",
1535
- "field": "secret_key",
1536
- "version": 1,
1537
- "format": None,
1538
- },
1539
- ),
1540
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1541
- name="template-param-2",
1542
- secret={
1543
- "path": "path/appsres03ue1/test-namespace/secret",
1544
- "field": "App-SRE-stage-secret_key",
1545
- "version": 1,
1546
- "format": None,
1547
- },
1548
- ),
1549
- ]
1550
-
1551
-
1552
- def test_render_templated_parameters_in_init(
1553
- gql_class_factory: Callable[..., SaasFile],
1554
- ) -> None:
1555
- saas_file = gql_class_factory(
1556
- SaasFile,
1557
- Fixtures("saasherder").get_anymarkup("saas-templated-params.gql.yml"),
1558
- )
1559
- SaasHerder(
1560
- [saas_file],
1561
- secret_reader=MockSecretReader(),
1562
- thread_pool_size=1,
1563
- integration="",
1564
- integration_version="",
1565
- hash_length=24,
1566
- repo_url="https://repo-url.com",
1567
- )
1568
- assert saas_file.resource_templates[0].targets[0].parameters == {
1569
- "no-template": "v1",
1570
- "ignore-go-template": "{{ .GO_PARAM }}-go",
1571
- "template-param-1": "test-namespace-ns",
1572
- "template-param-2": "appsres03ue1-cluster",
1573
- }
1574
- assert saas_file.resource_templates[0].targets[0].secret_parameters == [
1575
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1576
- name="no-template",
1577
- secret={
1578
- "path": "path/to/secret",
1579
- "field": "secret_key",
1580
- "version": 1,
1581
- "format": None,
1582
- },
1583
- ),
1584
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1585
- name="ignore-go-template",
1586
- secret={
1587
- "path": "path/{{ .GO_PARAM }}/secret",
1588
- "field": "{{ .GO_PARAM }}-secret_key",
1589
- "version": 1,
1590
- "format": None,
1591
- },
1592
- ),
1593
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1594
- name="template-param-1",
1595
- secret={
1596
- "path": "path/appsres03ue1/test-namespace/secret",
1597
- "field": "secret_key",
1598
- "version": 1,
1599
- "format": None,
1600
- },
1601
- ),
1602
- SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1603
- name="template-param-2",
1604
- secret={
1605
- "path": "path/appsres03ue1/test-namespace/secret",
1606
- "field": "App-SRE-stage-secret_key",
1607
- "version": 1,
1608
- "format": None,
1609
- },
1610
- ),
1611
- ]