qontract-reconcile 0.10.2.dev349__py3-none-any.whl → 0.10.2.dev414__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.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
- {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +15 -12
- reconcile/aus/base.py +26 -27
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +1 -1
- reconcile/automated_actions/config/integration.py +15 -3
- reconcile/aws_account_manager/integration.py +8 -8
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_ami_cleanup/integration.py +8 -12
- 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 +7 -41
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +32 -25
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- 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 +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +123 -117
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -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 +24 -10
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +13 -13
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +6 -8
- reconcile/external_resources/state.py +60 -17
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +8 -5
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_mr_sqs_consumer.py +2 -2
- reconcile/gitlab_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +5 -5
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +46 -7
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
- reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +5 -5
- reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +7 -5
- reconcile/gql_definitions/common/clusters_minimal.py +5 -5
- reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
- reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
- reconcile/gql_definitions/common/github_orgs.py +5 -5
- reconcile/gql_definitions/common/jira_settings.py +5 -5
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +5 -5
- reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
- reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
- reconcile/gql_definitions/common/ocm_environments.py +5 -5
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
- reconcile/gql_definitions/common/pipeline_providers.py +5 -5
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +5 -5
- reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
- reconcile/gql_definitions/common/state_aws_account.py +5 -5
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +5 -5
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +5 -5
- reconcile/gql_definitions/email_sender/users.py +5 -5
- reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
- reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
- reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/aws_organization.py +33 -0
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
- reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
- reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
- reconcile/gql_definitions/fragments/disable.py +5 -5
- reconcile/gql_definitions/fragments/email_service.py +5 -5
- reconcile/gql_definitions/fragments/email_user.py +5 -5
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
- reconcile/gql_definitions/fragments/membership_source.py +5 -5
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
- reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_values.py +5 -5
- reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
- reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
- reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
- reconcile/gql_definitions/fragments/terraform_state.py +5 -5
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
- reconcile/gql_definitions/fragments/user.py +5 -5
- reconcile/gql_definitions/fragments/vault_secret.py +5 -5
- reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
- reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
- reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
- reconcile/gql_definitions/integrations/integrations.py +5 -5
- reconcile/gql_definitions/introspection.json +2137 -1053
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
- reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +5 -5
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
- reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
- reconcile/gql_definitions/rhcs/certs.py +5 -5
- reconcile/gql_definitions/rhidp/organizations.py +5 -5
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
- reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
- reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +5 -5
- reconcile/gql_definitions/statuspage/statuspages.py +5 -5
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
- reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
- reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
- reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +10 -8
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -56
- reconcile/ocm_aws_infrastructure_access.py +1 -1
- reconcile/ocm_clusters.py +4 -4
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/ocm_machine_pools.py +23 -23
- reconcile/openshift_base.py +53 -2
- reconcile/openshift_cluster_bots.py +3 -2
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +97 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +5 -5
- reconcile/openshift_rolebindings.py +7 -11
- reconcile/openshift_saas_deploy.py +6 -7
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_mirror.py +1 -1
- reconcile/queries.py +131 -1
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +1 -1
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- 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/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +4 -3
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +5 -5
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +17 -7
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +10 -1
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/cost_report/app_names.py +1 -1
- reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
- reconcile/typed_queries/saas_files.py +13 -13
- reconcile/typed_queries/status_board.py +2 -2
- reconcile/unleash_feature_toggles/integration.py +4 -2
- reconcile/utils/acs/base.py +6 -3
- reconcile/utils/acs/policies.py +2 -2
- reconcile/utils/aggregated_list.py +4 -3
- reconcile/utils/aws_api.py +51 -54
- 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/aws_api_typed/organization.py +4 -2
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +11 -12
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +24 -1
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +7 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helm.py +2 -1
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jinja2/utils.py +6 -101
- 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 +70 -0
- reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
- reconcile/utils/membershipsources/models.py +16 -23
- reconcile/utils/membershipsources/resolver.py +4 -2
- reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
- reconcile/utils/merge_request_manager/parser.py +6 -6
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/base.py +2 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +118 -97
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +17 -20
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/products.py +9 -3
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/openshift_resource.py +10 -5
- reconcile/utils/output.py +3 -2
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +1 -4
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +25 -21
- reconcile/utils/saasherder/saasherder.py +35 -24
- reconcile/utils/slack_api.py +26 -4
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/sqs_gateway.py +2 -1
- reconcile/utils/state.py +2 -1
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +171 -114
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +107 -42
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/cost_report/cost_management_api.py +3 -3
- tools/cli_commands/cost_report/view.py +7 -6
- tools/cli_commands/erv2.py +3 -1
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +31 -18
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.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,
|
|
@@ -763,7 +765,8 @@ class SaasHerder:
|
|
|
763
765
|
case "gitlab":
|
|
764
766
|
if not self.gitlab:
|
|
765
767
|
raise Exception("gitlab is not initialized")
|
|
766
|
-
project
|
|
768
|
+
if not (project := self.gitlab.get_project(url)):
|
|
769
|
+
raise Exception(f"Could not find gitlab project for {url}")
|
|
767
770
|
content = self.gitlab.get_raw_file(
|
|
768
771
|
project=project,
|
|
769
772
|
path=path,
|
|
@@ -799,7 +802,8 @@ class SaasHerder:
|
|
|
799
802
|
case "gitlab":
|
|
800
803
|
if not self.gitlab:
|
|
801
804
|
raise Exception("gitlab is not initialized")
|
|
802
|
-
project
|
|
805
|
+
if not (project := self.gitlab.get_project(url)):
|
|
806
|
+
raise Exception(f"Could not find gitlab project for {url}")
|
|
803
807
|
dir_contents = self.gitlab.get_directory_contents(
|
|
804
808
|
project,
|
|
805
809
|
ref=commit_sha,
|
|
@@ -824,7 +828,8 @@ class SaasHerder:
|
|
|
824
828
|
case "gitlab":
|
|
825
829
|
if not self.gitlab:
|
|
826
830
|
raise Exception("gitlab is not initialized")
|
|
827
|
-
project
|
|
831
|
+
if not (project := self.gitlab.get_project(url)):
|
|
832
|
+
raise Exception(f"Could not find gitlab project for {url}")
|
|
828
833
|
commits = project.commits.list(ref_name=ref, per_page=1, page=1)
|
|
829
834
|
return commits[0].id
|
|
830
835
|
case _:
|
|
@@ -1177,13 +1182,13 @@ class SaasHerder:
|
|
|
1177
1182
|
images_list = threaded.run(
|
|
1178
1183
|
self._collect_images, resources, self.available_thread_pool_size
|
|
1179
1184
|
)
|
|
1180
|
-
|
|
1181
|
-
self.images.update(
|
|
1182
|
-
if not
|
|
1185
|
+
images_set = set(itertools.chain.from_iterable(images_list))
|
|
1186
|
+
self.images.update(images_set)
|
|
1187
|
+
if not images_set:
|
|
1183
1188
|
return False # no errors
|
|
1184
1189
|
images = threaded.run(
|
|
1185
1190
|
self._get_image,
|
|
1186
|
-
|
|
1191
|
+
images_set,
|
|
1187
1192
|
self.available_thread_pool_size,
|
|
1188
1193
|
image_patterns=spec.image_patterns,
|
|
1189
1194
|
image_auth=spec.image_auth,
|
|
@@ -1248,7 +1253,9 @@ class SaasHerder:
|
|
|
1248
1253
|
self.saas_files,
|
|
1249
1254
|
self.thread_pool_size,
|
|
1250
1255
|
)
|
|
1251
|
-
desired_state_specs = list(
|
|
1256
|
+
desired_state_specs: list[TargetSpec] = list(
|
|
1257
|
+
itertools.chain.from_iterable(results)
|
|
1258
|
+
)
|
|
1252
1259
|
promotions = threaded.run(
|
|
1253
1260
|
self.populate_desired_state_saas_file,
|
|
1254
1261
|
desired_state_specs,
|
|
@@ -1865,7 +1872,7 @@ class SaasHerder:
|
|
|
1865
1872
|
@staticmethod
|
|
1866
1873
|
def get_target_config_hash(target_config: Any) -> str:
|
|
1867
1874
|
m = hashlib.sha256()
|
|
1868
|
-
m.update(
|
|
1875
|
+
m.update(json_dumps(target_config).encode("utf-8"))
|
|
1869
1876
|
digest = m.hexdigest()[:16]
|
|
1870
1877
|
return digest
|
|
1871
1878
|
|
|
@@ -1896,21 +1903,23 @@ class SaasHerder:
|
|
|
1896
1903
|
name=target.name,
|
|
1897
1904
|
ref=target.ref,
|
|
1898
1905
|
promotion=(
|
|
1899
|
-
target.promotion.
|
|
1906
|
+
target.promotion.model_dump(by_alias=True) if target.promotion else None
|
|
1900
1907
|
),
|
|
1901
1908
|
secretParameters=(
|
|
1902
|
-
[p.
|
|
1909
|
+
[p.model_dump(by_alias=True) for p in target.secret_parameters]
|
|
1903
1910
|
if target.secret_parameters
|
|
1904
1911
|
else None
|
|
1905
1912
|
),
|
|
1906
1913
|
slos=(
|
|
1907
|
-
[slo.
|
|
1914
|
+
[slo.model_dump(by_alias=True) for slo in target.slos]
|
|
1908
1915
|
if target.slos
|
|
1909
1916
|
else None
|
|
1910
1917
|
),
|
|
1911
|
-
upstream=(
|
|
1918
|
+
upstream=(
|
|
1919
|
+
target.upstream.model_dump(by_alias=True) if target.upstream else None
|
|
1920
|
+
),
|
|
1912
1921
|
images=(
|
|
1913
|
-
[i.
|
|
1922
|
+
[i.model_dump(by_alias=True) for i in target.images]
|
|
1914
1923
|
if target.images
|
|
1915
1924
|
else None
|
|
1916
1925
|
),
|
|
@@ -1921,14 +1930,14 @@ class SaasHerder:
|
|
|
1921
1930
|
# before the GQL classes are introduced, the parameters attribute
|
|
1922
1931
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1923
1932
|
saas_file_parameters=(
|
|
1924
|
-
|
|
1933
|
+
json_dumps(saas_file.parameters, compact=True)
|
|
1925
1934
|
if saas_file.parameters is not None
|
|
1926
1935
|
else None
|
|
1927
1936
|
),
|
|
1928
1937
|
# before the GQL classes are introduced, the parameters attribute
|
|
1929
1938
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1930
1939
|
parameters=(
|
|
1931
|
-
|
|
1940
|
+
json_dumps(target.parameters, compact=True)
|
|
1932
1941
|
if target.parameters is not None
|
|
1933
1942
|
else None
|
|
1934
1943
|
),
|
|
@@ -1939,23 +1948,23 @@ class SaasHerder:
|
|
|
1939
1948
|
# before the GQL classes are introduced, the parameters attribute
|
|
1940
1949
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1941
1950
|
rt_parameters=(
|
|
1942
|
-
|
|
1951
|
+
json_dumps(resource_template.parameters, compact=True)
|
|
1943
1952
|
if resource_template.parameters is not None
|
|
1944
1953
|
else None
|
|
1945
1954
|
),
|
|
1946
1955
|
)
|
|
1947
1956
|
if saas_file.managed_resource_names:
|
|
1948
1957
|
state_content["saas_file_managed_resource_names"] = [
|
|
1949
|
-
m.
|
|
1958
|
+
m.model_dump() for m in saas_file.managed_resource_names
|
|
1950
1959
|
]
|
|
1951
1960
|
# include secret parameters from resource template and saas file
|
|
1952
1961
|
if resource_template.secret_parameters:
|
|
1953
1962
|
state_content["rt_secretparameters"] = [
|
|
1954
|
-
p.
|
|
1963
|
+
p.model_dump() for p in resource_template.secret_parameters
|
|
1955
1964
|
]
|
|
1956
1965
|
if saas_file.secret_parameters:
|
|
1957
1966
|
state_content["saas_file_secretparameters"] = [
|
|
1958
|
-
p.
|
|
1967
|
+
p.model_dump() for p in saas_file.secret_parameters
|
|
1959
1968
|
]
|
|
1960
1969
|
return state_content
|
|
1961
1970
|
|
|
@@ -2024,7 +2033,7 @@ class SaasHerder:
|
|
|
2024
2033
|
if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
|
|
2025
2034
|
return True
|
|
2026
2035
|
|
|
2027
|
-
now =
|
|
2036
|
+
now = utc_now()
|
|
2028
2037
|
passed_soak_days = timedelta(days=0)
|
|
2029
2038
|
|
|
2030
2039
|
for channel in promotion.subscribe:
|
|
@@ -2126,7 +2135,7 @@ class SaasHerder:
|
|
|
2126
2135
|
if not (self.state and self._promotion_state):
|
|
2127
2136
|
raise Exception("state is not initialized")
|
|
2128
2137
|
|
|
2129
|
-
now =
|
|
2138
|
+
now = utc_now()
|
|
2130
2139
|
for promotion in self.promotions:
|
|
2131
2140
|
if promotion is None:
|
|
2132
2141
|
continue
|
|
@@ -2238,7 +2247,9 @@ class SaasHerder:
|
|
|
2238
2247
|
for rt in saas_file.resource_templates:
|
|
2239
2248
|
for target in rt.targets:
|
|
2240
2249
|
template_vars = {
|
|
2241
|
-
"resource": {
|
|
2250
|
+
"resource": {
|
|
2251
|
+
"namespace": target.namespace.model_dump(by_alias=True)
|
|
2252
|
+
}
|
|
2242
2253
|
}
|
|
2243
2254
|
if target.parameters:
|
|
2244
2255
|
for param in target.parameters:
|
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
|
|
@@ -53,14 +71,14 @@ class HasClientGlobalConfig(Protocol):
|
|
|
53
71
|
max_retries: int | None
|
|
54
72
|
timeout: int | None
|
|
55
73
|
|
|
56
|
-
def
|
|
74
|
+
def model_dump(self) -> dict[str, int | None]: ...
|
|
57
75
|
|
|
58
76
|
|
|
59
77
|
class HasClientMethodConfig(Protocol):
|
|
60
78
|
name: str
|
|
61
79
|
args: Any
|
|
62
80
|
|
|
63
|
-
def
|
|
81
|
+
def model_dump(self) -> dict[str, str]: ...
|
|
64
82
|
|
|
65
83
|
|
|
66
84
|
class HasClientConfig(Protocol):
|
|
@@ -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
|
|
reconcile/utils/structs.py
CHANGED
|
@@ -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,
|
|
@@ -222,7 +223,7 @@ class TerraformClient:
|
|
|
222
223
|
if disable_deletions_detected:
|
|
223
224
|
raise RuntimeError("Terraform plan has disabled deletions detected")
|
|
224
225
|
|
|
225
|
-
@retry(no_retry_exceptions=RdsUpgradeValidationError)
|
|
226
|
+
@retry(no_retry_exceptions=(RdsUpgradeValidationError,))
|
|
226
227
|
def terraform_plan(
|
|
227
228
|
self, spec: TerraformSpec, enable_deletion: bool
|
|
228
229
|
) -> tuple[bool, list[AccountUser], bool]:
|
|
@@ -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(
|