qontract-reconcile 0.10.2.dev256__py3-none-any.whl → 0.10.2.dev258__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.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/RECORD +96 -95
- reconcile/aus/advanced_upgrade_service.py +1 -1
- reconcile/aus/base.py +2 -2
- reconcile/aus/version_gates/sts_version_gate_handler.py +2 -2
- reconcile/aws_account_manager/reconciler.py +22 -20
- reconcile/aws_iam_keys.py +5 -5
- reconcile/aws_iam_password_reset.py +5 -5
- reconcile/aws_saml_roles/integration.py +5 -5
- reconcile/aws_version_sync/integration.py +4 -3
- reconcile/cli.py +16 -12
- reconcile/closedbox_endpoint_monitoring_base.py +1 -0
- reconcile/database_access_manager.py +4 -4
- reconcile/dynatrace_token_provider/integration.py +2 -2
- reconcile/external_resources/manager.py +2 -2
- reconcile/external_resources/model.py +1 -1
- reconcile/external_resources/secrets_sync.py +2 -2
- reconcile/gabi_authorized_users.py +3 -3
- reconcile/github_org.py +2 -2
- reconcile/gitlab_housekeeping.py +1 -1
- reconcile/gitlab_mr_sqs_consumer.py +1 -1
- reconcile/glitchtip/integration.py +2 -2
- reconcile/jenkins_worker_fleets.py +5 -5
- reconcile/ldap_groups/integration.py +3 -3
- reconcile/ocm_clusters.py +2 -2
- reconcile/ocm_internal_notifications/integration.py +2 -2
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/openshift_base.py +12 -11
- reconcile/openshift_cluster_bots.py +2 -2
- reconcile/openshift_resources_base.py +3 -3
- reconcile/openshift_rhcs_certs.py +2 -2
- reconcile/openshift_saas_deploy.py +1 -1
- reconcile/quay_membership.py +4 -4
- reconcile/rhidp/common.py +3 -2
- reconcile/run_integration.py +7 -4
- reconcile/saas_auto_promotions_manager/dependencies.py +95 -0
- reconcile/saas_auto_promotions_manager/integration.py +85 -165
- reconcile/skupper_network/integration.py +3 -3
- reconcile/slack_usergroups.py +4 -4
- reconcile/status_board.py +3 -3
- reconcile/terraform_cloudflare_dns.py +5 -5
- reconcile/terraform_cloudflare_users.py +15 -17
- reconcile/terraform_resources.py +6 -6
- reconcile/terraform_vpc_peerings.py +9 -9
- reconcile/unleash_feature_toggles/integration.py +1 -1
- reconcile/utils/aggregated_list.py +2 -2
- reconcile/utils/aws_api_typed/iam.py +2 -2
- reconcile/utils/aws_api_typed/organization.py +4 -4
- reconcile/utils/aws_api_typed/service_quotas.py +4 -4
- reconcile/utils/aws_api_typed/support.py +9 -9
- reconcile/utils/aws_helper.py +1 -1
- reconcile/utils/config.py +8 -4
- reconcile/utils/deadmanssnitch_api.py +2 -4
- reconcile/utils/glitchtip/models.py +18 -12
- reconcile/utils/gql.py +4 -4
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/jinja2/utils.py +7 -3
- reconcile/utils/jjb_client.py +2 -2
- reconcile/utils/models.py +2 -1
- reconcile/utils/mr/__init__.py +3 -3
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/aws_access.py +5 -2
- reconcile/utils/mr/base.py +3 -3
- reconcile/utils/mr/user_maintenance.py +1 -1
- reconcile/utils/oc.py +11 -11
- reconcile/utils/oc_connection_parameters.py +4 -4
- reconcile/utils/ocm/base.py +3 -3
- reconcile/utils/ocm/products.py +8 -8
- reconcile/utils/ocm/search_filters.py +2 -2
- reconcile/utils/openshift_resource.py +21 -18
- reconcile/utils/pagerduty_api.py +5 -5
- reconcile/utils/quay_api.py +2 -2
- reconcile/utils/rosa/rosa_cli.py +1 -1
- reconcile/utils/rosa/session.py +2 -2
- reconcile/utils/runtime/desired_state_diff.py +7 -7
- reconcile/utils/saasherder/interfaces.py +1 -0
- reconcile/utils/saasherder/models.py +1 -1
- reconcile/utils/saasherder/saasherder.py +1 -1
- reconcile/utils/secret_reader.py +20 -20
- reconcile/utils/slack_api.py +5 -5
- reconcile/utils/slo_document_manager.py +6 -6
- reconcile/utils/state.py +8 -8
- reconcile/utils/terraform_client.py +3 -3
- reconcile/utils/terrascript/cloudflare_client.py +2 -2
- reconcile/utils/terrascript/cloudflare_resources.py +1 -0
- reconcile/utils/terrascript_aws_client.py +12 -11
- reconcile/utils/vault.py +22 -22
- reconcile/vault_replication.py +15 -15
- tools/cli_commands/erv2.py +3 -2
- tools/cli_commands/gpg_encrypt.py +9 -9
- tools/cli_commands/systems_and_tools.py +1 -1
- tools/qontract_cli.py +13 -14
- tools/saas_promotion_state/saas_promotion_state.py +4 -4
- tools/template_validation.py +5 -5
- {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/entry_points.txt +0 -0
reconcile/openshift_base.py
CHANGED
@@ -39,7 +39,7 @@ from reconcile.utils.oc import (
|
|
39
39
|
OCLogMsg,
|
40
40
|
PrimaryClusterIPCanNotBeUnsetError,
|
41
41
|
RequestEntityTooLargeError,
|
42
|
-
|
42
|
+
StatefulSetUpdateForbiddenError,
|
43
43
|
StatusCodeError,
|
44
44
|
UnsupportedMediaTypeError,
|
45
45
|
)
|
@@ -52,13 +52,19 @@ from reconcile.utils.three_way_diff_strategy import three_way_diff_using_hash
|
|
52
52
|
|
53
53
|
ACTION_APPLIED = "applied"
|
54
54
|
ACTION_DELETED = "deleted"
|
55
|
+
AUTH_METHOD_USER_KEY = {
|
56
|
+
"github-org": "github_username",
|
57
|
+
"github-org-team": "github_username",
|
58
|
+
"oidc": "org_username",
|
59
|
+
"rhidp": "org_username",
|
60
|
+
}
|
55
61
|
|
56
62
|
|
57
63
|
class ValidationError(Exception):
|
58
64
|
pass
|
59
65
|
|
60
66
|
|
61
|
-
class
|
67
|
+
class ValidationErrorJobFailedError(Exception):
|
62
68
|
pass
|
63
69
|
|
64
70
|
|
@@ -498,7 +504,7 @@ def apply(
|
|
498
504
|
|
499
505
|
oc.delete(namespace=namespace, kind=resource_type, name=resource.name)
|
500
506
|
oc.apply(namespace=namespace, resource=annotated)
|
501
|
-
except
|
507
|
+
except StatefulSetUpdateForbiddenError:
|
502
508
|
if resource_type != "StatefulSet":
|
503
509
|
raise
|
504
510
|
|
@@ -1228,7 +1234,7 @@ def validate_realized_data(actions: Iterable[dict[str, str]], oc_map: ClusterMap
|
|
1228
1234
|
for c in conditions:
|
1229
1235
|
if c.get("type") == "Failed":
|
1230
1236
|
msg = f"{name}: {c.get('reason')}"
|
1231
|
-
raise
|
1237
|
+
raise ValidationErrorJobFailedError(msg)
|
1232
1238
|
raise ValidationError(name)
|
1233
1239
|
elif kind == "ClowdApp":
|
1234
1240
|
deployments = status.get("deployments")
|
@@ -1256,7 +1262,7 @@ def validate_realized_data(actions: Iterable[dict[str, str]], oc_map: ClusterMap
|
|
1256
1262
|
if job_state == "Failed":
|
1257
1263
|
failed_jobs.append(job_name)
|
1258
1264
|
if failed_jobs:
|
1259
|
-
raise
|
1265
|
+
raise ValidationErrorJobFailedError(
|
1260
1266
|
f"CJI {name} failed jobs: {failed_jobs}"
|
1261
1267
|
)
|
1262
1268
|
else:
|
@@ -1413,12 +1419,7 @@ def determine_user_keys_for_access(
|
|
1413
1419
|
enforced_user_keys: list[str] | None = None,
|
1414
1420
|
) -> list[str]:
|
1415
1421
|
"""Return user keys based on enabled cluster authentication methods."""
|
1416
|
-
|
1417
|
-
"github-org": "github_username",
|
1418
|
-
"github-org-team": "github_username",
|
1419
|
-
"oidc": "org_username",
|
1420
|
-
"rhidp": "org_username",
|
1421
|
-
}
|
1422
|
+
|
1422
1423
|
user_keys: list[str] = []
|
1423
1424
|
|
1424
1425
|
if enforced_user_keys:
|
@@ -111,7 +111,7 @@ def sa_secret_name(sa: str) -> str:
|
|
111
111
|
return f"{sa}-token"
|
112
112
|
|
113
113
|
|
114
|
-
class
|
114
|
+
class TokenNotReadyError(Exception):
|
115
115
|
pass
|
116
116
|
|
117
117
|
|
@@ -120,7 +120,7 @@ class TokenNotReadyException(Exception):
|
|
120
120
|
def retrieve_token(kubeconfig: str, namespace: str, sa: str) -> str:
|
121
121
|
secret = oc(kubeconfig, namespace, ["get", "secret", sa_secret_name(sa)])
|
122
122
|
if not secret or "token" not in secret.get("data", {}):
|
123
|
-
raise
|
123
|
+
raise TokenNotReadyError()
|
124
124
|
b64_token = secret["data"]["token"]
|
125
125
|
return base64.b64decode(b64_token).decode()
|
126
126
|
|
@@ -66,8 +66,8 @@ from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
|
|
66
66
|
from reconcile.utils.semver_helper import make_semver
|
67
67
|
from reconcile.utils.sharding import is_in_shard
|
68
68
|
from reconcile.utils.vault import (
|
69
|
-
|
70
|
-
|
69
|
+
SecretVersionIsNoneError,
|
70
|
+
SecretVersionNotFoundError,
|
71
71
|
)
|
72
72
|
|
73
73
|
# +-----------------------+-------------------------+-------------+
|
@@ -602,7 +602,7 @@ def fetch_openshift_resource(
|
|
602
602
|
alertmanager_config_key=alertmanager_config_key,
|
603
603
|
settings=settings,
|
604
604
|
)
|
605
|
-
except (
|
605
|
+
except (SecretVersionNotFoundError, SecretVersionIsNoneError) as e:
|
606
606
|
raise FetchSecretError(e) from None
|
607
607
|
elif provider == "route":
|
608
608
|
path = resource["resource"]["path"]
|
@@ -35,7 +35,7 @@ from reconcile.utils.rhcsv2_certs import RhcsV2Cert, generate_cert
|
|
35
35
|
from reconcile.utils.runtime.integration import DesiredStateShardConfig
|
36
36
|
from reconcile.utils.secret_reader import create_secret_reader
|
37
37
|
from reconcile.utils.semver_helper import make_semver
|
38
|
-
from reconcile.utils.vault import
|
38
|
+
from reconcile.utils.vault import SecretNotFoundError, VaultClient
|
39
39
|
|
40
40
|
QONTRACT_INTEGRATION = "openshift-rhcs-certs"
|
41
41
|
QONTRACT_INTEGRATION_VERSION = make_semver(1, 9, 3)
|
@@ -124,7 +124,7 @@ def get_vault_cert_secret(
|
|
124
124
|
vault_cert_secret = vault.read_all({ # type: ignore[attr-defined]
|
125
125
|
"path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}"
|
126
126
|
})
|
127
|
-
except
|
127
|
+
except SecretNotFoundError:
|
128
128
|
logging.info(
|
129
129
|
f"No existing cert found for cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}''"
|
130
130
|
)
|
@@ -345,4 +345,4 @@ def run(
|
|
345
345
|
if image_auth.auth_server:
|
346
346
|
json_file = os.path.join(io_dir, "dockerconfigjson")
|
347
347
|
with open(json_file, "w", encoding="locale") as f:
|
348
|
-
f.write(json.dumps(image_auth.
|
348
|
+
f.write(json.dumps(image_auth.get_docker_config_json(), indent=2))
|
reconcile/quay_membership.py
CHANGED
@@ -18,9 +18,9 @@ from reconcile.utils import (
|
|
18
18
|
from reconcile.utils.aggregated_list import (
|
19
19
|
AggregatedDiffRunner,
|
20
20
|
AggregatedList,
|
21
|
-
|
21
|
+
RunnerError,
|
22
22
|
)
|
23
|
-
from reconcile.utils.quay_api import
|
23
|
+
from reconcile.utils.quay_api import QuayTeamNotFoundError
|
24
24
|
|
25
25
|
QONTRACT_INTEGRATION = "quay-membership"
|
26
26
|
|
@@ -63,7 +63,7 @@ def fetch_current_state(quay_api_store):
|
|
63
63
|
for team in teams:
|
64
64
|
try:
|
65
65
|
members = quay_api.list_team_members(team)
|
66
|
-
except
|
66
|
+
except QuayTeamNotFoundError:
|
67
67
|
logging.warning(
|
68
68
|
"Attempted to list members for team %s in "
|
69
69
|
"org %s/%s, but it doesn't exist",
|
@@ -149,7 +149,7 @@ class RunnerAction:
|
|
149
149
|
# Ensure all quay org/teams are declared as dependencies in a
|
150
150
|
# `/dependencies/quay-org-1.yml` datafile.
|
151
151
|
if team not in self.quay_api_store[org]["teams"]:
|
152
|
-
raise
|
152
|
+
raise RunnerError(
|
153
153
|
f"Quay team {team} is not defined as a "
|
154
154
|
f"managedTeam in the {org} org."
|
155
155
|
)
|
reconcile/rhidp/common.py
CHANGED
@@ -61,8 +61,9 @@ class ClusterAuth(BaseModel):
|
|
61
61
|
status: str
|
62
62
|
|
63
63
|
@root_validator
|
64
|
-
def name_no_spaces(
|
65
|
-
cls,
|
64
|
+
def name_no_spaces(
|
65
|
+
cls, # noqa: N805
|
66
|
+
values: MutableMapping[str, Any],
|
66
67
|
) -> MutableMapping[str, Any]:
|
67
68
|
values["name"] = values["name"].replace(" ", "-")
|
68
69
|
return values
|
reconcile/run_integration.py
CHANGED
@@ -65,10 +65,13 @@ HANDLERS = [STREAM_HANDLER]
|
|
65
65
|
# Messages to the log file
|
66
66
|
if LOG_FILE is not None:
|
67
67
|
FILE_HANDLER = logging.FileHandler(LOG_FILE)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
FILE_HANDLER.setFormatter(
|
69
|
+
logging.Formatter(
|
70
|
+
fmt="[%(levelname)s] %(message)s"
|
71
|
+
if PREFIX_LOG_LEVEL == "true"
|
72
|
+
else "%(message)s"
|
73
|
+
)
|
74
|
+
)
|
72
75
|
HANDLERS.append(FILE_HANDLER) # type: ignore
|
73
76
|
|
74
77
|
# Setting up the root logger
|
@@ -0,0 +1,95 @@
|
|
1
|
+
from typing import Self
|
2
|
+
|
3
|
+
from reconcile.openshift_saas_deploy import (
|
4
|
+
QONTRACT_INTEGRATION as OPENSHIFT_SAAS_DEPLOY,
|
5
|
+
)
|
6
|
+
from reconcile.saas_auto_promotions_manager.meta import QONTRACT_INTEGRATION
|
7
|
+
from reconcile.saas_auto_promotions_manager.utils.saas_files_inventory import (
|
8
|
+
SaasFilesInventory,
|
9
|
+
)
|
10
|
+
from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
|
11
|
+
from reconcile.typed_queries.app_interface_vault_settings import (
|
12
|
+
get_app_interface_vault_settings,
|
13
|
+
)
|
14
|
+
from reconcile.typed_queries.github_orgs import get_github_orgs
|
15
|
+
from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
|
16
|
+
from reconcile.typed_queries.saas_files import get_saas_files
|
17
|
+
from reconcile.utils.promotion_state import PromotionState
|
18
|
+
from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
|
19
|
+
from reconcile.utils.state import State, init_state
|
20
|
+
from reconcile.utils.unleash import get_feature_toggle_state
|
21
|
+
from reconcile.utils.vcs import VCS
|
22
|
+
|
23
|
+
|
24
|
+
class Dependencies:
|
25
|
+
"""
|
26
|
+
Dependencies class to hold all the external dependencies (API clients, state, etc.) for SAPM.
|
27
|
+
Dependency inversion simplifies setting up tests and centralizes dependency management.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
secret_reader: SecretReaderBase,
|
33
|
+
deployment_state: PromotionState,
|
34
|
+
vcs: VCS,
|
35
|
+
saas_file_inventory: SaasFilesInventory,
|
36
|
+
saas_deploy_state: State,
|
37
|
+
sapm_state: State,
|
38
|
+
):
|
39
|
+
self.secret_reader = secret_reader
|
40
|
+
self.deployment_state = deployment_state
|
41
|
+
self.vcs = vcs
|
42
|
+
self.saas_file_inventory = saas_file_inventory
|
43
|
+
self.saas_deploy_state = saas_deploy_state
|
44
|
+
self.sapm_state = sapm_state
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def create(
|
48
|
+
cls,
|
49
|
+
dry_run: bool,
|
50
|
+
thread_pool_size: int,
|
51
|
+
env_name: str | None = None,
|
52
|
+
app_name: str | None = None,
|
53
|
+
) -> Self:
|
54
|
+
vault_settings = get_app_interface_vault_settings()
|
55
|
+
allow_deleting_mrs = get_feature_toggle_state(
|
56
|
+
integration_name=f"{QONTRACT_INTEGRATION}-allow-deleting-mrs",
|
57
|
+
default=False,
|
58
|
+
)
|
59
|
+
allow_opening_mrs = get_feature_toggle_state(
|
60
|
+
integration_name=f"{QONTRACT_INTEGRATION}-allow-opening-mrs",
|
61
|
+
default=False,
|
62
|
+
)
|
63
|
+
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
64
|
+
vcs = VCS(
|
65
|
+
secret_reader=secret_reader,
|
66
|
+
github_orgs=get_github_orgs(),
|
67
|
+
gitlab_instances=get_gitlab_instances(),
|
68
|
+
app_interface_repo_url=get_app_interface_repo_url(),
|
69
|
+
dry_run=dry_run,
|
70
|
+
allow_deleting_mrs=allow_deleting_mrs,
|
71
|
+
allow_opening_mrs=allow_opening_mrs,
|
72
|
+
)
|
73
|
+
saas_files = get_saas_files(env_name=env_name, app_name=app_name)
|
74
|
+
saas_inventory = SaasFilesInventory(
|
75
|
+
saas_files=saas_files,
|
76
|
+
secret_reader=secret_reader,
|
77
|
+
thread_pool_size=thread_pool_size,
|
78
|
+
)
|
79
|
+
saas_deploy_state = init_state(
|
80
|
+
integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
|
81
|
+
)
|
82
|
+
deployment_state = PromotionState(
|
83
|
+
state=saas_deploy_state,
|
84
|
+
)
|
85
|
+
sapm_state = init_state(
|
86
|
+
integration=QONTRACT_INTEGRATION, secret_reader=secret_reader
|
87
|
+
)
|
88
|
+
return cls(
|
89
|
+
secret_reader=secret_reader,
|
90
|
+
deployment_state=deployment_state,
|
91
|
+
vcs=vcs,
|
92
|
+
saas_file_inventory=saas_inventory,
|
93
|
+
saas_deploy_state=saas_deploy_state,
|
94
|
+
sapm_state=sapm_state,
|
95
|
+
)
|
@@ -1,13 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from collections.abc import Callable
|
2
4
|
|
3
5
|
from sretoolbox.utils import threaded
|
4
6
|
|
5
|
-
from reconcile.
|
6
|
-
|
7
|
-
)
|
8
|
-
from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
|
9
|
-
Batcher,
|
10
|
-
)
|
7
|
+
from reconcile.saas_auto_promotions_manager.dependencies import Dependencies
|
8
|
+
from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import Batcher
|
11
9
|
from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
|
12
10
|
MergeRequestManagerV2,
|
13
11
|
)
|
@@ -21,189 +19,111 @@ from reconcile.saas_auto_promotions_manager.meta import QONTRACT_INTEGRATION
|
|
21
19
|
from reconcile.saas_auto_promotions_manager.publisher import Publisher
|
22
20
|
from reconcile.saas_auto_promotions_manager.s3_exporter import S3Exporter
|
23
21
|
from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
|
24
|
-
from reconcile.saas_auto_promotions_manager.utils.saas_files_inventory import (
|
25
|
-
SaasFilesInventory,
|
26
|
-
)
|
27
|
-
from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
|
28
|
-
from reconcile.typed_queries.app_interface_vault_settings import (
|
29
|
-
get_app_interface_vault_settings,
|
30
|
-
)
|
31
|
-
from reconcile.typed_queries.github_orgs import get_github_orgs
|
32
|
-
from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
|
33
|
-
from reconcile.typed_queries.saas_files import get_saas_files
|
34
22
|
from reconcile.utils.defer import defer
|
35
|
-
from reconcile.utils.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
from reconcile.utils.vcs import VCS
|
23
|
+
from reconcile.utils.runtime.integration import (
|
24
|
+
PydanticRunParams,
|
25
|
+
QontractReconcileIntegration,
|
26
|
+
)
|
40
27
|
|
41
28
|
|
42
|
-
class
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
29
|
+
class SaasAutoPromotionsManagerParams(PydanticRunParams):
|
30
|
+
thread_pool_size: int
|
31
|
+
env_name: str | None
|
32
|
+
app_name: str | None
|
33
|
+
|
34
|
+
|
35
|
+
class SaasAutoPromotionsManager(
|
36
|
+
QontractReconcileIntegration[SaasAutoPromotionsManagerParams]
|
37
|
+
):
|
38
|
+
@classmethod
|
39
|
+
def create(
|
40
|
+
cls,
|
51
41
|
dry_run: bool,
|
42
|
+
thread_pool_size: int,
|
43
|
+
env_name: str | None = None,
|
44
|
+
app_name: str | None = None,
|
45
|
+
) -> SaasAutoPromotionsManager:
|
46
|
+
dependencies = Dependencies.create(
|
47
|
+
dry_run=dry_run,
|
48
|
+
thread_pool_size=thread_pool_size,
|
49
|
+
env_name=env_name,
|
50
|
+
app_name=app_name,
|
51
|
+
)
|
52
|
+
params = SaasAutoPromotionsManagerParams(
|
53
|
+
thread_pool_size=thread_pool_size,
|
54
|
+
env_name=env_name,
|
55
|
+
app_name=app_name,
|
56
|
+
)
|
57
|
+
return cls(dependencies=dependencies, params=params)
|
58
|
+
|
59
|
+
def __init__(
|
60
|
+
self, dependencies: Dependencies, params: SaasAutoPromotionsManagerParams
|
52
61
|
):
|
53
|
-
|
54
|
-
self.
|
55
|
-
self._saas_file_inventory = saas_file_inventory
|
56
|
-
self._merge_request_manager_v2 = merge_request_manager_v2
|
57
|
-
self._s3_exporter = s3_exporter
|
58
|
-
self._thread_pool_size = thread_pool_size
|
59
|
-
self._dry_run = dry_run
|
62
|
+
super().__init__(params)
|
63
|
+
self._dependencies = dependencies
|
60
64
|
|
61
65
|
def _fetch_publisher_state(
|
62
66
|
self,
|
63
67
|
publisher: Publisher,
|
64
68
|
) -> None:
|
65
69
|
publisher.fetch_commit_shas_and_deployment_info(
|
66
|
-
vcs=self.
|
67
|
-
deployment_state=self.
|
70
|
+
vcs=self._dependencies.vcs,
|
71
|
+
deployment_state=self._dependencies.deployment_state,
|
68
72
|
)
|
69
73
|
|
70
|
-
def _fetch_publisher_real_world_states(self) -> None:
|
74
|
+
def _fetch_publisher_real_world_states(self, thread_pool_size: int) -> None:
|
71
75
|
threaded.run(
|
72
76
|
self._fetch_publisher_state,
|
73
|
-
self.
|
74
|
-
thread_pool_size=
|
77
|
+
self._dependencies.saas_file_inventory.publishers,
|
78
|
+
thread_pool_size=thread_pool_size,
|
75
79
|
)
|
76
80
|
|
77
81
|
def _compute_desired_subscriber_states(self) -> None:
|
78
|
-
for subscriber in self.
|
82
|
+
for subscriber in self._dependencies.saas_file_inventory.subscribers:
|
79
83
|
subscriber.compute_desired_state()
|
80
84
|
|
81
85
|
def _get_subscribers_with_diff(self) -> list[Subscriber]:
|
82
|
-
return [
|
86
|
+
return [
|
87
|
+
s
|
88
|
+
for s in self._dependencies.saas_file_inventory.subscribers
|
89
|
+
if s.has_diff()
|
90
|
+
]
|
83
91
|
|
84
|
-
def reconcile(self) -> None:
|
85
|
-
self.
|
86
|
-
self._fetch_publisher_real_world_states()
|
92
|
+
def reconcile(self, thread_pool_size: int, dry_run: bool) -> None:
|
93
|
+
self._dependencies.deployment_state.cache_commit_shas_from_s3()
|
94
|
+
self._fetch_publisher_real_world_states(thread_pool_size)
|
87
95
|
self._compute_desired_subscriber_states()
|
88
96
|
subscribers_with_diff = self._get_subscribers_with_diff()
|
89
|
-
|
90
|
-
self.
|
91
|
-
|
97
|
+
|
98
|
+
mr_parser = MRParser(vcs=self._dependencies.vcs)
|
99
|
+
merge_request_manager_v2 = MergeRequestManagerV2(
|
100
|
+
vcs=self._dependencies.vcs,
|
101
|
+
reconciler=Batcher(),
|
102
|
+
mr_parser=mr_parser,
|
103
|
+
renderer=Renderer(),
|
104
|
+
)
|
105
|
+
merge_request_manager_v2.reconcile(subscribers=subscribers_with_diff)
|
106
|
+
|
107
|
+
s3_exporter = S3Exporter(
|
108
|
+
state=self._dependencies.sapm_state,
|
109
|
+
dry_run=dry_run,
|
110
|
+
)
|
111
|
+
s3_exporter.export_publisher_data(
|
112
|
+
publishers=self._dependencies.saas_file_inventory.publishers
|
92
113
|
)
|
93
114
|
|
115
|
+
@property
|
116
|
+
def name(self) -> str:
|
117
|
+
return QONTRACT_INTEGRATION
|
94
118
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
) ->
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
State,
|
107
|
-
State,
|
108
|
-
]:
|
109
|
-
"""
|
110
|
-
Lets initialize everything that involves calls to external dependencies:
|
111
|
-
- VCS -> Gitlab / Github queries
|
112
|
-
- SaaSFileInventory -> qontract-server GQL query
|
113
|
-
- DeploymentState -> S3 queries
|
114
|
-
- MergeRequestManager -> Managing SAPM MRs with app-interface
|
115
|
-
"""
|
116
|
-
vault_settings = get_app_interface_vault_settings()
|
117
|
-
allow_deleting_mrs = get_feature_toggle_state(
|
118
|
-
integration_name=f"{QONTRACT_INTEGRATION}-allow-deleting-mrs",
|
119
|
-
default=False,
|
120
|
-
)
|
121
|
-
allow_opening_mrs = get_feature_toggle_state(
|
122
|
-
integration_name=f"{QONTRACT_INTEGRATION}-allow-opening-mrs",
|
123
|
-
default=False,
|
124
|
-
)
|
125
|
-
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
126
|
-
vcs = VCS(
|
127
|
-
secret_reader=secret_reader,
|
128
|
-
github_orgs=get_github_orgs(),
|
129
|
-
gitlab_instances=get_gitlab_instances(),
|
130
|
-
app_interface_repo_url=get_app_interface_repo_url(),
|
131
|
-
dry_run=dry_run,
|
132
|
-
allow_deleting_mrs=allow_deleting_mrs,
|
133
|
-
allow_opening_mrs=allow_opening_mrs,
|
134
|
-
)
|
135
|
-
mr_parser = MRParser(vcs=vcs)
|
136
|
-
merge_request_manager_v2 = MergeRequestManagerV2(
|
137
|
-
vcs=vcs,
|
138
|
-
reconciler=Batcher(),
|
139
|
-
mr_parser=mr_parser,
|
140
|
-
renderer=Renderer(),
|
141
|
-
)
|
142
|
-
saas_files = get_saas_files(env_name=env_name, app_name=app_name)
|
143
|
-
saas_inventory = SaasFilesInventory(
|
144
|
-
saas_files=saas_files,
|
145
|
-
secret_reader=secret_reader,
|
146
|
-
thread_pool_size=thread_pool_size,
|
147
|
-
)
|
148
|
-
saas_deploy_state = init_state(
|
149
|
-
integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
|
150
|
-
)
|
151
|
-
deployment_state = PromotionState(
|
152
|
-
state=saas_deploy_state,
|
153
|
-
)
|
154
|
-
sapm_state = init_state(
|
155
|
-
integration=QONTRACT_INTEGRATION, secret_reader=secret_reader
|
156
|
-
)
|
157
|
-
s3_exporter = S3Exporter(
|
158
|
-
state=sapm_state,
|
159
|
-
dry_run=dry_run,
|
160
|
-
)
|
161
|
-
return (
|
162
|
-
deployment_state,
|
163
|
-
vcs,
|
164
|
-
saas_inventory,
|
165
|
-
merge_request_manager_v2,
|
166
|
-
s3_exporter,
|
167
|
-
saas_deploy_state,
|
168
|
-
sapm_state,
|
169
|
-
)
|
170
|
-
|
171
|
-
|
172
|
-
@defer
|
173
|
-
def run(
|
174
|
-
dry_run: bool,
|
175
|
-
thread_pool_size: int,
|
176
|
-
env_name: str | None = None,
|
177
|
-
app_name: str | None = None,
|
178
|
-
defer: Callable | None = None,
|
179
|
-
) -> None:
|
180
|
-
(
|
181
|
-
deployment_state,
|
182
|
-
vcs,
|
183
|
-
saas_inventory,
|
184
|
-
merge_request_manager_v2,
|
185
|
-
s3_exporter,
|
186
|
-
saas_deploy_state,
|
187
|
-
sapm_state,
|
188
|
-
) = init_external_dependencies(
|
189
|
-
dry_run=dry_run,
|
190
|
-
env_name=env_name,
|
191
|
-
app_name=app_name,
|
192
|
-
thread_pool_size=thread_pool_size,
|
193
|
-
)
|
194
|
-
if defer:
|
195
|
-
defer(vcs.cleanup)
|
196
|
-
defer(saas_deploy_state.cleanup)
|
197
|
-
defer(sapm_state.cleanup)
|
198
|
-
|
199
|
-
integration = SaasAutoPromotionsManager(
|
200
|
-
deployment_state=deployment_state,
|
201
|
-
vcs=vcs,
|
202
|
-
saas_file_inventory=saas_inventory,
|
203
|
-
merge_request_manager_v2=merge_request_manager_v2,
|
204
|
-
s3_exporter=s3_exporter,
|
205
|
-
thread_pool_size=thread_pool_size,
|
206
|
-
dry_run=dry_run,
|
207
|
-
)
|
208
|
-
|
209
|
-
integration.reconcile()
|
119
|
+
@defer
|
120
|
+
def run(
|
121
|
+
self,
|
122
|
+
dry_run: bool,
|
123
|
+
defer: Callable | None = None,
|
124
|
+
) -> None:
|
125
|
+
if defer:
|
126
|
+
defer(self._dependencies.vcs.cleanup)
|
127
|
+
defer(self._dependencies.saas_deploy_state.cleanup)
|
128
|
+
defer(self._dependencies.sapm_state.cleanup)
|
129
|
+
self.reconcile(thread_pool_size=self.params.thread_pool_size, dry_run=dry_run)
|
@@ -41,7 +41,7 @@ SITE_CONTROLLER_LABELS = {
|
|
41
41
|
CONFIG_NAME = "skupper-site"
|
42
42
|
|
43
43
|
|
44
|
-
class
|
44
|
+
class SkupperNetworkError(Exception):
|
45
45
|
"""Base exception for Skupper Network integration."""
|
46
46
|
|
47
47
|
|
@@ -55,7 +55,7 @@ def load_site_controller_template(
|
|
55
55
|
resource["content"], undefined=jinja2.StrictUndefined
|
56
56
|
).render(variables)
|
57
57
|
except jinja2.exceptions.UndefinedError as e:
|
58
|
-
raise
|
58
|
+
raise SkupperNetworkError(
|
59
59
|
f"Failed to render template {path}: {e.message}"
|
60
60
|
) from None
|
61
61
|
return yaml.safe_load(body)
|
@@ -113,7 +113,7 @@ def compile_skupper_sites(
|
|
113
113
|
# we don't support skupper installations with just one site
|
114
114
|
for site in network_sites:
|
115
115
|
if site.is_island(network_sites):
|
116
|
-
raise
|
116
|
+
raise SkupperNetworkError(
|
117
117
|
f"{site}: Site is not connected to any other skupper site in the network."
|
118
118
|
)
|
119
119
|
|
reconcile/slack_usergroups.py
CHANGED
@@ -52,7 +52,7 @@ from reconcile.utils.extended_early_exit import (
|
|
52
52
|
from reconcile.utils.github_api import GithubRepositoryApi
|
53
53
|
from reconcile.utils.gitlab_api import GitLabApi
|
54
54
|
from reconcile.utils.pagerduty_api import (
|
55
|
-
|
55
|
+
PagerDutyApiError,
|
56
56
|
PagerDutyMap,
|
57
57
|
get_pagerduty_map,
|
58
58
|
)
|
@@ -64,7 +64,7 @@ from reconcile.utils.secret_reader import (
|
|
64
64
|
from reconcile.utils.slack_api import (
|
65
65
|
SlackApi,
|
66
66
|
SlackApiError,
|
67
|
-
|
67
|
+
UsergroupNotFoundError,
|
68
68
|
)
|
69
69
|
from reconcile.utils.vcs import VCS
|
70
70
|
|
@@ -204,7 +204,7 @@ def get_current_state(
|
|
204
204
|
continue
|
205
205
|
try:
|
206
206
|
users, channels, description = spec.slack.describe_usergroup(ug)
|
207
|
-
except
|
207
|
+
except UsergroupNotFoundError:
|
208
208
|
continue
|
209
209
|
current_state.setdefault(workspace, {})[ug] = State(
|
210
210
|
workspace=workspace,
|
@@ -249,7 +249,7 @@ def get_usernames_from_pagerduty(
|
|
249
249
|
pd = pagerduty_map.get(pagerduty.instance.name)
|
250
250
|
try:
|
251
251
|
pagerduty_names = pd.get_pagerduty_users(pd_resource_type, pd_resource_id)
|
252
|
-
except
|
252
|
+
except PagerDutyApiError as e:
|
253
253
|
logging.error(
|
254
254
|
f"[{usergroup}] PagerDuty API error: {e} "
|
255
255
|
"(hint: check that pagerduty schedule_id/escalation_policy_id is correct)"
|
reconcile/status_board.py
CHANGED
@@ -97,7 +97,7 @@ class Product(AbstractStatusBoard):
|
|
97
97
|
def update(self, ocm: OCMBaseClient) -> None:
|
98
98
|
err_msg = "Called update on StatusBoardHandler that doesn't have update method"
|
99
99
|
logging.error(err_msg)
|
100
|
-
raise
|
100
|
+
raise UpdateNotSupportedError(err_msg)
|
101
101
|
|
102
102
|
def delete(self, ocm: OCMBaseClient) -> None:
|
103
103
|
if not self.id:
|
@@ -133,7 +133,7 @@ class Application(AbstractStatusBoard):
|
|
133
133
|
def update(self, ocm: OCMBaseClient) -> None:
|
134
134
|
err_msg = "Called update on StatusBoardHandler that doesn't have update method"
|
135
135
|
logging.error(err_msg)
|
136
|
-
raise
|
136
|
+
raise UpdateNotSupportedError(err_msg)
|
137
137
|
|
138
138
|
def delete(self, ocm: OCMBaseClient) -> None:
|
139
139
|
if not self.id:
|
@@ -219,7 +219,7 @@ Application.update_forward_refs()
|
|
219
219
|
Service.update_forward_refs()
|
220
220
|
|
221
221
|
|
222
|
-
class
|
222
|
+
class UpdateNotSupportedError(Exception):
|
223
223
|
pass
|
224
224
|
|
225
225
|
|