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.
- qontract_reconcile-0.10.2.dev1.dist-info/METADATA +500 -0
- {qontract_reconcile-0.10.1rc1202.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/RECORD +12 -130
- {qontract_reconcile-0.10.1rc1202.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/WHEEL +1 -2
- {qontract_reconcile-0.10.1rc1202.dist-info → qontract_reconcile-0.10.2.dev1.dist-info}/entry_points.txt +1 -0
- reconcile/aws_account_manager/README.md +5 -0
- reconcile/change_owners/README.md +34 -0
- reconcile/glitchtip/README.md +150 -0
- reconcile/gql_definitions/introspection.json +51176 -0
- reconcile/run_integration.py +293 -0
- reconcile/utils/binary.py +2 -2
- reconcile/utils/mr/README.md +198 -0
- reconcile/utils/oc_map.py +2 -2
- tools/qontract_cli.py +0 -0
- qontract_reconcile-0.10.1rc1202.dist-info/METADATA +0 -64
- qontract_reconcile-0.10.1rc1202.dist-info/top_level.txt +0 -3
- reconcile/test/__init__.py +0 -0
- reconcile/test/conftest.py +0 -157
- reconcile/test/fixtures.py +0 -24
- reconcile/test/saas_auto_promotions_manager/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/conftest.py +0 -170
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -115
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -19
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_desired_state.py +0 -66
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -86
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +0 -352
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +0 -494
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/__init__.py +0 -0
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -25
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -37
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -81
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -61
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_json_path_selector.py +0 -74
- reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -52
- reconcile/test/saas_auto_promotions_manager/utils/__init__.py +0 -0
- reconcile/test/test_acs_notifiers.py +0 -393
- reconcile/test/test_acs_policies.py +0 -497
- reconcile/test/test_acs_rbac.py +0 -865
- reconcile/test/test_aggregated_list.py +0 -237
- reconcile/test/test_amtool.py +0 -37
- reconcile/test/test_aws_ami_cleanup.py +0 -230
- reconcile/test/test_aws_ami_share.py +0 -68
- reconcile/test/test_aws_cloudwatch_log_retention.py +0 -434
- reconcile/test/test_aws_iam_keys.py +0 -70
- reconcile/test/test_aws_iam_password_reset.py +0 -35
- reconcile/test/test_aws_support_cases_sos.py +0 -23
- reconcile/test/test_checkpoint.py +0 -178
- reconcile/test/test_cli.py +0 -41
- reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
- reconcile/test/test_dashdotdb_dora.py +0 -245
- reconcile/test/test_database_access_manager.py +0 -660
- reconcile/test/test_deadmanssnitch.py +0 -290
- reconcile/test/test_gabi_authorized_users.py +0 -72
- reconcile/test/test_gcr_mirror.py +0 -14
- reconcile/test/test_github_org.py +0 -156
- reconcile/test/test_github_repo_invites.py +0 -119
- reconcile/test/test_gitlab_housekeeping.py +0 -333
- reconcile/test/test_gitlab_labeler.py +0 -126
- reconcile/test/test_gitlab_members.py +0 -219
- reconcile/test/test_gitlab_permissions.py +0 -164
- reconcile/test/test_instrumented_wrappers.py +0 -18
- reconcile/test/test_integrations_manager.py +0 -1252
- reconcile/test/test_jenkins_worker_fleets.py +0 -57
- reconcile/test/test_jira_permissions_validator.py +0 -519
- reconcile/test/test_jump_host.py +0 -114
- reconcile/test/test_ldap_users.py +0 -125
- reconcile/test/test_make.py +0 -28
- reconcile/test/test_ocm_additional_routers.py +0 -133
- reconcile/test/test_ocm_clusters.py +0 -798
- reconcile/test/test_ocm_clusters_manifest_updates.py +0 -87
- reconcile/test/test_ocm_machine_pools.py +0 -1103
- reconcile/test/test_ocm_update_recommended_version.py +0 -145
- reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -125
- reconcile/test/test_openshift_base.py +0 -1269
- reconcile/test/test_openshift_cluster_bots.py +0 -240
- reconcile/test/test_openshift_namespace_labels.py +0 -344
- reconcile/test/test_openshift_namespaces.py +0 -256
- reconcile/test/test_openshift_resource.py +0 -443
- reconcile/test/test_openshift_resources_base.py +0 -478
- reconcile/test/test_openshift_saas_deploy.py +0 -188
- reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -308
- reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +0 -65
- reconcile/test/test_openshift_serviceaccount_tokens.py +0 -282
- reconcile/test/test_openshift_tekton_resources.py +0 -265
- reconcile/test/test_openshift_upgrade_watcher.py +0 -223
- reconcile/test/test_prometheus_rules_tester.py +0 -151
- reconcile/test/test_quay_membership.py +0 -86
- reconcile/test/test_quay_mirror.py +0 -172
- reconcile/test/test_quay_mirror_org.py +0 -82
- reconcile/test/test_quay_repos.py +0 -59
- reconcile/test/test_queries.py +0 -53
- reconcile/test/test_repo_owners.py +0 -47
- reconcile/test/test_requests_sender.py +0 -139
- reconcile/test/test_saasherder.py +0 -1611
- reconcile/test/test_saasherder_allowed_secret_paths.py +0 -125
- reconcile/test/test_secret_reader.py +0 -153
- reconcile/test/test_slack_base.py +0 -183
- reconcile/test/test_slack_usergroups.py +0 -785
- reconcile/test/test_sql_query.py +0 -316
- reconcile/test/test_status_board.py +0 -258
- reconcile/test/test_terraform_aws_route53.py +0 -29
- reconcile/test/test_terraform_cloudflare_dns.py +0 -117
- reconcile/test/test_terraform_cloudflare_resources.py +0 -408
- reconcile/test/test_terraform_cloudflare_users.py +0 -747
- reconcile/test/test_terraform_repo.py +0 -440
- reconcile/test/test_terraform_resources.py +0 -519
- reconcile/test/test_terraform_tgw_attachments.py +0 -1295
- reconcile/test/test_terraform_users.py +0 -152
- reconcile/test/test_terraform_vpc_peerings.py +0 -576
- reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1434
- reconcile/test/test_three_way_diff_strategy.py +0 -131
- reconcile/test/test_utils_jinja2.py +0 -130
- reconcile/test/test_vault_replication.py +0 -534
- reconcile/test/test_vault_utils.py +0 -47
- reconcile/test/test_version_bump.py +0 -18
- reconcile/test/test_vpc_peerings_validator.py +0 -194
- reconcile/test/test_wrong_region.py +0 -78
- release/__init__.py +0 -0
- release/test_version.py +0 -50
- release/version.py +0 -104
- tools/cli_commands/test/__init__.py +0 -0
- tools/cli_commands/test/conftest.py +0 -332
- tools/cli_commands/test/test_aws_cost_report.py +0 -258
- tools/cli_commands/test/test_cost_management_api.py +0 -326
- tools/cli_commands/test/test_gpg_encrypt.py +0 -235
- tools/cli_commands/test/test_openshift_cost_optimization_report.py +0 -255
- tools/cli_commands/test/test_openshift_cost_report.py +0 -295
- tools/cli_commands/test/test_util.py +0 -70
- tools/test/__init__.py +0 -0
- tools/test/conftest.py +0 -77
- tools/test/test_app_interface_metrics_exporter.py +0 -48
- tools/test/test_erv2.py +0 -80
- tools/test/test_get_container_images.py +0 -230
- tools/test/test_qontract_cli.py +0 -197
- tools/test/test_saas_promotion_state.py +0 -187
- tools/test/test_sd_app_sre_alert_report.py +0 -74
- 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()
|