qontract-reconcile 0.10.1rc441__py3-none-any.whl → 0.10.1rc449__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.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/RECORD +17 -17
- reconcile/aus/base.py +2 -0
- reconcile/aus/metrics.py +2 -0
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +2 -0
- reconcile/aws_version_sync/integration.py +26 -4
- reconcile/cli.py +1 -9
- reconcile/database_access_manager.py +6 -2
- reconcile/ocm_machine_pools.py +60 -12
- reconcile/terraform_repo.py +34 -18
- reconcile/test/test_database_access_manager.py +3 -1
- reconcile/test/test_ocm_machine_pools.py +253 -39
- reconcile/test/test_terraform_repo.py +33 -11
- reconcile/utils/semver_helper.py +10 -1
- {qontract_reconcile-0.10.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc441.dist-info → qontract_reconcile-0.10.1rc449.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.1rc449
|
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.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/RECORD
RENAMED
@@ -8,7 +8,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
|
|
8
8
|
reconcile/aws_support_cases_sos.py,sha256=i6bSWnlH9fh14P14PjVhFLwNl-q3fD733_rXKM_O51c,2992
|
9
9
|
reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
|
10
10
|
reconcile/checkpoint.py,sha256=figtZRuWUvdpdSnkhAqeGvO5dI02TT6J3heyeFhlwqM,5016
|
11
|
-
reconcile/cli.py,sha256=
|
11
|
+
reconcile/cli.py,sha256=NTYh8aT6jEhe6rBLeJKJvYyKESGU7N8WyflGCUJr-Uk,82140
|
12
12
|
reconcile/closedbox_endpoint_monitoring_base.py,sha256=GmEdDSp9yBnwpzzrla6VJfhOZd_qxYh-xtIN5bXjOBo,4909
|
13
13
|
reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
|
14
14
|
reconcile/dashdotdb_base.py,sha256=NkieQiK1bOlXxmFNHIQ-MOZNPfY1sE1T04yndq0o3gA,4811
|
@@ -16,7 +16,7 @@ reconcile/dashdotdb_cso.py,sha256=FoXrWGpOwXG5jf0eklN84tjJVUAYzKat7rtq_28JMlQ,36
|
|
16
16
|
reconcile/dashdotdb_dora.py,sha256=FINH-8dU3_r8EgUOMiF_fbxD9fKs6LFDxe6rufQ9XcM,17214
|
17
17
|
reconcile/dashdotdb_dvo.py,sha256=YXqpI6fBQAql-ybGI0grj9gWMzmKiAvPE__pNju6obk,8996
|
18
18
|
reconcile/dashdotdb_slo.py,sha256=bf1WSh5JP9obHVQsMy0OO71_VTYZgwAopElFZM6DmRo,6714
|
19
|
-
reconcile/database_access_manager.py,sha256=
|
19
|
+
reconcile/database_access_manager.py,sha256=AoeCrDIxC60sLct31M3gjbjry6DWjItjn9Y_3tA11s4,25586
|
20
20
|
reconcile/dynatrace_token_provider.py,sha256=HWItJ_LavPcUJlpYz5fmfnfOpP2-0Qjkar0EAzBJ5Cw,16602
|
21
21
|
reconcile/email_sender.py,sha256=-5L-Ag_jaEYSzYRoMr52KQBRXz1E8yx9GqLbg2X4XFU,3533
|
22
22
|
reconcile/gabi_authorized_users.py,sha256=rCosZv8Iu9jhWG88YiwK-gftX475aJ1R-PYIJYp_svY,4342
|
@@ -56,7 +56,7 @@ reconcile/ocm_clusters.py,sha256=ibK0o0rXhAnq4TF522aPkVlH7i0bdtgD8-IKmZ8PHjs,134
|
|
56
56
|
reconcile/ocm_external_configuration_labels.py,sha256=imEpDv1RBpCSj8tHDv0R76hmNCFtcUzVNgS1yOVl8vs,3870
|
57
57
|
reconcile/ocm_github_idp.py,sha256=IlSpoUlUhYOahqWee6azJiN-N5EZNxNAWh-lrPXTfEc,3946
|
58
58
|
reconcile/ocm_groups.py,sha256=_kiMUndKc6as6cbbvXxVnq8V_Lj7X5lxMJVCuRWuUFE,2888
|
59
|
-
reconcile/ocm_machine_pools.py,sha256=
|
59
|
+
reconcile/ocm_machine_pools.py,sha256=eebJ6iiTdUcuKE5zBcfNxW1OGmPOvgBtmVu3xNVOoyY,16608
|
60
60
|
reconcile/ocm_update_recommended_version.py,sha256=IYkfLXIprOW1jguZeELcGP1iBPuj-b53R-FTqKulMl8,4204
|
61
61
|
reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=Kti53htd5yeTSXzMW6jKvQojfaRIN7rMGe5-cC4k7N4,4105
|
62
62
|
reconcile/openshift_base.py,sha256=PFV3XoshiayqSHjOMtW7cOB2jhFLrPN8wq9km1SiWio,47332
|
@@ -108,7 +108,7 @@ reconcile/terraform_aws_route53.py,sha256=QJMlaYYwcw_N9TJu7VbgZfxrxxR8Wv62GuG05X
|
|
108
108
|
reconcile/terraform_cloudflare_dns.py,sha256=auU4bzeLwd4S8D8oqpqJbrCUoEdELXrgi7vHOedjYFk,13332
|
109
109
|
reconcile/terraform_cloudflare_resources.py,sha256=EbQQaoDnZ7brvRCpbFtwlD7KLk2hDVNcjhrJGaAywEk,15023
|
110
110
|
reconcile/terraform_cloudflare_users.py,sha256=DfeSnYC9YQgXX6AbJh85tQbJUDv1e2FjiGXgcpVQlPg,13964
|
111
|
-
reconcile/terraform_repo.py,sha256=
|
111
|
+
reconcile/terraform_repo.py,sha256=gjNJ76Yx5wFE6EbIgyfXRwLXjIrlz3FnvBptdjGABXE,15988
|
112
112
|
reconcile/terraform_resources.py,sha256=WUM0ZEpf2jgcXjfjy3QSwrzpuD6zMN7cyOxetx88f9g,17167
|
113
113
|
reconcile/terraform_tgw_attachments.py,sha256=6PVhBYeIvNnz2PwBdPF0I222_83EzOWX6VKJnqz7UPo,13915
|
114
114
|
reconcile/terraform_users.py,sha256=kXRUxCUchKCP2dbXXOzctynqMii4oyCP6bYZHQTrlTg,10202
|
@@ -118,11 +118,11 @@ reconcile/vpc_peerings_validator.py,sha256=oiYwmQ2yYBobFhIixmHNUP1GxzUADocMPJnCB
|
|
118
118
|
reconcile/aus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
119
119
|
reconcile/aus/advanced_upgrade_service.py,sha256=BxQU2O037MXopmTfcAaBDus4RJI4acXe05WqTKuPk_s,21098
|
120
120
|
reconcile/aus/aus_label_source.py,sha256=X6FD4NYcX27llMUSmmBcCh-pG7U5FnBd0zl-0zwCj2U,4118
|
121
|
-
reconcile/aus/base.py,sha256=
|
121
|
+
reconcile/aus/base.py,sha256=h_4MJbMHALy2TeaGs-d5KVTksu7EOGCBFljeFUERQbk,43635
|
122
122
|
reconcile/aus/cluster_version_data.py,sha256=j4UyEBi5mQuvPq5Lo7a_L_0blxvH790wJV07uAiikFU,7126
|
123
|
-
reconcile/aus/metrics.py,sha256=
|
123
|
+
reconcile/aus/metrics.py,sha256=lUNIjCCyAvU9f4aTx4i5RkQrjKjKD37i4AE4LlTriHs,3544
|
124
124
|
reconcile/aus/models.py,sha256=oBSVZ-3JTngxKg_bH1vAfREpz55t8K-Y3eC9TA4pOTw,6849
|
125
|
-
reconcile/aus/ocm_addons_upgrade_scheduler_org.py,sha256=
|
125
|
+
reconcile/aus/ocm_addons_upgrade_scheduler_org.py,sha256=fshslI27hrqT40qrVsVOQaWxD-jkmzcVZryXzX7plhY,8960
|
126
126
|
reconcile/aus/ocm_upgrade_scheduler.py,sha256=7cK2SakCFkl5EdnqUEAYdUo4pUnnf-SsUR10uytAGyE,3058
|
127
127
|
reconcile/aus/ocm_upgrade_scheduler_org.py,sha256=OBgE5mnVdQQV4tMH0AE2V_PDt9Gy6d-LyuPceqjORts,2331
|
128
128
|
reconcile/aus/upgrades.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -131,7 +131,7 @@ reconcile/aws_ami_cleanup/integration.py,sha256=IW95cpMj2P5ffs-AxsR_TDQCJnYFBhLI
|
|
131
131
|
reconcile/aws_cloudwatch_log_retention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
132
132
|
reconcile/aws_cloudwatch_log_retention/integration.py,sha256=0UcSZIrGvnGY4m9fj87oejIolIP_qTxtJInpmW9jrQ0,7772
|
133
133
|
reconcile/aws_version_sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
134
|
-
reconcile/aws_version_sync/integration.py,sha256=
|
134
|
+
reconcile/aws_version_sync/integration.py,sha256=fCDtfI3Lpz1l5l6AwC9tTnoiMeY7yvFLR6l7twixFlE,12248
|
135
135
|
reconcile/aws_version_sync/utils.py,sha256=sVv-48PKi2VITlqqvmpbjnFDOPeGqfKzgkpIszlmjL0,1708
|
136
136
|
reconcile/aws_version_sync/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
137
137
|
reconcile/aws_version_sync/merge_request_manager/merge_request.py,sha256=FeNcQaory5AXNVuVk-jJxPwtI4uSoURgkTH3rXAb2cc,6198
|
@@ -376,7 +376,7 @@ reconcile/test/test_checkpoint.py,sha256=sbDtqTbfw5yMZ_mCltMXxkyyGueVLGUjTDtcWhP
|
|
376
376
|
reconcile/test/test_cli.py,sha256=qx_iBwh4Z-YkK3sbjK1wEziPTgn060EN-baf9DNvR3k,1096
|
377
377
|
reconcile/test/test_closedbox_endpoint_monitoring.py,sha256=isMHYwRWMFARU2nbJgbl69kD6H0eA86noCM4MPVI1fo,7151
|
378
378
|
reconcile/test/test_dashdotdb_dora.py,sha256=XDMdnLDvup8sSqQEynxdPhXQZAafYbd2IUo9flSRJ8I,7917
|
379
|
-
reconcile/test/test_database_access_manager.py,sha256=
|
379
|
+
reconcile/test/test_database_access_manager.py,sha256=9yGFmsDt5ar6fy1WvKxBovbFETX5kmkUMOykMe2fEDU,19594
|
380
380
|
reconcile/test/test_gabi_authorized_users.py,sha256=6XnV5Q9inxP81ktGMVKyWucjBTUj8Imy2L0HG3YHyUE,2496
|
381
381
|
reconcile/test/test_github_org.py,sha256=j3KeB4OnSln1gm2hidce49xdMru-j75NS3cM-AEgzZc,4511
|
382
382
|
reconcile/test/test_github_repo_invites.py,sha256=QJ0VFk5B59rx4XtHoT6XOGWw9xRIZMen_cgtviN_Vi8,3419
|
@@ -392,7 +392,7 @@ reconcile/test/test_make.py,sha256=zTdjgq-3idFlec_0qJenk9wWw0QMLvSpJfPsptXmync,6
|
|
392
392
|
reconcile/test/test_ocm_additional_routers.py,sha256=dtbpUnD5un6Q3VoLbuFRb_njmt5SSCnBzvSSBcO_Xxs,4248
|
393
393
|
reconcile/test/test_ocm_clusters.py,sha256=MLH6r5G-T3e53tYDoyJYfC6X1oEZGmakk1SXR1w3jJw,22490
|
394
394
|
reconcile/test/test_ocm_clusters_manifest_updates.py,sha256=jFRVfc5jby1kI2x_gT6wcqPPgkav1et9wZH6JqQbNSY,3278
|
395
|
-
reconcile/test/test_ocm_machine_pools.py,sha256=
|
395
|
+
reconcile/test/test_ocm_machine_pools.py,sha256=uPtu3XG1xmRiEIZ3Uw_RTsZIgRkm_n6EGURWXqTyGyU,29789
|
396
396
|
reconcile/test/test_ocm_update_recommended_version.py,sha256=ONY3slwUHwEdonvIYRI2Z8nWPDMTSSLGZ2I1dqaz7fg,4328
|
397
397
|
reconcile/test/test_ocm_upgrade_scheduler_org_updater.py,sha256=zYRGUX7pAmxSv9oFYw2ZnPGa-YAPgDfmqXOJM4eE-8A,4353
|
398
398
|
reconcile/test/test_openshift_base.py,sha256=owJsyyatl_7z6igUEDeJcMMV5_jIwcV2Yj7YgsmYkXs,27035
|
@@ -423,7 +423,7 @@ reconcile/test/test_terraform_aws_route53.py,sha256=xHggb8K1P76OyCfFcogbkmyKle-N
|
|
423
423
|
reconcile/test/test_terraform_cloudflare_dns.py,sha256=aQTXX8Vr4h9aWvJZTnpZEhMGYoBpT2d45ZxU_ECIQ6o,3425
|
424
424
|
reconcile/test/test_terraform_cloudflare_resources.py,sha256=D-3yvANpQMW_hmxTthsUvRTuyguqCqLJoLaDX1THDks,9351
|
425
425
|
reconcile/test/test_terraform_cloudflare_users.py,sha256=RAFtMMdqZha3jNnNNsqbNQQUDSqUzdoM63rCw7fs4Fo,27456
|
426
|
-
reconcile/test/test_terraform_repo.py,sha256=
|
426
|
+
reconcile/test/test_terraform_repo.py,sha256=gN2fUVVIgqYR4PPLNyVzHi_gUudAkgJ2RNkVVtkcVhA,11668
|
427
427
|
reconcile/test/test_terraform_resources.py,sha256=1ny_QSFuRjV9jxZY8EeT4NVJ5dMv7cLrEEIx_cBpjgk,9075
|
428
428
|
reconcile/test/test_terraform_tgw_attachments.py,sha256=GgDA8hlQ1ujh5g8PtzbYQbJGpNScEgZ8PvDbMFbn68g,35493
|
429
429
|
reconcile/test/test_terraform_users.py,sha256=Xn4y6EcxnNQb6XcPoOhz_Ikxmh9Nrsu88OM1scN9hzY,5434
|
@@ -554,7 +554,7 @@ reconcile/utils/quay_api.py,sha256=EuOegpb-7ntEjkKLFwM2Oo4Nw7SyFtmyl3sQ9aXMtrM,8
|
|
554
554
|
reconcile/utils/raw_github_api.py,sha256=ZHC-SZuAyRe1zaMoOU7Krt1-zecDxENd9c_NzQYqK9g,2968
|
555
555
|
reconcile/utils/repo_owners.py,sha256=j-pUjc9PuDzq7KpjNLpnhqfU8tUG4nj2WMhFp4ick7g,6629
|
556
556
|
reconcile/utils/secret_reader.py,sha256=2DeYAAQFjUULEKlLw3UDAUoND6gbqvCh9uKPtlc-0us,10403
|
557
|
-
reconcile/utils/semver_helper.py,sha256=
|
557
|
+
reconcile/utils/semver_helper.py,sha256=dp86KxjlOc8LHzawMvbxRfZamv7KU7b2SVnZQL-Xg6U,1142
|
558
558
|
reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,839
|
559
559
|
reconcile/utils/slack_api.py,sha256=fntPGTn4hHRiScEIZu9LDPwbKeNPe9bR2Rmef9c-GIM,15830
|
560
560
|
reconcile/utils/smtp_client.py,sha256=gJNbBQJpAt5PX4t_TaeNHsXM8vt50bFgndml6yK2b5o,2800
|
@@ -646,8 +646,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=dmEcNwZltP1rd_4DbxIYakO
|
|
646
646
|
tools/test/test_qontract_cli.py,sha256=awwTHEc2DWlykuqGIYM0WOBoSL0KRnOraCLk3C7izis,1401
|
647
647
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
648
648
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
649
|
-
qontract_reconcile-0.10.
|
650
|
-
qontract_reconcile-0.10.
|
651
|
-
qontract_reconcile-0.10.
|
652
|
-
qontract_reconcile-0.10.
|
653
|
-
qontract_reconcile-0.10.
|
649
|
+
qontract_reconcile-0.10.1rc449.dist-info/METADATA,sha256=E2bO2KPEa-O0CBcDnYqJkEyOOndI0tpzL0CDdBumIDM,2348
|
650
|
+
qontract_reconcile-0.10.1rc449.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
651
|
+
qontract_reconcile-0.10.1rc449.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
|
652
|
+
qontract_reconcile-0.10.1rc449.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
653
|
+
qontract_reconcile-0.10.1rc449.dist-info/RECORD,,
|
reconcile/aus/base.py
CHANGED
@@ -296,6 +296,8 @@ class AdvancedUpgradeSchedulerBaseIntegration(
|
|
296
296
|
cluster_upgrade_spec.upgrade_policy.conditions.soak_days or 0
|
297
297
|
),
|
298
298
|
workloads=",".join(cluster_upgrade_spec.upgrade_policy.workloads),
|
299
|
+
product=cluster_upgrade_spec.cluster.product.id,
|
300
|
+
hypershift=cluster_upgrade_spec.cluster.hypershift.enabled,
|
299
301
|
),
|
300
302
|
)
|
301
303
|
|
reconcile/aus/metrics.py
CHANGED
@@ -211,6 +211,8 @@ class OCMAddonsUpgradeSchedulerOrgIntegration(
|
|
211
211
|
),
|
212
212
|
workloads=",".join(cluster_upgrade_spec.upgrade_policy.workloads),
|
213
213
|
addon=cluster_upgrade_spec.addon.id,
|
214
|
+
product=cluster_upgrade_spec.cluster.product.id,
|
215
|
+
hypershift=cluster_upgrade_spec.cluster.hypershift.enabled,
|
214
216
|
),
|
215
217
|
)
|
216
218
|
|
@@ -5,7 +5,11 @@ from collections.abc import (
|
|
5
5
|
)
|
6
6
|
from typing import Any
|
7
7
|
|
8
|
-
|
8
|
+
import semver
|
9
|
+
from pydantic import (
|
10
|
+
BaseModel,
|
11
|
+
validator,
|
12
|
+
)
|
9
13
|
|
10
14
|
from reconcile.aws_version_sync.merge_request_manager.merge_request import (
|
11
15
|
Parser,
|
@@ -42,6 +46,7 @@ from reconcile.utils.runtime.integration import (
|
|
42
46
|
PydanticRunParams,
|
43
47
|
QontractReconcileIntegration,
|
44
48
|
)
|
49
|
+
from reconcile.utils.semver_helper import parse_semver
|
45
50
|
from reconcile.utils.unleash import get_feature_toggle_state
|
46
51
|
from reconcile.utils.vcs import VCS
|
47
52
|
|
@@ -67,7 +72,10 @@ class ExternalResource(BaseModel):
|
|
67
72
|
resource_provider: str
|
68
73
|
resource_identifier: str
|
69
74
|
resource_engine: str
|
70
|
-
resource_engine_version:
|
75
|
+
resource_engine_version: semver.VersionInfo
|
76
|
+
|
77
|
+
class Config:
|
78
|
+
arbitrary_types_allowed = True
|
71
79
|
|
72
80
|
@property
|
73
81
|
def key(self) -> tuple:
|
@@ -79,6 +87,14 @@ class ExternalResource(BaseModel):
|
|
79
87
|
self.resource_engine,
|
80
88
|
)
|
81
89
|
|
90
|
+
@validator("resource_engine_version", pre=True)
|
91
|
+
def parse_resource_engine_version( # pylint: disable=no-self-argument
|
92
|
+
cls, v: str | semver.VersionInfo
|
93
|
+
) -> semver.VersionInfo:
|
94
|
+
if isinstance(v, semver.VersionInfo):
|
95
|
+
return v
|
96
|
+
return parse_semver(v, optional_minor_and_patch=True)
|
97
|
+
|
82
98
|
|
83
99
|
AwsExternalResources = list[ExternalResource]
|
84
100
|
AppInterfaceExternalResources = list[ExternalResource]
|
@@ -228,12 +244,18 @@ class AVSIntegration(QontractReconcileIntegration[AVSIntegrationParams]):
|
|
228
244
|
current=external_resources_app_interface,
|
229
245
|
desired=external_resources_aws,
|
230
246
|
key=lambda r: r.key,
|
231
|
-
equal=lambda
|
232
|
-
==
|
247
|
+
equal=lambda external_resources_app_interface, external_resources_aws: external_resources_app_interface.resource_engine_version
|
248
|
+
== external_resources_aws.resource_engine_version,
|
233
249
|
)
|
234
250
|
for diff_pair in diff.change.values():
|
235
251
|
aws_resource = diff_pair.desired
|
236
252
|
app_interface_resource = diff_pair.current
|
253
|
+
if (
|
254
|
+
aws_resource.resource_engine_version
|
255
|
+
<= app_interface_resource.resource_engine_version
|
256
|
+
):
|
257
|
+
# do not downgrade the version
|
258
|
+
continue
|
237
259
|
# make mypy happy
|
238
260
|
assert app_interface_resource.namespace_file
|
239
261
|
assert app_interface_resource.provisioner.path
|
reconcile/cli.py
CHANGED
@@ -1701,17 +1701,10 @@ def aws_version_sync(
|
|
1701
1701
|
"--output-file",
|
1702
1702
|
help="Specify where to place the output of the integration",
|
1703
1703
|
)
|
1704
|
-
@click.option(
|
1705
|
-
"--ignore-state-errors",
|
1706
|
-
is_flag=True,
|
1707
|
-
help="Instructs terraform-repo to ignore state load errors and re-create repo states",
|
1708
|
-
)
|
1709
1704
|
@click.argument("gitlab-project-id", required=False)
|
1710
1705
|
@click.argument("gitlab-merge-request-id", required=False)
|
1711
1706
|
@click.pass_context
|
1712
|
-
def terraform_repo(
|
1713
|
-
ctx, output_file, ignore_state_errors, gitlab_project_id, gitlab_merge_request_id
|
1714
|
-
):
|
1707
|
+
def terraform_repo(ctx, output_file, gitlab_project_id, gitlab_merge_request_id):
|
1715
1708
|
from reconcile import terraform_repo
|
1716
1709
|
|
1717
1710
|
run_class_integration(
|
@@ -1719,7 +1712,6 @@ def terraform_repo(
|
|
1719
1712
|
terraform_repo.TerraformRepoIntegrationParams(
|
1720
1713
|
output_file=output_file,
|
1721
1714
|
validate_git=True,
|
1722
|
-
ignore_state_errors=ignore_state_errors,
|
1723
1715
|
gitlab_project_id=gitlab_project_id,
|
1724
1716
|
gitlab_merge_request_id=gitlab_merge_request_id,
|
1725
1717
|
)
|
@@ -102,9 +102,13 @@ REVOKE ALL ON DATABASE "{self._get_db()}" FROM public;
|
|
102
102
|
|
103
103
|
\\c "{self._get_db()}"
|
104
104
|
|
105
|
-
select 'CREATE ROLE "{self._get_user()}"
|
105
|
+
select 'CREATE ROLE "{self._get_user()}"'
|
106
106
|
WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '{self._get_db()}');\\gexec
|
107
107
|
|
108
|
+
ALTER ROLE "{self._get_user()}" WITH LOGIN PASSWORD '{self.connection_parameter.password}' VALID UNTIL 'infinity';
|
109
|
+
|
110
|
+
GRANT CONNECT ON DATABASE "{self._get_db()}" to "{self._get_user()}";
|
111
|
+
|
108
112
|
-- rds specific, grant role to admin or create schema fails
|
109
113
|
GRANT "{self._get_user()}" to "{self._get_admin_user()}";
|
110
114
|
CREATE SCHEMA IF NOT EXISTS "{self._get_user()}" AUTHORIZATION "{self._get_user()}";"""
|
@@ -272,7 +276,7 @@ def get_job_spec(job_data: JobData) -> OpenshiftResource:
|
|
272
276
|
command,
|
273
277
|
],
|
274
278
|
"args": [
|
275
|
-
"-
|
279
|
+
"-b",
|
276
280
|
"--host=$(db.host)",
|
277
281
|
"--port=$(db.port)",
|
278
282
|
"--username=$(db.user)",
|
reconcile/ocm_machine_pools.py
CHANGED
@@ -4,6 +4,7 @@ from abc import (
|
|
4
4
|
abstractmethod,
|
5
5
|
)
|
6
6
|
from collections.abc import Mapping
|
7
|
+
from enum import Enum
|
7
8
|
from typing import (
|
8
9
|
Iterable,
|
9
10
|
Optional,
|
@@ -11,6 +12,7 @@ from typing import (
|
|
11
12
|
|
12
13
|
from pydantic import (
|
13
14
|
BaseModel,
|
15
|
+
Field,
|
14
16
|
root_validator,
|
15
17
|
)
|
16
18
|
|
@@ -24,6 +26,7 @@ from reconcile.typed_queries.clusters import get_clusters
|
|
24
26
|
from reconcile.utils.differ import diff_mappings
|
25
27
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
26
28
|
from reconcile.utils.ocm import (
|
29
|
+
DEFAULT_OCM_MACHINE_POOL_ID,
|
27
30
|
OCM,
|
28
31
|
OCMMap,
|
29
32
|
)
|
@@ -31,6 +34,12 @@ from reconcile.utils.ocm import (
|
|
31
34
|
QONTRACT_INTEGRATION = "ocm-machine-pools"
|
32
35
|
|
33
36
|
|
37
|
+
class ClusterType(Enum):
|
38
|
+
OSD = "osd"
|
39
|
+
ROSA_CLASSIC = "rosa"
|
40
|
+
ROSA_HCP = "hypershift"
|
41
|
+
|
42
|
+
|
34
43
|
class InvalidUpdateError(Exception):
|
35
44
|
pass
|
36
45
|
|
@@ -95,6 +104,7 @@ class AbstractPool(ABC, BaseModel):
|
|
95
104
|
taints: Optional[list[Mapping[str, str]]]
|
96
105
|
labels: Optional[Mapping[str, str]]
|
97
106
|
cluster: str
|
107
|
+
cluster_type: ClusterType = Field(..., exclude=True)
|
98
108
|
autoscaling: Optional[AbstractAutoscaling]
|
99
109
|
|
100
110
|
@root_validator()
|
@@ -180,10 +190,19 @@ class MachinePool(AbstractPool):
|
|
180
190
|
return None
|
181
191
|
|
182
192
|
def deletable(self) -> bool:
|
183
|
-
|
193
|
+
# OSD NON CCS clusters can't delete default worker machine pool
|
194
|
+
return not (
|
195
|
+
self.cluster_type == ClusterType.OSD
|
196
|
+
and self.id == DEFAULT_OCM_MACHINE_POOL_ID
|
197
|
+
)
|
184
198
|
|
185
199
|
@classmethod
|
186
|
-
def create_from_gql(
|
200
|
+
def create_from_gql(
|
201
|
+
cls,
|
202
|
+
pool: ClusterMachinePoolV1,
|
203
|
+
cluster: str,
|
204
|
+
cluster_type: ClusterType,
|
205
|
+
):
|
187
206
|
autoscaling: Optional[MachinePoolAutoscaling] = None
|
188
207
|
if pool.autoscale:
|
189
208
|
autoscaling = MachinePoolAutoscaling(
|
@@ -198,6 +217,7 @@ class MachinePool(AbstractPool):
|
|
198
217
|
taints=[p.dict(by_alias=True) for p in pool.taints or []],
|
199
218
|
labels=pool.labels,
|
200
219
|
cluster=cluster,
|
220
|
+
cluster_type=cluster_type,
|
201
221
|
)
|
202
222
|
|
203
223
|
|
@@ -254,11 +274,15 @@ class NodePool(AbstractPool):
|
|
254
274
|
return None
|
255
275
|
|
256
276
|
def deletable(self) -> bool:
|
257
|
-
|
258
|
-
return not self.id.startswith("workers")
|
277
|
+
return True
|
259
278
|
|
260
279
|
@classmethod
|
261
|
-
def create_from_gql(
|
280
|
+
def create_from_gql(
|
281
|
+
cls,
|
282
|
+
pool: ClusterMachinePoolV1,
|
283
|
+
cluster: str,
|
284
|
+
cluster_type: ClusterType,
|
285
|
+
):
|
262
286
|
autoscaling: Optional[NodePoolAutoscaling] = None
|
263
287
|
if pool.autoscale:
|
264
288
|
autoscaling = NodePoolAutoscaling(
|
@@ -277,6 +301,7 @@ class NodePool(AbstractPool):
|
|
277
301
|
labels=pool.labels,
|
278
302
|
subnet=pool.subnet,
|
279
303
|
cluster=cluster,
|
304
|
+
cluster_type=cluster_type,
|
280
305
|
)
|
281
306
|
|
282
307
|
|
@@ -303,7 +328,7 @@ class PoolHandler(BaseModel):
|
|
303
328
|
|
304
329
|
class DesiredMachinePool(BaseModel):
|
305
330
|
cluster_name: str
|
306
|
-
|
331
|
+
cluster_type: ClusterType
|
307
332
|
pools: list[ClusterMachinePoolV1]
|
308
333
|
|
309
334
|
def build_pool_handler(
|
@@ -312,11 +337,13 @@ class DesiredMachinePool(BaseModel):
|
|
312
337
|
pool: ClusterMachinePoolV1,
|
313
338
|
) -> PoolHandler:
|
314
339
|
pool_builder = (
|
315
|
-
NodePool.create_from_gql
|
340
|
+
NodePool.create_from_gql
|
341
|
+
if self.cluster_type == ClusterType.ROSA_HCP
|
342
|
+
else MachinePool.create_from_gql
|
316
343
|
)
|
317
344
|
return PoolHandler(
|
318
345
|
action=action,
|
319
|
-
pool=pool_builder(pool, self.cluster_name),
|
346
|
+
pool=pool_builder(pool, self.cluster_name, self.cluster_type),
|
320
347
|
)
|
321
348
|
|
322
349
|
|
@@ -330,12 +357,25 @@ def fetch_current_state(
|
|
330
357
|
}
|
331
358
|
|
332
359
|
|
333
|
-
def
|
334
|
-
|
360
|
+
def _classify_cluster_type(cluster: ClusterV1) -> ClusterType:
|
361
|
+
if cluster.spec is None:
|
362
|
+
raise ValueError(f"cluster {cluster.name} is missing spec")
|
363
|
+
match cluster.spec.product:
|
364
|
+
case "osd":
|
365
|
+
return ClusterType.OSD
|
366
|
+
case "rosa":
|
367
|
+
return (
|
368
|
+
ClusterType.ROSA_HCP
|
369
|
+
if cluster.spec.hypershift
|
370
|
+
else ClusterType.ROSA_CLASSIC
|
371
|
+
)
|
372
|
+
case _:
|
373
|
+
raise ValueError(f"unknown cluster type for cluster {cluster.name}")
|
335
374
|
|
336
375
|
|
337
376
|
def fetch_current_state_for_cluster(cluster, ocm):
|
338
|
-
|
377
|
+
cluster_type = _classify_cluster_type(cluster)
|
378
|
+
if cluster_type == ClusterType.ROSA_HCP:
|
339
379
|
return [
|
340
380
|
NodePool(
|
341
381
|
id=node_pool["id"],
|
@@ -354,6 +394,7 @@ def fetch_current_state_for_cluster(cluster, ocm):
|
|
354
394
|
labels=node_pool.get("labels"),
|
355
395
|
subnet=node_pool.get("subnet"),
|
356
396
|
cluster=cluster.name,
|
397
|
+
cluster_type=cluster_type,
|
357
398
|
)
|
358
399
|
for node_pool in ocm.get_node_pools(cluster.name)
|
359
400
|
]
|
@@ -371,6 +412,7 @@ def fetch_current_state_for_cluster(cluster, ocm):
|
|
371
412
|
taints=machine_pool.get("taints"),
|
372
413
|
labels=machine_pool.get("labels"),
|
373
414
|
cluster=cluster.name,
|
415
|
+
cluster_type=cluster_type,
|
374
416
|
)
|
375
417
|
for machine_pool in ocm.get_machine_pools(cluster.name)
|
376
418
|
]
|
@@ -382,7 +424,7 @@ def create_desired_state_from_gql(
|
|
382
424
|
return {
|
383
425
|
cluster.name: DesiredMachinePool(
|
384
426
|
cluster_name=cluster.name,
|
385
|
-
|
427
|
+
cluster_type=_classify_cluster_type(cluster),
|
386
428
|
pools=cluster.machine_pools,
|
387
429
|
)
|
388
430
|
for cluster in clusters
|
@@ -452,6 +494,12 @@ def calculate_diff(
|
|
452
494
|
pool=current_machine_pool,
|
453
495
|
)
|
454
496
|
)
|
497
|
+
else:
|
498
|
+
errors.append(
|
499
|
+
InvalidUpdateError(
|
500
|
+
f"can not delete machine pool {current_machine_pool.id} for cluster {cluster_name}"
|
501
|
+
)
|
502
|
+
)
|
455
503
|
|
456
504
|
return diffs, errors
|
457
505
|
|
reconcile/terraform_repo.py
CHANGED
@@ -66,7 +66,6 @@ class OutputFile(BaseModel):
|
|
66
66
|
class TerraformRepoIntegrationParams(PydanticRunParams):
|
67
67
|
output_file: Optional[str]
|
68
68
|
validate_git: bool
|
69
|
-
ignore_state_errors: bool
|
70
69
|
gitlab_project_id: Optional[str]
|
71
70
|
gitlab_merge_request_id: Optional[int]
|
72
71
|
|
@@ -97,14 +96,35 @@ class TerraformRepoIntegration(
|
|
97
96
|
defer(state.cleanup)
|
98
97
|
|
99
98
|
desired = self.get_repos(query_func=gqlapi.query)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
99
|
+
try:
|
100
|
+
existing = self.get_existing_state(state)
|
101
|
+
|
102
|
+
repo_diff_result = self.calculate_diff(
|
103
|
+
existing_state=existing,
|
104
|
+
desired_state=desired,
|
105
|
+
dry_run=dry_run,
|
106
|
+
state=state,
|
107
|
+
recreate_state=False,
|
108
|
+
)
|
105
109
|
|
106
|
-
|
107
|
-
|
110
|
+
if repo_diff_result:
|
111
|
+
self.print_output(repo_diff_result, dry_run)
|
112
|
+
except ValidationError as err:
|
113
|
+
# when updating TerraformRepoV1 GQL schema, Pydantic does not gracefully handle these changes and fails to parse
|
114
|
+
# the existing state stored in S3. This is due to a behavior in Pydantic V1 that has since been addressed in V2
|
115
|
+
# https://docs.pydantic.dev/latest/blog/pydantic-v2/#required-vs-nullable-cleanup
|
116
|
+
# so in this case, tf-repo will just recreate all of those state files in S3 and not actually do a plan or apply
|
117
|
+
logging.error(err)
|
118
|
+
logging.info(
|
119
|
+
"Unable to parse existing Terraform-Repo state from S3. Note that this is separate from the actual .tfstate files. Terraform Repo will re-create its own state upon merge and will not update any infrastructure. This typically occurs with changes to the Terraform Repo schema files and is normally resolved once state is re-created."
|
120
|
+
)
|
121
|
+
repo_diff_result = self.calculate_diff(
|
122
|
+
existing_state=[],
|
123
|
+
desired_state=desired,
|
124
|
+
dry_run=dry_run,
|
125
|
+
state=state,
|
126
|
+
recreate_state=True,
|
127
|
+
)
|
108
128
|
|
109
129
|
def print_output(self, diff: list[TerraformRepoV1], dry_run: bool) -> None:
|
110
130
|
"""Parses and prints the output of a Terraform Repo diff for the executor
|
@@ -182,15 +202,8 @@ class TerraformRepoIntegration(
|
|
182
202
|
keys = state.ls()
|
183
203
|
for key in keys:
|
184
204
|
if value := state.get(key.lstrip("/"), None):
|
185
|
-
|
186
|
-
|
187
|
-
repo_list.append(repo)
|
188
|
-
except ValidationError as err:
|
189
|
-
logging.error(
|
190
|
-
f"{err}\nUnable to parse existing state for repo: '{key}'"
|
191
|
-
)
|
192
|
-
if self.params.ignore_state_errors:
|
193
|
-
logging.info("Ignoring state load error")
|
205
|
+
repo = TerraformRepoV1.parse_obj(value)
|
206
|
+
repo_list.append(repo)
|
194
207
|
|
195
208
|
return repo_list
|
196
209
|
|
@@ -281,6 +294,7 @@ class TerraformRepoIntegration(
|
|
281
294
|
desired_state: list[TerraformRepoV1],
|
282
295
|
dry_run: bool,
|
283
296
|
state: Optional[State],
|
297
|
+
recreate_state: bool,
|
284
298
|
) -> Optional[list[TerraformRepoV1]]:
|
285
299
|
"""Calculated the difference between existing and desired state
|
286
300
|
to determine what actions the executor will need to take
|
@@ -293,6 +307,8 @@ class TerraformRepoIntegration(
|
|
293
307
|
:type dry_run: bool
|
294
308
|
:param state: AWS S3 state
|
295
309
|
:type state: Optional[State]
|
310
|
+
:param recreate_state: whether we are recreating our own state
|
311
|
+
:type recreate_state: bool
|
296
312
|
:raises ParameterError: if there is an invalid operation performed like trying to delete
|
297
313
|
a representation in A-I before setting the delete flag
|
298
314
|
:return: the terraform repo to act on
|
@@ -304,7 +320,7 @@ class TerraformRepoIntegration(
|
|
304
320
|
|
305
321
|
# validate that only one repo is being modified in each MR
|
306
322
|
# this lets us fail early and avoid multiple GL requests we don't need to make
|
307
|
-
if dry_run and len(merged) > 1 and not
|
323
|
+
if dry_run and len(merged) > 1 and not recreate_state:
|
308
324
|
raise Exception(
|
309
325
|
"Only one repository can be modified per merge request, please split your change out into multiple MRs. Hint: try rebasing your merge request"
|
310
326
|
)
|
@@ -131,7 +131,9 @@ def openshift_resource_secet() -> OpenshiftResource:
|
|
131
131
|
def _assert_create_script(script: str) -> None:
|
132
132
|
assert 'CREATE DATABASE "test"' in script
|
133
133
|
assert "REVOKE ALL ON DATABASE" in script
|
134
|
-
assert 'CREATE ROLE "test"
|
134
|
+
assert 'CREATE ROLE "test"' in script
|
135
|
+
assert 'ALTER ROLE "test" WITH LOGIN' in script
|
136
|
+
assert 'GRANT CONNECT ON DATABASE "test" to "test"' in script
|
135
137
|
assert "CREATE SCHEMA IF NOT EXISTS" in script
|
136
138
|
assert 'GRANT "test" to "admin";' in script
|
137
139
|
|
@@ -19,6 +19,7 @@ from reconcile.gql_definitions.common.clusters import (
|
|
19
19
|
from reconcile.ocm_machine_pools import (
|
20
20
|
AbstractPool,
|
21
21
|
AWSNodePool,
|
22
|
+
ClusterType,
|
22
23
|
DesiredMachinePool,
|
23
24
|
InvalidUpdateError,
|
24
25
|
MachinePool,
|
@@ -63,6 +64,7 @@ def test_pool() -> TestPool:
|
|
63
64
|
labels=None,
|
64
65
|
taints=None,
|
65
66
|
cluster="cluster1",
|
67
|
+
cluster_type=ClusterType.OSD,
|
66
68
|
)
|
67
69
|
|
68
70
|
|
@@ -77,6 +79,7 @@ def current_with_pool() -> Mapping[str, list[AbstractPool]]:
|
|
77
79
|
labels=None,
|
78
80
|
taints=None,
|
79
81
|
cluster="cluster1",
|
82
|
+
cluster_type=ClusterType.OSD,
|
80
83
|
)
|
81
84
|
]
|
82
85
|
}
|
@@ -90,6 +93,7 @@ def node_pool() -> NodePool:
|
|
90
93
|
labels=None,
|
91
94
|
taints=None,
|
92
95
|
cluster="cluster1",
|
96
|
+
cluster_type=ClusterType.ROSA_HCP,
|
93
97
|
subnet="subnet1",
|
94
98
|
aws_node_pool=AWSNodePool(
|
95
99
|
instance_type="m5.xlarge",
|
@@ -105,7 +109,7 @@ def machine_pool() -> MachinePool:
|
|
105
109
|
labels=None,
|
106
110
|
taints=None,
|
107
111
|
cluster="cluster1",
|
108
|
-
|
112
|
+
cluster_type=ClusterType.OSD,
|
109
113
|
instance_type="m5.xlarge",
|
110
114
|
)
|
111
115
|
|
@@ -129,7 +133,7 @@ def ocm_mock():
|
|
129
133
|
|
130
134
|
|
131
135
|
def test_diff__has_diff_autoscale(cluster_machine_pool: ClusterMachinePoolV1):
|
132
|
-
pool = TestPool(id="pool1", cluster="cluster1")
|
136
|
+
pool = TestPool(id="pool1", cluster="cluster1", cluster_type=ClusterType.OSD)
|
133
137
|
|
134
138
|
assert cluster_machine_pool.autoscale is None
|
135
139
|
assert not pool._has_diff_autoscale(cluster_machine_pool)
|
@@ -165,7 +169,7 @@ def test_calculate_diff_create():
|
|
165
169
|
desired = {
|
166
170
|
"cluster1": DesiredMachinePool(
|
167
171
|
cluster_name="cluster1",
|
168
|
-
|
172
|
+
cluster_type=ClusterType.OSD,
|
169
173
|
pools=[
|
170
174
|
ClusterMachinePoolV1(
|
171
175
|
id="pool1",
|
@@ -190,7 +194,7 @@ def test_calculate_diff_noop(current_with_pool):
|
|
190
194
|
desired = {
|
191
195
|
"cluster1": DesiredMachinePool(
|
192
196
|
cluster_name="cluster1",
|
193
|
-
|
197
|
+
cluster_type=ClusterType.OSD,
|
194
198
|
pools=[
|
195
199
|
ClusterMachinePoolV1(
|
196
200
|
id="pool1",
|
@@ -213,7 +217,7 @@ def test_calculate_diff_update(current_with_pool):
|
|
213
217
|
desired = {
|
214
218
|
"cluster1": DesiredMachinePool(
|
215
219
|
cluster_name="cluster1",
|
216
|
-
|
220
|
+
cluster_type=ClusterType.OSD,
|
217
221
|
pools=[
|
218
222
|
ClusterMachinePoolV1(
|
219
223
|
id="pool1",
|
@@ -245,6 +249,7 @@ def current_with_2_pools() -> Mapping[str, list[AbstractPool]]:
|
|
245
249
|
labels=None,
|
246
250
|
taints=None,
|
247
251
|
cluster="cluster1",
|
252
|
+
cluster_type=ClusterType.OSD,
|
248
253
|
),
|
249
254
|
MachinePool(
|
250
255
|
id="workers",
|
@@ -253,6 +258,7 @@ def current_with_2_pools() -> Mapping[str, list[AbstractPool]]:
|
|
253
258
|
labels=None,
|
254
259
|
taints=None,
|
255
260
|
cluster="cluster1",
|
261
|
+
cluster_type=ClusterType.OSD,
|
256
262
|
),
|
257
263
|
]
|
258
264
|
}
|
@@ -262,7 +268,7 @@ def test_calculate_diff_delete(current_with_2_pools):
|
|
262
268
|
desired = {
|
263
269
|
"cluster1": DesiredMachinePool(
|
264
270
|
cluster_name="cluster1",
|
265
|
-
|
271
|
+
cluster_type=ClusterType.OSD,
|
266
272
|
pools=[
|
267
273
|
ClusterMachinePoolV1(
|
268
274
|
id="pool1",
|
@@ -287,7 +293,7 @@ def test_calculate_diff_delete_all_fail_validation(current_with_pool):
|
|
287
293
|
desired = {
|
288
294
|
"cluster1": DesiredMachinePool(
|
289
295
|
cluster_name="cluster1",
|
290
|
-
|
296
|
+
cluster_type=ClusterType.OSD,
|
291
297
|
pools=[],
|
292
298
|
),
|
293
299
|
}
|
@@ -450,7 +456,33 @@ def test_run_no_action(mocker: MockerFixture) -> None:
|
|
450
456
|
|
451
457
|
|
452
458
|
@pytest.fixture
|
453
|
-
def
|
459
|
+
def osd_cluster_builder(
|
460
|
+
gql_class_factory: Callable[..., ClusterV1],
|
461
|
+
) -> Callable[..., ClusterV1]:
|
462
|
+
def builder(machine_pools: list[dict]) -> ClusterV1:
|
463
|
+
return gql_class_factory(
|
464
|
+
ClusterV1,
|
465
|
+
{
|
466
|
+
"name": "ocm-cluster",
|
467
|
+
"auth": [],
|
468
|
+
"spec": {
|
469
|
+
"product": "osd",
|
470
|
+
},
|
471
|
+
"ocm": {
|
472
|
+
"name": "ocm-name",
|
473
|
+
"environment": {
|
474
|
+
"accessTokenClientSecret": {},
|
475
|
+
},
|
476
|
+
},
|
477
|
+
"machinePools": machine_pools,
|
478
|
+
},
|
479
|
+
)
|
480
|
+
|
481
|
+
return builder
|
482
|
+
|
483
|
+
|
484
|
+
@pytest.fixture
|
485
|
+
def rosa_cluster_builder(
|
454
486
|
gql_class_factory: Callable[..., ClusterV1],
|
455
487
|
) -> Callable[..., ClusterV1]:
|
456
488
|
def builder(machine_pools: list[dict]) -> ClusterV1:
|
@@ -459,6 +491,9 @@ def ocm_cluster_builder(
|
|
459
491
|
{
|
460
492
|
"name": "ocm-cluster",
|
461
493
|
"auth": [],
|
494
|
+
"spec": {
|
495
|
+
"product": "rosa",
|
496
|
+
},
|
462
497
|
"ocm": {
|
463
498
|
"name": "ocm-name",
|
464
499
|
"environment": {
|
@@ -482,11 +517,19 @@ def default_worker_machine_pool() -> dict:
|
|
482
517
|
|
483
518
|
|
484
519
|
@pytest.fixture
|
485
|
-
def
|
486
|
-
|
520
|
+
def osd_cluster_with_default_machine_pool(
|
521
|
+
osd_cluster_builder,
|
522
|
+
default_worker_machine_pool: dict,
|
523
|
+
) -> ClusterV1:
|
524
|
+
return osd_cluster_builder([default_worker_machine_pool])
|
525
|
+
|
526
|
+
|
527
|
+
@pytest.fixture
|
528
|
+
def rosa_cluster_with_default_machine_pool(
|
529
|
+
rosa_cluster_builder,
|
487
530
|
default_worker_machine_pool: dict,
|
488
531
|
) -> ClusterV1:
|
489
|
-
return
|
532
|
+
return rosa_cluster_builder([default_worker_machine_pool])
|
490
533
|
|
491
534
|
|
492
535
|
@pytest.fixture
|
@@ -499,12 +542,26 @@ def new_workers_machine_pool() -> dict:
|
|
499
542
|
|
500
543
|
|
501
544
|
@pytest.fixture
|
502
|
-
def
|
503
|
-
|
545
|
+
def osd_cluster_with_default_and_new_machine_pools(
|
546
|
+
osd_cluster_builder,
|
547
|
+
default_worker_machine_pool: dict,
|
548
|
+
new_workers_machine_pool: dict,
|
549
|
+
) -> ClusterV1:
|
550
|
+
return osd_cluster_builder(
|
551
|
+
[
|
552
|
+
default_worker_machine_pool,
|
553
|
+
new_workers_machine_pool,
|
554
|
+
]
|
555
|
+
)
|
556
|
+
|
557
|
+
|
558
|
+
@pytest.fixture
|
559
|
+
def rosa_cluster_with_default_and_new_machine_pools(
|
560
|
+
rosa_cluster_builder,
|
504
561
|
default_worker_machine_pool: dict,
|
505
562
|
new_workers_machine_pool: dict,
|
506
563
|
) -> ClusterV1:
|
507
|
-
return
|
564
|
+
return rosa_cluster_builder(
|
508
565
|
[
|
509
566
|
default_worker_machine_pool,
|
510
567
|
new_workers_machine_pool,
|
@@ -525,22 +582,42 @@ def expected_ocm_machine_pool_create_payload() -> dict:
|
|
525
582
|
}
|
526
583
|
|
527
584
|
|
528
|
-
def
|
585
|
+
def test_run_create_machine_pool_for_osd_cluster(
|
529
586
|
mocker: MockerFixture,
|
530
|
-
|
587
|
+
osd_cluster_with_default_and_new_machine_pools: ClusterV1,
|
531
588
|
default_worker_machine_pool: dict,
|
532
589
|
expected_ocm_machine_pool_create_payload: dict,
|
533
590
|
) -> None:
|
534
591
|
mocks = setup_mocks(
|
535
592
|
mocker,
|
536
|
-
clusters=[
|
593
|
+
clusters=[osd_cluster_with_default_and_new_machine_pools],
|
537
594
|
machine_pools=[default_worker_machine_pool],
|
538
595
|
)
|
539
596
|
|
540
597
|
run(False)
|
541
598
|
|
542
599
|
mocks["OCM"].create_machine_pool.assert_called_once_with(
|
543
|
-
|
600
|
+
osd_cluster_with_default_and_new_machine_pools.name,
|
601
|
+
expected_ocm_machine_pool_create_payload,
|
602
|
+
)
|
603
|
+
|
604
|
+
|
605
|
+
def test_run_create_machine_pool_for_rosa_cluster(
|
606
|
+
mocker: MockerFixture,
|
607
|
+
rosa_cluster_with_default_and_new_machine_pools: ClusterV1,
|
608
|
+
default_worker_machine_pool: dict,
|
609
|
+
expected_ocm_machine_pool_create_payload: dict,
|
610
|
+
) -> None:
|
611
|
+
mocks = setup_mocks(
|
612
|
+
mocker,
|
613
|
+
clusters=[rosa_cluster_with_default_and_new_machine_pools],
|
614
|
+
machine_pools=[default_worker_machine_pool],
|
615
|
+
)
|
616
|
+
|
617
|
+
run(False)
|
618
|
+
|
619
|
+
mocks["OCM"].create_machine_pool.assert_called_once_with(
|
620
|
+
rosa_cluster_with_default_and_new_machine_pools.name,
|
544
621
|
expected_ocm_machine_pool_create_payload,
|
545
622
|
)
|
546
623
|
|
@@ -564,22 +641,42 @@ def expected_ocm_machine_pool_update_payload() -> dict:
|
|
564
641
|
}
|
565
642
|
|
566
643
|
|
567
|
-
def
|
644
|
+
def test_run_update_machine_pool_for_osd_cluster(
|
645
|
+
mocker: MockerFixture,
|
646
|
+
osd_cluster_with_default_machine_pool: ClusterV1,
|
647
|
+
existing_updated_default_machine_pool: dict,
|
648
|
+
expected_ocm_machine_pool_update_payload: dict,
|
649
|
+
) -> None:
|
650
|
+
mocks = setup_mocks(
|
651
|
+
mocker,
|
652
|
+
clusters=[osd_cluster_with_default_machine_pool],
|
653
|
+
machine_pools=[existing_updated_default_machine_pool],
|
654
|
+
)
|
655
|
+
|
656
|
+
run(False)
|
657
|
+
|
658
|
+
mocks["OCM"].update_machine_pool.assert_called_once_with(
|
659
|
+
osd_cluster_with_default_machine_pool.name,
|
660
|
+
expected_ocm_machine_pool_update_payload,
|
661
|
+
)
|
662
|
+
|
663
|
+
|
664
|
+
def test_run_update_machine_pool_for_rosa_cluster(
|
568
665
|
mocker: MockerFixture,
|
569
|
-
|
666
|
+
rosa_cluster_with_default_machine_pool: ClusterV1,
|
570
667
|
existing_updated_default_machine_pool: dict,
|
571
668
|
expected_ocm_machine_pool_update_payload: dict,
|
572
669
|
) -> None:
|
573
670
|
mocks = setup_mocks(
|
574
671
|
mocker,
|
575
|
-
clusters=[
|
672
|
+
clusters=[rosa_cluster_with_default_machine_pool],
|
576
673
|
machine_pools=[existing_updated_default_machine_pool],
|
577
674
|
)
|
578
675
|
|
579
676
|
run(False)
|
580
677
|
|
581
678
|
mocks["OCM"].update_machine_pool.assert_called_once_with(
|
582
|
-
|
679
|
+
rosa_cluster_with_default_machine_pool.name,
|
583
680
|
expected_ocm_machine_pool_update_payload,
|
584
681
|
)
|
585
682
|
|
@@ -593,14 +690,32 @@ def existing_default_machine_pool_with_different_instance_type() -> dict:
|
|
593
690
|
}
|
594
691
|
|
595
692
|
|
596
|
-
def
|
693
|
+
def test_run_update_machine_pool_error_for_osd_cluster(
|
597
694
|
mocker: MockerFixture,
|
598
|
-
|
695
|
+
osd_cluster_with_default_machine_pool: ClusterV1,
|
599
696
|
existing_default_machine_pool_with_different_instance_type: dict,
|
600
697
|
) -> None:
|
601
698
|
setup_mocks(
|
602
699
|
mocker,
|
603
|
-
clusters=[
|
700
|
+
clusters=[osd_cluster_with_default_machine_pool],
|
701
|
+
machine_pools=[existing_default_machine_pool_with_different_instance_type],
|
702
|
+
)
|
703
|
+
|
704
|
+
with pytest.raises(ExceptionGroup) as eg:
|
705
|
+
run(False)
|
706
|
+
|
707
|
+
assert len(eg.value.exceptions) == 1
|
708
|
+
assert isinstance(eg.value.exceptions[0], InvalidUpdateError)
|
709
|
+
|
710
|
+
|
711
|
+
def test_run_update_machine_pool_error_for_rosa_cluster(
|
712
|
+
mocker: MockerFixture,
|
713
|
+
rosa_cluster_with_default_machine_pool: ClusterV1,
|
714
|
+
existing_default_machine_pool_with_different_instance_type: dict,
|
715
|
+
) -> None:
|
716
|
+
setup_mocks(
|
717
|
+
mocker,
|
718
|
+
clusters=[rosa_cluster_with_default_machine_pool],
|
604
719
|
machine_pools=[existing_default_machine_pool_with_different_instance_type],
|
605
720
|
)
|
606
721
|
|
@@ -624,42 +739,70 @@ def expected_ocm_machine_pool_delete_payload() -> dict:
|
|
624
739
|
}
|
625
740
|
|
626
741
|
|
627
|
-
def
|
742
|
+
def test_run_delete_machine_pool_for_osd_cluster(
|
743
|
+
mocker: MockerFixture,
|
744
|
+
osd_cluster_with_default_machine_pool: ClusterV1,
|
745
|
+
default_worker_machine_pool: dict,
|
746
|
+
new_workers_machine_pool: dict,
|
747
|
+
expected_ocm_machine_pool_delete_payload: dict,
|
748
|
+
) -> None:
|
749
|
+
mocks = setup_mocks(
|
750
|
+
mocker,
|
751
|
+
clusters=[osd_cluster_with_default_machine_pool],
|
752
|
+
machine_pools=[default_worker_machine_pool, new_workers_machine_pool],
|
753
|
+
)
|
754
|
+
|
755
|
+
run(False)
|
756
|
+
|
757
|
+
mocks["OCM"].delete_machine_pool.assert_called_once_with(
|
758
|
+
osd_cluster_with_default_machine_pool.name,
|
759
|
+
expected_ocm_machine_pool_delete_payload,
|
760
|
+
)
|
761
|
+
|
762
|
+
|
763
|
+
def test_run_delete_machine_pool_for_rosa_cluster(
|
628
764
|
mocker: MockerFixture,
|
629
|
-
|
630
|
-
default_worker_machine_pool,
|
765
|
+
rosa_cluster_with_default_machine_pool: ClusterV1,
|
766
|
+
default_worker_machine_pool: dict,
|
631
767
|
new_workers_machine_pool: dict,
|
632
768
|
expected_ocm_machine_pool_delete_payload: dict,
|
633
769
|
) -> None:
|
634
770
|
mocks = setup_mocks(
|
635
771
|
mocker,
|
636
|
-
clusters=[
|
772
|
+
clusters=[rosa_cluster_with_default_machine_pool],
|
637
773
|
machine_pools=[default_worker_machine_pool, new_workers_machine_pool],
|
638
774
|
)
|
639
775
|
|
640
776
|
run(False)
|
641
777
|
|
642
778
|
mocks["OCM"].delete_machine_pool.assert_called_once_with(
|
643
|
-
|
779
|
+
rosa_cluster_with_default_machine_pool.name,
|
644
780
|
expected_ocm_machine_pool_delete_payload,
|
645
781
|
)
|
646
782
|
|
647
783
|
|
648
784
|
@pytest.fixture
|
649
|
-
def
|
650
|
-
|
785
|
+
def osd_cluster_without_machine_pools(
|
786
|
+
osd_cluster_builder,
|
651
787
|
) -> ClusterV1:
|
652
|
-
return
|
788
|
+
return osd_cluster_builder([])
|
653
789
|
|
654
790
|
|
655
|
-
|
791
|
+
@pytest.fixture
|
792
|
+
def rosa_cluster_without_machine_pools(
|
793
|
+
rosa_cluster_builder,
|
794
|
+
) -> ClusterV1:
|
795
|
+
return rosa_cluster_builder([])
|
796
|
+
|
797
|
+
|
798
|
+
def test_run_delete_machine_pool_fail_validation_for_osd_cluster(
|
656
799
|
mocker: MockerFixture,
|
657
|
-
|
658
|
-
default_worker_machine_pool,
|
800
|
+
osd_cluster_without_machine_pools: ClusterV1,
|
801
|
+
default_worker_machine_pool: dict,
|
659
802
|
) -> None:
|
660
803
|
setup_mocks(
|
661
804
|
mocker,
|
662
|
-
clusters=[
|
805
|
+
clusters=[osd_cluster_without_machine_pools],
|
663
806
|
machine_pools=[default_worker_machine_pool],
|
664
807
|
)
|
665
808
|
|
@@ -670,6 +813,76 @@ def test_run_delete_machine_pool_fail_validation(
|
|
670
813
|
assert isinstance(eg.value.exceptions[0], InvalidUpdateError)
|
671
814
|
|
672
815
|
|
816
|
+
def test_run_delete_machine_pool_fail_validation_for_rosa_cluster(
|
817
|
+
mocker: MockerFixture,
|
818
|
+
rosa_cluster_without_machine_pools: ClusterV1,
|
819
|
+
default_worker_machine_pool: dict,
|
820
|
+
) -> None:
|
821
|
+
setup_mocks(
|
822
|
+
mocker,
|
823
|
+
clusters=[rosa_cluster_without_machine_pools],
|
824
|
+
machine_pools=[default_worker_machine_pool],
|
825
|
+
)
|
826
|
+
|
827
|
+
with pytest.raises(ExceptionGroup) as eg:
|
828
|
+
run(False)
|
829
|
+
|
830
|
+
assert len(eg.value.exceptions) == 1
|
831
|
+
assert isinstance(eg.value.exceptions[0], InvalidUpdateError)
|
832
|
+
|
833
|
+
|
834
|
+
@pytest.fixture
|
835
|
+
def osd_cluster_with_new_machine_pool(
|
836
|
+
osd_cluster_builder,
|
837
|
+
new_workers_machine_pool: dict,
|
838
|
+
) -> ClusterV1:
|
839
|
+
return osd_cluster_builder([new_workers_machine_pool])
|
840
|
+
|
841
|
+
|
842
|
+
@pytest.fixture
|
843
|
+
def rosa_cluster_with_new_machine_pool(
|
844
|
+
rosa_cluster_builder,
|
845
|
+
new_workers_machine_pool: dict,
|
846
|
+
) -> ClusterV1:
|
847
|
+
return rosa_cluster_builder([new_workers_machine_pool])
|
848
|
+
|
849
|
+
|
850
|
+
def test_run_delete_default_machine_pool_fail_validation_for_osd_cluster(
|
851
|
+
mocker: MockerFixture,
|
852
|
+
osd_cluster_with_new_machine_pool: ClusterV1,
|
853
|
+
default_worker_machine_pool: dict,
|
854
|
+
new_workers_machine_pool: dict,
|
855
|
+
) -> None:
|
856
|
+
setup_mocks(
|
857
|
+
mocker,
|
858
|
+
clusters=[osd_cluster_with_new_machine_pool],
|
859
|
+
machine_pools=[default_worker_machine_pool, new_workers_machine_pool],
|
860
|
+
)
|
861
|
+
|
862
|
+
with pytest.raises(ExceptionGroup) as eg:
|
863
|
+
run(False)
|
864
|
+
|
865
|
+
assert len(eg.value.exceptions) == 1
|
866
|
+
assert isinstance(eg.value.exceptions[0], InvalidUpdateError)
|
867
|
+
|
868
|
+
|
869
|
+
def test_run_delete_default_machine_pool_success_for_rosa_cluster(
|
870
|
+
mocker: MockerFixture,
|
871
|
+
rosa_cluster_with_new_machine_pool: ClusterV1,
|
872
|
+
default_worker_machine_pool: dict,
|
873
|
+
new_workers_machine_pool: dict,
|
874
|
+
) -> None:
|
875
|
+
mocks = setup_mocks(
|
876
|
+
mocker,
|
877
|
+
clusters=[rosa_cluster_with_new_machine_pool],
|
878
|
+
machine_pools=[default_worker_machine_pool, new_workers_machine_pool],
|
879
|
+
)
|
880
|
+
|
881
|
+
run(False)
|
882
|
+
|
883
|
+
mocks["OCM"].delete_machine_pool.assert_called_once()
|
884
|
+
|
885
|
+
|
673
886
|
@pytest.fixture
|
674
887
|
def hypershift_cluster_builder(
|
675
888
|
gql_class_factory: Callable[..., ClusterV1],
|
@@ -687,6 +900,7 @@ def hypershift_cluster_builder(
|
|
687
900
|
},
|
688
901
|
},
|
689
902
|
"spec": {
|
903
|
+
"product": "rosa",
|
690
904
|
"hypershift": True,
|
691
905
|
},
|
692
906
|
"machinePools": machine_pools,
|
@@ -880,7 +1094,7 @@ def existing_multiple_hypershift_node_pools_with_defaults() -> list[dict]:
|
|
880
1094
|
]
|
881
1095
|
|
882
1096
|
|
883
|
-
def
|
1097
|
+
def test_run_delete_default_node_pool(
|
884
1098
|
mocker: MockerFixture,
|
885
1099
|
hypershift_cluster_without_default_worker_machine_pools: ClusterV1,
|
886
1100
|
existing_multiple_hypershift_node_pools_with_defaults: list[dict],
|
@@ -893,4 +1107,4 @@ def test_run_delete_node_pool_skip_workers_ones(
|
|
893
1107
|
|
894
1108
|
run(False)
|
895
1109
|
|
896
|
-
mocks["OCM"].delete_node_pool.
|
1110
|
+
mocks["OCM"].delete_node_pool.assert_called()
|
@@ -140,17 +140,13 @@ def aws_account_no_state(automation_token) -> AWSAccountV1:
|
|
140
140
|
|
141
141
|
@pytest.fixture
|
142
142
|
def int_params() -> TerraformRepoIntegrationParams:
|
143
|
-
return TerraformRepoIntegrationParams(
|
144
|
-
output_file=None, validate_git=False, ignore_state_errors=False
|
145
|
-
)
|
143
|
+
return TerraformRepoIntegrationParams(output_file=None, validate_git=False)
|
146
144
|
|
147
145
|
|
148
146
|
@pytest.fixture
|
149
147
|
def int_params_print_to_tmp(tmp_path) -> TerraformRepoIntegrationParams:
|
150
148
|
return TerraformRepoIntegrationParams(
|
151
|
-
output_file=f"{tmp_path}/tf-repo.yaml",
|
152
|
-
validate_git=False,
|
153
|
-
ignore_state_errors=False,
|
149
|
+
output_file=f"{tmp_path}/tf-repo.yaml", validate_git=False
|
154
150
|
)
|
155
151
|
|
156
152
|
|
@@ -165,7 +161,11 @@ def test_addition_to_existing_repo(existing_repo, new_repo, int_params, state_mo
|
|
165
161
|
|
166
162
|
integration = TerraformRepoIntegration(params=int_params)
|
167
163
|
diff = integration.calculate_diff(
|
168
|
-
existing_state=existing,
|
164
|
+
existing_state=existing,
|
165
|
+
desired_state=desired,
|
166
|
+
dry_run=False,
|
167
|
+
state=state_mock,
|
168
|
+
recreate_state=False,
|
169
169
|
)
|
170
170
|
|
171
171
|
assert diff == [new_repo]
|
@@ -187,6 +187,7 @@ def test_updating_repo_ref(existing_repo, int_params, state_mock):
|
|
187
187
|
desired_state=[updated_repo],
|
188
188
|
dry_run=False,
|
189
189
|
state=state_mock,
|
190
|
+
recreate_state=False,
|
190
191
|
)
|
191
192
|
|
192
193
|
assert diff == [updated_repo]
|
@@ -213,6 +214,7 @@ def test_fail_on_update_invalid_repo_params(existing_repo, int_params):
|
|
213
214
|
desired_state=[updated_repo],
|
214
215
|
dry_run=True,
|
215
216
|
state=None,
|
217
|
+
recreate_state=False,
|
216
218
|
)
|
217
219
|
|
218
220
|
|
@@ -228,6 +230,7 @@ def test_delete_repo(existing_repo, int_params, state_mock):
|
|
228
230
|
desired_state=[updated_repo],
|
229
231
|
dry_run=False,
|
230
232
|
state=state_mock,
|
233
|
+
recreate_state=False,
|
231
234
|
)
|
232
235
|
|
233
236
|
assert diff == [updated_repo]
|
@@ -242,7 +245,11 @@ def test_delete_repo_without_flag(existing_repo, int_params):
|
|
242
245
|
|
243
246
|
with pytest.raises(ParameterError):
|
244
247
|
integration.calculate_diff(
|
245
|
-
existing_state=existing,
|
248
|
+
existing_state=existing,
|
249
|
+
desired_state=[],
|
250
|
+
dry_run=True,
|
251
|
+
state=None,
|
252
|
+
recreate_state=False,
|
246
253
|
)
|
247
254
|
|
248
255
|
|
@@ -303,6 +310,7 @@ def test_update_repo_state(int_params, existing_repo, state_mock):
|
|
303
310
|
desired_state=desired_state,
|
304
311
|
dry_run=False,
|
305
312
|
state=state_mock,
|
313
|
+
recreate_state=False,
|
306
314
|
)
|
307
315
|
|
308
316
|
state_mock.add.assert_called_once_with(
|
@@ -327,6 +335,7 @@ def test_output_correct_statefile(
|
|
327
335
|
desired_state=desired_state,
|
328
336
|
dry_run=True,
|
329
337
|
state=state_mock,
|
338
|
+
recreate_state=False,
|
330
339
|
)
|
331
340
|
|
332
341
|
assert diff
|
@@ -353,6 +362,7 @@ def test_output_correct_no_statefile(
|
|
353
362
|
desired_state=desired_state,
|
354
363
|
dry_run=True,
|
355
364
|
state=state_mock,
|
365
|
+
recreate_state=False,
|
356
366
|
)
|
357
367
|
|
358
368
|
assert diff
|
@@ -371,7 +381,11 @@ def test_fail_on_multiple_repos_dry_run(int_params, existing_repo, new_repo):
|
|
371
381
|
|
372
382
|
with pytest.raises(Exception):
|
373
383
|
integration.calculate_diff(
|
374
|
-
existing_state=[],
|
384
|
+
existing_state=[],
|
385
|
+
desired_state=desired_state,
|
386
|
+
dry_run=True,
|
387
|
+
state=None,
|
388
|
+
recreate_state=False,
|
375
389
|
)
|
376
390
|
|
377
391
|
|
@@ -381,7 +395,11 @@ def test_succeed_on_multiple_repos_non_dry_run(int_params, existing_repo, new_re
|
|
381
395
|
desired_state = [existing_repo, new_repo]
|
382
396
|
|
383
397
|
diff = integration.calculate_diff(
|
384
|
-
existing_state=[],
|
398
|
+
existing_state=[],
|
399
|
+
desired_state=desired_state,
|
400
|
+
dry_run=False,
|
401
|
+
state=None,
|
402
|
+
recreate_state=False,
|
385
403
|
)
|
386
404
|
|
387
405
|
assert diff
|
@@ -397,7 +415,11 @@ def test_no_op_succeeds(int_params, existing_repo):
|
|
397
415
|
state = [existing_repo]
|
398
416
|
|
399
417
|
diff = integration.calculate_diff(
|
400
|
-
existing_state=state,
|
418
|
+
existing_state=state,
|
419
|
+
desired_state=state,
|
420
|
+
dry_run=True,
|
421
|
+
state=None,
|
422
|
+
recreate_state=False,
|
401
423
|
)
|
402
424
|
|
403
425
|
assert diff is None
|
reconcile/utils/semver_helper.py
CHANGED
@@ -7,7 +7,16 @@ def make_semver(major: int, minor: int, patch: int) -> str:
|
|
7
7
|
return str(semver.VersionInfo(major=major, minor=minor, patch=patch))
|
8
8
|
|
9
9
|
|
10
|
-
def parse_semver(
|
10
|
+
def parse_semver(
|
11
|
+
version: str, optional_minor_and_patch: bool = False
|
12
|
+
) -> semver.VersionInfo:
|
13
|
+
if optional_minor_and_patch:
|
14
|
+
# semver3 supports optional minor and patch.
|
15
|
+
# until we upgrade to semver3, we support this by adding a default minor and/or patch
|
16
|
+
if "." not in version:
|
17
|
+
version = f"{version}.0.0"
|
18
|
+
elif version.count(".") == 1:
|
19
|
+
version = f"{version}.0"
|
11
20
|
return semver.VersionInfo.parse(version)
|
12
21
|
|
13
22
|
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc441.dist-info → qontract_reconcile-0.10.1rc449.dist-info}/top_level.txt
RENAMED
File without changes
|