qontract-reconcile 0.10.1rc595__py3-none-any.whl → 0.10.1rc597__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.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/RECORD +15 -10
- reconcile/aus/base.py +45 -59
- reconcile/aus/version_gate_approver.py +204 -0
- reconcile/aus/version_gates/__init__.py +9 -0
- reconcile/aus/version_gates/handler.py +33 -0
- reconcile/aus/version_gates/ocp_gate_handler.py +26 -0
- reconcile/aus/version_gates/sts_version_gate_handler.py +101 -0
- reconcile/cli.py +59 -0
- reconcile/utils/aws_helper.py +11 -0
- reconcile/utils/ocm/base.py +61 -2
- reconcile/utils/semver_helper.py +5 -0
- {qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc597
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
6
|
Author: Red Hat App-SRE Team
|
{qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/RECORD
RENAMED
@@ -9,7 +9,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
|
|
9
9
|
reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
|
10
10
|
reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
|
11
11
|
reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
|
12
|
-
reconcile/cli.py,sha256=
|
12
|
+
reconcile/cli.py,sha256=eZfqiWuI1r_BEx_VzxTPYrqqs1PHiQPAnEt00qZ3vNo,91518
|
13
13
|
reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
|
14
14
|
reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
|
15
15
|
reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
|
@@ -120,7 +120,7 @@ reconcile/vpc_peerings_validator.py,sha256=Kv22HJVlTW9l9GB2eXwjPWqdDbr_VuvQBNPtt
|
|
120
120
|
reconcile/aus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
121
121
|
reconcile/aus/advanced_upgrade_service.py,sha256=PkVcXBMrveW5euvqjEBO4e5-9KDb_6hszLI2GrWpx2w,21378
|
122
122
|
reconcile/aus/aus_label_source.py,sha256=qoP8Fgxuu1tCuhG6ixCWve7Ll-KD6a79E2uLAmC0ifw,4184
|
123
|
-
reconcile/aus/base.py,sha256=
|
123
|
+
reconcile/aus/base.py,sha256=ESycSlsxNGjpy1v2G2NXB7WB6NMYrXCaN4kSVMicRKk,44462
|
124
124
|
reconcile/aus/cluster_version_data.py,sha256=j4UyEBi5mQuvPq5Lo7a_L_0blxvH790wJV07uAiikFU,7126
|
125
125
|
reconcile/aus/metrics.py,sha256=fIew-rzi_kYuI5Gxn3-4bQVIr2oNibiKPyGnhB-xKU4,3538
|
126
126
|
reconcile/aus/models.py,sha256=muBmbovxYtSNLFrTLVRcJYZ4dx6JLh8n3Q1-DjWJOHM,7098
|
@@ -128,6 +128,11 @@ reconcile/aus/ocm_addons_upgrade_scheduler_org.py,sha256=fshslI27hrqT40qrVsVOQaW
|
|
128
128
|
reconcile/aus/ocm_upgrade_scheduler.py,sha256=7cK2SakCFkl5EdnqUEAYdUo4pUnnf-SsUR10uytAGyE,3058
|
129
129
|
reconcile/aus/ocm_upgrade_scheduler_org.py,sha256=OBgE5mnVdQQV4tMH0AE2V_PDt9Gy6d-LyuPceqjORts,2331
|
130
130
|
reconcile/aus/upgrades.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
131
|
+
reconcile/aus/version_gate_approver.py,sha256=VJ6Lrzkapr14QvulzIsE-sTfawIDcyn9UGOC3pIM1gs,6895
|
132
|
+
reconcile/aus/version_gates/__init__.py,sha256=fWx-IvS132Wpa4gWNIuoNvFwqhkuUuFWYWq5-xiLklI,362
|
133
|
+
reconcile/aus/version_gates/handler.py,sha256=S_isQPYHbG4DERiUEvQBZ6ngiFX3uMmATA-Q_eNKmFk,839
|
134
|
+
reconcile/aus/version_gates/ocp_gate_handler.py,sha256=RW1ppDaCZXVegV9AzzqYXxDUu_Z_7d43Z5h2Pk_piKc,716
|
135
|
+
reconcile/aus/version_gates/sts_version_gate_handler.py,sha256=PhJ7yBh2q-rv9CJcfFhc0H11nyDyG7NAryNS3F74xdY,3697
|
131
136
|
reconcile/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
132
137
|
reconcile/aws_ami_cleanup/integration.py,sha256=IW95cpMj2P5ffs-AxsR_TDQCJnYFBhLIfP2de7dz_8A,10109
|
133
138
|
reconcile/aws_cloudwatch_log_retention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -535,7 +540,7 @@ reconcile/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
535
540
|
reconcile/utils/aggregated_list.py,sha256=pkYoBj7WwmaNgEefETqEOFTnQMcUzHE3mdsVdzGYj60,3372
|
536
541
|
reconcile/utils/amtool.py,sha256=JV5-to_e_FaIcvJWTKYA9d6L3LwzwijM0MjUWn83eD4,2204
|
537
542
|
reconcile/utils/aws_api.py,sha256=Wy040GBQ3HrWmtxe1QAx3zl1I3phVDVjXEx_OITEcOw,69191
|
538
|
-
reconcile/utils/aws_helper.py,sha256=
|
543
|
+
reconcile/utils/aws_helper.py,sha256=6Nfgsz0aQ97LBAJ0JBRdnPaFTAkEBSqXvCH6_pVIWdw,2006
|
539
544
|
reconcile/utils/binary.py,sha256=3IBnwjKakHM367skPPvG6yVSQYjKt5muQlFNdoa63DU,2352
|
540
545
|
reconcile/utils/config.py,sha256=aId5zrPjM_84u_T4yTRE_Psu3zo5-5_JCR6_7Wgv5UQ,990
|
541
546
|
reconcile/utils/constants.py,sha256=pOUd97bqZdsAu5RWJ8NUs9cwCY7K9y0eW9VVeJ4fZIU,138
|
@@ -593,7 +598,7 @@ reconcile/utils/raw_github_api.py,sha256=ZHC-SZuAyRe1zaMoOU7Krt1-zecDxENd9c_NzQY
|
|
593
598
|
reconcile/utils/repo_owners.py,sha256=j-pUjc9PuDzq7KpjNLpnhqfU8tUG4nj2WMhFp4ick7g,6629
|
594
599
|
reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
|
595
600
|
reconcile/utils/secret_reader.py,sha256=2DeYAAQFjUULEKlLw3UDAUoND6gbqvCh9uKPtlc-0us,10403
|
596
|
-
reconcile/utils/semver_helper.py,sha256
|
601
|
+
reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
|
597
602
|
reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,839
|
598
603
|
reconcile/utils/slack_api.py,sha256=OPmzU6L9rJx2XXDlZkMlxLjOWu17yC-fVCoUItzQrXw,16295
|
599
604
|
reconcile/utils/smtp_client.py,sha256=gJNbBQJpAt5PX4t_TaeNHsXM8vt50bFgndml6yK2b5o,2800
|
@@ -642,7 +647,7 @@ reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py,sha256=RzEKRT_BhvB2ud9py
|
|
642
647
|
reconcile/utils/mr/user_maintenance.py,sha256=cHPBn8zrReWLHalyk-EFdkFJe9zjVjRoZhT4t2zZfGE,3956
|
643
648
|
reconcile/utils/ocm/__init__.py,sha256=5Pcf5cyftDWT5XRi1EzvNklOVxGplJi-v12HN3TDarc,57
|
644
649
|
reconcile/utils/ocm/addons.py,sha256=8wVrt16i69KkXq1fQByVheSQRhrRELbuOHb7Tz9bKT0,1675
|
645
|
-
reconcile/utils/ocm/base.py,sha256=
|
650
|
+
reconcile/utils/ocm/base.py,sha256=GclZtCrPkPJmGP9HHvqIlV-8VXSKuaQTQJkA2pklN60,13817
|
646
651
|
reconcile/utils/ocm/cluster_groups.py,sha256=F8oqVqN_4QUnGL0K61zZhoYIzJeP57EcmZpwmoV0mr4,1751
|
647
652
|
reconcile/utils/ocm/clusters.py,sha256=Q6g5kGSNfxZUZ56LPFAYjOz8xJ2c6QG76V78GvyLxB0,7448
|
648
653
|
reconcile/utils/ocm/identity_providers.py,sha256=dKed09N8iWmn39tI_MpwgVe47x23eLsknGbjMUxtwr4,2175
|
@@ -693,8 +698,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
693
698
|
tools/test/test_qontract_cli.py,sha256=OvalpVRfY4pNmpMaWHHYqBjV68b1eGQjX8SCyTAXb1w,3501
|
694
699
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
695
700
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
696
|
-
qontract_reconcile-0.10.
|
697
|
-
qontract_reconcile-0.10.
|
698
|
-
qontract_reconcile-0.10.
|
699
|
-
qontract_reconcile-0.10.
|
700
|
-
qontract_reconcile-0.10.
|
701
|
+
qontract_reconcile-0.10.1rc597.dist-info/METADATA,sha256=d8mSQupiOAr8Iya1HYE2kxpuoqxKJv1EYTjBdA4WdOs,2349
|
702
|
+
qontract_reconcile-0.10.1rc597.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
703
|
+
qontract_reconcile-0.10.1rc597.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
|
704
|
+
qontract_reconcile-0.10.1rc597.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
705
|
+
qontract_reconcile-0.10.1rc597.dist-info/RECORD,,
|
reconcile/aus/base.py
CHANGED
@@ -19,7 +19,7 @@ from typing import (
|
|
19
19
|
|
20
20
|
import semver
|
21
21
|
from croniter import croniter
|
22
|
-
from pydantic import BaseModel
|
22
|
+
from pydantic import BaseModel, Extra
|
23
23
|
from semver import VersionInfo
|
24
24
|
|
25
25
|
from reconcile.aus.cluster_version_data import (
|
@@ -44,6 +44,7 @@ from reconcile.aus.models import (
|
|
44
44
|
OrganizationUpgradeSpec,
|
45
45
|
Sector,
|
46
46
|
)
|
47
|
+
from reconcile.aus.version_gates import HANDLERS
|
47
48
|
from reconcile.gql_definitions.advanced_upgrade_service.aus_organization import (
|
48
49
|
query as aus_organizations_query,
|
49
50
|
)
|
@@ -71,7 +72,6 @@ from reconcile.utils.ocm.upgrades import (
|
|
71
72
|
create_control_plane_upgrade_policy,
|
72
73
|
create_node_pool_upgrade_policy,
|
73
74
|
create_upgrade_policy,
|
74
|
-
create_version_agreement,
|
75
75
|
delete_addon_upgrade_policy,
|
76
76
|
delete_control_plane_upgrade_policy,
|
77
77
|
delete_upgrade_policy,
|
@@ -88,6 +88,7 @@ from reconcile.utils.runtime.integration import (
|
|
88
88
|
QontractReconcileIntegration,
|
89
89
|
)
|
90
90
|
from reconcile.utils.semver_helper import (
|
91
|
+
get_version_prefix,
|
91
92
|
parse_semver,
|
92
93
|
sort_versions,
|
93
94
|
)
|
@@ -299,21 +300,6 @@ class AdvancedUpgradeSchedulerBaseIntegration(
|
|
299
300
|
)
|
300
301
|
|
301
302
|
|
302
|
-
class GateAgreement(BaseModel):
|
303
|
-
gate: OCMVersionGate
|
304
|
-
|
305
|
-
def create(self, ocm_api: OCMBaseClient, cluster: OCMCluster) -> None:
|
306
|
-
logging.info(
|
307
|
-
f"create agreement for gate {self.gate.id} on cluster {cluster.name} (id={cluster.id})"
|
308
|
-
)
|
309
|
-
agreement = create_version_agreement(ocm_api, self.gate.id, cluster.id)
|
310
|
-
if agreement.get("version_gate") is None:
|
311
|
-
logging.error(
|
312
|
-
"Unexpected response while creating version "
|
313
|
-
f"agreement with id {self.gate.id} for cluster {cluster.name} (id={cluster.id})"
|
314
|
-
)
|
315
|
-
|
316
|
-
|
317
303
|
class RemainingSoakDayMetricsBuilder(Protocol):
|
318
304
|
def __call__(
|
319
305
|
self, cluster_uuid: str, soaking_version: str
|
@@ -467,18 +453,12 @@ class NodePoolUpgradePolicy(AbstractUpgradePolicy):
|
|
467
453
|
return f"node pool upgrade policy - {remove_none_values_from_dict(details)}"
|
468
454
|
|
469
455
|
|
470
|
-
class UpgradePolicyHandler(BaseModel):
|
456
|
+
class UpgradePolicyHandler(BaseModel, extra=Extra.forbid):
|
471
457
|
"""Class to handle upgrade policy actions"""
|
472
458
|
|
473
459
|
action: str
|
474
460
|
policy: AbstractUpgradePolicy
|
475
461
|
|
476
|
-
gates_to_agree: Optional[list[GateAgreement]]
|
477
|
-
|
478
|
-
def _create_gate_agreements(self, ocm_api: OCMBaseClient) -> None:
|
479
|
-
for gate in self.gates_to_agree or []:
|
480
|
-
gate.create(ocm_api, self.policy.cluster)
|
481
|
-
|
482
462
|
def act(self, dry_run: bool, ocm_api: OCMBaseClient) -> None:
|
483
463
|
logging.info(f"{self.action} {self.policy.summarize()}")
|
484
464
|
if dry_run:
|
@@ -489,7 +469,6 @@ class UpgradePolicyHandler(BaseModel):
|
|
489
469
|
elif self.action == "delete":
|
490
470
|
self.policy.delete(ocm_api)
|
491
471
|
elif self.action == "create":
|
492
|
-
self._create_gate_agreements(ocm_api)
|
493
472
|
self.policy.create(ocm_api)
|
494
473
|
|
495
474
|
|
@@ -727,10 +706,34 @@ def gates_for_minor_version(
|
|
727
706
|
return [g for g in gates if g.version_raw_id_prefix == target_version_prefix]
|
728
707
|
|
729
708
|
|
709
|
+
def is_gate_applicable_to_cluster(gate: OCMVersionGate, cluster: OCMCluster) -> bool:
|
710
|
+
# check that the cluster has an upgrade path that crosses the gate version
|
711
|
+
minor_version_upgrade_paths = {
|
712
|
+
get_version_prefix(version) for version in cluster.available_upgrades()
|
713
|
+
}
|
714
|
+
if gate.version_raw_id_prefix not in minor_version_upgrade_paths:
|
715
|
+
return False
|
716
|
+
|
717
|
+
# consider only gates after the clusters current minor version
|
718
|
+
# OCM onls supports creating gate agreements for later minor versions than the
|
719
|
+
# current cluster version
|
720
|
+
if not semver.match(
|
721
|
+
f"{cluster.minor_version()}.0", f"<{gate.version_raw_id_prefix}.0"
|
722
|
+
):
|
723
|
+
return False
|
724
|
+
|
725
|
+
# check the handler for the gate type if it is responsible for this kind
|
726
|
+
# of cluster
|
727
|
+
handler = HANDLERS.get(gate.label)
|
728
|
+
if handler:
|
729
|
+
return handler.gate_applicable_to_cluster(cluster)
|
730
|
+
return False
|
731
|
+
|
732
|
+
|
730
733
|
def gates_to_agree(
|
731
734
|
gates: list[OCMVersionGate],
|
732
735
|
cluster: OCMCluster,
|
733
|
-
|
736
|
+
acked_gate_ids: set[str],
|
734
737
|
) -> list[OCMVersionGate]:
|
735
738
|
"""Check via OCM if a version is agreed
|
736
739
|
|
@@ -742,36 +745,13 @@ def gates_to_agree(
|
|
742
745
|
Returns:
|
743
746
|
list[OCMVersionGate]: list of gates a cluster has not agreed on yet
|
744
747
|
"""
|
745
|
-
applicable_gates = [
|
746
|
-
g
|
747
|
-
for g in gates
|
748
|
-
# todo: sts version gates need special handling - https://issues.redhat.com/browse/APPSRE-7949
|
749
|
-
# until this is solved, we can't do automated upgrades for STS clusters that cross a version gate
|
750
|
-
# once we have proper and secure handling get gate agreements for STS clusters, we can use this condition:
|
751
|
-
# `and (not g.sts_only or g.sts_only == cluster.is_sts())`
|
752
|
-
if not g.sts_only
|
753
|
-
# consider only gates after the clusters current minor version
|
754
|
-
# OCM onls supports creating gate agreements for later minor versions than the
|
755
|
-
# current cluster version
|
756
|
-
and semver.match(
|
757
|
-
f"{cluster.minor_version()}.0", f"<{g.version_raw_id_prefix}.0"
|
758
|
-
)
|
759
|
-
]
|
748
|
+
applicable_gates = [g for g in gates if is_gate_applicable_to_cluster(g, cluster)]
|
760
749
|
|
761
750
|
if applicable_gates:
|
762
|
-
|
763
|
-
agreement["version_gate"]["id"]
|
764
|
-
for agreement in get_version_agreement(ocm_api, cluster.id)
|
765
|
-
}
|
766
|
-
return [gate for gate in applicable_gates if gate.id not in current_agreements]
|
751
|
+
return [gate for gate in applicable_gates if gate.id not in acked_gate_ids]
|
767
752
|
return []
|
768
753
|
|
769
754
|
|
770
|
-
def get_version_prefix(version: str) -> str:
|
771
|
-
semver = parse_semver(version)
|
772
|
-
return f"{semver.major}.{semver.minor}"
|
773
|
-
|
774
|
-
|
775
755
|
def upgradeable_version(
|
776
756
|
spec: ClusterUpgradeSpec,
|
777
757
|
version_data: VersionData,
|
@@ -1024,18 +1004,24 @@ def calculate_diff(
|
|
1024
1004
|
"Skip creation of an upgrade policy."
|
1025
1005
|
)
|
1026
1006
|
continue
|
1007
|
+
gates = gates_to_agree(
|
1008
|
+
gates=minor_version_gates,
|
1009
|
+
cluster=spec.cluster,
|
1010
|
+
acked_gate_ids={
|
1011
|
+
agreement["version_gate"]["id"]
|
1012
|
+
for agreement in get_version_agreement(ocm_api, spec.cluster.id)
|
1013
|
+
},
|
1014
|
+
)
|
1015
|
+
if gates:
|
1016
|
+
gate_ids = [gate.id for gate in gates]
|
1017
|
+
logging.debug(
|
1018
|
+
f"[{spec.org.org_id}/{spec.org.name}/{spec.cluster.name}] found gates for {target_version_prefix} - {gate_ids} "
|
1019
|
+
"Skip creation of an upgrade policy until all of them have been acked by the version-gate-approver integration or a user."
|
1020
|
+
)
|
1027
1021
|
diffs.append(
|
1028
1022
|
UpgradePolicyHandler(
|
1029
1023
|
action="create",
|
1030
1024
|
policy=_create_upgrade_policy(next_schedule, spec, version),
|
1031
|
-
gates_to_agree=[
|
1032
|
-
GateAgreement(gate=g)
|
1033
|
-
for g in gates_to_agree(
|
1034
|
-
minor_version_gates,
|
1035
|
-
spec.cluster,
|
1036
|
-
ocm_api,
|
1037
|
-
)
|
1038
|
-
],
|
1039
1025
|
)
|
1040
1026
|
)
|
1041
1027
|
set_mutex(locked, spec.cluster.id, spec.upgrade_policy.conditions.mutexes)
|
@@ -0,0 +1,204 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import (
|
3
|
+
Callable,
|
4
|
+
Iterable,
|
5
|
+
Optional,
|
6
|
+
)
|
7
|
+
|
8
|
+
import semver
|
9
|
+
|
10
|
+
from reconcile.aus.advanced_upgrade_service import aus_label_key
|
11
|
+
from reconcile.aus.base import gates_to_agree, get_orgs_for_environment
|
12
|
+
from reconcile.aus.version_gates import ocp_gate_handler, sts_version_gate_handler
|
13
|
+
from reconcile.aus.version_gates.handler import GateHandler
|
14
|
+
from reconcile.gql_definitions.common.ocm_environments import (
|
15
|
+
query as ocm_environment_query,
|
16
|
+
)
|
17
|
+
from reconcile.utils import gql
|
18
|
+
from reconcile.utils.grouping import group_by
|
19
|
+
from reconcile.utils.jobcontroller.controller import (
|
20
|
+
build_job_controller,
|
21
|
+
)
|
22
|
+
from reconcile.utils.ocm.base import (
|
23
|
+
ClusterDetails,
|
24
|
+
LabelContainer,
|
25
|
+
OCMCluster,
|
26
|
+
OCMVersionGate,
|
27
|
+
)
|
28
|
+
from reconcile.utils.ocm.clusters import discover_clusters_by_labels
|
29
|
+
from reconcile.utils.ocm.search_filters import Filter
|
30
|
+
from reconcile.utils.ocm.upgrades import (
|
31
|
+
create_version_agreement,
|
32
|
+
get_version_agreement,
|
33
|
+
get_version_gates,
|
34
|
+
)
|
35
|
+
from reconcile.utils.ocm_base_client import (
|
36
|
+
OCMBaseClient,
|
37
|
+
init_ocm_base_client,
|
38
|
+
init_ocm_base_client_for_org,
|
39
|
+
)
|
40
|
+
from reconcile.utils.runtime.integration import (
|
41
|
+
PydanticRunParams,
|
42
|
+
QontractReconcileIntegration,
|
43
|
+
)
|
44
|
+
|
45
|
+
QONTRACT_INTEGRATION = "version-gate-approver"
|
46
|
+
QONTRACT_INTEGRATION_VERSION = semver.format_version(0, 1, 0)
|
47
|
+
|
48
|
+
|
49
|
+
class VersionGateApproverParams(PydanticRunParams):
|
50
|
+
job_controller_cluster: str
|
51
|
+
job_controller_namespace: str
|
52
|
+
rosa_job_service_account: str
|
53
|
+
rosa_role: str
|
54
|
+
rosa_job_image: Optional[str] = None
|
55
|
+
|
56
|
+
|
57
|
+
class VersionGateApprover(QontractReconcileIntegration[VersionGateApproverParams]):
|
58
|
+
@property
|
59
|
+
def name(self) -> str:
|
60
|
+
return QONTRACT_INTEGRATION
|
61
|
+
|
62
|
+
def initialize_handlers(self, query_func: Callable) -> None:
|
63
|
+
self.handlers: dict[str, GateHandler] = {
|
64
|
+
sts_version_gate_handler.GATE_LABEL: sts_version_gate_handler.STSGateHandler(
|
65
|
+
job_controller=build_job_controller(
|
66
|
+
integration=QONTRACT_INTEGRATION,
|
67
|
+
integration_version=QONTRACT_INTEGRATION_VERSION,
|
68
|
+
cluster=self.params.job_controller_cluster,
|
69
|
+
namespace=self.params.job_controller_namespace,
|
70
|
+
secret_reader=self.secret_reader,
|
71
|
+
dry_run=False,
|
72
|
+
),
|
73
|
+
aws_iam_role=self.params.rosa_role,
|
74
|
+
rosa_job_service_account=self.params.rosa_job_service_account,
|
75
|
+
rosa_job_image=self.params.rosa_job_image,
|
76
|
+
),
|
77
|
+
ocp_gate_handler.GATE_LABEL: ocp_gate_handler.OCPGateHandler(),
|
78
|
+
}
|
79
|
+
|
80
|
+
def run(self, dry_run: bool) -> None:
|
81
|
+
gql_api = gql.get_api()
|
82
|
+
self.initialize_handlers(gql_api.query)
|
83
|
+
environments = ocm_environment_query(gql_api.query).environments
|
84
|
+
ocm_apis = {
|
85
|
+
env.name: init_ocm_base_client(env, self.secret_reader)
|
86
|
+
for env in environments
|
87
|
+
}
|
88
|
+
for env in environments:
|
89
|
+
self.process_environment(
|
90
|
+
ocm_env_name=env.name,
|
91
|
+
ocm_api=ocm_apis[env.name],
|
92
|
+
query_func=gql_api.query,
|
93
|
+
dry_run=dry_run,
|
94
|
+
)
|
95
|
+
|
96
|
+
def process_environment(
|
97
|
+
self,
|
98
|
+
ocm_env_name: str,
|
99
|
+
ocm_api: OCMBaseClient,
|
100
|
+
query_func: Callable,
|
101
|
+
dry_run: bool,
|
102
|
+
) -> None:
|
103
|
+
"""
|
104
|
+
Find all clusters with AUS labels in the organization and process them
|
105
|
+
org by org.
|
106
|
+
"""
|
107
|
+
# lookup clusters
|
108
|
+
clusters = discover_clusters_by_labels(
|
109
|
+
ocm_api=ocm_api,
|
110
|
+
label_filter=Filter().like("key", aus_label_key("%")),
|
111
|
+
)
|
112
|
+
clusters_by_org_id = group_by(clusters, lambda c: c.organization_id)
|
113
|
+
|
114
|
+
# lookup version gates
|
115
|
+
gates = get_version_gates(ocm_api)
|
116
|
+
|
117
|
+
# lookup organization metadata
|
118
|
+
organizations = get_orgs_for_environment(
|
119
|
+
integration=QONTRACT_INTEGRATION,
|
120
|
+
ocm_env_name=ocm_env_name,
|
121
|
+
query_func=query_func,
|
122
|
+
ocm_organization_ids=set(clusters_by_org_id.keys()),
|
123
|
+
)
|
124
|
+
|
125
|
+
for org in organizations:
|
126
|
+
ocm_org_api = init_ocm_base_client_for_org(org, self.secret_reader)
|
127
|
+
self.process_organization(
|
128
|
+
clusters=clusters_by_org_id.get(org.org_id, []),
|
129
|
+
gates=gates,
|
130
|
+
ocm_api=ocm_org_api,
|
131
|
+
dry_run=dry_run,
|
132
|
+
)
|
133
|
+
|
134
|
+
def process_organization(
|
135
|
+
self,
|
136
|
+
clusters: Iterable[ClusterDetails],
|
137
|
+
gates: list[OCMVersionGate],
|
138
|
+
ocm_api: OCMBaseClient,
|
139
|
+
dry_run: bool,
|
140
|
+
) -> None:
|
141
|
+
"""
|
142
|
+
Process all clusters in an organization.
|
143
|
+
"""
|
144
|
+
for cluster in clusters:
|
145
|
+
unacked_gates = gates_to_agree(
|
146
|
+
cluster=cluster.ocm_cluster,
|
147
|
+
gates=gates,
|
148
|
+
acked_gate_ids={
|
149
|
+
agreement["version_gate"]["id"]
|
150
|
+
for agreement in get_version_agreement(
|
151
|
+
ocm_api, cluster.ocm_cluster.id
|
152
|
+
)
|
153
|
+
},
|
154
|
+
)
|
155
|
+
if not unacked_gates:
|
156
|
+
continue
|
157
|
+
self.process_cluster(
|
158
|
+
cluster=cluster.ocm_cluster,
|
159
|
+
enabled_gate_handlers=get_enabled_gate_handlers(cluster.labels),
|
160
|
+
gates=unacked_gates,
|
161
|
+
ocm_api=ocm_api,
|
162
|
+
ocm_org_id=cluster.organization_id,
|
163
|
+
dry_run=dry_run,
|
164
|
+
)
|
165
|
+
|
166
|
+
def process_cluster(
|
167
|
+
self,
|
168
|
+
cluster: OCMCluster,
|
169
|
+
enabled_gate_handlers: set[str],
|
170
|
+
gates: list[OCMVersionGate],
|
171
|
+
ocm_api: OCMBaseClient,
|
172
|
+
ocm_org_id: str,
|
173
|
+
dry_run: bool,
|
174
|
+
) -> None:
|
175
|
+
"""
|
176
|
+
Process all unacknowledged gates for a cluster.
|
177
|
+
"""
|
178
|
+
for gate in gates:
|
179
|
+
if gate.label in self.handlers and gate.label not in enabled_gate_handlers:
|
180
|
+
continue
|
181
|
+
success = self.handlers[gate.label].handle(
|
182
|
+
ocm_api=ocm_api,
|
183
|
+
ocm_org_id=ocm_org_id,
|
184
|
+
cluster=cluster,
|
185
|
+
gate=gate,
|
186
|
+
dry_run=dry_run,
|
187
|
+
)
|
188
|
+
if success and not dry_run:
|
189
|
+
create_version_agreement(ocm_api, gate.id, cluster.id)
|
190
|
+
elif not success:
|
191
|
+
logging.error(
|
192
|
+
f"Failed to handle gate {gate.id} for cluster {cluster.name}"
|
193
|
+
)
|
194
|
+
|
195
|
+
|
196
|
+
def get_enabled_gate_handlers(labels: LabelContainer) -> set[str]:
|
197
|
+
"""
|
198
|
+
Get the set of enabled gate handlers from the labels. Default to the OCP
|
199
|
+
gate to keep backwards compatibility (for now).
|
200
|
+
"""
|
201
|
+
handler_csv = labels.get_label_value(aus_label_key("version-gate-approvals"))
|
202
|
+
if not handler_csv:
|
203
|
+
return {ocp_gate_handler.GATE_LABEL}
|
204
|
+
return set(handler_csv.split(","))
|
@@ -0,0 +1,9 @@
|
|
1
|
+
from typing import Type
|
2
|
+
|
3
|
+
from reconcile.aus.version_gates import ocp_gate_handler, sts_version_gate_handler
|
4
|
+
from reconcile.aus.version_gates.handler import GateHandler
|
5
|
+
|
6
|
+
HANDLERS: dict[str, Type[GateHandler]] = {
|
7
|
+
ocp_gate_handler.GATE_LABEL: ocp_gate_handler.OCPGateHandler,
|
8
|
+
sts_version_gate_handler.GATE_LABEL: sts_version_gate_handler.STSGateHandler,
|
9
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from reconcile.utils.ocm.base import OCMCluster, OCMVersionGate
|
4
|
+
from reconcile.utils.ocm_base_client import OCMBaseClient
|
5
|
+
|
6
|
+
|
7
|
+
class GateHandler(ABC):
|
8
|
+
"""
|
9
|
+
A protocol for version gate handlers.
|
10
|
+
"""
|
11
|
+
|
12
|
+
@staticmethod
|
13
|
+
@abstractmethod
|
14
|
+
def gate_applicable_to_cluster(cluster: OCMCluster) -> bool:
|
15
|
+
"""
|
16
|
+
Check if the gate represented by this handler is applicable for the given cluster.
|
17
|
+
"""
|
18
|
+
...
|
19
|
+
|
20
|
+
@abstractmethod
|
21
|
+
def handle(
|
22
|
+
self,
|
23
|
+
ocm_api: OCMBaseClient,
|
24
|
+
ocm_org_id: str,
|
25
|
+
cluster: OCMCluster,
|
26
|
+
gate: OCMVersionGate,
|
27
|
+
dry_run: bool,
|
28
|
+
) -> bool:
|
29
|
+
"""
|
30
|
+
Take all necessary actions required by a version gate.
|
31
|
+
If successful, return True. Otherwise, return False.
|
32
|
+
"""
|
33
|
+
...
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from reconcile.aus.version_gates.handler import GateHandler
|
2
|
+
from reconcile.utils.ocm.base import OCMCluster, OCMVersionGate
|
3
|
+
from reconcile.utils.ocm_base_client import OCMBaseClient
|
4
|
+
|
5
|
+
GATE_LABEL = "api.openshift.com/gate-ocp"
|
6
|
+
|
7
|
+
|
8
|
+
class OCPGateHandler(GateHandler):
|
9
|
+
"""
|
10
|
+
Right now we just ack all gate-ocp gates...
|
11
|
+
We could do better in the future, e.g. inspecting insights findings on the cluster
|
12
|
+
"""
|
13
|
+
|
14
|
+
@staticmethod
|
15
|
+
def gate_applicable_to_cluster(_: OCMCluster) -> bool:
|
16
|
+
return True
|
17
|
+
|
18
|
+
def handle(
|
19
|
+
self,
|
20
|
+
ocm_api: OCMBaseClient,
|
21
|
+
ocm_org_id: str,
|
22
|
+
cluster: OCMCluster,
|
23
|
+
gate: OCMVersionGate,
|
24
|
+
dry_run: bool,
|
25
|
+
) -> bool:
|
26
|
+
return True
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from reconcile.aus.version_gates.handler import GateHandler
|
5
|
+
from reconcile.utils.jobcontroller.controller import K8sJobController
|
6
|
+
from reconcile.utils.ocm.base import OCMCluster, OCMVersionGate
|
7
|
+
from reconcile.utils.ocm_base_client import OCMBaseClient
|
8
|
+
from reconcile.utils.rosa.rosa_cli import RosaCliException
|
9
|
+
from reconcile.utils.rosa.session import RosaSession
|
10
|
+
|
11
|
+
GATE_LABEL = "api.openshift.com/gate-sts"
|
12
|
+
|
13
|
+
|
14
|
+
class STSGateHandler(GateHandler):
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
job_controller: K8sJobController,
|
18
|
+
aws_iam_role: str,
|
19
|
+
rosa_job_service_account: Optional[str] = None,
|
20
|
+
rosa_job_image: Optional[str] = None,
|
21
|
+
) -> None:
|
22
|
+
self.job_controller = job_controller
|
23
|
+
self.aws_iam_role = aws_iam_role
|
24
|
+
self.rosa_job_image = rosa_job_image
|
25
|
+
self.rosa_job_service_account = rosa_job_service_account
|
26
|
+
|
27
|
+
@staticmethod
|
28
|
+
def gate_applicable_to_cluster(cluster: OCMCluster) -> bool:
|
29
|
+
"""
|
30
|
+
The STS Gate is applicable to all clusters with STS enabled.
|
31
|
+
This could potentially also be OSD STS clusters. While this handler
|
32
|
+
does not handle OSD clusters as of now, it is still important that
|
33
|
+
we report the STS gate to be applicable to OSD STS clusters.
|
34
|
+
"""
|
35
|
+
return cluster.is_sts()
|
36
|
+
|
37
|
+
def handle(
|
38
|
+
self,
|
39
|
+
ocm_api: OCMBaseClient,
|
40
|
+
ocm_org_id: str,
|
41
|
+
cluster: OCMCluster,
|
42
|
+
gate: OCMVersionGate,
|
43
|
+
dry_run: bool,
|
44
|
+
) -> bool:
|
45
|
+
if (
|
46
|
+
not cluster.id
|
47
|
+
or not cluster.aws
|
48
|
+
or not cluster.aws.sts
|
49
|
+
or not cluster.is_sts()
|
50
|
+
):
|
51
|
+
# checked already but mypy :/
|
52
|
+
return False
|
53
|
+
|
54
|
+
if cluster.is_rosa_hypershift():
|
55
|
+
# thanks to hypershift managed policies, there is nothing to do for us here
|
56
|
+
# returning True will ack the version gate
|
57
|
+
return True
|
58
|
+
if not cluster.is_rosa_classic():
|
59
|
+
# we manage roels only for rosa classic clusters
|
60
|
+
# returning here will prevent OSD STS clusters to be handled right now
|
61
|
+
logging.error(
|
62
|
+
f"Cluster {cluster.id} is not a ROSA cluster. "
|
63
|
+
"STS version gates are only handled for ROSA classic clusters."
|
64
|
+
)
|
65
|
+
return False
|
66
|
+
|
67
|
+
rosa = RosaSession(
|
68
|
+
aws_account_id=cluster.aws.aws_account_id,
|
69
|
+
aws_region=cluster.region.id,
|
70
|
+
aws_iam_role=self.aws_iam_role,
|
71
|
+
ocm_org_id=ocm_org_id,
|
72
|
+
ocm_api=ocm_api,
|
73
|
+
job_controller=self.job_controller,
|
74
|
+
image=self.rosa_job_image,
|
75
|
+
service_account=self.rosa_job_service_account,
|
76
|
+
)
|
77
|
+
|
78
|
+
try:
|
79
|
+
# account role handling
|
80
|
+
account_role_prefix = cluster.aws.account_role_prefix
|
81
|
+
if not account_role_prefix:
|
82
|
+
raise Exception(
|
83
|
+
f"Can't upgrade account roles. Cluster {cluster.name} does not define spec.aws.account_role_prefix"
|
84
|
+
)
|
85
|
+
rosa.upgrade_account_roles(
|
86
|
+
role_prefix=account_role_prefix,
|
87
|
+
minor_version=gate.version_raw_id_prefix,
|
88
|
+
channel_group=cluster.version.channel_group,
|
89
|
+
dry_run=dry_run,
|
90
|
+
)
|
91
|
+
|
92
|
+
# operator role handling
|
93
|
+
rosa.upgrade_operator_roles(
|
94
|
+
cluster_id=cluster.id,
|
95
|
+
dry_run=dry_run,
|
96
|
+
)
|
97
|
+
except RosaCliException as e:
|
98
|
+
logging.error(f"Failed to upgrade roles for cluster {cluster.name}: {e}")
|
99
|
+
e.write_logs_to_logger(logging.error)
|
100
|
+
return False
|
101
|
+
return True
|
reconcile/cli.py
CHANGED
@@ -2448,6 +2448,65 @@ def advanced_upgrade_scheduler(
|
|
2448
2448
|
)
|
2449
2449
|
|
2450
2450
|
|
2451
|
+
@integration.command(short_help="Approves OCM cluster upgrade version gates.")
|
2452
|
+
@click.option(
|
2453
|
+
"--job-controller-cluster",
|
2454
|
+
help="The cluster holding the job-controller namepsace",
|
2455
|
+
required=True,
|
2456
|
+
envvar="JOB_CONTROLLER_CLUSTER",
|
2457
|
+
)
|
2458
|
+
@click.option(
|
2459
|
+
"--job-controller-namespace",
|
2460
|
+
help="The namespace used for ROSA jobs",
|
2461
|
+
required=True,
|
2462
|
+
envvar="JOB_CONTROLLER_NAMESPACE",
|
2463
|
+
)
|
2464
|
+
@click.option(
|
2465
|
+
"--rosa-job-service-account",
|
2466
|
+
help="The service-account used for ROSA jobs",
|
2467
|
+
required=True,
|
2468
|
+
envvar="ROSA_JOB_SERVICE_ACCOUNT",
|
2469
|
+
)
|
2470
|
+
@click.option(
|
2471
|
+
"--rosa-job-image",
|
2472
|
+
help="The container image to use to run ROSA cli command jobs",
|
2473
|
+
required=False,
|
2474
|
+
envvar="ROSA_JOB_IMAGE",
|
2475
|
+
)
|
2476
|
+
@click.option(
|
2477
|
+
"--rosa-role",
|
2478
|
+
help="The role to assume in the ROSA cluster account",
|
2479
|
+
required=True,
|
2480
|
+
envvar="ROSA_ROLE",
|
2481
|
+
)
|
2482
|
+
@click.pass_context
|
2483
|
+
def version_gate_approver(
|
2484
|
+
ctx,
|
2485
|
+
job_controller_cluster: str,
|
2486
|
+
job_controller_namespace: str,
|
2487
|
+
rosa_job_service_account: str,
|
2488
|
+
rosa_role: str,
|
2489
|
+
rosa_job_image: Optional[str],
|
2490
|
+
) -> None:
|
2491
|
+
from reconcile.aus.version_gate_approver import (
|
2492
|
+
VersionGateApprover,
|
2493
|
+
VersionGateApproverParams,
|
2494
|
+
)
|
2495
|
+
|
2496
|
+
run_class_integration(
|
2497
|
+
integration=VersionGateApprover(
|
2498
|
+
VersionGateApproverParams(
|
2499
|
+
job_controller_cluster=job_controller_cluster,
|
2500
|
+
job_controller_namespace=job_controller_namespace,
|
2501
|
+
rosa_job_service_account=rosa_job_service_account,
|
2502
|
+
rosa_job_image=rosa_job_image,
|
2503
|
+
rosa_role=rosa_role,
|
2504
|
+
)
|
2505
|
+
),
|
2506
|
+
ctx=ctx.obj,
|
2507
|
+
)
|
2508
|
+
|
2509
|
+
|
2451
2510
|
@integration.command(short_help="Manage Databases and Database Users.")
|
2452
2511
|
@vault_output_path
|
2453
2512
|
@click.pass_context
|
reconcile/utils/aws_helper.py
CHANGED
@@ -21,6 +21,17 @@ def get_account_uid_from_arn(arn):
|
|
21
21
|
return arn.split(":")[4]
|
22
22
|
|
23
23
|
|
24
|
+
def get_role_name_from_arn(arn: str) -> str:
|
25
|
+
# arn:aws:iam::12345:role/role-1 --> role-1
|
26
|
+
return arn.split("/")[-1]
|
27
|
+
|
28
|
+
|
29
|
+
def is_aws_managed_resource(arn: str) -> bool:
|
30
|
+
# arn:aws:iam::aws:role/role-1 --> True
|
31
|
+
# arn:aws:iam::12345:role/role-1 --> False
|
32
|
+
return get_account_uid_from_arn(arn) == "aws"
|
33
|
+
|
34
|
+
|
24
35
|
def get_details_from_role_link(role_link):
|
25
36
|
# https://signin.aws.amazon.com/switchrole?
|
26
37
|
# account=<uid>&roleName=<role_name> -->
|
reconcile/utils/ocm/base.py
CHANGED
@@ -14,6 +14,8 @@ from pydantic import (
|
|
14
14
|
Field,
|
15
15
|
)
|
16
16
|
|
17
|
+
from reconcile.utils.aws_helper import get_account_uid_from_arn, get_role_name_from_arn
|
18
|
+
|
17
19
|
LabelSetTypeVar = TypeVar("LabelSetTypeVar", bound=BaseModel)
|
18
20
|
ACTIVE_SUBSCRIPTION_STATES = {"Active", "Reserved"}
|
19
21
|
CAPABILITY_MANAGE_CLUSTER_ADMIN = "capability.cluster.manage_cluster_admin"
|
@@ -47,6 +49,12 @@ class OCMVersionGate(BaseModel):
|
|
47
49
|
id: str
|
48
50
|
version_raw_id_prefix: str
|
49
51
|
sts_only: bool
|
52
|
+
label: str
|
53
|
+
"""
|
54
|
+
the label field holds a readable name for a verion gate, e.g.
|
55
|
+
- api.openshift.com/gate-sts
|
56
|
+
- api.openshift.com/gate-ocp
|
57
|
+
"""
|
50
58
|
|
51
59
|
|
52
60
|
class OCMClusterGroupId(Enum):
|
@@ -118,13 +126,62 @@ class OCMClusterFlag(BaseModel):
|
|
118
126
|
enabled: bool
|
119
127
|
|
120
128
|
|
129
|
+
class OCMClusterAWSOperatorRole(BaseModel):
|
130
|
+
id: str
|
131
|
+
name: str
|
132
|
+
namespace: str
|
133
|
+
role_arn: str
|
134
|
+
service_account: str
|
135
|
+
|
136
|
+
|
137
|
+
class OCMAWSSTS(OCMClusterFlag):
|
138
|
+
role_arn: Optional[str]
|
139
|
+
support_role_arn: Optional[str]
|
140
|
+
oidc_endpoint_url: Optional[str]
|
141
|
+
operator_iam_roles: Optional[list[OCMClusterAWSOperatorRole]]
|
142
|
+
instance_iam_roles: Optional[dict[str, str]]
|
143
|
+
operator_role_prefix: Optional[str]
|
144
|
+
|
145
|
+
|
121
146
|
class OCMClusterAWSSettings(BaseModel):
|
122
|
-
sts: Optional[
|
147
|
+
sts: Optional[OCMAWSSTS]
|
123
148
|
|
124
149
|
@property
|
125
150
|
def sts_enabled(self) -> bool:
|
126
151
|
return self.sts is not None and self.sts.enabled
|
127
152
|
|
153
|
+
@property
|
154
|
+
def aws_account_id(self) -> str:
|
155
|
+
return get_account_uid_from_arn(self.account_roles[0])
|
156
|
+
|
157
|
+
@property
|
158
|
+
def account_roles(self) -> list[str]:
|
159
|
+
if not self.sts or not self.sts.enabled:
|
160
|
+
return []
|
161
|
+
roles = []
|
162
|
+
if self.sts.role_arn:
|
163
|
+
roles.append(self.sts.role_arn)
|
164
|
+
if self.sts.support_role_arn:
|
165
|
+
roles.append(self.sts.support_role_arn)
|
166
|
+
for instance_iam_role in (self.sts.instance_iam_roles or {}).values():
|
167
|
+
roles.append(instance_iam_role)
|
168
|
+
return roles
|
169
|
+
|
170
|
+
@property
|
171
|
+
def account_role_prefix(self) -> Optional[str]:
|
172
|
+
INSTALLER_ROLE_BASE_NAME = "-Installer-Role"
|
173
|
+
installer_role_arn = self.sts.role_arn if self.sts else None
|
174
|
+
if installer_role_arn and installer_role_arn.endswith(INSTALLER_ROLE_BASE_NAME):
|
175
|
+
installer_role_name = get_role_name_from_arn(installer_role_arn)
|
176
|
+
return installer_role_name.removesuffix(INSTALLER_ROLE_BASE_NAME)
|
177
|
+
return None
|
178
|
+
|
179
|
+
@property
|
180
|
+
def operator_roles(self) -> list[str]:
|
181
|
+
if not self.sts:
|
182
|
+
return []
|
183
|
+
return [role.role_arn for role in self.sts.operator_iam_roles or []]
|
184
|
+
|
128
185
|
|
129
186
|
class OCMClusterVersion(BaseModel):
|
130
187
|
id: str
|
@@ -147,7 +204,6 @@ class OCMClusterDns(BaseModel):
|
|
147
204
|
|
148
205
|
|
149
206
|
class OCMExternalConfiguration(BaseModel):
|
150
|
-
kind: str
|
151
207
|
syncsets: dict
|
152
208
|
|
153
209
|
|
@@ -199,6 +255,9 @@ class OCMCluster(BaseModel):
|
|
199
255
|
def is_osd(self) -> bool:
|
200
256
|
return self.product.id == PRODUCT_ID_OSD
|
201
257
|
|
258
|
+
def is_rosa(self) -> bool:
|
259
|
+
return self.product.id == PRODUCT_ID_ROSA
|
260
|
+
|
202
261
|
def is_rosa_classic(self) -> bool:
|
203
262
|
return self.product.id == PRODUCT_ID_ROSA and not self.hypershift.enabled
|
204
263
|
|
reconcile/utils/semver_helper.py
CHANGED
@@ -35,3 +35,8 @@ def sort_versions(versions: Iterable[str]) -> list[str]:
|
|
35
35
|
|
36
36
|
def is_version_bumped(current_version: str, previous_version: str) -> bool:
|
37
37
|
return parse_semver(current_version) > parse_semver(previous_version)
|
38
|
+
|
39
|
+
|
40
|
+
def get_version_prefix(version: str) -> str:
|
41
|
+
semver = parse_semver(version)
|
42
|
+
return f"{semver.major}.{semver.minor}"
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc595.dist-info → qontract_reconcile-0.10.1rc597.dist-info}/top_level.txt
RENAMED
File without changes
|