qontract-reconcile 0.10.2.dev310__py3-none-any.whl → 0.10.2.dev439__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.
Potentially problematic release.
This version of qontract-reconcile might be problematic. Click here for more details.
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/base.py +134 -32
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
- reconcile/automated_actions/config/integration.py +16 -4
- 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 +5 -5
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +32 -25
- reconcile/aws_version_sync/integration.py +125 -84
- 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 +12 -4
- reconcile/cli.py +111 -18
- reconcile/cluster_deployment_mapper.py +2 -3
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +125 -121
- reconcile/deadmanssnitch.py +1 -5
- 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 +8 -5
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +20 -20
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +10 -14
- reconcile/external_resources/state.py +26 -16
- 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 +10 -10
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +6 -6
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
- 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 +51 -12
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
- 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 +9 -9
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
- reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
- 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 +120 -0
- reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
- 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 +22 -9
- reconcile/gql_definitions/common/aws_vpcs.py +11 -11
- reconcile/gql_definitions/common/clusters.py +37 -35
- reconcile/gql_definitions/common/clusters_minimal.py +14 -14
- reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
- reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
- reconcile/gql_definitions/common/github_orgs.py +10 -10
- reconcile/gql_definitions/common/jira_settings.py +10 -10
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +42 -44
- reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
- reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
- reconcile/gql_definitions/common/ocm_environments.py +19 -19
- reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
- reconcile/gql_definitions/common/pipeline_providers.py +29 -29
- 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 +44 -44
- reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
- 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 +19 -19
- reconcile/gql_definitions/common/state_aws_account.py +7 -8
- 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 +9 -9
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
- 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 +8 -8
- reconcile/gql_definitions/email_sender/users.py +6 -6
- reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
- reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
- reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
- reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
- 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_vpc_request_subnet.py → aws_organization.py} +12 -8
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -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 +9 -9
- reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
- reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
- reconcile/gql_definitions/integrations/integrations.py +48 -51
- reconcile/gql_definitions/introspection.json +3510 -1865
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
- reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +9 -9
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
- reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
- reconcile/gql_definitions/rhcs/certs.py +33 -87
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +18 -18
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
- reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
- reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
- 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 +6 -7
- reconcile/gql_definitions/statuspage/statuspages.py +9 -9
- 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 +6 -6
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
- reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
- reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
- reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_job_builder.py +1 -1
- reconcile/jenkins_worker_fleets.py +80 -11
- 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 +33 -27
- reconcile/openshift_base.py +122 -10
- reconcile/openshift_cluster_bots.py +5 -5
- reconcile/openshift_groups.py +5 -0
- reconcile/openshift_limitranges.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +97 -101
- reconcile/openshift_resources_base.py +10 -5
- reconcile/openshift_rhcs_certs.py +77 -40
- reconcile/openshift_rolebindings.py +230 -130
- 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 +8 -7
- reconcile/openshift_tekton_resources.py +1 -1
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/openshift_users.py +5 -3
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/oum/providers.py +1 -1
- reconcile/prometheus_rules_tester/integration.py +4 -4
- reconcile/quay_mirror.py +1 -1
- reconcile/queries.py +131 -0
- reconcile/requests_sender.py +8 -3
- reconcile/resource_scraper.py +1 -5
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +19 -10
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/sendgrid_teammates.py +20 -9
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status.py +2 -2
- 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 +5 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
- reconcile/templating/lib/merge_request_manager.py +2 -2
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +12 -13
- reconcile/terraform_aws_route53.py +18 -8
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +12 -13
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +18 -10
- reconcile/terraform_tgw_attachments.py +28 -20
- reconcile/terraform_users.py +27 -22
- reconcile/terraform_vpc_peerings.py +15 -3
- reconcile/terraform_vpc_resources/integration.py +23 -8
- reconcile/typed_queries/app_interface_roles.py +10 -0
- 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 -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/aws_api_typed/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- 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/gpg.py +5 -3
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helm.py +2 -1
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/imap_client.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/jenkins_api.py +24 -1
- reconcile/utils/jinja2/utils.py +6 -8
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +78 -46
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +74 -0
- reconcile/utils/ldap_client.py +4 -3
- reconcile/utils/lean_terraform_client.py +3 -1
- 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/__init__.py +3 -1
- reconcile/utils/mr/app_interface_reporter.py +6 -3
- reconcile/utils/mr/aws_access.py +1 -1
- reconcile/utils/mr/base.py +7 -13
- reconcile/utils/mr/clusters_updates.py +4 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
- reconcile/utils/mr/promote_qontract.py +28 -12
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +7 -6
- reconcile/utils/oc.py +445 -336
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +18 -21
- 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/ocm.py +81 -71
- 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/ocm_base_client.py +4 -4
- reconcile/utils/openshift_resource.py +83 -52
- reconcile/utils/openssl.py +2 -2
- 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 +11 -8
- reconcile/utils/repo_owners.py +21 -29
- reconcile/utils/rhcsv2_certs.py +138 -35
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/meta.py +2 -1
- 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 +60 -32
- reconcile/utils/secret_reader.py +6 -6
- reconcile/utils/sharding.py +1 -1
- reconcile/utils/slack_api.py +26 -4
- reconcile/utils/sloth.py +224 -0
- reconcile/utils/sqs_gateway.py +16 -11
- reconcile/utils/state.py +2 -1
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +29 -26
- reconcile/utils/terrascript_aws_client.py +200 -116
- reconcile/utils/three_way_diff_strategy.py +1 -1
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +44 -41
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +119 -58
- 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 +1 -1
- tools/cli_commands/gpg_encrypt.py +4 -1
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +36 -21
- tools/template_validation.py +3 -1
- reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
- reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
- reconcile/jenkins/__init__.py +0 -0
- reconcile/jenkins/types.py +0 -77
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.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,
|
|
@@ -90,8 +92,7 @@ from reconcile.utils.state import State
|
|
|
90
92
|
from reconcile.utils.vcs import VCS
|
|
91
93
|
|
|
92
94
|
TARGET_CONFIG_HASH = "target_config_hash"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
TEMPLATE_API_VERSION = "template.openshift.io/v1"
|
|
95
96
|
UNIQUE_SAAS_FILE_ENV_COMBO_LEN = 56
|
|
96
97
|
REQUEST_TIMEOUT = 60
|
|
97
98
|
|
|
@@ -763,7 +764,8 @@ class SaasHerder:
|
|
|
763
764
|
case "gitlab":
|
|
764
765
|
if not self.gitlab:
|
|
765
766
|
raise Exception("gitlab is not initialized")
|
|
766
|
-
project
|
|
767
|
+
if not (project := self.gitlab.get_project(url)):
|
|
768
|
+
raise Exception(f"Could not find gitlab project for {url}")
|
|
767
769
|
content = self.gitlab.get_raw_file(
|
|
768
770
|
project=project,
|
|
769
771
|
path=path,
|
|
@@ -799,7 +801,8 @@ class SaasHerder:
|
|
|
799
801
|
case "gitlab":
|
|
800
802
|
if not self.gitlab:
|
|
801
803
|
raise Exception("gitlab is not initialized")
|
|
802
|
-
project
|
|
804
|
+
if not (project := self.gitlab.get_project(url)):
|
|
805
|
+
raise Exception(f"Could not find gitlab project for {url}")
|
|
803
806
|
dir_contents = self.gitlab.get_directory_contents(
|
|
804
807
|
project,
|
|
805
808
|
ref=commit_sha,
|
|
@@ -824,7 +827,8 @@ class SaasHerder:
|
|
|
824
827
|
case "gitlab":
|
|
825
828
|
if not self.gitlab:
|
|
826
829
|
raise Exception("gitlab is not initialized")
|
|
827
|
-
project
|
|
830
|
+
if not (project := self.gitlab.get_project(url)):
|
|
831
|
+
raise Exception(f"Could not find gitlab project for {url}")
|
|
828
832
|
commits = project.commits.list(ref_name=ref, per_page=1, page=1)
|
|
829
833
|
return commits[0].id
|
|
830
834
|
case _:
|
|
@@ -869,12 +873,27 @@ class SaasHerder:
|
|
|
869
873
|
"""
|
|
870
874
|
if parameter_name in consolidated_parameters:
|
|
871
875
|
return False
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
+
return any(
|
|
877
|
+
template_parameter["name"] == parameter_name
|
|
878
|
+
for template_parameter in template.get("parameters") or []
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
@staticmethod
|
|
882
|
+
def _pre_process_template(template: dict[str, Any]) -> dict[str, Any]:
|
|
883
|
+
"""
|
|
884
|
+
The only supported apiVersion for OpenShift Template is "template.openshift.io/v1".
|
|
885
|
+
There are examples of templates using "v1", it can't pass validation on 4.19+ oc versions.
|
|
876
886
|
|
|
877
|
-
|
|
887
|
+
Args:
|
|
888
|
+
template (dict): The OpenShift template dictionary.
|
|
889
|
+
Returns:
|
|
890
|
+
dict: The OpenShift template dictionary with the correct apiVersion.
|
|
891
|
+
"""
|
|
892
|
+
return template | {"apiVersion": TEMPLATE_API_VERSION}
|
|
893
|
+
|
|
894
|
+
def _process_template(
|
|
895
|
+
self, spec: TargetSpec
|
|
896
|
+
) -> tuple[Iterable[Any], Promotion | None]:
|
|
878
897
|
saas_file_name = spec.saas_file_name
|
|
879
898
|
resource_template_name = spec.resource_template_name
|
|
880
899
|
url = spec.url
|
|
@@ -959,7 +978,10 @@ class SaasHerder:
|
|
|
959
978
|
|
|
960
979
|
oc = OCLocal("cluster", None, None, local=True)
|
|
961
980
|
try:
|
|
962
|
-
resources = oc.process(
|
|
981
|
+
resources: Iterable[Mapping[str, Any]] = oc.process(
|
|
982
|
+
template=self._pre_process_template(template),
|
|
983
|
+
parameters=consolidated_parameters,
|
|
984
|
+
)
|
|
963
985
|
except StatusCodeError as e:
|
|
964
986
|
logging.error(f"{error_prefix} error processing template: {e!s}")
|
|
965
987
|
|
|
@@ -1173,13 +1195,13 @@ class SaasHerder:
|
|
|
1173
1195
|
images_list = threaded.run(
|
|
1174
1196
|
self._collect_images, resources, self.available_thread_pool_size
|
|
1175
1197
|
)
|
|
1176
|
-
|
|
1177
|
-
self.images.update(
|
|
1178
|
-
if not
|
|
1198
|
+
images_set = set(itertools.chain.from_iterable(images_list))
|
|
1199
|
+
self.images.update(images_set)
|
|
1200
|
+
if not images_set:
|
|
1179
1201
|
return False # no errors
|
|
1180
1202
|
images = threaded.run(
|
|
1181
1203
|
self._get_image,
|
|
1182
|
-
|
|
1204
|
+
images_set,
|
|
1183
1205
|
self.available_thread_pool_size,
|
|
1184
1206
|
image_patterns=spec.image_patterns,
|
|
1185
1207
|
image_auth=spec.image_auth,
|
|
@@ -1244,7 +1266,9 @@ class SaasHerder:
|
|
|
1244
1266
|
self.saas_files,
|
|
1245
1267
|
self.thread_pool_size,
|
|
1246
1268
|
)
|
|
1247
|
-
desired_state_specs = list(
|
|
1269
|
+
desired_state_specs: list[TargetSpec] = list(
|
|
1270
|
+
itertools.chain.from_iterable(results)
|
|
1271
|
+
)
|
|
1248
1272
|
promotions = threaded.run(
|
|
1249
1273
|
self.populate_desired_state_saas_file,
|
|
1250
1274
|
desired_state_specs,
|
|
@@ -1861,7 +1885,7 @@ class SaasHerder:
|
|
|
1861
1885
|
@staticmethod
|
|
1862
1886
|
def get_target_config_hash(target_config: Any) -> str:
|
|
1863
1887
|
m = hashlib.sha256()
|
|
1864
|
-
m.update(
|
|
1888
|
+
m.update(json_dumps(target_config).encode("utf-8"))
|
|
1865
1889
|
digest = m.hexdigest()[:16]
|
|
1866
1890
|
return digest
|
|
1867
1891
|
|
|
@@ -1892,21 +1916,23 @@ class SaasHerder:
|
|
|
1892
1916
|
name=target.name,
|
|
1893
1917
|
ref=target.ref,
|
|
1894
1918
|
promotion=(
|
|
1895
|
-
target.promotion.
|
|
1919
|
+
target.promotion.model_dump(by_alias=True) if target.promotion else None
|
|
1896
1920
|
),
|
|
1897
1921
|
secretParameters=(
|
|
1898
|
-
[p.
|
|
1922
|
+
[p.model_dump(by_alias=True) for p in target.secret_parameters]
|
|
1899
1923
|
if target.secret_parameters
|
|
1900
1924
|
else None
|
|
1901
1925
|
),
|
|
1902
1926
|
slos=(
|
|
1903
|
-
[slo.
|
|
1927
|
+
[slo.model_dump(by_alias=True) for slo in target.slos]
|
|
1904
1928
|
if target.slos
|
|
1905
1929
|
else None
|
|
1906
1930
|
),
|
|
1907
|
-
upstream=(
|
|
1931
|
+
upstream=(
|
|
1932
|
+
target.upstream.model_dump(by_alias=True) if target.upstream else None
|
|
1933
|
+
),
|
|
1908
1934
|
images=(
|
|
1909
|
-
[i.
|
|
1935
|
+
[i.model_dump(by_alias=True) for i in target.images]
|
|
1910
1936
|
if target.images
|
|
1911
1937
|
else None
|
|
1912
1938
|
),
|
|
@@ -1917,14 +1943,14 @@ class SaasHerder:
|
|
|
1917
1943
|
# before the GQL classes are introduced, the parameters attribute
|
|
1918
1944
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1919
1945
|
saas_file_parameters=(
|
|
1920
|
-
|
|
1946
|
+
json_dumps(saas_file.parameters, compact=True)
|
|
1921
1947
|
if saas_file.parameters is not None
|
|
1922
1948
|
else None
|
|
1923
1949
|
),
|
|
1924
1950
|
# before the GQL classes are introduced, the parameters attribute
|
|
1925
1951
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1926
1952
|
parameters=(
|
|
1927
|
-
|
|
1953
|
+
json_dumps(target.parameters, compact=True)
|
|
1928
1954
|
if target.parameters is not None
|
|
1929
1955
|
else None
|
|
1930
1956
|
),
|
|
@@ -1935,23 +1961,23 @@ class SaasHerder:
|
|
|
1935
1961
|
# before the GQL classes are introduced, the parameters attribute
|
|
1936
1962
|
# was a json string. Keep it that way to be backwards compatible.
|
|
1937
1963
|
rt_parameters=(
|
|
1938
|
-
|
|
1964
|
+
json_dumps(resource_template.parameters, compact=True)
|
|
1939
1965
|
if resource_template.parameters is not None
|
|
1940
1966
|
else None
|
|
1941
1967
|
),
|
|
1942
1968
|
)
|
|
1943
1969
|
if saas_file.managed_resource_names:
|
|
1944
1970
|
state_content["saas_file_managed_resource_names"] = [
|
|
1945
|
-
m.
|
|
1971
|
+
m.model_dump() for m in saas_file.managed_resource_names
|
|
1946
1972
|
]
|
|
1947
1973
|
# include secret parameters from resource template and saas file
|
|
1948
1974
|
if resource_template.secret_parameters:
|
|
1949
1975
|
state_content["rt_secretparameters"] = [
|
|
1950
|
-
p.
|
|
1976
|
+
p.model_dump() for p in resource_template.secret_parameters
|
|
1951
1977
|
]
|
|
1952
1978
|
if saas_file.secret_parameters:
|
|
1953
1979
|
state_content["saas_file_secretparameters"] = [
|
|
1954
|
-
p.
|
|
1980
|
+
p.model_dump() for p in saas_file.secret_parameters
|
|
1955
1981
|
]
|
|
1956
1982
|
return state_content
|
|
1957
1983
|
|
|
@@ -2020,7 +2046,7 @@ class SaasHerder:
|
|
|
2020
2046
|
if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
|
|
2021
2047
|
return True
|
|
2022
2048
|
|
|
2023
|
-
now =
|
|
2049
|
+
now = utc_now()
|
|
2024
2050
|
passed_soak_days = timedelta(days=0)
|
|
2025
2051
|
|
|
2026
2052
|
for channel in promotion.subscribe:
|
|
@@ -2122,7 +2148,7 @@ class SaasHerder:
|
|
|
2122
2148
|
if not (self.state and self._promotion_state):
|
|
2123
2149
|
raise Exception("state is not initialized")
|
|
2124
2150
|
|
|
2125
|
-
now =
|
|
2151
|
+
now = utc_now()
|
|
2126
2152
|
for promotion in self.promotions:
|
|
2127
2153
|
if promotion is None:
|
|
2128
2154
|
continue
|
|
@@ -2234,7 +2260,9 @@ class SaasHerder:
|
|
|
2234
2260
|
for rt in saas_file.resource_templates:
|
|
2235
2261
|
for target in rt.targets:
|
|
2236
2262
|
template_vars = {
|
|
2237
|
-
"resource": {
|
|
2263
|
+
"resource": {
|
|
2264
|
+
"namespace": target.namespace.model_dump(by_alias=True)
|
|
2265
|
+
}
|
|
2238
2266
|
}
|
|
2239
2267
|
if target.parameters:
|
|
2240
2268
|
for param in target.parameters:
|
reconcile/utils/secret_reader.py
CHANGED
|
@@ -146,14 +146,14 @@ class VaultSecretReader(SecretReaderBase):
|
|
|
146
146
|
@property
|
|
147
147
|
def vault_client(self) -> VaultClient:
|
|
148
148
|
if self._vault_client is None:
|
|
149
|
-
self._vault_client = VaultClient()
|
|
149
|
+
self._vault_client = VaultClient.get_instance()
|
|
150
150
|
return self._vault_client
|
|
151
151
|
|
|
152
152
|
def _read_all(
|
|
153
153
|
self, path: str, field: str, format: str | None, version: int | None
|
|
154
154
|
) -> dict[str, str]:
|
|
155
155
|
try:
|
|
156
|
-
data = self.vault_client.read_all(
|
|
156
|
+
data = self.vault_client.read_all(
|
|
157
157
|
self._parameters_to_dict(
|
|
158
158
|
path=path,
|
|
159
159
|
field=field,
|
|
@@ -173,7 +173,7 @@ class VaultSecretReader(SecretReaderBase):
|
|
|
173
173
|
self, path: str, field: str, format: str | None, version: int | None
|
|
174
174
|
) -> str:
|
|
175
175
|
try:
|
|
176
|
-
data = self.vault_client.read(
|
|
176
|
+
data = self.vault_client.read(
|
|
177
177
|
self._parameters_to_dict(
|
|
178
178
|
path=path,
|
|
179
179
|
field=field,
|
|
@@ -251,7 +251,7 @@ class SecretReader(SecretReaderBase):
|
|
|
251
251
|
@property
|
|
252
252
|
def vault_client(self) -> VaultClient:
|
|
253
253
|
if self._vault_client is None:
|
|
254
|
-
self._vault_client = VaultClient()
|
|
254
|
+
self._vault_client = VaultClient.get_instance()
|
|
255
255
|
return self._vault_client
|
|
256
256
|
|
|
257
257
|
def _read(
|
|
@@ -278,7 +278,7 @@ class SecretReader(SecretReaderBase):
|
|
|
278
278
|
|
|
279
279
|
if self.settings and self.settings.get("vault"):
|
|
280
280
|
try:
|
|
281
|
-
data = self.vault_client.read(params)
|
|
281
|
+
data = self.vault_client.read(params)
|
|
282
282
|
except vault.SecretNotFoundError as e:
|
|
283
283
|
raise SecretNotFoundError(*e.args) from e
|
|
284
284
|
else:
|
|
@@ -312,7 +312,7 @@ class SecretReader(SecretReaderBase):
|
|
|
312
312
|
|
|
313
313
|
if self.settings and self.settings.get("vault"):
|
|
314
314
|
try:
|
|
315
|
-
data = self.vault_client.read_all(params)
|
|
315
|
+
data = self.vault_client.read_all(params)
|
|
316
316
|
except Forbidden:
|
|
317
317
|
raise VaultForbiddenError(
|
|
318
318
|
f"permission denied reading vault secret at {path}"
|
reconcile/utils/sharding.py
CHANGED
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
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import tempfile
|
|
3
|
+
from io import StringIO
|
|
4
|
+
from typing import Any, NotRequired, TypedDict
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from reconcile.utils.ruamel import create_ruamel_instance
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PrometheusRule(TypedDict):
|
|
12
|
+
record: NotRequired[str]
|
|
13
|
+
alert: NotRequired[str]
|
|
14
|
+
expr: str
|
|
15
|
+
labels: NotRequired[dict[str, str]]
|
|
16
|
+
annotations: NotRequired[dict[str, str]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PrometheusRuleGroup(TypedDict):
|
|
20
|
+
name: str
|
|
21
|
+
rules: list[PrometheusRule]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PrometheusRuleSpec(TypedDict):
|
|
25
|
+
groups: list[PrometheusRuleGroup]
|
|
26
|
+
|
|
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
|
+
|
|
66
|
+
def process_sloth_output(output_file_path: str) -> str:
|
|
67
|
+
ruamel_instance = create_ruamel_instance()
|
|
68
|
+
with open(output_file_path, encoding="utf-8") as f:
|
|
69
|
+
data: PrometheusRuleSpec = ruamel_instance.load(f)
|
|
70
|
+
for group in data.get("groups", []):
|
|
71
|
+
for rule in group.get("rules", []):
|
|
72
|
+
labels = (
|
|
73
|
+
# sloth adds several sloth_* labels to alerting rules that are not compliant with prometheus-rule-1 schema
|
|
74
|
+
# see https://sloth.dev/examples/default/getting-started/#__tabbed_1_2
|
|
75
|
+
{k: v for k, v in rule["labels"].items() if not k.startswith("sloth")}
|
|
76
|
+
if rule.get("alert")
|
|
77
|
+
else rule["labels"] # retain all labels on record rules
|
|
78
|
+
)
|
|
79
|
+
annotations = (
|
|
80
|
+
# sloth adds a `title` key within annotations for alert rules: https://sloth.dev/examples/default/getting-started/#__tabbed_1_2
|
|
81
|
+
# this is not compliant with schema and is discarded
|
|
82
|
+
{k: v for k, v in rule["annotations"].items() if k != "title"}
|
|
83
|
+
if rule.get("alert")
|
|
84
|
+
else {} # record rules do not support annotations
|
|
85
|
+
)
|
|
86
|
+
if labels:
|
|
87
|
+
rule["labels"] = labels
|
|
88
|
+
else:
|
|
89
|
+
rule.pop("labels", None)
|
|
90
|
+
if annotations:
|
|
91
|
+
rule["annotations"] = annotations
|
|
92
|
+
else:
|
|
93
|
+
rule.pop("annotations", None)
|
|
94
|
+
with StringIO() as s:
|
|
95
|
+
ruamel_instance.dump(data, s)
|
|
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
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
4
|
+
from typing import Any, Self
|
|
3
5
|
|
|
4
6
|
from reconcile.utils.aws_api import AWSApi
|
|
7
|
+
from reconcile.utils.json import json_dumps
|
|
5
8
|
from reconcile.utils.secret_reader import SecretReader
|
|
6
9
|
|
|
7
10
|
|
|
@@ -12,7 +15,9 @@ class SQSGatewayInitError(Exception):
|
|
|
12
15
|
class SQSGateway:
|
|
13
16
|
"""Wrapper around SQS AWS SDK"""
|
|
14
17
|
|
|
15
|
-
def __init__(
|
|
18
|
+
def __init__(
|
|
19
|
+
self, accounts: Iterable[Mapping[str, Any]], secret_reader: SecretReader
|
|
20
|
+
) -> None:
|
|
16
21
|
queue_url = os.environ.get("gitlab_pr_submitter_queue_url") # noqa: SIM112
|
|
17
22
|
if not queue_url:
|
|
18
23
|
raise SQSGatewayInitError(
|
|
@@ -30,17 +35,17 @@ class SQSGateway:
|
|
|
30
35
|
self.sqs = self._aws_api.get_session_client(session, "sqs")
|
|
31
36
|
self.queue_url = queue_url
|
|
32
37
|
|
|
33
|
-
def __enter__(self):
|
|
38
|
+
def __enter__(self) -> Self:
|
|
34
39
|
return self
|
|
35
40
|
|
|
36
|
-
def __exit__(self, *ext):
|
|
41
|
+
def __exit__(self, *ext: Any) -> None:
|
|
37
42
|
self.cleanup()
|
|
38
43
|
|
|
39
|
-
def cleanup(self):
|
|
44
|
+
def cleanup(self) -> None:
|
|
40
45
|
self._aws_api.cleanup()
|
|
41
46
|
|
|
42
47
|
@staticmethod
|
|
43
|
-
def get_queue_account(accounts, queue_url):
|
|
48
|
+
def get_queue_account(accounts: Iterable[Mapping[str, Any]], queue_url: str) -> str:
|
|
44
49
|
queue_account_uid = queue_url.split("/")[3]
|
|
45
50
|
queue_account_name = [
|
|
46
51
|
a["name"] for a in accounts if a["uid"] == queue_account_uid
|
|
@@ -49,14 +54,14 @@ class SQSGateway:
|
|
|
49
54
|
raise SQSGatewayInitError(f"account uid not found: {queue_account_uid}")
|
|
50
55
|
return queue_account_name[0]
|
|
51
56
|
|
|
52
|
-
def send_message(self, body):
|
|
53
|
-
self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=
|
|
57
|
+
def send_message(self, body: Mapping[str, Any]) -> None:
|
|
58
|
+
self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=json_dumps(body))
|
|
54
59
|
|
|
55
60
|
def receive_messages(
|
|
56
61
|
self,
|
|
57
|
-
visibility_timeout=30,
|
|
58
|
-
wait_time_seconds=20,
|
|
59
|
-
):
|
|
62
|
+
visibility_timeout: int = 30,
|
|
63
|
+
wait_time_seconds: int = 20,
|
|
64
|
+
) -> list[tuple[str, dict[str, Any]]]:
|
|
60
65
|
messages = self.sqs.receive_message(
|
|
61
66
|
QueueUrl=self.queue_url,
|
|
62
67
|
VisibilityTimeout=visibility_timeout,
|
|
@@ -64,5 +69,5 @@ class SQSGateway:
|
|
|
64
69
|
).get("Messages", [])
|
|
65
70
|
return [(m["ReceiptHandle"], json.loads(m["Body"])) for m in messages]
|
|
66
71
|
|
|
67
|
-
def delete_message(self, receipt_handle):
|
|
72
|
+
def delete_message(self, receipt_handle: str) -> None:
|
|
68
73
|
self.sqs.delete_message(QueueUrl=self.queue_url, ReceiptHandle=receipt_handle)
|
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
|
|