qontract-reconcile 0.10.1rc1152__py3-none-any.whl → 0.10.1rc1154__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.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/RECORD +10 -10
- reconcile/external_resources/aws.py +52 -0
- reconcile/external_resources/factories.py +4 -0
- reconcile/external_resources/model.py +23 -22
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +19 -1
- reconcile/utils/helpers.py +8 -0
- {qontract_reconcile-0.10.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.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.1rc1154
|
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.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/RECORD
RENAMED
@@ -192,14 +192,14 @@ reconcile/endpoints_discovery/integration.py,sha256=znfnlm8bZesfcNbQnaR2aaVM-DTB
|
|
192
192
|
reconcile/endpoints_discovery/merge_request.py,sha256=_yLb4tnvoZMCko8rta2C_CvOInJa9pa3HzSmHNtjgGU,2978
|
193
193
|
reconcile/endpoints_discovery/merge_request_manager.py,sha256=wUMsumxv8RnWaRattax4HfoRlhtVzmgro3GiJJ1C4Vc,6392
|
194
194
|
reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
195
|
-
reconcile/external_resources/aws.py,sha256=
|
196
|
-
reconcile/external_resources/factories.py,sha256=
|
195
|
+
reconcile/external_resources/aws.py,sha256=309Zui7rE8XFJA1ZBLupl55Vp8Y5KKgXdsKQWqKbg8I,7069
|
196
|
+
reconcile/external_resources/factories.py,sha256=KrJDh52_8PeCEVjwfeVr1jwAJDdhMXRQ_XcBETfnKY4,4988
|
197
197
|
reconcile/external_resources/integration.py,sha256=gBVO5dE8JyZ3xYcYik-MTIp_18oU7_hpYc_oztyfElQ,6753
|
198
198
|
reconcile/external_resources/integration_secrets_sync.py,sha256=dX09O3r6KURziUYYfiki10orNjOGVma-XojhVqd0ww4,1667
|
199
199
|
reconcile/external_resources/manager.py,sha256=wcqTawNS4qoBHFVfyCfHtWXh4L3AlgcNYx_Ov_vEjNg,17914
|
200
200
|
reconcile/external_resources/meta.py,sha256=noaytFzmShpzLA_ebGh7wuP45mOfHIOnnoUxivjDa1I,672
|
201
201
|
reconcile/external_resources/metrics.py,sha256=nMbyonGZEJDD1lYzpQY2eR9TNwvxYC4ZCcpi6wrExcM,1037
|
202
|
-
reconcile/external_resources/model.py,sha256=
|
202
|
+
reconcile/external_resources/model.py,sha256=Ta8eOJ97RxCgBtWqsF4MWzQhgsOC2PoFfBKM5y3rM6U,8378
|
203
203
|
reconcile/external_resources/reconciler.py,sha256=3KFmkHsN7YAwJUSBpN1Xd_D2zM9Ea5_c2uMGWsfruZo,9707
|
204
204
|
reconcile/external_resources/secrets_sync.py,sha256=6n0oDPLjd9Ql0lf6zsr1AZw8A6EEe3yCzl20XodtgkE,16229
|
205
205
|
reconcile/external_resources/state.py,sha256=UupSa6tl4-73_J6Fhisn-qHal3v3uAUS5s5sk85LGDs,9343
|
@@ -292,7 +292,7 @@ reconcile/gql_definitions/endpoints_discovery/namespaces.py,sha256=FqJ0H7NdsIm5B
|
|
292
292
|
reconcile/gql_definitions/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
293
293
|
reconcile/gql_definitions/external_resources/aws_accounts.py,sha256=XR69j9dpTQ0gv8y-AZN7AJ0dPvO-wbHscyCDgrax6Bk,2046
|
294
294
|
reconcile/gql_definitions/external_resources/external_resources_modules.py,sha256=HFOQjmNbNxk0j5nChxppQeCnJjeDsqibJkPgA7R1zRw,2417
|
295
|
-
reconcile/gql_definitions/external_resources/external_resources_namespaces.py,sha256
|
295
|
+
reconcile/gql_definitions/external_resources/external_resources_namespaces.py,sha256=KSASnKycr-e5kCUSOkqnS8w7Gvo_nXA_5NQcS8vGeLY,43466
|
296
296
|
reconcile/gql_definitions/external_resources/external_resources_settings.py,sha256=Hw9n_90BPG6Lnt2PT3mHc6p0KEm2CxKxvSGRFc_Dhus,2982
|
297
297
|
reconcile/gql_definitions/fragments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
298
298
|
reconcile/gql_definitions/fragments/aus_organization.py,sha256=uBKbTuBa3CZmTXR5HOcGhRcu2U9kM93KbYmoWTxcpB0,4767
|
@@ -683,7 +683,7 @@ reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
|
|
683
683
|
reconcile/utils/gql.py,sha256=C0thIm_k9MBldfqwHzyqtYZk9sIvMdm9IbbnXLGwjD8,14158
|
684
684
|
reconcile/utils/grouping.py,sha256=vr9SFHZ7bqmHYrvYcEZt-Er3-yQYfAAdq5sHLZVmXPY,456
|
685
685
|
reconcile/utils/helm.py,sha256=hr4J_9mBZwbc1FDNfFh4QKAj0h3eLxyTN2Y3UxIRp8U,3893
|
686
|
-
reconcile/utils/helpers.py,sha256=
|
686
|
+
reconcile/utils/helpers.py,sha256=1PK_6gTzg2kJ8Ac8zZHB2yEubewDWtVspTl5bVbvbEY,1462
|
687
687
|
reconcile/utils/imap_client.py,sha256=h8YDiCSCvroErhpH_-KGYI7Y2WU2Q2oSpuxDFbOkSbY,1989
|
688
688
|
reconcile/utils/instrumented_wrappers.py,sha256=eVwMoa6FCrYxLv3RML3WpZF9qKVfCTjMxphgVXG03OM,1073
|
689
689
|
reconcile/utils/jenkins_api.py,sha256=RaKuZmO7_lbI-hE6c_Pq2a6CQdmBVj7BcP2jR68cIbI,7081
|
@@ -880,8 +880,8 @@ tools/test/test_qontract_cli.py,sha256=iuzKbQ6ahinvjoQmQLBrG4shey0z-1rB6qCgS8T6d
|
|
880
880
|
tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
|
881
881
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
882
882
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
883
|
-
qontract_reconcile-0.10.
|
884
|
-
qontract_reconcile-0.10.
|
885
|
-
qontract_reconcile-0.10.
|
886
|
-
qontract_reconcile-0.10.
|
887
|
-
qontract_reconcile-0.10.
|
883
|
+
qontract_reconcile-0.10.1rc1154.dist-info/METADATA,sha256=S0xB9gH65bXGiy_EaEsoUw8L6KbifPatRapCwmnBAS0,2213
|
884
|
+
qontract_reconcile-0.10.1rc1154.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
885
|
+
qontract_reconcile-0.10.1rc1154.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
886
|
+
qontract_reconcile-0.10.1rc1154.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
887
|
+
qontract_reconcile-0.10.1rc1154.dist-info/RECORD,,
|
@@ -9,6 +9,7 @@ from reconcile.utils.external_resource_spec import (
|
|
9
9
|
ExternalResourceSpec,
|
10
10
|
)
|
11
11
|
from reconcile.utils.external_resources import ResourceValueResolver
|
12
|
+
from reconcile.utils.helpers import generate_random_password
|
12
13
|
from reconcile.utils.secret_reader import SecretReaderBase
|
13
14
|
|
14
15
|
|
@@ -33,6 +34,57 @@ class AWSDefaultResourceFactory(AWSResourceFactory):
|
|
33
34
|
def validate(self, resource: ExternalResource) -> None: ...
|
34
35
|
|
35
36
|
|
37
|
+
class AWSElasticacheFactory(AWSDefaultResourceFactory):
|
38
|
+
def _get_source_db_spec(
|
39
|
+
self, provisioner: str, identifier: str
|
40
|
+
) -> ExternalResourceSpec:
|
41
|
+
return self.er_inventory.get_inventory_spec(
|
42
|
+
"aws", provisioner, "elasticache", identifier
|
43
|
+
)
|
44
|
+
|
45
|
+
def resolve(self, spec: ExternalResourceSpec) -> dict[str, Any]:
|
46
|
+
"""Resolve the elasticache resource specification and translate some attributes to AWS >= 5.60.0 provider format."""
|
47
|
+
rvr = ResourceValueResolver(spec=spec, identifier_as_value=True)
|
48
|
+
data = rvr.resolve()
|
49
|
+
data["output_prefix"] = spec.output_prefix
|
50
|
+
|
51
|
+
if "replication_group_id" not in data:
|
52
|
+
data["replication_group_id"] = spec.identifier
|
53
|
+
|
54
|
+
if cluster_mode := data.pop("cluster_mode", {}):
|
55
|
+
for k, v in cluster_mode.items():
|
56
|
+
data[k] = v
|
57
|
+
|
58
|
+
if "parameter_group" in data:
|
59
|
+
pg_data = rvr._get_values(data["parameter_group"])
|
60
|
+
data["parameter_group"] = pg_data
|
61
|
+
|
62
|
+
if data.get("transit_encryption_enabled", False):
|
63
|
+
data["auth_token"] = (
|
64
|
+
spec.get_secret_field("db.auth_token") or generate_random_password()
|
65
|
+
)
|
66
|
+
return data
|
67
|
+
|
68
|
+
def validate(self, resource: ExternalResource) -> None:
|
69
|
+
"""Validate the elasticache resource specification."""
|
70
|
+
data = resource.data
|
71
|
+
if data.get("parameter_group"):
|
72
|
+
if not data["parameter_group"].get("name"):
|
73
|
+
data["parameter_group"]["name"] = f"{data['replication_group_id']}-pg"
|
74
|
+
|
75
|
+
if (
|
76
|
+
data.get("parameter_group_name")
|
77
|
+
and data["parameter_group"]["name"] != data["parameter_group_name"]
|
78
|
+
):
|
79
|
+
raise ValueError(
|
80
|
+
"Custom parameter_group set and parameter_group_name given. Either remove parameter_group_name or set it to the same value as parameter_group.name."
|
81
|
+
)
|
82
|
+
|
83
|
+
if not data.get("parameter_group_name"):
|
84
|
+
# automatically set /aws/elasticache-defaults-1.yml/parameter_group_name to /aws/parameter-group-1.yml/name
|
85
|
+
data["parameter_group_name"] = data["parameter_group"]["name"]
|
86
|
+
|
87
|
+
|
36
88
|
class AWSRdsFactory(AWSDefaultResourceFactory):
|
37
89
|
def _get_source_db_spec(
|
38
90
|
self, provisioner: str, identifier: str
|
@@ -6,6 +6,7 @@ from typing import Generic, TypeVar
|
|
6
6
|
|
7
7
|
from reconcile.external_resources.aws import (
|
8
8
|
AWSDefaultResourceFactory,
|
9
|
+
AWSElasticacheFactory,
|
9
10
|
AWSMskFactory,
|
10
11
|
AWSRdsFactory,
|
11
12
|
AWSResourceFactory,
|
@@ -87,6 +88,9 @@ def setup_aws_resource_factories(
|
|
87
88
|
er_inventory: ExternalResourcesInventory, secret_reader: SecretReaderBase
|
88
89
|
) -> ObjectFactory[AWSResourceFactory]:
|
89
90
|
f = ObjectFactory[AWSResourceFactory]()
|
91
|
+
f.register_factory(
|
92
|
+
"elasticache", AWSElasticacheFactory(er_inventory, secret_reader)
|
93
|
+
)
|
90
94
|
f.register_factory("rds", AWSRdsFactory(er_inventory, secret_reader))
|
91
95
|
f.register_factory("msk", AWSMskFactory(er_inventory, secret_reader))
|
92
96
|
f.register_factory(
|
@@ -24,6 +24,7 @@ from reconcile.gql_definitions.external_resources.external_resources_modules imp
|
|
24
24
|
from reconcile.gql_definitions.external_resources.external_resources_namespaces import (
|
25
25
|
ExternalResourcesModuleOverridesV1,
|
26
26
|
NamespaceTerraformProviderResourceAWSV1,
|
27
|
+
NamespaceTerraformResourceElastiCacheV1,
|
27
28
|
NamespaceTerraformResourceMskV1,
|
28
29
|
NamespaceTerraformResourceRDSV1,
|
29
30
|
NamespaceV1,
|
@@ -68,12 +69,32 @@ class ExternalResourceKey(BaseModel, frozen=True):
|
|
68
69
|
|
69
70
|
SUPPORTED_RESOURCE_PROVIDERS = NamespaceTerraformProviderResourceAWSV1
|
70
71
|
SUPPORTED_RESOURCE_TYPES = (
|
71
|
-
NamespaceTerraformResourceRDSV1
|
72
|
+
NamespaceTerraformResourceRDSV1
|
73
|
+
| NamespaceTerraformResourceMskV1
|
74
|
+
| NamespaceTerraformResourceElastiCacheV1
|
72
75
|
)
|
73
76
|
|
74
77
|
|
75
78
|
class ExternalResourcesInventory(MutableMapping):
|
76
|
-
|
79
|
+
def __init__(self, namespaces: Iterable[NamespaceV1]) -> None:
|
80
|
+
self._inventory: dict[ExternalResourceKey, ExternalResourceSpec] = {}
|
81
|
+
|
82
|
+
desired_providers = [
|
83
|
+
(p, ns)
|
84
|
+
for ns in namespaces
|
85
|
+
for p in ns.external_resources or []
|
86
|
+
if isinstance(p, SUPPORTED_RESOURCE_PROVIDERS) and p.resources
|
87
|
+
]
|
88
|
+
|
89
|
+
desired_specs = [
|
90
|
+
self._build_external_resource_spec(ns, p, r)
|
91
|
+
for (p, ns) in desired_providers
|
92
|
+
for r in p.resources
|
93
|
+
if isinstance(r, SUPPORTED_RESOURCE_TYPES) and r.managed_by_erv2
|
94
|
+
]
|
95
|
+
|
96
|
+
for spec in desired_specs:
|
97
|
+
self._inventory[ExternalResourceKey.from_spec(spec)] = spec
|
77
98
|
|
78
99
|
def _build_external_resource_spec(
|
79
100
|
self,
|
@@ -97,24 +118,6 @@ class ExternalResourcesInventory(MutableMapping):
|
|
97
118
|
spec.metadata[MODULE_OVERRIDES] = resource.module_overrides
|
98
119
|
return spec
|
99
120
|
|
100
|
-
def __init__(self, namespaces: Iterable[NamespaceV1]) -> None:
|
101
|
-
desired_providers = [
|
102
|
-
(p, ns)
|
103
|
-
for ns in namespaces
|
104
|
-
for p in ns.external_resources or []
|
105
|
-
if isinstance(p, SUPPORTED_RESOURCE_PROVIDERS) and p.resources
|
106
|
-
]
|
107
|
-
|
108
|
-
desired_specs = [
|
109
|
-
self._build_external_resource_spec(ns, p, r)
|
110
|
-
for (p, ns) in desired_providers
|
111
|
-
for r in p.resources
|
112
|
-
if isinstance(r, SUPPORTED_RESOURCE_TYPES) and r.managed_by_erv2
|
113
|
-
]
|
114
|
-
|
115
|
-
for spec in desired_specs:
|
116
|
-
self._inventory[ExternalResourceKey.from_spec(spec)] = spec
|
117
|
-
|
118
121
|
def __getitem__(self, key: ExternalResourceKey) -> ExternalResourceSpec | None:
|
119
122
|
return self._inventory[key]
|
120
123
|
|
@@ -158,8 +161,6 @@ class ExternalResourceModuleKey(BaseModel, frozen=True):
|
|
158
161
|
|
159
162
|
|
160
163
|
class ModuleInventory:
|
161
|
-
inventory: dict[ExternalResourceModuleKey, ExternalResourcesModuleV1]
|
162
|
-
|
163
164
|
def __init__(
|
164
165
|
self, inventory: dict[ExternalResourceModuleKey, ExternalResourcesModuleV1]
|
165
166
|
):
|
@@ -144,6 +144,14 @@ query ExternalResourcesNamespaces {
|
|
144
144
|
overrides
|
145
145
|
output_resource_name
|
146
146
|
annotations
|
147
|
+
managed_by_erv2
|
148
|
+
delete
|
149
|
+
module_overrides {
|
150
|
+
module_type
|
151
|
+
image
|
152
|
+
version
|
153
|
+
reconcile_timeout_minutes
|
154
|
+
}
|
147
155
|
}
|
148
156
|
... on NamespaceTerraformResourceServiceAccount_v1 {
|
149
157
|
identifier
|
@@ -602,6 +610,13 @@ class NamespaceTerraformResourceS3V1(NamespaceTerraformResourceAWSV1):
|
|
602
610
|
annotations: Optional[str] = Field(..., alias="annotations")
|
603
611
|
|
604
612
|
|
613
|
+
class NamespaceTerraformResourceElastiCacheV1_ExternalResourcesModuleOverridesV1(ConfiguredBaseModel):
|
614
|
+
module_type: Optional[str] = Field(..., alias="module_type")
|
615
|
+
image: Optional[str] = Field(..., alias="image")
|
616
|
+
version: Optional[str] = Field(..., alias="version")
|
617
|
+
reconcile_timeout_minutes: Optional[int] = Field(..., alias="reconcile_timeout_minutes")
|
618
|
+
|
619
|
+
|
605
620
|
class NamespaceTerraformResourceElastiCacheV1(NamespaceTerraformResourceAWSV1):
|
606
621
|
identifier: str = Field(..., alias="identifier")
|
607
622
|
defaults: str = Field(..., alias="defaults")
|
@@ -610,6 +625,9 @@ class NamespaceTerraformResourceElastiCacheV1(NamespaceTerraformResourceAWSV1):
|
|
610
625
|
overrides: Optional[str] = Field(..., alias="overrides")
|
611
626
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
612
627
|
annotations: Optional[str] = Field(..., alias="annotations")
|
628
|
+
managed_by_erv2: Optional[bool] = Field(..., alias="managed_by_erv2")
|
629
|
+
delete: Optional[bool] = Field(..., alias="delete")
|
630
|
+
module_overrides: Optional[NamespaceTerraformResourceElastiCacheV1_ExternalResourcesModuleOverridesV1] = Field(..., alias="module_overrides")
|
613
631
|
|
614
632
|
|
615
633
|
class ClusterV1(ConfiguredBaseModel):
|
@@ -1014,7 +1032,7 @@ class NamespaceTerraformResourceMskV1(NamespaceTerraformResourceAWSV1):
|
|
1014
1032
|
|
1015
1033
|
class NamespaceTerraformProviderResourceAWSV1(NamespaceExternalResourceV1):
|
1016
1034
|
provisioner: AWSAccountV1 = Field(..., alias="provisioner")
|
1017
|
-
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceMskV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceSNSTopicV1,
|
1035
|
+
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceElastiCacheV1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceMskV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceSNSTopicV1, NamespaceTerraformResourceServiceAccountV1, NamespaceTerraformResourceS3SQSV1, NamespaceTerraformResourceCloudWatchV1, NamespaceTerraformResourceRosaAuthenticatorVPCEV1, NamespaceTerraformResourceS3CloudFrontV1, NamespaceTerraformResourceKMSV1, NamespaceTerraformResourceElasticSearchV1, NamespaceTerraformResourceACMV1, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
|
1018
1036
|
|
1019
1037
|
|
1020
1038
|
class EnvironmentV1(ConfiguredBaseModel):
|
reconcile/utils/helpers.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
import logging
|
2
|
+
import random
|
3
|
+
import string
|
2
4
|
from collections import Counter
|
3
5
|
from collections.abc import (
|
4
6
|
Iterable,
|
@@ -44,3 +46,9 @@ Item = TypeVar("Item")
|
|
44
46
|
|
45
47
|
def find_duplicates(items: Iterable[Item]) -> list[Item]:
|
46
48
|
return [item for item, count in Counter(items).items() if count > 1]
|
49
|
+
|
50
|
+
|
51
|
+
def generate_random_password(string_length: int = 20) -> str:
|
52
|
+
"""Generate a random string of letters and digits"""
|
53
|
+
letters_and_digits = string.ascii_letters + string.digits
|
54
|
+
return "".join(random.choices(letters_and_digits, k=string_length))
|
{qontract_reconcile-0.10.1rc1152.dist-info → qontract_reconcile-0.10.1rc1154.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|