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
|
@@ -70,10 +70,13 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
|
|
|
70
70
|
dry_run_suffix: str = ""
|
|
71
71
|
|
|
72
72
|
def name_prefix(self) -> str:
|
|
73
|
+
identifier = (
|
|
74
|
+
f"{self.reconciliation.key.provider}-{self.reconciliation.key.identifier}"
|
|
75
|
+
)
|
|
73
76
|
if self.is_dry_run:
|
|
74
|
-
return f"er-dry-run-mr-{self.dry_run_suffix}"
|
|
77
|
+
return f"er-dry-run-mr-{self.dry_run_suffix}-{identifier}"
|
|
75
78
|
else:
|
|
76
|
-
return "er"
|
|
79
|
+
return f"er-{identifier}"
|
|
77
80
|
|
|
78
81
|
def unit_of_work_identity(self) -> Any:
|
|
79
82
|
return self.reconciliation.key
|
|
@@ -3,7 +3,6 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
from abc import abstractmethod
|
|
5
5
|
from collections.abc import Iterable, Mapping
|
|
6
|
-
from datetime import UTC, datetime
|
|
7
6
|
from hashlib import shake_128
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
@@ -18,17 +17,18 @@ from reconcile.external_resources.meta import (
|
|
|
18
17
|
SECRET_ANN_PROVISION_PROVIDER,
|
|
19
18
|
SECRET_ANN_PROVISIONER,
|
|
20
19
|
SECRET_UPDATED_AT,
|
|
21
|
-
SECRET_UPDATED_AT_TIMEFORMAT,
|
|
22
20
|
)
|
|
23
21
|
from reconcile.external_resources.model import (
|
|
24
22
|
ExternalResourceKey,
|
|
25
23
|
)
|
|
26
24
|
from reconcile.openshift_base import ApplyOptions, apply_action
|
|
27
25
|
from reconcile.typed_queries.clusters_minimal import get_clusters_minimal
|
|
26
|
+
from reconcile.utils.datetime_util import to_utc_seconds_iso_format, utc_now
|
|
28
27
|
from reconcile.utils.differ import diff_mappings
|
|
29
28
|
from reconcile.utils.external_resource_spec import (
|
|
30
29
|
ExternalResourceSpec,
|
|
31
30
|
)
|
|
31
|
+
from reconcile.utils.json import json_dumps
|
|
32
32
|
from reconcile.utils.oc import (
|
|
33
33
|
OCCli,
|
|
34
34
|
)
|
|
@@ -154,7 +154,7 @@ class SecretsReconciler:
|
|
|
154
154
|
annotations[SECRET_ANN_PROVIDER] = spec.provider
|
|
155
155
|
annotations[SECRET_ANN_IDENTIFIER] = spec.identifier
|
|
156
156
|
annotations[SECRET_UPDATED_AT] = spec.metadata[SECRET_UPDATED_AT]
|
|
157
|
-
spec.resource["annotations"] =
|
|
157
|
+
spec.resource["annotations"] = json_dumps(annotations)
|
|
158
158
|
|
|
159
159
|
def _specs_with_secret(
|
|
160
160
|
self,
|
|
@@ -351,9 +351,7 @@ class InClusterSecretsReconciler(SecretsReconciler):
|
|
|
351
351
|
secret_name = secret["metadata"]["name"]
|
|
352
352
|
spec = secrets_map[secret_name]
|
|
353
353
|
spec.secret = self.output_secrets_formatter.format(secret["data"])
|
|
354
|
-
spec.metadata[SECRET_UPDATED_AT] =
|
|
355
|
-
SECRET_UPDATED_AT_TIMEFORMAT
|
|
356
|
-
)
|
|
354
|
+
spec.metadata[SECRET_UPDATED_AT] = to_utc_seconds_iso_format(utc_now())
|
|
357
355
|
|
|
358
356
|
def _delete_source_secret(self, spec: ExternalResourceSpec) -> None:
|
|
359
357
|
secret_name = self._get_spec_outputs_secret_name(spec)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from collections.abc import Mapping
|
|
3
|
-
from datetime import
|
|
3
|
+
from datetime import datetime
|
|
4
4
|
from enum import StrEnum
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
@@ -16,6 +16,7 @@ from reconcile.external_resources.model import (
|
|
|
16
16
|
ResourceStatus,
|
|
17
17
|
)
|
|
18
18
|
from reconcile.utils.aws_api_typed.api import AWSApi
|
|
19
|
+
from reconcile.utils.datetime_util import to_utc_microseconds_iso_format, utc_now
|
|
19
20
|
|
|
20
21
|
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
|
21
22
|
|
|
@@ -41,7 +42,7 @@ class ExternalResourceState(BaseModel):
|
|
|
41
42
|
self, reconciliation_status: ReconciliationStatus
|
|
42
43
|
) -> None:
|
|
43
44
|
if self.reconciliation_needs_state_update(reconciliation_status):
|
|
44
|
-
self.ts =
|
|
45
|
+
self.ts = utc_now()
|
|
45
46
|
self.resource_status = reconciliation_status.resource_status
|
|
46
47
|
|
|
47
48
|
def reconciliation_needs_state_update(
|
|
@@ -170,7 +171,7 @@ class DynamoDBStateAdapter:
|
|
|
170
171
|
def serialize(self, state: ExternalResourceState) -> dict[str, Any]:
|
|
171
172
|
return {
|
|
172
173
|
self.ER_KEY_HASH: {"S": state.key.hash()},
|
|
173
|
-
self.TIMESTAMP: {"S": state.ts
|
|
174
|
+
self.TIMESTAMP: {"S": to_utc_microseconds_iso_format(state.ts)},
|
|
174
175
|
self.RESOURCE_STATUS: {"S": state.resource_status.value},
|
|
175
176
|
self.ER_KEY: {
|
|
176
177
|
"M": {
|
|
@@ -271,7 +272,7 @@ class ExternalResourcesStateDynamoDB:
|
|
|
271
272
|
else:
|
|
272
273
|
return ExternalResourceState(
|
|
273
274
|
key=key,
|
|
274
|
-
ts=
|
|
275
|
+
ts=utc_now(),
|
|
275
276
|
resource_status=ResourceStatus.NOT_EXISTS,
|
|
276
277
|
reconciliation=Reconciliation(key=key),
|
|
277
278
|
reconciliation_errors=0,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import sys
|
|
4
3
|
from collections.abc import (
|
|
@@ -17,9 +16,11 @@ from reconcile import queries
|
|
|
17
16
|
from reconcile.status import ExitCodes
|
|
18
17
|
from reconcile.utils.aggregated_list import RunnerError
|
|
19
18
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
19
|
+
from reconcile.utils.datetime_util import ensure_utc, utc_now
|
|
20
20
|
from reconcile.utils.defer import defer
|
|
21
21
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
|
22
22
|
from reconcile.utils.external_resources import get_external_resource_specs
|
|
23
|
+
from reconcile.utils.json import json_dumps
|
|
23
24
|
from reconcile.utils.openshift_resource import (
|
|
24
25
|
OpenshiftResource,
|
|
25
26
|
ResourceInventory,
|
|
@@ -39,12 +40,12 @@ def construct_gabi_oc_resource(
|
|
|
39
40
|
"kind": "ConfigMap",
|
|
40
41
|
"metadata": {"name": name, "annotations": {"qontract.recycle": "true"}},
|
|
41
42
|
"data": {
|
|
42
|
-
"config.json":
|
|
43
|
+
"config.json": json_dumps(
|
|
43
44
|
{
|
|
44
45
|
"expiration": str(expiration_date),
|
|
45
46
|
"users": users,
|
|
46
47
|
},
|
|
47
|
-
|
|
48
|
+
compact=True,
|
|
48
49
|
),
|
|
49
50
|
},
|
|
50
51
|
}
|
|
@@ -65,8 +66,10 @@ def fetch_desired_state(
|
|
|
65
66
|
gabi_instances: Iterable[Mapping], ri: ResourceInventory
|
|
66
67
|
) -> None:
|
|
67
68
|
for g in gabi_instances:
|
|
68
|
-
expiration_date =
|
|
69
|
-
|
|
69
|
+
expiration_date = ensure_utc(
|
|
70
|
+
datetime.strptime(g["expirationDate"], "%Y-%m-%d") # noqa: DTZ007
|
|
71
|
+
).date()
|
|
72
|
+
if (expiration_date - utc_now().date()).days > EXPIRATION_DAYS_MAX:
|
|
70
73
|
raise RunnerError(
|
|
71
74
|
f"The maximum expiration date of {g['name']} shall not "
|
|
72
75
|
f"exceed {EXPIRATION_DAYS_MAX} days from today"
|
reconcile/gitlab_housekeeping.py
CHANGED
|
@@ -6,7 +6,6 @@ from collections.abc import (
|
|
|
6
6
|
from contextlib import suppress
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from datetime import (
|
|
9
|
-
UTC,
|
|
10
9
|
datetime,
|
|
11
10
|
timedelta,
|
|
12
11
|
)
|
|
@@ -30,6 +29,7 @@ from sretoolbox.utils import retry
|
|
|
30
29
|
|
|
31
30
|
from reconcile import queries
|
|
32
31
|
from reconcile.change_owners.change_types import ChangeTypePriority
|
|
32
|
+
from reconcile.utils.datetime_util import ensure_utc, from_utc_iso_format, utc_now
|
|
33
33
|
from reconcile.utils.gitlab_api import (
|
|
34
34
|
GitLabApi,
|
|
35
35
|
MRState,
|
|
@@ -72,7 +72,6 @@ HOLD_LABELS = [
|
|
|
72
72
|
]
|
|
73
73
|
|
|
74
74
|
QONTRACT_INTEGRATION = "gitlab-housekeeping"
|
|
75
|
-
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
76
75
|
EXPIRATION_DATE_FORMAT = "%Y-%m-%d"
|
|
77
76
|
SQUASH_OPTION_ALWAYS = "always"
|
|
78
77
|
|
|
@@ -128,9 +127,7 @@ def _calculate_time_since_approval(approved_at: str) -> float:
|
|
|
128
127
|
Returns the number of minutes since a MR has been approved.
|
|
129
128
|
:param approved_at: the datetime the MR was approved in format %Y-%m-%dT%H:%M:%S.%fZ
|
|
130
129
|
"""
|
|
131
|
-
time_since_approval =
|
|
132
|
-
approved_at, DATE_FORMAT
|
|
133
|
-
)
|
|
130
|
+
time_since_approval = utc_now() - from_utc_iso_format(approved_at)
|
|
134
131
|
return time_since_approval.total_seconds() / 60
|
|
135
132
|
|
|
136
133
|
|
|
@@ -138,7 +135,7 @@ def get_timed_out_pipelines(
|
|
|
138
135
|
pipelines: list[ProjectMergeRequestPipeline],
|
|
139
136
|
pipeline_timeout: int = 60,
|
|
140
137
|
) -> list[ProjectMergeRequestPipeline]:
|
|
141
|
-
now =
|
|
138
|
+
now = utc_now()
|
|
142
139
|
|
|
143
140
|
pending_pipelines = [
|
|
144
141
|
p
|
|
@@ -152,7 +149,7 @@ def get_timed_out_pipelines(
|
|
|
152
149
|
timed_out_pipelines = []
|
|
153
150
|
|
|
154
151
|
for p in pending_pipelines:
|
|
155
|
-
update_time =
|
|
152
|
+
update_time = from_utc_iso_format(p.updated_at)
|
|
156
153
|
|
|
157
154
|
elapsed = (now - update_time).total_seconds()
|
|
158
155
|
|
|
@@ -279,7 +276,7 @@ def handle_stale_items(
|
|
|
279
276
|
) -> None:
|
|
280
277
|
LABEL = "stale" # noqa: N806
|
|
281
278
|
|
|
282
|
-
now =
|
|
279
|
+
now = utc_now()
|
|
283
280
|
for item in items:
|
|
284
281
|
if AUTO_MERGE in item.labels:
|
|
285
282
|
if item.merge_status == MRStatus.UNCHECKED:
|
|
@@ -287,7 +284,7 @@ def handle_stale_items(
|
|
|
287
284
|
item = gl.get_merge_request(item.iid)
|
|
288
285
|
if item.merge_status == MRStatus.CANNOT_BE_MERGED:
|
|
289
286
|
close_item(dry_run, gl, enable_closing, item_type, item)
|
|
290
|
-
update_date =
|
|
287
|
+
update_date = from_utc_iso_format(item.updated_at)
|
|
291
288
|
|
|
292
289
|
# if item is over days_interval
|
|
293
290
|
current_interval = now.date() - update_date.date()
|
|
@@ -315,10 +312,9 @@ def handle_stale_items(
|
|
|
315
312
|
if not cancel_notes:
|
|
316
313
|
continue
|
|
317
314
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
latest_cancel_note_date = max(d for d in cancel_notes_dates)
|
|
315
|
+
latest_cancel_note_date = max(
|
|
316
|
+
from_utc_iso_format(note.updated_at) for note in cancel_notes
|
|
317
|
+
)
|
|
322
318
|
# if the latest cancel note is under
|
|
323
319
|
# days_interval - remove 'stale' label
|
|
324
320
|
current_interval = now.date() - latest_cancel_note_date.date()
|
|
@@ -653,8 +649,10 @@ def publish_access_token_expiration_metrics(gl: GitLabApi) -> None:
|
|
|
653
649
|
|
|
654
650
|
for pat in pats:
|
|
655
651
|
if pat.active:
|
|
656
|
-
expiration_date =
|
|
657
|
-
|
|
652
|
+
expiration_date = ensure_utc(
|
|
653
|
+
datetime.strptime(pat.expires_at, EXPIRATION_DATE_FORMAT) # noqa: DTZ007
|
|
654
|
+
)
|
|
655
|
+
days_until_expiration = expiration_date.date() - utc_now().date()
|
|
658
656
|
gitlab_token_expiration.labels(pat.name).set(days_until_expiration.days)
|
|
659
657
|
else:
|
|
660
658
|
with suppress(KeyError, ValueError):
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
SQS Consumer to create Gitlab merge requests.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
import logging
|
|
7
6
|
import sys
|
|
8
7
|
from collections.abc import Callable
|
|
@@ -11,6 +10,7 @@ from reconcile import queries
|
|
|
11
10
|
from reconcile.utils import mr
|
|
12
11
|
from reconcile.utils.defer import defer
|
|
13
12
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
13
|
+
from reconcile.utils.json import json_dumps
|
|
14
14
|
from reconcile.utils.secret_reader import SecretReader
|
|
15
15
|
from reconcile.utils.sqs_gateway import SQSGateway
|
|
16
16
|
|
|
@@ -51,7 +51,7 @@ def run(dry_run: str, gitlab_project_id: str, defer: Callable | None = None) ->
|
|
|
51
51
|
for m in messages:
|
|
52
52
|
receipt_handle, body = m[0], m[1]
|
|
53
53
|
logging.info(
|
|
54
|
-
"received message %s with body %s", receipt_handle[:6],
|
|
54
|
+
"received message %s with body %s", receipt_handle[:6], json_dumps(body)
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
if not dry_run:
|
reconcile/gitlab_owners.py
CHANGED
|
@@ -2,12 +2,12 @@ import logging
|
|
|
2
2
|
from collections.abc import Callable, Mapping
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from dateutil import parser as dateparser
|
|
6
5
|
from gitlab.v4.objects import ProjectMergeRequest
|
|
7
6
|
from sretoolbox.utils import threaded
|
|
8
7
|
|
|
9
8
|
from reconcile import queries
|
|
10
9
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
10
|
+
from reconcile.utils.datetime_util import from_utc_iso_format
|
|
11
11
|
from reconcile.utils.defer import defer
|
|
12
12
|
from reconcile.utils.gitlab_api import (
|
|
13
13
|
GitLabApi,
|
|
@@ -49,12 +49,14 @@ class MRApproval:
|
|
|
49
49
|
self.dry_run = dry_run
|
|
50
50
|
self.persistent_lgtm = persistent_lgtm
|
|
51
51
|
|
|
52
|
-
# Get the date of the most recent commit (top commit) in the MR
|
|
53
|
-
self.top_commit_created_at =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
# Get the date of the most recent commit (top commit) in the MR
|
|
53
|
+
self.top_commit_created_at = next(
|
|
54
|
+
(
|
|
55
|
+
from_utc_iso_format(commit.created_at)
|
|
56
|
+
for commit in merge_request.commits()
|
|
57
|
+
),
|
|
58
|
+
None,
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
def get_change_owners_map(self) -> dict[str, dict[str, list[str]]]:
|
|
60
62
|
"""
|
|
@@ -95,9 +97,10 @@ class MRApproval:
|
|
|
95
97
|
|
|
96
98
|
# Only interested in comments created after the top commit
|
|
97
99
|
# creation time
|
|
98
|
-
comment_created_at =
|
|
100
|
+
comment_created_at = from_utc_iso_format(comment.created_at)
|
|
99
101
|
if (
|
|
100
|
-
|
|
102
|
+
self.top_commit_created_at is not None
|
|
103
|
+
and comment_created_at < self.top_commit_created_at
|
|
101
104
|
and not self.persistent_lgtm
|
|
102
105
|
):
|
|
103
106
|
continue
|
|
@@ -185,9 +188,10 @@ class MRApproval:
|
|
|
185
188
|
# If the comment was created before the last commit,
|
|
186
189
|
# it means we had a push after the comment. In this case,
|
|
187
190
|
# we delete the comment and move on.
|
|
188
|
-
comment_created_at =
|
|
191
|
+
comment_created_at = from_utc_iso_format(comment.created_at)
|
|
189
192
|
if (
|
|
190
|
-
|
|
193
|
+
self.top_commit_created_at is not None
|
|
194
|
+
and comment_created_at < self.top_commit_created_at
|
|
191
195
|
and comment.note is not None
|
|
192
196
|
):
|
|
193
197
|
# Deleting stale comments
|
|
@@ -132,6 +132,21 @@ query AutomatedActionsInstances {
|
|
|
132
132
|
identifier
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
+
... on AutomatedActionOpenshiftTriggerCronjob_v1 {
|
|
136
|
+
openshift_trigger_cronjob_arguments: arguments {
|
|
137
|
+
namespace {
|
|
138
|
+
name
|
|
139
|
+
delete
|
|
140
|
+
cluster {
|
|
141
|
+
name
|
|
142
|
+
disable {
|
|
143
|
+
integrations
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
cronjob
|
|
148
|
+
}
|
|
149
|
+
}
|
|
135
150
|
... on AutomatedActionOpenshiftWorkloadDelete_v1 {
|
|
136
151
|
openshift_workload_delete_arguments: arguments {
|
|
137
152
|
namespace {
|
|
@@ -297,11 +312,35 @@ class DisableClusterAutomationsV1(ConfiguredBaseModel):
|
|
|
297
312
|
integrations: Optional[list[str]] = Field(..., alias="integrations")
|
|
298
313
|
|
|
299
314
|
|
|
300
|
-
class
|
|
315
|
+
class AutomatedActionOpenshiftTriggerCronjobArgumentV1_NamespaceV1_ClusterV1(ConfiguredBaseModel):
|
|
301
316
|
name: str = Field(..., alias="name")
|
|
302
317
|
disable: Optional[DisableClusterAutomationsV1] = Field(..., alias="disable")
|
|
303
318
|
|
|
304
319
|
|
|
320
|
+
class AutomatedActionOpenshiftTriggerCronjobArgumentV1_NamespaceV1(ConfiguredBaseModel):
|
|
321
|
+
name: str = Field(..., alias="name")
|
|
322
|
+
delete: Optional[bool] = Field(..., alias="delete")
|
|
323
|
+
cluster: AutomatedActionOpenshiftTriggerCronjobArgumentV1_NamespaceV1_ClusterV1 = Field(..., alias="cluster")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class AutomatedActionOpenshiftTriggerCronjobArgumentV1(ConfiguredBaseModel):
|
|
327
|
+
namespace: AutomatedActionOpenshiftTriggerCronjobArgumentV1_NamespaceV1 = Field(..., alias="namespace")
|
|
328
|
+
cronjob: str = Field(..., alias="cronjob")
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class AutomatedActionOpenshiftTriggerCronjobV1(AutomatedActionV1):
|
|
332
|
+
openshift_trigger_cronjob_arguments: list[AutomatedActionOpenshiftTriggerCronjobArgumentV1] = Field(..., alias="openshift_trigger_cronjob_arguments")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class AutomatedActionOpenshiftWorkloadDeleteArgumentV1_NamespaceV1_ClusterV1_DisableClusterAutomationsV1(ConfiguredBaseModel):
|
|
336
|
+
integrations: Optional[list[str]] = Field(..., alias="integrations")
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class AutomatedActionOpenshiftWorkloadDeleteArgumentV1_NamespaceV1_ClusterV1(ConfiguredBaseModel):
|
|
340
|
+
name: str = Field(..., alias="name")
|
|
341
|
+
disable: Optional[AutomatedActionOpenshiftWorkloadDeleteArgumentV1_NamespaceV1_ClusterV1_DisableClusterAutomationsV1] = Field(..., alias="disable")
|
|
342
|
+
|
|
343
|
+
|
|
305
344
|
class AutomatedActionOpenshiftWorkloadDeleteArgumentV1_NamespaceV1(ConfiguredBaseModel):
|
|
306
345
|
name: str = Field(..., alias="name")
|
|
307
346
|
delete: Optional[bool] = Field(..., alias="delete")
|
|
@@ -347,7 +386,7 @@ class AutomatedActionOpenshiftWorkloadRestartV1(AutomatedActionV1):
|
|
|
347
386
|
class AutomatedActionsInstanceV1(ConfiguredBaseModel):
|
|
348
387
|
name: str = Field(..., alias="name")
|
|
349
388
|
deployment: NamespaceV1 = Field(..., alias="deployment")
|
|
350
|
-
actions: Optional[list[Union[AutomatedActionActionListV1, AutomatedActionExternalResourceFlushElastiCacheV1, AutomatedActionExternalResourceRdsRebootV1, AutomatedActionExternalResourceRdsSnapshotV1, AutomatedActionOpenshiftWorkloadDeleteV1, AutomatedActionOpenshiftWorkloadRestartV1, AutomatedActionV1]]] = Field(..., alias="actions")
|
|
389
|
+
actions: Optional[list[Union[AutomatedActionActionListV1, AutomatedActionExternalResourceFlushElastiCacheV1, AutomatedActionExternalResourceRdsRebootV1, AutomatedActionExternalResourceRdsSnapshotV1, AutomatedActionOpenshiftTriggerCronjobV1, AutomatedActionOpenshiftWorkloadDeleteV1, AutomatedActionOpenshiftWorkloadRestartV1, AutomatedActionV1]]] = Field(..., alias="actions")
|
|
351
390
|
|
|
352
391
|
|
|
353
392
|
class AutomatedActionsInstancesQueryData(ConfiguredBaseModel):
|
|
@@ -51,6 +51,16 @@ fragment AWSAccountCommon on AWSAccount_v1 {
|
|
|
51
51
|
deleteKeys
|
|
52
52
|
premiumSupport
|
|
53
53
|
partition
|
|
54
|
+
organization {
|
|
55
|
+
...AWSOrganization
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fragment AWSOrganization on AWSOrganization_v1 {
|
|
60
|
+
payerAccount {
|
|
61
|
+
organizationAccountTags
|
|
62
|
+
}
|
|
63
|
+
tags
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
fragment TerraformState on TerraformStateAWS_v1 {
|
|
@@ -17,43 +17,38 @@ from pydantic import ( # noqa: F401 # pylint: disable=W0611
|
|
|
17
17
|
Json,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
from reconcile.gql_definitions.fragments.aws_organization import AWSOrganization
|
|
21
|
+
from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
DEFINITION = """
|
|
25
|
+
fragment AWSOrganization on AWSOrganization_v1 {
|
|
26
|
+
payerAccount {
|
|
27
|
+
organizationAccountTags
|
|
28
|
+
}
|
|
29
|
+
tags
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fragment VaultSecret on VaultSecret_v1 {
|
|
33
|
+
path
|
|
34
|
+
field
|
|
35
|
+
version
|
|
36
|
+
format
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
query AWSAccountsCloudwatchLogRetentionCleanup {
|
|
23
40
|
accounts: awsaccounts_v1 {
|
|
24
|
-
path
|
|
25
41
|
name
|
|
26
|
-
uid
|
|
27
|
-
terraformUsername
|
|
28
|
-
consoleUrl
|
|
29
42
|
resourcesDefaultRegion
|
|
30
|
-
supportedDeploymentRegions
|
|
31
|
-
providerVersion
|
|
32
|
-
accountOwners {
|
|
33
|
-
name
|
|
34
|
-
email
|
|
35
|
-
}
|
|
36
43
|
automationToken {
|
|
37
|
-
|
|
38
|
-
field
|
|
39
|
-
version
|
|
40
|
-
format
|
|
41
|
-
}
|
|
42
|
-
enableDeletion
|
|
43
|
-
deletionApprovals {
|
|
44
|
-
type
|
|
45
|
-
name
|
|
46
|
-
expiration
|
|
44
|
+
...VaultSecret
|
|
47
45
|
}
|
|
48
46
|
disable {
|
|
49
47
|
integrations
|
|
50
48
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
ecrs {
|
|
54
|
-
region
|
|
49
|
+
organization {
|
|
50
|
+
...AWSOrganization
|
|
55
51
|
}
|
|
56
|
-
partition
|
|
57
52
|
cleanup {
|
|
58
53
|
provider
|
|
59
54
|
... on AWSAccountCleanupOptionCloudWatch_v1 {
|
|
@@ -74,32 +69,10 @@ class ConfiguredBaseModel(BaseModel):
|
|
|
74
69
|
extra=Extra.forbid
|
|
75
70
|
|
|
76
71
|
|
|
77
|
-
class OwnerV1(ConfiguredBaseModel):
|
|
78
|
-
name: str = Field(..., alias="name")
|
|
79
|
-
email: str = Field(..., alias="email")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class VaultSecretV1(ConfiguredBaseModel):
|
|
83
|
-
path: str = Field(..., alias="path")
|
|
84
|
-
field: str = Field(..., alias="field")
|
|
85
|
-
version: Optional[int] = Field(..., alias="version")
|
|
86
|
-
q_format: Optional[str] = Field(..., alias="format")
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class DeletionApprovalV1(ConfiguredBaseModel):
|
|
90
|
-
q_type: str = Field(..., alias="type")
|
|
91
|
-
name: str = Field(..., alias="name")
|
|
92
|
-
expiration: str = Field(..., alias="expiration")
|
|
93
|
-
|
|
94
|
-
|
|
95
72
|
class DisableClusterAutomationsV1(ConfiguredBaseModel):
|
|
96
73
|
integrations: Optional[list[str]] = Field(..., alias="integrations")
|
|
97
74
|
|
|
98
75
|
|
|
99
|
-
class AWSECRV1(ConfiguredBaseModel):
|
|
100
|
-
region: str = Field(..., alias="region")
|
|
101
|
-
|
|
102
|
-
|
|
103
76
|
class AWSAccountCleanupOptionV1(ConfiguredBaseModel):
|
|
104
77
|
provider: str = Field(..., alias="provider")
|
|
105
78
|
|
|
@@ -112,23 +85,11 @@ class AWSAccountCleanupOptionCloudWatchV1(AWSAccountCleanupOptionV1):
|
|
|
112
85
|
|
|
113
86
|
|
|
114
87
|
class AWSAccountV1(ConfiguredBaseModel):
|
|
115
|
-
path: str = Field(..., alias="path")
|
|
116
88
|
name: str = Field(..., alias="name")
|
|
117
|
-
uid: str = Field(..., alias="uid")
|
|
118
|
-
terraform_username: Optional[str] = Field(..., alias="terraformUsername")
|
|
119
|
-
console_url: str = Field(..., alias="consoleUrl")
|
|
120
89
|
resources_default_region: str = Field(..., alias="resourcesDefaultRegion")
|
|
121
|
-
|
|
122
|
-
provider_version: str = Field(..., alias="providerVersion")
|
|
123
|
-
account_owners: list[OwnerV1] = Field(..., alias="accountOwners")
|
|
124
|
-
automation_token: VaultSecretV1 = Field(..., alias="automationToken")
|
|
125
|
-
enable_deletion: Optional[bool] = Field(..., alias="enableDeletion")
|
|
126
|
-
deletion_approvals: Optional[list[DeletionApprovalV1]] = Field(..., alias="deletionApprovals")
|
|
90
|
+
automation_token: VaultSecret = Field(..., alias="automationToken")
|
|
127
91
|
disable: Optional[DisableClusterAutomationsV1] = Field(..., alias="disable")
|
|
128
|
-
|
|
129
|
-
premium_support: bool = Field(..., alias="premiumSupport")
|
|
130
|
-
ecrs: Optional[list[AWSECRV1]] = Field(..., alias="ecrs")
|
|
131
|
-
partition: Optional[str] = Field(..., alias="partition")
|
|
92
|
+
organization: Optional[AWSOrganization] = Field(..., alias="organization")
|
|
132
93
|
cleanup: Optional[list[Union[AWSAccountCleanupOptionCloudWatchV1, AWSAccountCleanupOptionV1]]] = Field(..., alias="cleanup")
|
|
133
94
|
|
|
134
95
|
|
|
@@ -51,6 +51,16 @@ fragment AWSAccountCommon on AWSAccount_v1 {
|
|
|
51
51
|
deleteKeys
|
|
52
52
|
premiumSupport
|
|
53
53
|
partition
|
|
54
|
+
organization {
|
|
55
|
+
...AWSOrganization
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fragment AWSOrganization on AWSOrganization_v1 {
|
|
60
|
+
payerAccount {
|
|
61
|
+
organizationAccountTags
|
|
62
|
+
}
|
|
63
|
+
tags
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
fragment TerraformState on TerraformStateAWS_v1 {
|
|
@@ -51,6 +51,16 @@ fragment AWSAccountCommon on AWSAccount_v1 {
|
|
|
51
51
|
deleteKeys
|
|
52
52
|
premiumSupport
|
|
53
53
|
partition
|
|
54
|
+
organization {
|
|
55
|
+
...AWSOrganization
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fragment AWSOrganization on AWSOrganization_v1 {
|
|
60
|
+
payerAccount {
|
|
61
|
+
organizationAccountTags
|
|
62
|
+
}
|
|
63
|
+
tags
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
fragment TerraformState on TerraformStateAWS_v1 {
|
|
@@ -21,6 +21,13 @@ from reconcile.gql_definitions.fragments.aws_vpc_request import VPCRequest
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
DEFINITION = """
|
|
24
|
+
fragment AWSOrganization on AWSOrganization_v1 {
|
|
25
|
+
payerAccount {
|
|
26
|
+
organizationAccountTags
|
|
27
|
+
}
|
|
28
|
+
tags
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
fragment TerraformState on TerraformStateAWS_v1 {
|
|
25
32
|
provider
|
|
26
33
|
bucket
|
|
@@ -53,6 +60,9 @@ fragment VPCRequest on VPCRequest_v1 {
|
|
|
53
60
|
name
|
|
54
61
|
expiration
|
|
55
62
|
}
|
|
63
|
+
organization {
|
|
64
|
+
...AWSOrganization
|
|
65
|
+
}
|
|
56
66
|
}
|
|
57
67
|
region
|
|
58
68
|
cidr_block {
|
|
@@ -207,6 +207,7 @@ query Clusters($name: String) {
|
|
|
207
207
|
private
|
|
208
208
|
provision_shard_id
|
|
209
209
|
disable_user_workload_monitoring
|
|
210
|
+
fips
|
|
210
211
|
}
|
|
211
212
|
externalConfiguration {
|
|
212
213
|
labels
|
|
@@ -419,6 +420,7 @@ class ClusterSpecV1(ConfiguredBaseModel):
|
|
|
419
420
|
private: bool = Field(..., alias="private")
|
|
420
421
|
provision_shard_id: Optional[str] = Field(..., alias="provision_shard_id")
|
|
421
422
|
disable_user_workload_monitoring: Optional[bool] = Field(..., alias="disable_user_workload_monitoring")
|
|
423
|
+
fips: Optional[bool] = Field(..., alias="fips")
|
|
422
424
|
|
|
423
425
|
|
|
424
426
|
class ClusterSpecOSDV1(ClusterSpecV1):
|