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,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()