qontract-reconcile 0.10.2.dev345__py3-none-any.whl → 0.10.2.dev408__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/METADATA +11 -10
- {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/RECORD +126 -120
- reconcile/aus/base.py +17 -14
- reconcile/automated_actions/config/integration.py +12 -0
- reconcile/aws_account_manager/integration.py +2 -2
- reconcile/aws_ami_cleanup/integration.py +6 -7
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
- reconcile/aws_ecr_image_pull_secrets.py +2 -2
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +7 -1
- reconcile/aws_saml_roles/integration.py +9 -3
- reconcile/change_owners/change_owners.py +1 -1
- reconcile/change_owners/diff.py +2 -4
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +33 -8
- reconcile/dashdotdb_dora.py +4 -11
- reconcile/database_access_manager.py +118 -111
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request_manager.py +9 -11
- reconcile/external_resources/factories.py +5 -12
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +5 -3
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/model.py +10 -10
- reconcile/external_resources/reconciler.py +5 -2
- reconcile/external_resources/secrets_sync.py +4 -6
- reconcile/external_resources/state.py +5 -4
- reconcile/gabi_authorized_users.py +8 -5
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_mr_sqs_consumer.py +2 -2
- reconcile/gitlab_owners.py +15 -11
- reconcile/gql_definitions/automated_actions/instance.py +41 -2
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +10 -0
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +22 -61
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +10 -0
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +10 -0
- reconcile/gql_definitions/common/aws_vpc_requests.py +10 -0
- reconcile/gql_definitions/common/clusters.py +2 -0
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +84 -1
- reconcile/gql_definitions/external_resources/external_resources_settings.py +2 -0
- reconcile/gql_definitions/fragments/aws_account_common.py +2 -0
- reconcile/gql_definitions/fragments/aws_organization.py +33 -0
- reconcile/gql_definitions/fragments/aws_vpc_request.py +2 -0
- reconcile/gql_definitions/introspection.json +3474 -1986
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +4 -0
- reconcile/gql_definitions/terraform_init/aws_accounts.py +14 -0
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +33 -1
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +10 -0
- reconcile/jenkins_worker_fleets.py +1 -0
- reconcile/jira_permissions_validator.py +236 -121
- reconcile/ocm/types.py +6 -0
- reconcile/openshift_base.py +47 -1
- reconcile/openshift_cluster_bots.py +2 -1
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_saas_deploy.py +2 -2
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_upgrade_watcher.py +3 -3
- reconcile/queries.py +131 -0
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/slack_usergroups.py +4 -3
- reconcile/sql_query.py +1 -0
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
- reconcile/templating/renderer.py +2 -1
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_init/integration.py +185 -21
- reconcile/terraform_resources.py +11 -1
- reconcile/terraform_tgw_attachments.py +7 -1
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +7 -0
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/saas_files.py +2 -2
- reconcile/utils/aggregated_list.py +4 -3
- reconcile/utils/aws_api.py +51 -20
- reconcile/utils/aws_api_typed/api.py +38 -9
- reconcile/utils/aws_api_typed/cloudformation.py +149 -0
- reconcile/utils/aws_api_typed/logs.py +73 -0
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +3 -2
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +24 -1
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/helm.py +2 -1
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/jinja2/utils.py +4 -96
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +9 -12
- reconcile/utils/jobcontroller/controller.py +1 -1
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +32 -0
- reconcile/utils/merge_request_manager/merge_request_manager.py +3 -3
- reconcile/utils/merge_request_manager/parser.py +2 -2
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/base.py +2 -2
- reconcile/utils/mr/notificator.py +2 -2
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/oc.py +113 -95
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/products.py +6 -0
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +3 -5
- reconcile/utils/openshift_resource.py +10 -5
- reconcile/utils/output.py +3 -2
- reconcile/utils/pagerduty_api.py +5 -5
- reconcile/utils/runtime/integration.py +1 -2
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/models.py +2 -1
- reconcile/utils/saasherder/saasherder.py +9 -7
- reconcile/utils/slack_api.py +24 -2
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/sqs_gateway.py +2 -1
- reconcile/utils/state.py +2 -1
- reconcile/utils/terraform_client.py +4 -3
- reconcile/utils/terrascript_aws_client.py +165 -111
- reconcile/utils/vault.py +1 -1
- reconcile/vault_replication.py +107 -42
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +25 -13
- {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/entry_points.txt +0 -0
|
@@ -16,7 +16,7 @@ from collections.abc import (
|
|
|
16
16
|
Sequence,
|
|
17
17
|
)
|
|
18
18
|
from contextlib import suppress
|
|
19
|
-
from datetime import
|
|
19
|
+
from datetime import datetime, timedelta
|
|
20
20
|
from types import TracebackType
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
@@ -37,10 +37,12 @@ from sretoolbox.utils import (
|
|
|
37
37
|
from reconcile.github_org import get_default_config
|
|
38
38
|
from reconcile.status import RunningState
|
|
39
39
|
from reconcile.utils import helm
|
|
40
|
+
from reconcile.utils.datetime_util import utc_now
|
|
40
41
|
from reconcile.utils.github_api import GithubRepositoryApi
|
|
41
42
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
42
43
|
from reconcile.utils.jenkins_api import JenkinsApi, JobBuildState
|
|
43
44
|
from reconcile.utils.jjb_client import JJB
|
|
45
|
+
from reconcile.utils.json import json_dumps
|
|
44
46
|
from reconcile.utils.oc import (
|
|
45
47
|
OCLocal,
|
|
46
48
|
StatusCodeError,
|
|
@@ -1865,7 +1867,7 @@ class SaasHerder:
|
|
|
1865
1867
|
@staticmethod
|
|
1866
1868
|
def get_target_config_hash(target_config: Any) -> str:
|
|
1867
1869
|
m = hashlib.sha256()
|
|
1868
|
-
m.update(
|
|
1870
|
+
m.update(json_dumps(target_config).encode("utf-8"))
|
|
1869
1871
|
digest = m.hexdigest()[:16]
|
|
1870
1872
|
return digest
|
|
1871
1873
|
|
|
@@ -1921,14 +1923,14 @@ class SaasHerder:
|
|
|
1921
1923
|
# before the GQL classes are introduced, the parameters attribute
|
|
1922
1924
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1923
1925
|
saas_file_parameters=(
|
|
1924
|
-
|
|
1926
|
+
json_dumps(saas_file.parameters, compact=True)
|
|
1925
1927
|
if saas_file.parameters is not None
|
|
1926
1928
|
else None
|
|
1927
1929
|
),
|
|
1928
1930
|
# before the GQL classes are introduced, the parameters attribute
|
|
1929
1931
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1930
1932
|
parameters=(
|
|
1931
|
-
|
|
1933
|
+
json_dumps(target.parameters, compact=True)
|
|
1932
1934
|
if target.parameters is not None
|
|
1933
1935
|
else None
|
|
1934
1936
|
),
|
|
@@ -1939,7 +1941,7 @@ class SaasHerder:
|
|
|
1939
1941
|
# before the GQL classes are introduced, the parameters attribute
|
|
1940
1942
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1941
1943
|
rt_parameters=(
|
|
1942
|
-
|
|
1944
|
+
json_dumps(resource_template.parameters, compact=True)
|
|
1943
1945
|
if resource_template.parameters is not None
|
|
1944
1946
|
else None
|
|
1945
1947
|
),
|
|
@@ -2024,7 +2026,7 @@ class SaasHerder:
|
|
|
2024
2026
|
if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
|
|
2025
2027
|
return True
|
|
2026
2028
|
|
|
2027
|
-
now =
|
|
2029
|
+
now = utc_now()
|
|
2028
2030
|
passed_soak_days = timedelta(days=0)
|
|
2029
2031
|
|
|
2030
2032
|
for channel in promotion.subscribe:
|
|
@@ -2126,7 +2128,7 @@ class SaasHerder:
|
|
|
2126
2128
|
if not (self.state and self._promotion_state):
|
|
2127
2129
|
raise Exception("state is not initialized")
|
|
2128
2130
|
|
|
2129
|
-
now =
|
|
2131
|
+
now = utc_now()
|
|
2130
2132
|
for promotion in self.promotions:
|
|
2131
2133
|
if promotion is None:
|
|
2132
2134
|
continue
|
reconcile/utils/slack_api.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
+
import os
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
7
8
|
Any,
|
|
@@ -26,6 +27,23 @@ if TYPE_CHECKING:
|
|
|
26
27
|
MAX_RETRIES = 5
|
|
27
28
|
TIMEOUT = 30
|
|
28
29
|
|
|
30
|
+
# Slack API base URLs for different workspace types
|
|
31
|
+
SLACK_API_BASE_URL = "https://slack.com/api/"
|
|
32
|
+
SLACK_GOV_API_BASE_URL = "https://slack-gov.com/api/"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_gov_slack_workspace() -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Determine if a workspace is a government Slack workspace.
|
|
38
|
+
|
|
39
|
+
:return: True if it's a gov-slack workspace, False otherwise
|
|
40
|
+
"""
|
|
41
|
+
# Check GOV_SLACK environment variable from OpenShift YAML configuration
|
|
42
|
+
# If not set, defaults to False (regular Slack)
|
|
43
|
+
gov_slack_env = os.getenv("GOV_SLACK", "false")
|
|
44
|
+
|
|
45
|
+
return gov_slack_env.lower() == "true"
|
|
46
|
+
|
|
29
47
|
|
|
30
48
|
class UserNotFoundError(Exception):
|
|
31
49
|
pass
|
|
@@ -165,7 +183,6 @@ class SlackApi:
|
|
|
165
183
|
api_config: SlackApiConfig | None = None,
|
|
166
184
|
init_usergroups: bool = True,
|
|
167
185
|
channel: str | None = None,
|
|
168
|
-
slack_url: str | None = None,
|
|
169
186
|
**chat_kwargs: Any,
|
|
170
187
|
) -> None:
|
|
171
188
|
"""
|
|
@@ -187,10 +204,15 @@ class SlackApi:
|
|
|
187
204
|
else:
|
|
188
205
|
self.config = SlackApiConfig()
|
|
189
206
|
|
|
207
|
+
# Determine the appropriate Slack API base URL based on GOV_SLACK environment variable
|
|
208
|
+
base_url = (
|
|
209
|
+
SLACK_GOV_API_BASE_URL if is_gov_slack_workspace() else SLACK_API_BASE_URL
|
|
210
|
+
)
|
|
211
|
+
|
|
190
212
|
self._sc = WebClient(
|
|
191
213
|
token=token,
|
|
192
214
|
timeout=self.config.timeout,
|
|
193
|
-
base_url=
|
|
215
|
+
base_url=base_url,
|
|
194
216
|
)
|
|
195
217
|
self._configure_client_retry()
|
|
196
218
|
|
reconcile/utils/sloth.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import tempfile
|
|
1
3
|
from io import StringIO
|
|
2
|
-
from typing import NotRequired, TypedDict
|
|
4
|
+
from typing import Any, NotRequired, TypedDict
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
3
7
|
|
|
4
8
|
from reconcile.utils.ruamel import create_ruamel_instance
|
|
5
9
|
|
|
@@ -21,6 +25,44 @@ class PrometheusRuleSpec(TypedDict):
|
|
|
21
25
|
groups: list[PrometheusRuleGroup]
|
|
22
26
|
|
|
23
27
|
|
|
28
|
+
class SLOParametersDict(TypedDict):
|
|
29
|
+
window: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SLO(TypedDict):
|
|
33
|
+
name: str
|
|
34
|
+
SLIType: str
|
|
35
|
+
SLISpecification: str
|
|
36
|
+
SLOTarget: float
|
|
37
|
+
SLOTargetUnit: str
|
|
38
|
+
SLOParameters: SLOParametersDict
|
|
39
|
+
SLODetails: str
|
|
40
|
+
dashboard: str
|
|
41
|
+
expr: str
|
|
42
|
+
SLIErrorQuery: NotRequired[str]
|
|
43
|
+
SLITotalQuery: NotRequired[str]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class App(TypedDict):
|
|
47
|
+
name: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SLODocument(TypedDict):
|
|
51
|
+
name: str
|
|
52
|
+
app: App
|
|
53
|
+
slos: NotRequired[list[SLO]]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class SlothGenerateError(Exception):
|
|
57
|
+
def __init__(self, msg: Any):
|
|
58
|
+
super().__init__("sloth generate failed: " + str(msg))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SlothInputError(Exception):
|
|
62
|
+
def __init__(self, msg: Any):
|
|
63
|
+
super().__init__("sloth input validation failed: " + str(msg))
|
|
64
|
+
|
|
65
|
+
|
|
24
66
|
def process_sloth_output(output_file_path: str) -> str:
|
|
25
67
|
ruamel_instance = create_ruamel_instance()
|
|
26
68
|
with open(output_file_path, encoding="utf-8") as f:
|
|
@@ -49,7 +91,134 @@ def process_sloth_output(output_file_path: str) -> str:
|
|
|
49
91
|
rule["annotations"] = annotations
|
|
50
92
|
else:
|
|
51
93
|
rule.pop("annotations", None)
|
|
52
|
-
|
|
53
94
|
with StringIO() as s:
|
|
54
95
|
ruamel_instance.dump(data, s)
|
|
55
96
|
return s.getvalue()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_sloth(spec: dict[str, Any]) -> str:
|
|
100
|
+
with (
|
|
101
|
+
tempfile.NamedTemporaryFile(
|
|
102
|
+
encoding="utf-8", mode="w", suffix=".yml"
|
|
103
|
+
) as input_file,
|
|
104
|
+
tempfile.NamedTemporaryFile(
|
|
105
|
+
encoding="utf-8", mode="w", suffix=".yml"
|
|
106
|
+
) as output_file,
|
|
107
|
+
):
|
|
108
|
+
yaml.dump(spec, input_file, allow_unicode=True)
|
|
109
|
+
cmd = ["sloth", "generate", "-i", input_file.name, "-o", output_file.name]
|
|
110
|
+
try:
|
|
111
|
+
subprocess.run(cmd, capture_output=True, check=True, text=True)
|
|
112
|
+
except subprocess.CalledProcessError as e:
|
|
113
|
+
error_msg = f"{e}"
|
|
114
|
+
if e.stdout:
|
|
115
|
+
error_msg += f"\nstdout: {e.stdout}"
|
|
116
|
+
if e.stderr:
|
|
117
|
+
error_msg += f"\nstderr: {e.stderr}"
|
|
118
|
+
raise SlothGenerateError(error_msg) from e
|
|
119
|
+
return process_sloth_output(output_file.name)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_slo_target(slo: SLO) -> float:
|
|
123
|
+
"""
|
|
124
|
+
Ensure SLO target unit aligns with format expected by sloth for 'Objective' attribute
|
|
125
|
+
https://pkg.go.dev/github.com/slok/sloth/pkg/prometheus/api/v1#section-readme
|
|
126
|
+
"""
|
|
127
|
+
val = float(slo["SLOTarget"])
|
|
128
|
+
return val * (100.0 if slo.get("SLOTargetUnit") == "percent_0_1" else 1.0)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def generate_sloth_rules(
|
|
132
|
+
slo_document: SLODocument,
|
|
133
|
+
version: str = "prometheus/v1",
|
|
134
|
+
) -> str:
|
|
135
|
+
"""Generate Prometheus rules for an slo_document_v1 using sloth
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
slo_document query:
|
|
139
|
+
{
|
|
140
|
+
slo_docs: slo_document_v1(filter: {name: "foo"}) {
|
|
141
|
+
name
|
|
142
|
+
app {
|
|
143
|
+
name
|
|
144
|
+
}
|
|
145
|
+
slos {
|
|
146
|
+
name
|
|
147
|
+
SLIType
|
|
148
|
+
SLOTargetUnit
|
|
149
|
+
SLOParameters {
|
|
150
|
+
window
|
|
151
|
+
}
|
|
152
|
+
expr
|
|
153
|
+
SLOTarget
|
|
154
|
+
SLIErrorQuery
|
|
155
|
+
SLITotalQuery
|
|
156
|
+
SLODetails
|
|
157
|
+
dashboard
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
version: Spec version (default: "prometheus/v1")
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Generated Prometheus rules as YAML string
|
|
165
|
+
"""
|
|
166
|
+
if not slo_document.get("slos"):
|
|
167
|
+
raise SlothInputError("SLO document has no SLOs defined")
|
|
168
|
+
|
|
169
|
+
service = slo_document["app"]["name"]
|
|
170
|
+
# only process SLOs that have both error and total queries defined
|
|
171
|
+
slo_input = [
|
|
172
|
+
{
|
|
173
|
+
"name": slo["name"],
|
|
174
|
+
"objective": get_slo_target(slo),
|
|
175
|
+
"description": f"{slo['name']} SLO for {service}",
|
|
176
|
+
"sli": {
|
|
177
|
+
"events": {
|
|
178
|
+
"error_query": slo["SLIErrorQuery"].replace(
|
|
179
|
+
"{{window}}", "{{.window}}"
|
|
180
|
+
),
|
|
181
|
+
"total_query": slo["SLITotalQuery"].replace(
|
|
182
|
+
"{{window}}", "{{.window}}"
|
|
183
|
+
),
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"alerting": {
|
|
187
|
+
"name": f"{service.title()}{slo['name'].title()}",
|
|
188
|
+
"annotations": {
|
|
189
|
+
"summary": f"High error rate on {service} {slo['name']}",
|
|
190
|
+
"message": f"High error rate on {service} {slo['name']}",
|
|
191
|
+
"runbook": slo["SLODetails"],
|
|
192
|
+
"dashboard": slo["dashboard"],
|
|
193
|
+
},
|
|
194
|
+
"page_alert": {
|
|
195
|
+
"labels": {
|
|
196
|
+
"severity": "critical",
|
|
197
|
+
"service": service,
|
|
198
|
+
"slo": slo["name"],
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"ticket_alert": {
|
|
202
|
+
"labels": {
|
|
203
|
+
"severity": "high",
|
|
204
|
+
"service": service,
|
|
205
|
+
"slo": slo["name"],
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
for slo in slo_document["slos"]
|
|
211
|
+
if slo.get("SLIErrorQuery") and slo.get("SLITotalQuery")
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
if not slo_input:
|
|
215
|
+
raise SlothInputError(
|
|
216
|
+
"No SLOs found with both SLIErrorQuery and SLITotalQuery defined"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
spec = {
|
|
220
|
+
"version": version,
|
|
221
|
+
"service": service,
|
|
222
|
+
"slos": slo_input,
|
|
223
|
+
}
|
|
224
|
+
return run_sloth(spec)
|
reconcile/utils/sqs_gateway.py
CHANGED
|
@@ -4,6 +4,7 @@ from collections.abc import Iterable, Mapping
|
|
|
4
4
|
from typing import Any, Self
|
|
5
5
|
|
|
6
6
|
from reconcile.utils.aws_api import AWSApi
|
|
7
|
+
from reconcile.utils.json import json_dumps
|
|
7
8
|
from reconcile.utils.secret_reader import SecretReader
|
|
8
9
|
|
|
9
10
|
|
|
@@ -54,7 +55,7 @@ class SQSGateway:
|
|
|
54
55
|
return queue_account_name[0]
|
|
55
56
|
|
|
56
57
|
def send_message(self, body: Mapping[str, Any]) -> None:
|
|
57
|
-
self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=
|
|
58
|
+
self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=json_dumps(body))
|
|
58
59
|
|
|
59
60
|
def receive_messages(
|
|
60
61
|
self,
|
reconcile/utils/state.py
CHANGED
|
@@ -28,6 +28,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
|
|
|
28
28
|
)
|
|
29
29
|
from reconcile.typed_queries.get_state_aws_account import get_state_aws_account
|
|
30
30
|
from reconcile.utils.aws_api import aws_config_file_path
|
|
31
|
+
from reconcile.utils.json import json_dumps
|
|
31
32
|
from reconcile.utils.secret_reader import (
|
|
32
33
|
SecretReaderBase,
|
|
33
34
|
create_secret_reader,
|
|
@@ -355,7 +356,7 @@ class State:
|
|
|
355
356
|
self.client.put_object(
|
|
356
357
|
Bucket=self.bucket,
|
|
357
358
|
Key=f"{self.state_path}/{key}",
|
|
358
|
-
Body=
|
|
359
|
+
Body=json_dumps(value),
|
|
359
360
|
Metadata=metadata or {},
|
|
360
361
|
)
|
|
361
362
|
|
|
@@ -36,6 +36,7 @@ from reconcile.typed_queries.app_interface_custom_messages import (
|
|
|
36
36
|
)
|
|
37
37
|
from reconcile.utils.aws_api import AWSApi
|
|
38
38
|
from reconcile.utils.aws_helper import get_region_from_availability_zone
|
|
39
|
+
from reconcile.utils.datetime_util import ensure_utc, utc_now
|
|
39
40
|
from reconcile.utils.external_resource_spec import (
|
|
40
41
|
ExternalResourceSpec,
|
|
41
42
|
ExternalResourceSpecInventory,
|
|
@@ -419,11 +420,11 @@ class TerraformClient:
|
|
|
419
420
|
deletion_approvals = account.get("deletionApprovals")
|
|
420
421
|
if not deletion_approvals:
|
|
421
422
|
return False
|
|
422
|
-
now =
|
|
423
|
+
now = utc_now()
|
|
423
424
|
for da in deletion_approvals:
|
|
424
425
|
try:
|
|
425
|
-
expiration =
|
|
426
|
-
da["expiration"], DATE_FORMAT
|
|
426
|
+
expiration = ensure_utc(
|
|
427
|
+
datetime.strptime(da["expiration"], DATE_FORMAT) # noqa: DTZ007
|
|
427
428
|
) + timedelta(days=1)
|
|
428
429
|
except ValueError:
|
|
429
430
|
raise DeletionApprovalExpirationValueError(
|