qontract-reconcile 0.10.1rc762__py3-none-any.whl → 0.10.1rc764__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.1rc762.dist-info → qontract_reconcile-0.10.1rc764.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc762.dist-info → qontract_reconcile-0.10.1rc764.dist-info}/RECORD +17 -6
- reconcile/external_resources/__init__.py +0 -0
- reconcile/external_resources/aws.py +85 -0
- reconcile/external_resources/factories.py +133 -0
- reconcile/external_resources/integration.py +95 -0
- reconcile/external_resources/manager.py +350 -0
- reconcile/external_resources/meta.py +4 -0
- reconcile/external_resources/metrics.py +20 -0
- reconcile/external_resources/model.py +244 -0
- reconcile/external_resources/reconciler.py +249 -0
- reconcile/external_resources/secrets_sync.py +229 -0
- reconcile/external_resources/state.py +246 -0
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +4 -0
- {qontract_reconcile-0.10.1rc762.dist-info → qontract_reconcile-0.10.1rc764.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc762.dist-info → qontract_reconcile-0.10.1rc764.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc762.dist-info → qontract_reconcile-0.10.1rc764.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc762.dist-info → qontract_reconcile-0.10.1rc764.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.1rc764
|
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.1rc762.dist-info → qontract_reconcile-0.10.1rc764.dist-info}/RECORD
RENAMED
@@ -176,6 +176,17 @@ reconcile/cna/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
176
176
|
reconcile/cna/assets/asset.py,sha256=1v51uYSaD1NOc9cI_YxG7h0NOcR1ng-mkmD2UzQ8PXE,866
|
177
177
|
reconcile/cna/assets/asset_factory.py,sha256=7T7X_J6xIsoGETqBRI45_EyIKEdQcnRPt_GAuVuLQcc,785
|
178
178
|
reconcile/cna/assets/null.py,sha256=Fby1Fbn7oNRIGNasdyhRDvXJ0ktpxv-pUAPN0lZWSzk,1684
|
179
|
+
reconcile/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
180
|
+
reconcile/external_resources/aws.py,sha256=JvjKaABy2Pg8u8Lq82Acv4zMvpE3_qGKes7OG-zlHOM,2956
|
181
|
+
reconcile/external_resources/factories.py,sha256=bLboXX5Dq0xN60mtDGNjCOLC6HlKofXMWQxVbRwMMwo,4485
|
182
|
+
reconcile/external_resources/integration.py,sha256=QQZdKTeHoUD7afUTNKh878u-KWovYptAFZwjUBTIbCg,3317
|
183
|
+
reconcile/external_resources/manager.py,sha256=APszaw9PRIiHnFyCffHZjIFseIwhlYROIPLt18pHUTQ,13685
|
184
|
+
reconcile/external_resources/meta.py,sha256=SA4Km1r7ePdcNqHn2GA4ByQp4ZnIeo_n8qOOd-11IEg,151
|
185
|
+
reconcile/external_resources/metrics.py,sha256=m2TIOao2N7pD6k45driFbBGVCC_N7ai44m-lLPfa5qk,454
|
186
|
+
reconcile/external_resources/model.py,sha256=FJUb7rHU2l7YSAv-t4QaacL9pqheFBxhPydWSPqu3vY,7413
|
187
|
+
reconcile/external_resources/reconciler.py,sha256=E50X_lnOD0OWYXMzyZld1P6dCFJFYjHGyICWff9bxlc,9323
|
188
|
+
reconcile/external_resources/secrets_sync.py,sha256=g-ksvzmTlCTwo3PM3FgYXm0LUBcnwfAxcvisuR1jAMY,7982
|
189
|
+
reconcile/external_resources/state.py,sha256=rRePQdA3A6U9oFDDs9aaL5Ja7nSIXpOC1wHwY8R47_8,9197
|
179
190
|
reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
180
191
|
reconcile/glitchtip/integration.py,sha256=Y7ofQg_xCt3dOln3pjeXp7rAnwohCgD2zcUAb-Hciis,8375
|
181
192
|
reconcile/glitchtip/reconciler.py,sha256=nUvDv7qG1ly0cA16MmlL6NV71yl1mJYLT2mui7lmi0Y,12402
|
@@ -194,7 +205,7 @@ reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py,sha256=uF
|
|
194
205
|
reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
195
206
|
reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py,sha256=uVEEqU6YYmKsNTo6EWlFnoVmqha2rvBDx-wiD64VmG0,1679
|
196
207
|
reconcile/gql_definitions/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
197
|
-
reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=
|
208
|
+
reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=jJfIzTtDiW6rasv3PFEbyVHA0d8bfRYSVYP5HxslYnY,4649
|
198
209
|
reconcile/gql_definitions/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
199
210
|
reconcile/gql_definitions/aws_ami_cleanup/asg_namespaces.py,sha256=OJmeTu7uirLGAysZ3IQTtRXqMyL8noi_QZxPuWYxxmI,3678
|
200
211
|
reconcile/gql_definitions/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -786,8 +797,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
786
797
|
tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
|
787
798
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
788
799
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
789
|
-
qontract_reconcile-0.10.
|
790
|
-
qontract_reconcile-0.10.
|
791
|
-
qontract_reconcile-0.10.
|
792
|
-
qontract_reconcile-0.10.
|
793
|
-
qontract_reconcile-0.10.
|
800
|
+
qontract_reconcile-0.10.1rc764.dist-info/METADATA,sha256=Eov5cvApy_dlSvDlyNdI1Uk8pAO7tJZfv4cOMjUfeEA,2382
|
801
|
+
qontract_reconcile-0.10.1rc764.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
802
|
+
qontract_reconcile-0.10.1rc764.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
|
803
|
+
qontract_reconcile-0.10.1rc764.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
804
|
+
qontract_reconcile-0.10.1rc764.dist-info/RECORD,,
|
File without changes
|
@@ -0,0 +1,85 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from reconcile.external_resources.model import (
|
5
|
+
ExternalResource,
|
6
|
+
ExternalResourcesInventory,
|
7
|
+
)
|
8
|
+
from reconcile.utils.external_resource_spec import (
|
9
|
+
ExternalResourceSpec,
|
10
|
+
)
|
11
|
+
from reconcile.utils.external_resources import ResourceValueResolver
|
12
|
+
from reconcile.utils.secret_reader import SecretReaderBase
|
13
|
+
|
14
|
+
|
15
|
+
class AWSResourceFactory(ABC):
|
16
|
+
def __init__(
|
17
|
+
self, er_inventory: ExternalResourcesInventory, secrets_reader: SecretReaderBase
|
18
|
+
):
|
19
|
+
self.er_inventory = er_inventory
|
20
|
+
self.secrets_reader = secrets_reader
|
21
|
+
|
22
|
+
@abstractmethod
|
23
|
+
def resolve(self, spec: ExternalResourceSpec) -> dict[str, Any]: ...
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
def validate(self, resource: ExternalResource) -> None: ...
|
27
|
+
|
28
|
+
|
29
|
+
class AWSDefaultResourceFactory(AWSResourceFactory):
|
30
|
+
def resolve(self, spec: ExternalResourceSpec) -> dict[str, Any]:
|
31
|
+
return ResourceValueResolver(spec=spec, identifier_as_value=True).resolve()
|
32
|
+
|
33
|
+
def validate(self, resource: ExternalResource) -> None: ...
|
34
|
+
|
35
|
+
|
36
|
+
class AWSRdsFactory(AWSDefaultResourceFactory):
|
37
|
+
def _get_source_db_spec(
|
38
|
+
self, provisioner: str, identifier: str
|
39
|
+
) -> ExternalResourceSpec:
|
40
|
+
return self.er_inventory.get_inventory_spec(
|
41
|
+
"aws", provisioner, "rds", identifier
|
42
|
+
)
|
43
|
+
|
44
|
+
def _get_kms_key_spec(
|
45
|
+
self, provisioner: str, identifier: str
|
46
|
+
) -> ExternalResourceSpec:
|
47
|
+
return self.er_inventory.get_inventory_spec(
|
48
|
+
"aws", provisioner, "kms", identifier
|
49
|
+
)
|
50
|
+
|
51
|
+
def resolve(self, spec: ExternalResourceSpec) -> dict[str, Any]:
|
52
|
+
rvr = ResourceValueResolver(spec=spec, identifier_as_value=True)
|
53
|
+
data = rvr.resolve()
|
54
|
+
|
55
|
+
data["output_prefix"] = spec.output_prefix
|
56
|
+
|
57
|
+
if "parameter_group" in data:
|
58
|
+
pg_data = rvr._get_values(data["parameter_group"])
|
59
|
+
data["parameter_group"] = pg_data
|
60
|
+
if "old_parameter_group" in data:
|
61
|
+
old_pg_data = rvr._get_values(data["old_parameter_group"])
|
62
|
+
data["old_parameter_group"] = old_pg_data
|
63
|
+
if "replica_source" in data:
|
64
|
+
sourcedb_spec = self._get_source_db_spec(
|
65
|
+
spec.provisioner_name, data["replica_source"]
|
66
|
+
)
|
67
|
+
sourcedb = self.resolve(sourcedb_spec)
|
68
|
+
sourcedb_region = (
|
69
|
+
sourcedb.get("region", None)
|
70
|
+
or sourcedb_spec.provisioner["resources_default_region"]
|
71
|
+
)
|
72
|
+
data["replica_source"] = {
|
73
|
+
"identifier": sourcedb["identifier"],
|
74
|
+
"region": sourcedb_region,
|
75
|
+
}
|
76
|
+
|
77
|
+
kms_key_id: str = data.get("kms_key_id", None)
|
78
|
+
if kms_key_id and not kms_key_id.startswith("arn:"):
|
79
|
+
data["kms_key_id"] = self._get_kms_key_spec(
|
80
|
+
spec.provisioner_name, kms_key_id
|
81
|
+
).identifier
|
82
|
+
|
83
|
+
return data
|
84
|
+
|
85
|
+
def validate(self, resource: ExternalResource) -> None: ...
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from abc import (
|
2
|
+
ABC,
|
3
|
+
abstractmethod,
|
4
|
+
)
|
5
|
+
from typing import Generic, TypeVar
|
6
|
+
|
7
|
+
from reconcile.external_resources.aws import (
|
8
|
+
AWSDefaultResourceFactory,
|
9
|
+
AWSRdsFactory,
|
10
|
+
AWSResourceFactory,
|
11
|
+
)
|
12
|
+
from reconcile.external_resources.model import (
|
13
|
+
ExternalResource,
|
14
|
+
ExternalResourceKey,
|
15
|
+
ExternalResourceProvision,
|
16
|
+
ExternalResourcesInventory,
|
17
|
+
ModuleInventory,
|
18
|
+
ModuleProvisionData,
|
19
|
+
TerraformModuleProvisionData,
|
20
|
+
)
|
21
|
+
from reconcile.gql_definitions.external_resources.external_resources_settings import (
|
22
|
+
ExternalResourcesSettingsV1,
|
23
|
+
)
|
24
|
+
from reconcile.utils.external_resource_spec import (
|
25
|
+
ExternalResourceSpec,
|
26
|
+
)
|
27
|
+
from reconcile.utils.secret_reader import SecretReaderBase
|
28
|
+
|
29
|
+
T = TypeVar("T")
|
30
|
+
|
31
|
+
|
32
|
+
class ObjectFactory(Generic[T]):
|
33
|
+
def __init__(self) -> None:
|
34
|
+
self._factories: dict[str, T] = {}
|
35
|
+
|
36
|
+
def register_factory(self, id: str, t: T) -> None:
|
37
|
+
self._factories[id] = t
|
38
|
+
|
39
|
+
def get_factory(self, id: str) -> T:
|
40
|
+
return self._factories[id]
|
41
|
+
|
42
|
+
|
43
|
+
class ExternalResourceFactory(ABC):
|
44
|
+
@abstractmethod
|
45
|
+
def create_external_resource(self, spec: ExternalResourceSpec) -> ExternalResource:
|
46
|
+
pass
|
47
|
+
|
48
|
+
@abstractmethod
|
49
|
+
def validate_external_resource(self, resource: ExternalResource) -> None:
|
50
|
+
pass
|
51
|
+
|
52
|
+
|
53
|
+
class ModuleProvisionDataFactory(ABC):
|
54
|
+
@abstractmethod
|
55
|
+
def create_provision_data(self, ers: ExternalResourceSpec) -> ModuleProvisionData:
|
56
|
+
pass
|
57
|
+
|
58
|
+
|
59
|
+
class TerraformModuleProvisionDataFactory(ModuleProvisionDataFactory):
|
60
|
+
def __init__(self, settings: ExternalResourcesSettingsV1):
|
61
|
+
self.settings = settings
|
62
|
+
|
63
|
+
def create_provision_data(
|
64
|
+
self, spec: ExternalResourceSpec
|
65
|
+
) -> TerraformModuleProvisionData:
|
66
|
+
key = ExternalResourceKey.from_spec(spec)
|
67
|
+
|
68
|
+
return TerraformModuleProvisionData(
|
69
|
+
tf_state_bucket=self.settings.tf_state_bucket,
|
70
|
+
tf_state_region=self.settings.tf_state_region,
|
71
|
+
tf_state_dynamodb_table=self.settings.tf_state_dynamodb_table,
|
72
|
+
tf_state_key=key.state_path + "/terraform.tfstate",
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
def setup_aws_resource_factories(
|
77
|
+
er_inventory: ExternalResourcesInventory, secrets_reader: SecretReaderBase
|
78
|
+
) -> ObjectFactory[AWSResourceFactory]:
|
79
|
+
f = ObjectFactory[AWSResourceFactory]()
|
80
|
+
f.register_factory("rds", AWSRdsFactory(er_inventory, secrets_reader))
|
81
|
+
f.register_factory(
|
82
|
+
"default", AWSDefaultResourceFactory(er_inventory, secrets_reader)
|
83
|
+
)
|
84
|
+
return f
|
85
|
+
|
86
|
+
|
87
|
+
class AWSExternalResourceFactory(ExternalResourceFactory):
|
88
|
+
def __init__(
|
89
|
+
self,
|
90
|
+
module_inventory: ModuleInventory,
|
91
|
+
er_inventory: ExternalResourcesInventory,
|
92
|
+
secret_reader: SecretReaderBase,
|
93
|
+
provision_factories: ObjectFactory[ModuleProvisionDataFactory],
|
94
|
+
resource_factories: ObjectFactory[AWSResourceFactory],
|
95
|
+
):
|
96
|
+
self.provision_factories = provision_factories
|
97
|
+
self.resource_factories = resource_factories
|
98
|
+
self.module_inventory = module_inventory
|
99
|
+
self.er_inventory = er_inventory
|
100
|
+
self.secret_reader = secret_reader
|
101
|
+
|
102
|
+
def create_external_resource(self, spec: ExternalResourceSpec) -> ExternalResource:
|
103
|
+
f = self.resource_factories.get_factory(spec.provider)
|
104
|
+
data = f.resolve(spec)
|
105
|
+
|
106
|
+
region = data.get("region")
|
107
|
+
if region:
|
108
|
+
if region not in spec.provisioner["supported_deployment_regions"]:
|
109
|
+
raise ValueError(region)
|
110
|
+
else:
|
111
|
+
region = spec.provisioner["resources_default_region"]
|
112
|
+
data["region"] = region
|
113
|
+
|
114
|
+
module_type = self.module_inventory.get_from_spec(spec).module_type
|
115
|
+
provision_factory = self.provision_factories.get_factory(module_type)
|
116
|
+
module_provision_data = provision_factory.create_provision_data(spec)
|
117
|
+
|
118
|
+
provision = ExternalResourceProvision(
|
119
|
+
provision_provider=spec.provision_provider,
|
120
|
+
provisioner=spec.provisioner_name,
|
121
|
+
provider=spec.provider,
|
122
|
+
identifier=spec.identifier,
|
123
|
+
target_cluster=spec.cluster_name,
|
124
|
+
target_namespace=spec.namespace_name,
|
125
|
+
target_secret_name=spec.output_resource_name,
|
126
|
+
module_provision_data=module_provision_data,
|
127
|
+
)
|
128
|
+
|
129
|
+
return ExternalResource(data=data, provision=provision)
|
130
|
+
|
131
|
+
def validate_external_resource(self, resource: ExternalResource) -> None:
|
132
|
+
f = self.resource_factories.get_factory(resource.provision.provider)
|
133
|
+
f.validate(resource)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from reconcile.external_resources.manager import (
|
4
|
+
ExternalResourcesInventory,
|
5
|
+
ExternalResourcesManager,
|
6
|
+
setup_factories,
|
7
|
+
)
|
8
|
+
from reconcile.external_resources.meta import (
|
9
|
+
QONTRACT_INTEGRATION,
|
10
|
+
QONTRACT_INTEGRATION_VERSION,
|
11
|
+
)
|
12
|
+
from reconcile.external_resources.model import load_module_inventory
|
13
|
+
from reconcile.external_resources.reconciler import K8sExternalResourcesReconciler
|
14
|
+
from reconcile.external_resources.secrets_sync import (
|
15
|
+
build_incluster_secrets_reconciler,
|
16
|
+
)
|
17
|
+
from reconcile.external_resources.state import ExternalResourcesStateDynamoDB
|
18
|
+
from reconcile.typed_queries.app_interface_vault_settings import (
|
19
|
+
get_app_interface_vault_settings,
|
20
|
+
)
|
21
|
+
from reconcile.typed_queries.external_resources import (
|
22
|
+
get_modules,
|
23
|
+
get_namespaces,
|
24
|
+
get_settings,
|
25
|
+
)
|
26
|
+
from reconcile.utils.jobcontroller.controller import (
|
27
|
+
build_job_controller,
|
28
|
+
)
|
29
|
+
from reconcile.utils.oc import (
|
30
|
+
OCCli,
|
31
|
+
)
|
32
|
+
from reconcile.utils.openshift_resource import OpenshiftResource, ResourceInventory
|
33
|
+
from reconcile.utils.secret_reader import create_secret_reader
|
34
|
+
|
35
|
+
|
36
|
+
def fetch_current_state(
|
37
|
+
ri: ResourceInventory, oc: OCCli, cluster: str, namespace: str
|
38
|
+
) -> None:
|
39
|
+
for item in oc.get_items("Job", namespace=namespace):
|
40
|
+
r = OpenshiftResource(item, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION)
|
41
|
+
ri.add_current(cluster, namespace, "Job", r.name, r)
|
42
|
+
|
43
|
+
|
44
|
+
def run(
|
45
|
+
dry_run: bool,
|
46
|
+
cluster: str,
|
47
|
+
namespace: str,
|
48
|
+
dry_run_job_suffix: str,
|
49
|
+
thread_pool_size: int,
|
50
|
+
) -> None:
|
51
|
+
vault_settings = get_app_interface_vault_settings()
|
52
|
+
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
53
|
+
er_settings = get_settings()[0]
|
54
|
+
m_inventory = load_module_inventory(get_modules())
|
55
|
+
namespaces = [ns for ns in get_namespaces() if ns.external_resources]
|
56
|
+
er_inventory = ExternalResourcesInventory(namespaces)
|
57
|
+
|
58
|
+
er_mgr = ExternalResourcesManager(
|
59
|
+
thread_pool_size=thread_pool_size,
|
60
|
+
settings=er_settings,
|
61
|
+
secret_reader=secret_reader,
|
62
|
+
factories=setup_factories(
|
63
|
+
er_settings, m_inventory, er_inventory, secret_reader
|
64
|
+
),
|
65
|
+
er_inventory=er_inventory,
|
66
|
+
module_inventory=m_inventory,
|
67
|
+
state_manager=ExternalResourcesStateDynamoDB(
|
68
|
+
table_name=er_settings.state_dynamodb_table,
|
69
|
+
region_name=er_settings.state_dynamodb_region,
|
70
|
+
),
|
71
|
+
reconciler=K8sExternalResourcesReconciler(
|
72
|
+
controller=build_job_controller(
|
73
|
+
integration=QONTRACT_INTEGRATION,
|
74
|
+
integration_version=QONTRACT_INTEGRATION_VERSION,
|
75
|
+
cluster=cluster,
|
76
|
+
namespace=namespace,
|
77
|
+
secret_reader=secret_reader,
|
78
|
+
dry_run=dry_run,
|
79
|
+
),
|
80
|
+
dry_run=dry_run,
|
81
|
+
dry_run_job_suffix=dry_run_job_suffix,
|
82
|
+
),
|
83
|
+
secrets_reconciler=build_incluster_secrets_reconciler(
|
84
|
+
cluster, namespace, secret_reader, vault_path="app-sre"
|
85
|
+
),
|
86
|
+
)
|
87
|
+
|
88
|
+
if dry_run:
|
89
|
+
er_mgr.handle_dry_run_resources()
|
90
|
+
if er_mgr.errors:
|
91
|
+
logging.error("Validation Errors:")
|
92
|
+
for k, e in er_mgr.errors.items():
|
93
|
+
logging.error("ExternalResourceKey: %s, Error: %s" % (k, e))
|
94
|
+
else:
|
95
|
+
er_mgr.handle_resources()
|