qontract-reconcile 0.10.1rc1202__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 (138) hide show
  1. qontract_reconcile-0.10.2.dev1.dist-info/METADATA +500 -0
  2. {qontract_reconcile-0.10.1rc1202.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/RECORD +12 -130
  3. {qontract_reconcile-0.10.1rc1202.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/WHEEL +1 -2
  4. {qontract_reconcile-0.10.1rc1202.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/glitchtip/README.md +150 -0
  8. reconcile/gql_definitions/introspection.json +51176 -0
  9. reconcile/run_integration.py +293 -0
  10. reconcile/utils/binary.py +2 -2
  11. reconcile/utils/mr/README.md +198 -0
  12. reconcile/utils/oc_map.py +2 -2
  13. tools/qontract_cli.py +0 -0
  14. qontract_reconcile-0.10.1rc1202.dist-info/METADATA +0 -64
  15. qontract_reconcile-0.10.1rc1202.dist-info/top_level.txt +0 -3
  16. reconcile/test/__init__.py +0 -0
  17. reconcile/test/conftest.py +0 -157
  18. reconcile/test/fixtures.py +0 -24
  19. reconcile/test/saas_auto_promotions_manager/__init__.py +0 -0
  20. reconcile/test/saas_auto_promotions_manager/conftest.py +0 -170
  21. reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py +0 -0
  22. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py +0 -0
  23. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -115
  24. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -19
  25. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_desired_state.py +0 -66
  26. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -86
  27. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +0 -352
  28. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +0 -494
  29. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/__init__.py +0 -0
  30. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -25
  31. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -37
  32. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -81
  33. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -61
  34. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_json_path_selector.py +0 -74
  35. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -52
  36. reconcile/test/saas_auto_promotions_manager/utils/__init__.py +0 -0
  37. reconcile/test/test_acs_notifiers.py +0 -393
  38. reconcile/test/test_acs_policies.py +0 -497
  39. reconcile/test/test_acs_rbac.py +0 -865
  40. reconcile/test/test_aggregated_list.py +0 -237
  41. reconcile/test/test_amtool.py +0 -37
  42. reconcile/test/test_aws_ami_cleanup.py +0 -230
  43. reconcile/test/test_aws_ami_share.py +0 -68
  44. reconcile/test/test_aws_cloudwatch_log_retention.py +0 -434
  45. reconcile/test/test_aws_iam_keys.py +0 -70
  46. reconcile/test/test_aws_iam_password_reset.py +0 -35
  47. reconcile/test/test_aws_support_cases_sos.py +0 -23
  48. reconcile/test/test_checkpoint.py +0 -178
  49. reconcile/test/test_cli.py +0 -41
  50. reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
  51. reconcile/test/test_dashdotdb_dora.py +0 -245
  52. reconcile/test/test_database_access_manager.py +0 -660
  53. reconcile/test/test_deadmanssnitch.py +0 -290
  54. reconcile/test/test_gabi_authorized_users.py +0 -72
  55. reconcile/test/test_gcr_mirror.py +0 -14
  56. reconcile/test/test_github_org.py +0 -156
  57. reconcile/test/test_github_repo_invites.py +0 -119
  58. reconcile/test/test_gitlab_housekeeping.py +0 -333
  59. reconcile/test/test_gitlab_labeler.py +0 -126
  60. reconcile/test/test_gitlab_members.py +0 -219
  61. reconcile/test/test_gitlab_permissions.py +0 -164
  62. reconcile/test/test_instrumented_wrappers.py +0 -18
  63. reconcile/test/test_integrations_manager.py +0 -1252
  64. reconcile/test/test_jenkins_worker_fleets.py +0 -57
  65. reconcile/test/test_jira_permissions_validator.py +0 -519
  66. reconcile/test/test_jump_host.py +0 -114
  67. reconcile/test/test_ldap_users.py +0 -125
  68. reconcile/test/test_make.py +0 -28
  69. reconcile/test/test_ocm_additional_routers.py +0 -133
  70. reconcile/test/test_ocm_clusters.py +0 -798
  71. reconcile/test/test_ocm_clusters_manifest_updates.py +0 -87
  72. reconcile/test/test_ocm_machine_pools.py +0 -1103
  73. reconcile/test/test_ocm_update_recommended_version.py +0 -145
  74. reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -125
  75. reconcile/test/test_openshift_base.py +0 -1269
  76. reconcile/test/test_openshift_cluster_bots.py +0 -240
  77. reconcile/test/test_openshift_namespace_labels.py +0 -344
  78. reconcile/test/test_openshift_namespaces.py +0 -256
  79. reconcile/test/test_openshift_resource.py +0 -443
  80. reconcile/test/test_openshift_resources_base.py +0 -478
  81. reconcile/test/test_openshift_saas_deploy.py +0 -188
  82. reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -308
  83. reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +0 -65
  84. reconcile/test/test_openshift_serviceaccount_tokens.py +0 -282
  85. reconcile/test/test_openshift_tekton_resources.py +0 -265
  86. reconcile/test/test_openshift_upgrade_watcher.py +0 -223
  87. reconcile/test/test_prometheus_rules_tester.py +0 -151
  88. reconcile/test/test_quay_membership.py +0 -86
  89. reconcile/test/test_quay_mirror.py +0 -172
  90. reconcile/test/test_quay_mirror_org.py +0 -82
  91. reconcile/test/test_quay_repos.py +0 -59
  92. reconcile/test/test_queries.py +0 -53
  93. reconcile/test/test_repo_owners.py +0 -47
  94. reconcile/test/test_requests_sender.py +0 -139
  95. reconcile/test/test_saasherder.py +0 -1611
  96. reconcile/test/test_saasherder_allowed_secret_paths.py +0 -125
  97. reconcile/test/test_secret_reader.py +0 -153
  98. reconcile/test/test_slack_base.py +0 -183
  99. reconcile/test/test_slack_usergroups.py +0 -785
  100. reconcile/test/test_sql_query.py +0 -316
  101. reconcile/test/test_status_board.py +0 -258
  102. reconcile/test/test_terraform_aws_route53.py +0 -29
  103. reconcile/test/test_terraform_cloudflare_dns.py +0 -117
  104. reconcile/test/test_terraform_cloudflare_resources.py +0 -408
  105. reconcile/test/test_terraform_cloudflare_users.py +0 -747
  106. reconcile/test/test_terraform_repo.py +0 -440
  107. reconcile/test/test_terraform_resources.py +0 -519
  108. reconcile/test/test_terraform_tgw_attachments.py +0 -1295
  109. reconcile/test/test_terraform_users.py +0 -152
  110. reconcile/test/test_terraform_vpc_peerings.py +0 -576
  111. reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1434
  112. reconcile/test/test_three_way_diff_strategy.py +0 -131
  113. reconcile/test/test_utils_jinja2.py +0 -130
  114. reconcile/test/test_vault_replication.py +0 -534
  115. reconcile/test/test_vault_utils.py +0 -47
  116. reconcile/test/test_version_bump.py +0 -18
  117. reconcile/test/test_vpc_peerings_validator.py +0 -194
  118. reconcile/test/test_wrong_region.py +0 -78
  119. release/__init__.py +0 -0
  120. release/test_version.py +0 -50
  121. release/version.py +0 -104
  122. tools/cli_commands/test/__init__.py +0 -0
  123. tools/cli_commands/test/conftest.py +0 -332
  124. tools/cli_commands/test/test_aws_cost_report.py +0 -258
  125. tools/cli_commands/test/test_cost_management_api.py +0 -326
  126. tools/cli_commands/test/test_gpg_encrypt.py +0 -235
  127. tools/cli_commands/test/test_openshift_cost_optimization_report.py +0 -255
  128. tools/cli_commands/test/test_openshift_cost_report.py +0 -295
  129. tools/cli_commands/test/test_util.py +0 -70
  130. tools/test/__init__.py +0 -0
  131. tools/test/conftest.py +0 -77
  132. tools/test/test_app_interface_metrics_exporter.py +0 -48
  133. tools/test/test_erv2.py +0 -80
  134. tools/test/test_get_container_images.py +0 -230
  135. tools/test/test_qontract_cli.py +0 -197
  136. tools/test/test_saas_promotion_state.py +0 -187
  137. tools/test/test_sd_app_sre_alert_report.py +0 -74
  138. 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
- ]