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,240 +0,0 @@
1
- import base64
2
- from collections.abc import Callable
3
- from subprocess import CalledProcessError
4
- from typing import Any
5
- from unittest.mock import MagicMock
6
- from urllib.error import URLError
7
-
8
- import pytest
9
- from pytest_mock import MockerFixture
10
-
11
- import reconcile.openshift_cluster_bots as ocb
12
- from reconcile.gql_definitions.openshift_cluster_bots.clusters import (
13
- ClusterV1,
14
- VaultSecret,
15
- )
16
-
17
-
18
- def vault_secret(path: str, field: str) -> VaultSecret:
19
- return VaultSecret(path=path, field=field, version=None, format=None)
20
-
21
-
22
- def vault_secret_dict(path: str, field: str) -> dict[str, str | None]:
23
- return vault_secret(path=path, field=field).dict(by_alias=True)
24
-
25
-
26
- @pytest.fixture
27
- def secret() -> VaultSecret:
28
- return vault_secret(path="app-sre/bot", field="token")
29
-
30
-
31
- @pytest.fixture
32
- def admin_secret() -> VaultSecret:
33
- return vault_secret(path="app-sre/admin-bot", field="token")
34
-
35
-
36
- @pytest.fixture
37
- def cluster(
38
- gql_class_factory: Callable[..., ClusterV1],
39
- ) -> Callable[..., ClusterV1]:
40
- def builder(
41
- server_url: str = "",
42
- secret: dict | None = None,
43
- admin: bool | None = None,
44
- admin_secret: dict | None = None,
45
- ocm: bool = True,
46
- ) -> ClusterV1:
47
- ocm_data = {
48
- "name": "ocm-production",
49
- "environment": {
50
- "name": "ocm-production",
51
- "url": "https://api.openshift.com",
52
- "accessTokenClientId": "ocm-client-id",
53
- "accessTokenUrl": "https://sso.com/openid/token",
54
- "accessTokenClientSecret": vault_secret_dict(
55
- path="ocm/creds", field="client_secret"
56
- ),
57
- },
58
- "orgId": "ocm-org-id",
59
- "accessTokenClientId": "ocm-client-id",
60
- "accessTokenUrl": "https://sso.com/openid/token",
61
- "accessTokenClientSecret": vault_secret_dict(
62
- path="ocm/creds", field="client_secret"
63
- ),
64
- }
65
- return gql_class_factory(
66
- ClusterV1,
67
- {
68
- "name": "cluster",
69
- "serverUrl": server_url,
70
- "ocm": ocm_data if ocm else None,
71
- "automationToken": secret,
72
- "clusterAdmin": admin,
73
- "clusterAdminAutomationToken": admin_secret,
74
- "disable": None,
75
- },
76
- )
77
-
78
- return builder
79
-
80
-
81
- def test_cluster_misses_bot_tokens(
82
- cluster: Callable, secret: VaultSecret, admin_secret: VaultSecret
83
- ) -> None:
84
- assert ocb.cluster_misses_bot_tokens(cluster())
85
- assert not ocb.cluster_misses_bot_tokens(cluster(secret=secret))
86
- assert ocb.cluster_misses_bot_tokens(cluster(secret=secret, admin=True))
87
- assert not ocb.cluster_misses_bot_tokens(
88
- cluster(secret=secret, admin=True, admin_secret=admin_secret)
89
- )
90
-
91
-
92
- def test_cluster_is_reachable(mocker: MockerFixture, cluster: Callable) -> None:
93
- assert not ocb.cluster_is_reachable(cluster(server_url=""))
94
- urlopen_mock = mocker.patch(
95
- "reconcile.openshift_cluster_bots.urllib.request.urlopen", autospec=True
96
- )
97
- c = cluster(server_url="https://my.api")
98
- urlopen_mock.return_value.getcode.return_value = 200
99
- assert ocb.cluster_is_reachable(c)
100
-
101
- urlopen_mock.return_value.getcode.return_value = 404
102
- assert not ocb.cluster_is_reachable(c)
103
-
104
- urlopen_mock.return_value = None
105
- assert not ocb.cluster_is_reachable(c)
106
-
107
- urlopen_mock.side_effect = URLError(reason="something")
108
- assert not ocb.cluster_is_reachable(c)
109
-
110
-
111
- def test_oc(mocker: MockerFixture) -> None:
112
- run_mock = mocker.patch(
113
- "reconcile.openshift_cluster_bots.subprocess.run", autospec=True
114
- )
115
- ret_mock = run_mock.return_value
116
-
117
- args: list = ["kc", "ns", ["cmd", "attr"]]
118
- run_args: list = [
119
- "oc",
120
- "--kubeconfig",
121
- "kc",
122
- "-n",
123
- "ns",
124
- "-o",
125
- "json",
126
- "cmd",
127
- "attr",
128
- ]
129
- run_kwargs = {"input": None, "check": True, "capture_output": True}
130
- ret_mock.stdout = None
131
- assert ocb.oc(*args) is None
132
- run_mock.assert_called_once_with(run_args, **run_kwargs)
133
-
134
- ret_mock.stdout = b"{}"
135
- assert ocb.oc(*args) == {}
136
-
137
- ret_mock.stdout = b""
138
- assert ocb.oc(*args) is None
139
-
140
- run_mock.side_effect = CalledProcessError(returncode=4, cmd="oc")
141
- with pytest.raises(CalledProcessError):
142
- ocb.oc(*args)
143
-
144
-
145
- def test_retrieve_token(mocker: MockerFixture) -> None:
146
- oc_mock = mocker.patch("reconcile.openshift_cluster_bots.oc", autospec=True)
147
- # avoid waiting during retries
148
- mocker.patch("sretoolbox.utils.retry.time.sleep")
149
-
150
- oc_mock.return_value = {}
151
- with pytest.raises(ocb.TokenNotReadyException):
152
- ocb.retrieve_token("kc", "ns", "sa")
153
- assert oc_mock.call_count == 3
154
-
155
- oc_mock.return_value = {"data": {"token": base64.b64encode(b"Got It!")}}
156
- assert ocb.retrieve_token("kc", "ns", "sa") == "Got It!"
157
-
158
-
159
- @pytest.fixture
160
- def integ_params() -> dict[str, Any]:
161
- return {
162
- "gitlab_project_id": "000",
163
- "vault_creds_path": "/vault/path",
164
- "dedicated_admin_ns": "dedicated-admin-ns",
165
- "dedicated_admin_sa": "dedicated-admin-sa",
166
- "cluster_admin_ns": "cluster-admin-ns",
167
- "cluster_admin_sa": "cluster-admin-sa",
168
- "dry_run": False,
169
- }
170
-
171
-
172
- class Mocks:
173
- def __init__(self, oc: MagicMock, vault: MagicMock, submit_mr: MagicMock) -> None:
174
- self.oc = oc
175
- self.vault = vault
176
- self.submit_mr = submit_mr
177
-
178
-
179
- def _setup_mocks(mocker: MockerFixture, filtered_clusters: list[ClusterV1]) -> Mocks:
180
- mocker.patch("reconcile.openshift_cluster_bots.gql")
181
- mocker.patch("reconcile.openshift_cluster_bots.clusters_gql")
182
- filter_clusters = mocker.patch(
183
- "reconcile.openshift_cluster_bots.filter_clusters", autospec=True
184
- )
185
- filter_clusters.return_value = filtered_clusters
186
- # avoid waiting during retries
187
- mocker.patch("sretoolbox.utils.retry.time.sleep")
188
- ocm_map = mocker.patch("reconcile.openshift_cluster_bots.OCMMap", autospec=True)
189
- get_ocm_map = mocker.patch(
190
- "reconcile.openshift_cluster_bots.get_ocm_map", autospec=True
191
- )
192
- get_ocm_map.return_value = ocm_map
193
- mocker.patch("reconcile.openshift_cluster_bots.tempfile", autospec=True)
194
- oc = mocker.patch("reconcile.openshift_cluster_bots.oc", autospec=True)
195
- vault = mocker.patch("reconcile.openshift_cluster_bots.VaultClient")
196
- submit_mr = mocker.patch(
197
- "reconcile.openshift_cluster_bots.submit_mr", autospec=True
198
- )
199
- return Mocks(oc, vault, submit_mr)
200
-
201
-
202
- def test_run_nothing_to_do(mocker: MockerFixture, integ_params: dict[str, Any]) -> None:
203
- _setup_mocks(mocker, filtered_clusters=[])
204
- with pytest.raises(SystemExit):
205
- ocb.run(**integ_params)
206
-
207
-
208
- def test_run_dry_run(
209
- mocker: MockerFixture, integ_params: dict[str, Any], cluster: Callable
210
- ) -> None:
211
- integ_params["dry_run"] = True
212
- mocks = _setup_mocks(mocker, filtered_clusters=[cluster(server_url="https://api")])
213
- ocb.run(**integ_params)
214
- mocks.oc.assert_not_called()
215
- mocks.vault.assert_not_called()
216
- mocks.submit_mr.assert_not_called()
217
-
218
-
219
- def test_run_no_cluster_admin(
220
- mocker: MockerFixture, integ_params: dict[str, Any], cluster: Callable
221
- ) -> None:
222
- mocks = _setup_mocks(mocker, filtered_clusters=[cluster(server_url="https://api")])
223
- mocks.oc.return_value = {"data": {"token": base64.b64encode(b"mytoken")}}
224
- ocb.run(**integ_params)
225
- assert mocks.oc.call_count == 3
226
- mocks.vault.assert_called_once()
227
- mocks.submit_mr.assert_called_once()
228
-
229
-
230
- def test_run_cluster_admin(
231
- mocker: MockerFixture, integ_params: dict[str, Any], cluster: Callable
232
- ) -> None:
233
- mocks = _setup_mocks(
234
- mocker, filtered_clusters=[cluster(server_url="https://api", admin=True)]
235
- )
236
- mocks.oc.return_value = {"data": {"token": base64.b64encode(b"mytoken")}}
237
- ocb.run(**integ_params)
238
- assert mocks.oc.call_count == 8
239
- mocks.vault.assert_called_once()
240
- mocks.submit_mr.assert_called_once()
@@ -1,344 +0,0 @@
1
- from dataclasses import dataclass
2
- from unittest import TestCase
3
- from unittest.mock import (
4
- Mock,
5
- call,
6
- create_autospec,
7
- patch,
8
- )
9
-
10
- from reconcile import openshift_namespace_labels
11
- from reconcile.gql_definitions.common.app_interface_vault_settings import (
12
- AppInterfaceSettingsV1,
13
- )
14
- from reconcile.gql_definitions.common.namespaces import NamespaceV1
15
- from reconcile.openshift_namespace_labels import state_key
16
- from reconcile.test.fixtures import Fixtures
17
- from reconcile.utils.oc_map import OCMap
18
- from reconcile.utils.secret_reader import SecretReaderBase
19
-
20
- fxt = Fixtures("openshift_namespace_labels")
21
-
22
-
23
- def load_namespace(name: str) -> NamespaceV1:
24
- content = fxt.get_anymarkup(name)
25
- return NamespaceV1(**content)
26
-
27
-
28
- @dataclass
29
- class NS:
30
- """
31
- Simple utility class holding test information about current,
32
- desired and managed labels for a single cluster/namespace. It allows to
33
- simplify the test data creation.
34
- Some methods are there to convert this data into GQL, State or OC outputs.
35
- """
36
-
37
- cluster: str
38
- name: str
39
- current: dict[str, str] | None
40
- managed: list[str] | None
41
- desired: dict[str, str]
42
- exists: bool = True
43
-
44
- def data(self):
45
- """Get typed namespace data"""
46
- namespace = load_namespace(f"{self.name}.yml")
47
- namespace.name = self.name
48
- namespace.cluster.name = self.cluster
49
- if self.desired is not None:
50
- namespace.labels = self.desired
51
- return namespace
52
-
53
- def oc_get_all(self):
54
- """Get this namespace as an output of oc get namespace"""
55
- if not self.exists:
56
- return None
57
- d = {"metadata": {"name": self.name}}
58
- if self.current:
59
- d["metadata"]["labels"] = self.current
60
- return d
61
-
62
- def state_key(self):
63
- """Get the managed state key for this namespace"""
64
- return state_key(self.cluster, self.name)
65
-
66
-
67
- # Some shortcuts
68
- c1, c2 = "cluster1", "cluster2"
69
- k1v1, k2v2, k3v3, k1v2 = {"k1": "v1"}, {"k2": "v2"}, {"k3": "v3"}, {"k1": "v2"}
70
- k1v1_k2v2, k2v3_k3v3 = {"k1": "v1", "k2": "v2"}, {"k2": "v3", "k3": "v3"}
71
- k1, k2, k1_k2, k2_k3 = ["k1"], ["k2"], ["k1", "k2"], ["k2", "k3"]
72
- k1_k2_k3 = ["k1", "k2", "k3"]
73
-
74
-
75
- def run_integration(
76
- dry_run=False,
77
- thread_pool_size=1,
78
- internal=None,
79
- use_jump_host=True,
80
- raise_errors=True,
81
- ):
82
- """Calls the integration with sensible overridable defaults"""
83
- openshift_namespace_labels.run(
84
- dry_run=dry_run,
85
- thread_pool_size=thread_pool_size,
86
- internal=internal,
87
- use_jump_host=use_jump_host,
88
- raise_errors=raise_errors,
89
- )
90
-
91
-
92
- class TestOpenshiftNamespaceLabels(TestCase):
93
- """
94
- This test case class runs the full openshift-namespace-labels integration
95
- in several cases. For this we mock OC, State and GQL queries, patching them
96
- with functions that use the individual tests data from self.test_ns.
97
- This allows to code teh mock logic only once.
98
- """
99
-
100
- def _get_namespaces(self):
101
- """Mock get_namespaces() by returning our test data"""
102
- return [ns.data() for ns in self.test_ns]
103
-
104
- def _oc_map_clusters(self):
105
- """Mock OCMap.clusters() by listing clusters in our test data"""
106
- return list({ns.cluster for ns in self.test_ns if ns.exists})
107
-
108
- def _oc_map_get(self, cluster):
109
- """Mock OCMap.get() by getting namespaces from our test data"""
110
- oc = self.oc_clients.setdefault(cluster, Mock(name=f"oc_{cluster}"))
111
- ns = [
112
- ns.oc_get_all()
113
- for ns in self.test_ns
114
- if ns.exists and ns.cluster == cluster
115
- ]
116
- oc.get_all.return_value = {"items": ns}
117
- return oc
118
-
119
- def _state_ls(self):
120
- """Mock State.ls() by getting state keys from our test data"""
121
- return [f"/{ns.state_key()}" for ns in self.test_ns if ns.managed is not None]
122
-
123
- def _ns_from_key(self, key):
124
- """Get a namespace from test data, matching the provided state key"""
125
- for ns in self.test_ns:
126
- if key == ns.state_key():
127
- return ns
128
- return None
129
-
130
- def _state_get(self, key, default):
131
- """Mock State.get() by getting managed state from our test data"""
132
- ns = self._ns_from_key(key)
133
- if ns is not None:
134
- return ns.managed
135
- return default
136
-
137
- # We could avoid implementing this method.
138
- # We just ensure it is called with correct parameters
139
- def _state_add(self, key, value=None, force=False):
140
- """Mock State.add() by updating our test data"""
141
- ns = self._ns_from_key(key)
142
- self.assertIsNotNone(ns)
143
- ns.managed = value
144
-
145
- def setUp(self):
146
- """Setup GQL, State and Openshift mocks, using self.test_ns data"""
147
- self.test_ns = []
148
- self.oc_clients = {}
149
-
150
- module = "reconcile.openshift_namespace_labels"
151
-
152
- self.queries_patcher = patch("reconcile.queries")
153
- self.queries = self.queries_patcher.start()
154
-
155
- self.namespaces_patcher = patch(f"{module}.get_namespaces")
156
- self.namespaces = self.namespaces_patcher.start()
157
- self.namespaces.side_effect = self._get_namespaces
158
-
159
- vault_settings = AppInterfaceSettingsV1(vault=False)
160
- self.get_vault_settings_patcher = patch(
161
- f"{module}.get_app_interface_vault_settings"
162
- )
163
- self.get_vault_settings = self.get_vault_settings_patcher.start()
164
- self.get_vault_settings.side_effect = [vault_settings]
165
-
166
- self.create_secret_reader_patcher = patch(f"{module}.create_secret_reader")
167
- self.create_secret_reader = self.create_secret_reader_patcher.start()
168
- self.create_secret_reader.side_effect = [create_autospec(spec=SecretReaderBase)]
169
-
170
- self.oc_map = create_autospec(spec=OCMap)
171
- self.oc_map.clusters.side_effect = self._oc_map_clusters
172
- self.oc_map.get.side_effect = self._oc_map_get
173
- self.init_oc_map_patcher = patch(f"{module}.init_oc_map_from_namespaces")
174
- self.init_oc_map = self.init_oc_map_patcher.start()
175
- self.init_oc_map.side_effect = [self.oc_map]
176
- self.state_patcher = patch(f"{module}.init_state")
177
- self.state = self.state_patcher.start().return_value
178
- self.state.ls.side_effect = self._state_ls
179
- self.state.get.side_effect = self._state_get
180
- self.state.add.side_effect = self._state_add
181
-
182
- def tearDown(self) -> None:
183
- """cleanup patches created in self.setUp"""
184
- self.init_oc_map_patcher.stop()
185
- self.queries_patcher.stop()
186
- self.state_patcher.stop()
187
- self.create_secret_reader_patcher.stop()
188
- self.namespaces_patcher.stop()
189
-
190
- def test_no_change(self):
191
- """No label change: nothing should be done"""
192
- self.test_ns = [
193
- NS(c1, "namespace", k1v1, k1, k1v1),
194
- ]
195
- run_integration()
196
- self.state.add.assert_not_called()
197
- for oc in self.oc_clients.values():
198
- oc.label.assert_not_called()
199
-
200
- def test_update(self):
201
- """single label value change"""
202
- self.test_ns = [
203
- NS(c1, "namespace", k1v1, k1, k1v2),
204
- ]
205
- run_integration()
206
- # no change in the managed key store: we have the same keys than before
207
- self.state.add.assert_not_called()
208
- oc = self.oc_clients[c1]
209
- oc.label.assert_called_once_with(
210
- None, "Namespace", "namespace", k1v2, overwrite=True
211
- )
212
-
213
- def test_add(self):
214
- """addition of a label"""
215
- self.test_ns = [
216
- NS(c1, "namespace", k1v1, k1, k1v1_k2v2),
217
- ]
218
- run_integration()
219
- self.state.add.assert_called_once_with(
220
- state_key(c1, "namespace"), k1_k2, force=True
221
- )
222
- oc = self.oc_clients[c1]
223
- oc.label.assert_called_once_with(
224
- None, "Namespace", "namespace", k2v2, overwrite=True
225
- )
226
-
227
- def test_add_from_none(self):
228
- """addition of a label from none on the current namespace"""
229
- self.test_ns = [
230
- NS(c1, "namespace", {}, None, k1v1),
231
- ]
232
- run_integration()
233
- self.state.add.assert_called_once_with(
234
- state_key(c1, "namespace"), k1, force=True
235
- )
236
- oc = self.oc_clients[c1]
237
- oc.label.assert_called_once_with(
238
- None, "Namespace", "namespace", k1v1, overwrite=True
239
- )
240
-
241
- def test_remove_step1(self):
242
- """removal of a label step 1: remove label"""
243
- self.test_ns = [
244
- NS(c1, "namespace", k1v1_k2v2, k1_k2, k1v1),
245
- ]
246
- run_integration()
247
- self.state.add.assert_not_called()
248
- oc = self.oc_clients[c1]
249
- oc.label.assert_called_once_with(
250
- None, "Namespace", "namespace", {"k2": None}, overwrite=True
251
- )
252
-
253
- def test_remove_step2(self):
254
- """removal of a label step 2: remove key from managed state"""
255
- self.test_ns = [
256
- NS(c1, "namespace", k1v1, k1_k2, k1v1),
257
- ]
258
- run_integration()
259
- self.state.add.assert_called_once_with(
260
- state_key(c1, "namespace"), k1, force=True
261
- )
262
- oc = self.oc_clients[c1]
263
- oc.label.assert_not_called()
264
-
265
- def test_remove_add_modify_step1(self):
266
- """Remove, add and modify labels all at once, step 1 (removals are in
267
- two steps)"""
268
- self.test_ns = [
269
- NS(c1, "namespace", k1v1_k2v2, k1_k2, k2v3_k3v3),
270
- ]
271
- run_integration()
272
- self.state.add.assert_called_once_with(
273
- state_key(c1, "namespace"), k1_k2_k3, force=True
274
- )
275
- oc = self.oc_clients[c1]
276
- labels = {"k1": None, "k2": "v3", "k3": "v3"}
277
- oc.label.assert_called_once_with(
278
- None, "Namespace", "namespace", labels, overwrite=True
279
- )
280
-
281
- def test_remove_add_modify_step2(self):
282
- """Remove, add and modify labels all at once, step 2 (removals are in
283
- two steps)"""
284
- self.test_ns = [
285
- NS(c1, "namespace", k2v3_k3v3, k1_k2_k3, k2v3_k3v3),
286
- ]
287
- run_integration()
288
- self.state.add.assert_called_once_with(
289
- state_key(c1, "namespace"), k2_k3, force=True
290
- )
291
- oc = self.oc_clients[c1]
292
- oc.label.assert_not_called()
293
-
294
- def test_namespace_not_exists(self):
295
- """namespace does not exist (yet)"""
296
- self.test_ns = [
297
- NS(c1, "namespace", None, None, k1v1, exists=False),
298
- ]
299
- run_integration()
300
- self.state.add.assert_not_called()
301
- self.assertNotIn(c1, self.oc_clients.keys())
302
-
303
- def test_duplicate_namespace(self):
304
- """Namespace declared several times in a single cluster: ignored"""
305
- self.test_ns = [
306
- NS(c1, "namespace", k1v1, k1, k1v1),
307
- NS(c1, "namespace", k1v1, k1, k2v2),
308
- NS(c1, "namespace", k1v1, k1, k1v2),
309
- ]
310
- run_integration()
311
- self.state.add.assert_not_called()
312
- for oc in self.oc_clients.values():
313
- oc.label.assert_not_called()
314
-
315
- def test_multi_cluster(self):
316
- """Namespace declared in several clusters. All get updated"""
317
- self.test_ns = [
318
- NS(c1, "namespace", k1v1, k1, k1v1_k2v2),
319
- NS(c2, "namespace", k1v1, k1, k1v1_k2v2),
320
- ]
321
- run_integration()
322
- self.assertEqual(self.state.add.call_count, 2)
323
- calls = [
324
- call(state_key(c1, "namespace"), k1_k2, force=True),
325
- call(state_key(c2, "namespace"), k1_k2, force=True),
326
- ]
327
- self.state.add.assert_has_calls(calls)
328
-
329
- self.assertIn(c1, self.oc_clients)
330
- self.assertIn(c2, self.oc_clients)
331
- for oc in self.oc_clients.values():
332
- oc.label.assert_called_once_with(
333
- None, "Namespace", "namespace", k2v2, overwrite=True
334
- )
335
-
336
- def test_dry_run(self):
337
- """Ensures nothing is done in dry_run mode"""
338
- self.test_ns = [
339
- NS(c1, "namespace", k1v1_k2v2, k1_k2, k2v3_k3v3),
340
- ]
341
- run_integration(dry_run=True)
342
- self.state.add.assert_not_called()
343
- oc = self.oc_clients[c1]
344
- oc.label.assert_not_called()