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.
Files changed (126) hide show
  1. {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/METADATA +11 -10
  2. {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/RECORD +126 -120
  3. reconcile/aus/base.py +17 -14
  4. reconcile/automated_actions/config/integration.py +12 -0
  5. reconcile/aws_account_manager/integration.py +2 -2
  6. reconcile/aws_ami_cleanup/integration.py +6 -7
  7. reconcile/aws_ami_share.py +69 -62
  8. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  9. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  10. reconcile/aws_iam_keys.py +1 -0
  11. reconcile/aws_saml_idp/integration.py +7 -1
  12. reconcile/aws_saml_roles/integration.py +9 -3
  13. reconcile/change_owners/change_owners.py +1 -1
  14. reconcile/change_owners/diff.py +2 -4
  15. reconcile/checkpoint.py +11 -3
  16. reconcile/cli.py +33 -8
  17. reconcile/dashdotdb_dora.py +4 -11
  18. reconcile/database_access_manager.py +118 -111
  19. reconcile/endpoints_discovery/integration.py +4 -1
  20. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  21. reconcile/external_resources/factories.py +5 -12
  22. reconcile/external_resources/integration.py +1 -1
  23. reconcile/external_resources/manager.py +5 -3
  24. reconcile/external_resources/meta.py +0 -1
  25. reconcile/external_resources/model.py +10 -10
  26. reconcile/external_resources/reconciler.py +5 -2
  27. reconcile/external_resources/secrets_sync.py +4 -6
  28. reconcile/external_resources/state.py +5 -4
  29. reconcile/gabi_authorized_users.py +8 -5
  30. reconcile/gitlab_housekeeping.py +13 -15
  31. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  32. reconcile/gitlab_owners.py +15 -11
  33. reconcile/gql_definitions/automated_actions/instance.py +41 -2
  34. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +10 -0
  35. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +22 -61
  36. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +10 -0
  37. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +10 -0
  38. reconcile/gql_definitions/common/aws_vpc_requests.py +10 -0
  39. reconcile/gql_definitions/common/clusters.py +2 -0
  40. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +84 -1
  41. reconcile/gql_definitions/external_resources/external_resources_settings.py +2 -0
  42. reconcile/gql_definitions/fragments/aws_account_common.py +2 -0
  43. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  44. reconcile/gql_definitions/fragments/aws_vpc_request.py +2 -0
  45. reconcile/gql_definitions/introspection.json +3474 -1986
  46. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +4 -0
  47. reconcile/gql_definitions/terraform_init/aws_accounts.py +14 -0
  48. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +33 -1
  49. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +10 -0
  50. reconcile/jenkins_worker_fleets.py +1 -0
  51. reconcile/jira_permissions_validator.py +236 -121
  52. reconcile/ocm/types.py +6 -0
  53. reconcile/openshift_base.py +47 -1
  54. reconcile/openshift_cluster_bots.py +2 -1
  55. reconcile/openshift_resources_base.py +6 -2
  56. reconcile/openshift_saas_deploy.py +2 -2
  57. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  58. reconcile/openshift_upgrade_watcher.py +3 -3
  59. reconcile/queries.py +131 -0
  60. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  61. reconcile/slack_usergroups.py +4 -3
  62. reconcile/sql_query.py +1 -0
  63. reconcile/statuspage/integrations/maintenances.py +4 -3
  64. reconcile/statuspage/status.py +5 -8
  65. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  66. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  67. reconcile/templating/renderer.py +2 -1
  68. reconcile/terraform_aws_route53.py +7 -1
  69. reconcile/terraform_init/integration.py +185 -21
  70. reconcile/terraform_resources.py +11 -1
  71. reconcile/terraform_tgw_attachments.py +7 -1
  72. reconcile/terraform_users.py +7 -0
  73. reconcile/terraform_vpc_peerings.py +14 -3
  74. reconcile/terraform_vpc_resources/integration.py +7 -0
  75. reconcile/typed_queries/aws_account_tags.py +41 -0
  76. reconcile/typed_queries/saas_files.py +2 -2
  77. reconcile/utils/aggregated_list.py +4 -3
  78. reconcile/utils/aws_api.py +51 -20
  79. reconcile/utils/aws_api_typed/api.py +38 -9
  80. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  81. reconcile/utils/aws_api_typed/logs.py +73 -0
  82. reconcile/utils/datetime_util.py +67 -0
  83. reconcile/utils/differ.py +2 -3
  84. reconcile/utils/early_exit_cache.py +3 -2
  85. reconcile/utils/expiration.py +7 -3
  86. reconcile/utils/external_resource_spec.py +24 -1
  87. reconcile/utils/filtering.py +1 -1
  88. reconcile/utils/helm.py +2 -1
  89. reconcile/utils/helpers.py +1 -1
  90. reconcile/utils/jinja2/utils.py +4 -96
  91. reconcile/utils/jira_client.py +82 -63
  92. reconcile/utils/jjb_client.py +9 -12
  93. reconcile/utils/jobcontroller/controller.py +1 -1
  94. reconcile/utils/jobcontroller/models.py +17 -1
  95. reconcile/utils/json.py +32 -0
  96. reconcile/utils/merge_request_manager/merge_request_manager.py +3 -3
  97. reconcile/utils/merge_request_manager/parser.py +2 -2
  98. reconcile/utils/mr/app_interface_reporter.py +2 -2
  99. reconcile/utils/mr/base.py +2 -2
  100. reconcile/utils/mr/notificator.py +2 -2
  101. reconcile/utils/mr/update_access_report_base.py +3 -4
  102. reconcile/utils/oc.py +113 -95
  103. reconcile/utils/oc_filters.py +3 -3
  104. reconcile/utils/ocm/products.py +6 -0
  105. reconcile/utils/ocm/search_filters.py +3 -6
  106. reconcile/utils/ocm/service_log.py +3 -5
  107. reconcile/utils/openshift_resource.py +10 -5
  108. reconcile/utils/output.py +3 -2
  109. reconcile/utils/pagerduty_api.py +5 -5
  110. reconcile/utils/runtime/integration.py +1 -2
  111. reconcile/utils/runtime/runner.py +2 -2
  112. reconcile/utils/saasherder/models.py +2 -1
  113. reconcile/utils/saasherder/saasherder.py +9 -7
  114. reconcile/utils/slack_api.py +24 -2
  115. reconcile/utils/sloth.py +171 -2
  116. reconcile/utils/sqs_gateway.py +2 -1
  117. reconcile/utils/state.py +2 -1
  118. reconcile/utils/terraform_client.py +4 -3
  119. reconcile/utils/terrascript_aws_client.py +165 -111
  120. reconcile/utils/vault.py +1 -1
  121. reconcile/vault_replication.py +107 -42
  122. tools/app_interface_reporter.py +4 -4
  123. tools/cli_commands/systems_and_tools.py +5 -1
  124. tools/qontract_cli.py +25 -13
  125. {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/WHEEL +0 -0
  126. {qontract_reconcile-0.10.2.dev345.dist-info → qontract_reconcile-0.10.2.dev408.dist-info}/entry_points.txt +0 -0
reconcile/aus/base.py CHANGED
@@ -1,4 +1,3 @@
1
- import datetime as dt
2
1
  import logging
3
2
  import sys
4
3
  from abc import (
@@ -71,6 +70,12 @@ from reconcile.utils.clusterhealth.telemeter import (
71
70
  TELEMETER_SOURCE,
72
71
  TelemeterClusterHealthProvider,
73
72
  )
73
+ from reconcile.utils.datetime_util import (
74
+ ensure_utc,
75
+ from_utc_iso_format,
76
+ to_utc_seconds_iso_format,
77
+ utc_now,
78
+ )
74
79
  from reconcile.utils.defer import defer
75
80
  from reconcile.utils.disabled_integrations import integration_is_enabled
76
81
  from reconcile.utils.filtering import remove_none_values_from_dict
@@ -420,9 +425,9 @@ class AbstractUpgradePolicy(ABC, BaseModel):
420
425
 
421
426
 
422
427
  def addon_upgrade_policy_soonest_next_run() -> str:
423
- now = datetime.now(tz=dt.UTC)
428
+ now = utc_now()
424
429
  next_run = now + timedelta(minutes=MIN_DELTA_MINUTES)
425
- return next_run.strftime("%Y-%m-%dT%H:%M:%SZ")
430
+ return to_utc_seconds_iso_format(next_run)
426
431
 
427
432
 
428
433
  class AddonUpgradePolicy(AbstractUpgradePolicy):
@@ -638,8 +643,8 @@ def update_history(
638
643
  version_data (VersionData): version data, including history of soakdays
639
644
  upgrade_policies (list): query results of clusters upgrade policies
640
645
  """
641
- now = datetime.utcnow()
642
- check_in = version_data.check_in or now
646
+ now = utc_now()
647
+ check_in = ensure_utc(version_data.check_in or now)
643
648
 
644
649
  # we iterate over clusters upgrade policies and update the version history
645
650
  for spec in org_upgrade_spec.specs:
@@ -930,7 +935,7 @@ def verify_schedule_should_skip(
930
935
  # immediately
931
936
  delay_minutes = 1 if addon_id else MIN_DELTA_MINUTES
932
937
  next_schedule = iter.get_next(
933
- dt.datetime, start_time=now + timedelta(minutes=delay_minutes)
938
+ datetime, start_time=now + timedelta(minutes=delay_minutes)
934
939
  )
935
940
  next_schedule_in_seconds = (next_schedule - now).total_seconds()
936
941
  next_schedule_in_hours = next_schedule_in_seconds / 3600 # seconds in hour
@@ -947,7 +952,7 @@ def verify_schedule_should_skip(
947
952
  f"[{desired.org.org_id}/{desired.org.name}/{desired.cluster.name}] skipping cluster with no upcoming upgrade"
948
953
  )
949
954
  return None
950
- return next_schedule.strftime("%Y-%m-%dT%H:%M:%SZ")
955
+ return to_utc_seconds_iso_format(next_schedule)
951
956
 
952
957
 
953
958
  def verify_max_upgrades_should_skip(
@@ -1024,8 +1029,8 @@ def _calculate_node_pool_diffs(
1024
1029
  ) -> UpgradePolicyHandler | None:
1025
1030
  for pool in spec.node_pools:
1026
1031
  if parse_semver(pool.version).match(f"<{spec.current_version}"):
1027
- next_schedule = (now + timedelta(minutes=MIN_DELTA_MINUTES)).strftime(
1028
- "%Y-%m-%dT%H:%M:%SZ"
1032
+ next_schedule = to_utc_seconds_iso_format(
1033
+ now + timedelta(minutes=MIN_DELTA_MINUTES)
1029
1034
  )
1030
1035
  return UpgradePolicyHandler(
1031
1036
  action="create",
@@ -1082,7 +1087,7 @@ def calculate_diff(
1082
1087
  set_upgrading(spec.cluster.id, spec.effective_mutexes, sector_name)
1083
1088
 
1084
1089
  addon_service = init_addon_service(desired_state.org.environment)
1085
- now = datetime.utcnow()
1090
+ now = utc_now()
1086
1091
  gates = get_version_gates(ocm_api)
1087
1092
  for spec in desired_state.specs:
1088
1093
  sector_name = spec.upgrade_policy.conditions.sector
@@ -1297,10 +1302,8 @@ def remaining_soak_day_metric_values_for_cluster(
1297
1302
  remaining_soakdays[idx] = UPGRADE_STARTED_METRIC_VALUE
1298
1303
  if current_upgrade.next_run:
1299
1304
  # if an upgrade runs for over 6 hours, we mark it as a long running upgrade
1300
- next_run = datetime.strptime(
1301
- current_upgrade.next_run, "%Y-%m-%dT%H:%M:%SZ"
1302
- )
1303
- now = datetime.utcnow()
1305
+ next_run = from_utc_iso_format(current_upgrade.next_run)
1306
+ now = utc_now()
1304
1307
  hours_ago = (now - next_run).total_seconds() / 3600
1305
1308
  if hours_ago >= 6:
1306
1309
  remaining_soakdays[idx] = UPGRADE_LONG_RUNNING_METRIC_VALUE
@@ -20,6 +20,7 @@ from reconcile.gql_definitions.automated_actions.instance import (
20
20
  AutomatedActionExternalResourceFlushElastiCacheV1,
21
21
  AutomatedActionExternalResourceRdsRebootV1,
22
22
  AutomatedActionExternalResourceRdsSnapshotV1,
23
+ AutomatedActionOpenshiftTriggerCronjobV1,
23
24
  AutomatedActionOpenshiftWorkloadDeleteV1,
24
25
  AutomatedActionOpenshiftWorkloadRestartArgumentV1,
25
26
  AutomatedActionOpenshiftWorkloadRestartV1,
@@ -205,6 +206,17 @@ class AutomatedActionsConfigIntegration(
205
206
  "account": f"^{rds_snapshot_er.provisioner.name}$",
206
207
  "identifier": rds_snapshot_arg.identifier,
207
208
  })
209
+ case AutomatedActionOpenshiftTriggerCronjobV1():
210
+ parameters.extend(
211
+ {
212
+ # all parameter values are regexes in the OPA policy
213
+ # therefore, cluster and namespace must be fixed to the current strings
214
+ "cluster": f"^{arg.namespace.cluster.name}$",
215
+ "namespace": f"^{arg.namespace.name}$",
216
+ "cronjob": arg.cronjob,
217
+ }
218
+ for arg in action.openshift_trigger_cronjob_arguments
219
+ )
208
220
  case AutomatedActionOpenshiftWorkloadDeleteV1():
209
221
  parameters.extend(
210
222
  {
@@ -1,5 +1,4 @@
1
1
  from collections.abc import Callable, Iterable
2
- from datetime import UTC, datetime
3
2
  from typing import Any
4
3
 
5
4
  import jinja2
@@ -26,6 +25,7 @@ from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
26
25
  from reconcile.utils import gql, metrics
27
26
  from reconcile.utils.aws_api_typed.api import AWSApi, AWSStaticCredentials
28
27
  from reconcile.utils.aws_api_typed.iam import AWSAccessKey
28
+ from reconcile.utils.datetime_util import utc_now
29
29
  from reconcile.utils.defer import defer
30
30
  from reconcile.utils.disabled_integrations import integration_is_enabled
31
31
  from reconcile.utils.runtime.integration import (
@@ -101,7 +101,7 @@ class AwsAccountMgmtIntegration(
101
101
  "accountRequest": account_request.dict(by_alias=True),
102
102
  "uid": uid,
103
103
  "settings": settings,
104
- "timestamp": int(datetime.now(tz=UTC).timestamp()),
104
+ "timestamp": int(utc_now().timestamp()),
105
105
  })
106
106
  return tmpl
107
107
 
@@ -26,6 +26,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
26
26
  )
27
27
  from reconcile.utils import gql
28
28
  from reconcile.utils.aws_api import AWSApi
29
+ from reconcile.utils.datetime_util import from_utc_iso_format, utc_now
29
30
  from reconcile.utils.defer import defer
30
31
  from reconcile.utils.parse_dhms_duration import dhms_to_seconds
31
32
  from reconcile.utils.secret_reader import create_secret_reader
@@ -77,7 +78,7 @@ def get_aws_amis(
77
78
  owner: str,
78
79
  regex: str,
79
80
  age_in_seconds: int,
80
- utc_now: datetime,
81
+ now: datetime,
81
82
  ) -> list[AWSAmi]:
82
83
  """Get amis that match regex older than given age"""
83
84
 
@@ -89,10 +90,8 @@ def get_aws_amis(
89
90
  if not re.search(pattern, image["Name"]):
90
91
  continue
91
92
 
92
- creation_date = datetime.strptime(
93
- image["CreationDate"], "%Y-%m-%dT%H:%M:%S.%fZ"
94
- )
95
- current_delta = utc_now - creation_date
93
+ creation_date = from_utc_iso_format(image["CreationDate"])
94
+ current_delta = now - creation_date
96
95
  delete_delta = timedelta(seconds=age_in_seconds)
97
96
 
98
97
  if current_delta < delete_delta:
@@ -135,7 +134,7 @@ def get_region(
135
134
 
136
135
  @defer
137
136
  def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) -> None:
138
- utc_now = datetime.utcnow()
137
+ now = utc_now()
139
138
  gqlapi = gql.get_api()
140
139
  aws_accounts = aws_accounts_query(gqlapi.query).accounts
141
140
 
@@ -222,7 +221,7 @@ def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) ->
222
221
  owner=account.uid,
223
222
  regex=cleanup_config.regex,
224
223
  age_in_seconds=age_in_seconds,
225
- utc_now=utc_now,
224
+ now=now,
226
225
  )
227
226
 
228
227
  for ami in aws_amis:
@@ -1,17 +1,19 @@
1
1
  import logging
2
+ import re
2
3
  from collections.abc import (
3
- Callable,
4
4
  Iterable,
5
5
  Mapping,
6
6
  )
7
7
  from typing import Any
8
8
 
9
9
  from reconcile import queries
10
+ from reconcile.typed_queries.aws_account_tags import get_aws_account_tags
11
+ from reconcile.typed_queries.external_resources import get_settings
10
12
  from reconcile.utils.aws_api import AWSApi
11
- from reconcile.utils.defer import defer
12
13
 
13
14
  QONTRACT_INTEGRATION = "aws-ami-share"
14
- MANAGED_TAG = {"Key": "managed_by_integration", "Value": QONTRACT_INTEGRATION}
15
+
16
+ MANAGED_TAG = {"managed_by_integration": QONTRACT_INTEGRATION}
15
17
 
16
18
 
17
19
  def filter_accounts(accounts: Iterable[dict[str, Any]]) -> list[dict[str, Any]]:
@@ -37,65 +39,70 @@ def get_region(
37
39
  return region
38
40
 
39
41
 
40
- @defer
41
- def run(dry_run: bool, defer: Callable | None = None) -> None:
42
+ def share_ami(
43
+ dry_run: bool,
44
+ src_account: Mapping[str, Any],
45
+ share: Mapping[str, Any],
46
+ default_tags: dict[str, str],
47
+ aws_api: AWSApi,
48
+ ) -> None:
49
+ dst_account = share["account"]
50
+ regex = re.compile(share["regex"])
51
+ region = get_region(share, src_account, dst_account)
52
+ src_amis = aws_api.get_amis_details(src_account, src_account, regex, region)
53
+ dst_amis = aws_api.get_amis_details(dst_account, src_account, regex, region)
54
+
55
+ for ami_id, src_ami_tags in src_amis.items():
56
+ dst_ami_tags = dst_amis.get(ami_id)
57
+ if dst_ami_tags is None:
58
+ logging.info([
59
+ "share_ami",
60
+ src_account["name"],
61
+ dst_account["name"],
62
+ ami_id,
63
+ ])
64
+ if not dry_run:
65
+ aws_api.share_ami(src_account, dst_account["uid"], ami_id, region)
66
+ dst_account_tags = default_tags | get_aws_account_tags(
67
+ dst_account.get("organization", None)
68
+ )
69
+ desired_tags = src_ami_tags | dst_account_tags | MANAGED_TAG
70
+ current_tags = dst_ami_tags or {}
71
+
72
+ if desired_tags != current_tags:
73
+ logging.info([
74
+ "tag_shared_ami",
75
+ dst_account["name"],
76
+ ami_id,
77
+ desired_tags,
78
+ ])
79
+ if not dry_run:
80
+ aws_api.create_tags(dst_account, ami_id, desired_tags)
81
+
82
+
83
+ def run(dry_run: bool) -> None:
42
84
  accounts = queries.get_aws_accounts(sharing=True)
43
85
  sharing_accounts = filter_accounts(accounts)
44
86
  settings = queries.get_app_interface_settings()
45
- aws_api = AWSApi(1, sharing_accounts, settings=settings, init_users=False)
46
- if defer:
47
- defer(aws_api.cleanup)
48
-
49
- for src_account in sharing_accounts:
50
- sharing = src_account.get("sharing")
51
- if not sharing:
52
- continue
53
- for share in sharing:
54
- if share["provider"] != "ami":
55
- continue
56
- dst_account = share["account"]
57
- regex = share["regex"]
58
- region = get_region(share, src_account, dst_account)
59
- src_amis = aws_api.get_amis_details(src_account, src_account, regex, region)
60
- dst_amis = aws_api.get_amis_details(dst_account, src_account, regex, region)
61
-
62
- for src_ami in src_amis:
63
- src_ami_id = src_ami["image_id"]
64
- found_dst_amis = [d for d in dst_amis if d["image_id"] == src_ami_id]
65
- if not found_dst_amis:
66
- logging.info([
67
- "share_ami",
68
- src_account["name"],
69
- dst_account["name"],
70
- src_ami_id,
71
- ])
72
- if not dry_run:
73
- aws_api.share_ami(
74
- src_account, dst_account["uid"], src_ami_id, region
75
- )
76
- # we assume an unshared ami does not have tags
77
- found_dst_amis = [{"image_id": src_ami_id, "tags": []}]
78
-
79
- dst_ami = found_dst_amis[0]
80
- dst_ami_id = dst_ami["image_id"]
81
- dst_ami_tags = dst_ami["tags"]
82
- if MANAGED_TAG not in dst_ami_tags:
83
- logging.info([
84
- "tag_shared_ami",
85
- dst_account["name"],
86
- dst_ami_id,
87
- MANAGED_TAG,
88
- ])
89
- if not dry_run:
90
- aws_api.create_tag(dst_account, dst_ami_id, MANAGED_TAG)
91
- src_ami_tags = src_ami["tags"]
92
- for src_tag in src_ami_tags:
93
- if src_tag not in dst_ami_tags:
94
- logging.info([
95
- "tag_shared_ami",
96
- dst_account["name"],
97
- dst_ami_id,
98
- src_tag,
99
- ])
100
- if not dry_run:
101
- aws_api.create_tag(dst_account, dst_ami_id, src_tag)
87
+ try:
88
+ default_tags = get_settings().default_tags
89
+ except ValueError:
90
+ # no external resources settings found
91
+ default_tags = {}
92
+
93
+ with AWSApi(
94
+ 1,
95
+ sharing_accounts,
96
+ settings=settings,
97
+ init_users=False,
98
+ ) as aws_api:
99
+ for src_account in sharing_accounts:
100
+ for share in src_account.get("sharing") or []:
101
+ if share["provider"] == "ami":
102
+ share_ami(
103
+ dry_run=dry_run,
104
+ src_account=src_account,
105
+ share=share,
106
+ default_tags=default_tags,
107
+ aws_api=aws_api,
108
+ )