qontract-reconcile 0.10.2.dev299__py3-none-any.whl → 0.10.2.dev430__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.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +399 -394
- 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 +4 -4
- 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 +8 -11
- 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 +23 -10
- 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 +492 -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 +10 -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 +3050 -1393
- 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 +448 -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 +5 -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 +4 -0
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
- 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 +27 -19
- reconcile/terraform_users.py +29 -21
- reconcile/terraform_vpc_peerings.py +16 -4
- reconcile/terraform_vpc_resources/integration.py +32 -2
- 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 +59 -43
- 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 +27 -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/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 +4 -4
- reconcile/utils/terraform_client.py +32 -29
- reconcile/utils/terrascript_aws_client.py +658 -480
- reconcile/utils/three_way_diff_strategy.py +1 -1
- reconcile/utils/throughput.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
- reconcile/vpc_peerings_validator.py +2 -2
- 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/sre_checkpoints/util.py +5 -3
- 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.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
|
@@ -6,9 +6,11 @@ from functools import cached_property
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
7
7
|
|
|
8
8
|
from boto3 import Session
|
|
9
|
+
from botocore.config import Config
|
|
9
10
|
from pydantic import BaseModel
|
|
10
11
|
|
|
11
12
|
import reconcile.utils.aws_api_typed.account
|
|
13
|
+
import reconcile.utils.aws_api_typed.cloudformation
|
|
12
14
|
import reconcile.utils.aws_api_typed.dynamodb
|
|
13
15
|
import reconcile.utils.aws_api_typed.iam
|
|
14
16
|
import reconcile.utils.aws_api_typed.organization
|
|
@@ -17,8 +19,10 @@ import reconcile.utils.aws_api_typed.service_quotas
|
|
|
17
19
|
import reconcile.utils.aws_api_typed.sts
|
|
18
20
|
import reconcile.utils.aws_api_typed.support
|
|
19
21
|
from reconcile.utils.aws_api_typed.account import AWSApiAccount
|
|
22
|
+
from reconcile.utils.aws_api_typed.cloudformation import AWSApiCloudFormation
|
|
20
23
|
from reconcile.utils.aws_api_typed.dynamodb import AWSApiDynamoDB
|
|
21
24
|
from reconcile.utils.aws_api_typed.iam import AWSApiIam
|
|
25
|
+
from reconcile.utils.aws_api_typed.logs import AWSApiLogs
|
|
22
26
|
from reconcile.utils.aws_api_typed.organization import AWSApiOrganizations
|
|
23
27
|
from reconcile.utils.aws_api_typed.s3 import AWSApiS3
|
|
24
28
|
from reconcile.utils.aws_api_typed.service_quotas import AWSApiServiceQuotas
|
|
@@ -31,7 +35,9 @@ if TYPE_CHECKING:
|
|
|
31
35
|
SubApi = TypeVar(
|
|
32
36
|
"SubApi",
|
|
33
37
|
AWSApiAccount,
|
|
38
|
+
AWSApiCloudFormation,
|
|
34
39
|
AWSApiDynamoDB,
|
|
40
|
+
AWSApiLogs,
|
|
35
41
|
AWSApiIam,
|
|
36
42
|
AWSApiOrganizations,
|
|
37
43
|
AWSApiS3,
|
|
@@ -40,6 +46,13 @@ SubApi = TypeVar(
|
|
|
40
46
|
AWSApiSupport,
|
|
41
47
|
)
|
|
42
48
|
|
|
49
|
+
DEFAULT_CONFIG = Config(
|
|
50
|
+
retries={
|
|
51
|
+
"mode": "standard",
|
|
52
|
+
"total_max_attempts": 10,
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
43
56
|
|
|
44
57
|
class AWSCredentials(ABC):
|
|
45
58
|
@abstractmethod
|
|
@@ -174,28 +187,34 @@ class AWSApi:
|
|
|
174
187
|
"""Return a new or cached sub api client."""
|
|
175
188
|
match api_cls:
|
|
176
189
|
case reconcile.utils.aws_api_typed.account.AWSApiAccount:
|
|
177
|
-
client = self.session.client("account")
|
|
190
|
+
client = self.session.client("account", config=DEFAULT_CONFIG)
|
|
191
|
+
api = api_cls(client)
|
|
192
|
+
case reconcile.utils.aws_api_typed.cloudformation.AWSApiCloudFormation:
|
|
193
|
+
client = self.session.client("cloudformation", config=DEFAULT_CONFIG)
|
|
178
194
|
api = api_cls(client)
|
|
179
195
|
case reconcile.utils.aws_api_typed.dynamodb.AWSApiDynamoDB:
|
|
180
|
-
client = self.session.client("dynamodb")
|
|
196
|
+
client = self.session.client("dynamodb", config=DEFAULT_CONFIG)
|
|
181
197
|
api = api_cls(client)
|
|
182
198
|
case reconcile.utils.aws_api_typed.iam.AWSApiIam:
|
|
183
|
-
client = self.session.client("iam")
|
|
199
|
+
client = self.session.client("iam", config=DEFAULT_CONFIG)
|
|
200
|
+
api = api_cls(client)
|
|
201
|
+
case reconcile.utils.aws_api_typed.logs.AWSApiLogs:
|
|
202
|
+
client = self.session.client("logs", config=DEFAULT_CONFIG)
|
|
184
203
|
api = api_cls(client)
|
|
185
204
|
case reconcile.utils.aws_api_typed.organization.AWSApiOrganizations:
|
|
186
|
-
client = self.session.client("organizations")
|
|
205
|
+
client = self.session.client("organizations", config=DEFAULT_CONFIG)
|
|
187
206
|
api = api_cls(client)
|
|
188
207
|
case reconcile.utils.aws_api_typed.s3.AWSApiS3:
|
|
189
|
-
client = self.session.client("s3")
|
|
208
|
+
client = self.session.client("s3", config=DEFAULT_CONFIG)
|
|
190
209
|
api = api_cls(client)
|
|
191
210
|
case reconcile.utils.aws_api_typed.service_quotas.AWSApiServiceQuotas:
|
|
192
|
-
client = self.session.client("service-quotas")
|
|
211
|
+
client = self.session.client("service-quotas", config=DEFAULT_CONFIG)
|
|
193
212
|
api = api_cls(client)
|
|
194
213
|
case reconcile.utils.aws_api_typed.sts.AWSApiSts:
|
|
195
|
-
client = self.session.client("sts")
|
|
214
|
+
client = self.session.client("sts", config=DEFAULT_CONFIG)
|
|
196
215
|
api = api_cls(client)
|
|
197
216
|
case reconcile.utils.aws_api_typed.support.AWSApiSupport:
|
|
198
|
-
client = self.session.client("support")
|
|
217
|
+
client = self.session.client("support", config=DEFAULT_CONFIG)
|
|
199
218
|
api = api_cls(client)
|
|
200
219
|
case _:
|
|
201
220
|
raise ValueError(f"Unknown API class: {api_cls}")
|
|
@@ -205,9 +224,14 @@ class AWSApi:
|
|
|
205
224
|
|
|
206
225
|
@cached_property
|
|
207
226
|
def account(self) -> AWSApiAccount:
|
|
208
|
-
"""Return an AWS
|
|
227
|
+
"""Return an AWS Account Api client"""
|
|
209
228
|
return self._init_sub_api(AWSApiAccount)
|
|
210
229
|
|
|
230
|
+
@cached_property
|
|
231
|
+
def cloudformation(self) -> AWSApiCloudFormation:
|
|
232
|
+
"""Return an AWS CloudFormation Api client"""
|
|
233
|
+
return self._init_sub_api(AWSApiCloudFormation)
|
|
234
|
+
|
|
211
235
|
@cached_property
|
|
212
236
|
def dynamodb(self) -> AWSApiDynamoDB:
|
|
213
237
|
"""Return an AWS DynamoDB Api client"""
|
|
@@ -218,6 +242,11 @@ class AWSApi:
|
|
|
218
242
|
"""Return an AWS IAM Api client."""
|
|
219
243
|
return self._init_sub_api(AWSApiIam)
|
|
220
244
|
|
|
245
|
+
@cached_property
|
|
246
|
+
def logs(self) -> AWSApiLogs:
|
|
247
|
+
"""Return an AWS Logs Api client."""
|
|
248
|
+
return self._init_sub_api(AWSApiLogs)
|
|
249
|
+
|
|
221
250
|
@cached_property
|
|
222
251
|
def organizations(self) -> AWSApiOrganizations:
|
|
223
252
|
"""Return an AWS Organizations Api client."""
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from reconcile.utils.json import json_dumps
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from mypy_boto3_cloudformation import CloudFormationClient
|
|
9
|
+
from mypy_boto3_cloudformation.type_defs import (
|
|
10
|
+
ParameterTypeDef,
|
|
11
|
+
StackTypeDef,
|
|
12
|
+
TagTypeDef,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AWSApiCloudFormation:
|
|
17
|
+
def __init__(self, client: CloudFormationClient) -> None:
|
|
18
|
+
self.client = client
|
|
19
|
+
|
|
20
|
+
def create_stack(
|
|
21
|
+
self,
|
|
22
|
+
stack_name: str,
|
|
23
|
+
change_set_name: str,
|
|
24
|
+
template_body: str,
|
|
25
|
+
parameters: dict[str, str] | None = None,
|
|
26
|
+
tags: dict[str, str] | None = None,
|
|
27
|
+
) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Create a CloudFormation stack using a change set with import existing resources.
|
|
30
|
+
This method creates a change set of type "CREATE" with the provided template and parameters,
|
|
31
|
+
waits for the change set to be created, executes it, and then waits for the stack creation to complete.
|
|
32
|
+
It returns the StackId of the created stack.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
stack_name (str): The name of the stack to create.
|
|
36
|
+
change_set_name (str): The name of the change set to create.
|
|
37
|
+
template_body (str): The CloudFormation template body as a string.
|
|
38
|
+
parameters (dict[str, str] | None): A dictionary of parameter key-value pairs for
|
|
39
|
+
the stack. Defaults to None.
|
|
40
|
+
tags (dict[str, str] | None): A dictionary of tag key-value pairs to
|
|
41
|
+
associate with the stack. Defaults to None.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: The StackId of the created stack.
|
|
45
|
+
"""
|
|
46
|
+
response = self.client.create_change_set(
|
|
47
|
+
StackName=stack_name,
|
|
48
|
+
ChangeSetName=change_set_name,
|
|
49
|
+
TemplateBody=template_body,
|
|
50
|
+
ChangeSetType="CREATE",
|
|
51
|
+
Parameters=self._build_parameters(parameters or {}),
|
|
52
|
+
Tags=self._build_tags(tags or {}),
|
|
53
|
+
ImportExistingResources=True,
|
|
54
|
+
)
|
|
55
|
+
change_set_arn = response["Id"]
|
|
56
|
+
self.client.get_waiter("change_set_create_complete").wait(
|
|
57
|
+
ChangeSetName=change_set_arn
|
|
58
|
+
)
|
|
59
|
+
self.client.execute_change_set(ChangeSetName=change_set_arn)
|
|
60
|
+
self.client.get_waiter("stack_create_complete").wait(StackName=stack_name)
|
|
61
|
+
return response["StackId"]
|
|
62
|
+
|
|
63
|
+
def update_stack(
|
|
64
|
+
self,
|
|
65
|
+
stack_name: str,
|
|
66
|
+
template_body: str,
|
|
67
|
+
parameters: dict[str, str] | None = None,
|
|
68
|
+
tags: dict[str, str] | None = None,
|
|
69
|
+
) -> str:
|
|
70
|
+
"""
|
|
71
|
+
Update a CloudFormation stack with the provided template and parameters.
|
|
72
|
+
This method updates the specified stack, waits for the update to complete,
|
|
73
|
+
and returns the StackId of the updated stack.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
stack_name (str): The name of the stack to update.
|
|
77
|
+
template_body (str): The CloudFormation template body as a string.
|
|
78
|
+
parameters (dict[str, str] | None): A dictionary of parameter key-value pairs for
|
|
79
|
+
the stack. Defaults to None.
|
|
80
|
+
tags (dict[str, str] | None): A dictionary of tag key-value pairs to
|
|
81
|
+
associate with the stack. Defaults to None.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
str: The StackId of the updated stack.
|
|
85
|
+
"""
|
|
86
|
+
response = self.client.update_stack(
|
|
87
|
+
StackName=stack_name,
|
|
88
|
+
TemplateBody=template_body,
|
|
89
|
+
Parameters=self._build_parameters(parameters or {}),
|
|
90
|
+
Tags=self._build_tags(tags or {}),
|
|
91
|
+
)
|
|
92
|
+
self.client.get_waiter("stack_update_complete").wait(StackName=stack_name)
|
|
93
|
+
return response["StackId"]
|
|
94
|
+
|
|
95
|
+
def get_stack(self, stack_name: str) -> StackTypeDef | None:
|
|
96
|
+
"""
|
|
97
|
+
Retrieve information about a CloudFormation stack by its name.
|
|
98
|
+
If the stack exists, it returns the stack details as a dictionary.
|
|
99
|
+
If the stack does not exist, it returns None.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
stack_name (str): The name of the stack to retrieve.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
StackTypeDef | None: The stack details if found, otherwise None.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
response = self.client.describe_stacks(StackName=stack_name)
|
|
109
|
+
return response["Stacks"][0]
|
|
110
|
+
except self.client.exceptions.ClientError as e:
|
|
111
|
+
if e.response["Error"]["Code"] == "ValidationError":
|
|
112
|
+
return None
|
|
113
|
+
raise
|
|
114
|
+
|
|
115
|
+
def get_template_body(self, stack_name: str) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Retrieve the CloudFormation template body for a specified stack.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
stack_name (str): The name of the stack whose template is to be retrieved.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str: The CloudFormation template body as a string.
|
|
124
|
+
"""
|
|
125
|
+
response = self.client.get_template(StackName=stack_name)
|
|
126
|
+
# TemplateBody is str when using yaml
|
|
127
|
+
if isinstance(response["TemplateBody"], str):
|
|
128
|
+
return response["TemplateBody"]
|
|
129
|
+
return json_dumps(response["TemplateBody"])
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def _build_parameters(parameters: dict[str, str]) -> list[ParameterTypeDef]:
|
|
133
|
+
return [
|
|
134
|
+
{
|
|
135
|
+
"ParameterKey": key,
|
|
136
|
+
"ParameterValue": value,
|
|
137
|
+
}
|
|
138
|
+
for key, value in sorted(parameters.items())
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _build_tags(tags: dict[str, str]) -> list[TagTypeDef]:
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
"Key": key,
|
|
146
|
+
"Value": value,
|
|
147
|
+
}
|
|
148
|
+
for key, value in sorted(tags.items())
|
|
149
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from collections.abc import Iterable, Iterator
|
|
7
|
+
|
|
8
|
+
from mypy_boto3_logs import CloudWatchLogsClient
|
|
9
|
+
from mypy_boto3_logs.type_defs import LogGroupTypeDef
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AWSApiLogs:
|
|
13
|
+
def __init__(self, client: CloudWatchLogsClient) -> None:
|
|
14
|
+
self.client = client
|
|
15
|
+
|
|
16
|
+
def get_log_groups(self) -> Iterator[LogGroupTypeDef]:
|
|
17
|
+
paginator = self.client.get_paginator("describe_log_groups")
|
|
18
|
+
for page in paginator.paginate():
|
|
19
|
+
yield from page["logGroups"]
|
|
20
|
+
|
|
21
|
+
def delete_log_group(self, log_group_name: str) -> None:
|
|
22
|
+
self.client.delete_log_group(logGroupName=log_group_name)
|
|
23
|
+
|
|
24
|
+
def put_retention_policy(
|
|
25
|
+
self,
|
|
26
|
+
log_group_name: str,
|
|
27
|
+
retention_in_days: int,
|
|
28
|
+
) -> None:
|
|
29
|
+
self.client.put_retention_policy(
|
|
30
|
+
logGroupName=log_group_name,
|
|
31
|
+
retentionInDays=retention_in_days,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def get_tags(self, arn: str) -> dict[str, str]:
|
|
35
|
+
tags = self.client.list_tags_for_resource(
|
|
36
|
+
resourceArn=self._normalize_log_group_arn(arn),
|
|
37
|
+
)
|
|
38
|
+
return tags.get("tags") or {}
|
|
39
|
+
|
|
40
|
+
def set_tags(
|
|
41
|
+
self,
|
|
42
|
+
arn: str,
|
|
43
|
+
tags: dict[str, str],
|
|
44
|
+
) -> None:
|
|
45
|
+
self.client.tag_resource(
|
|
46
|
+
resourceArn=self._normalize_log_group_arn(arn),
|
|
47
|
+
tags=tags,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def delete_tags(
|
|
51
|
+
self,
|
|
52
|
+
arn: str,
|
|
53
|
+
tag_keys: Iterable[str],
|
|
54
|
+
) -> None:
|
|
55
|
+
self.client.untag_resource(
|
|
56
|
+
resourceArn=self._normalize_log_group_arn(arn),
|
|
57
|
+
tagKeys=list(tag_keys),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _normalize_log_group_arn(arn: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Normalize a log group ARN by removing any trailing ":*".
|
|
64
|
+
|
|
65
|
+
DescribeLogGroups response arn has additional :* at the end.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
arn: The ARN of the log group.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The normalized ARN without the trailing ":*".
|
|
72
|
+
"""
|
|
73
|
+
return arn.rstrip(":*")
|
|
@@ -52,9 +52,11 @@ class AwsOrganizationOU(BaseModel):
|
|
|
52
52
|
class AWSAccountStatus(BaseModel):
|
|
53
53
|
id: str = Field(..., alias="Id")
|
|
54
54
|
name: str = Field(..., alias="AccountName")
|
|
55
|
-
uid: str | None = Field(alias="AccountId")
|
|
55
|
+
uid: str | None = Field(None, alias="AccountId")
|
|
56
56
|
state: str = Field(..., alias="State")
|
|
57
|
-
failure_reason: CreateAccountFailureReasonType | None = Field(
|
|
57
|
+
failure_reason: CreateAccountFailureReasonType | None = Field(
|
|
58
|
+
None, alias="FailureReason"
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
class AWSAccount(BaseModel):
|
reconcile/utils/binary.py
CHANGED
|
@@ -38,10 +38,7 @@ def binary_version(
|
|
|
38
38
|
def deco_binary_version(f: Callable) -> Callable:
|
|
39
39
|
@wraps(f)
|
|
40
40
|
def f_binary_version(*args: Any, **kwargs: Any) -> None:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cmd = [binary]
|
|
44
|
-
cmd.extend(version_args)
|
|
41
|
+
cmd = [binary, *version_args]
|
|
45
42
|
try:
|
|
46
43
|
result = subprocess.run(cmd, capture_output=True, check=True)
|
|
47
44
|
except subprocess.CalledProcessError as e:
|
|
@@ -50,15 +47,13 @@ def binary_version(
|
|
|
50
47
|
)
|
|
51
48
|
raise Exception(msg) from e
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
found = True
|
|
59
|
-
break
|
|
50
|
+
match = re.search(
|
|
51
|
+
search_regex,
|
|
52
|
+
result.stdout.decode("utf-8"),
|
|
53
|
+
re.MULTILINE,
|
|
54
|
+
)
|
|
60
55
|
|
|
61
|
-
if
|
|
56
|
+
if match is None:
|
|
62
57
|
raise Exception(
|
|
63
58
|
f"Could not find version for binary '{binary}' via regex "
|
|
64
59
|
f"for binary version check: "
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
2
|
+
|
|
3
|
+
ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
|
|
4
|
+
ISO8601_MICRO = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def utc_now() -> datetime:
|
|
8
|
+
"""
|
|
9
|
+
Get the current UTC datetime.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
A datetime object representing the current time in UTC.
|
|
13
|
+
"""
|
|
14
|
+
return datetime.now(tz=UTC)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def ensure_utc(dt: datetime) -> datetime:
|
|
18
|
+
"""
|
|
19
|
+
Ensure the provided datetime is in UTC timezone.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
dt: A datetime object.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A datetime object in UTC timezone.
|
|
26
|
+
"""
|
|
27
|
+
if dt.tzinfo is None:
|
|
28
|
+
return dt.replace(tzinfo=UTC)
|
|
29
|
+
return dt.astimezone(UTC)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_utc_seconds_iso_format(dt: datetime) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Convert a datetime object to ISO 8601 format YYYY-MM-DDTHH:MM:SSZ.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
dt: A datetime object.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A string representing the datetime in ISO 8601 format.
|
|
41
|
+
"""
|
|
42
|
+
return ensure_utc(dt).strftime(ISO8601)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_utc_microseconds_iso_format(dt: datetime) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Convert a datetime object to ISO 8601 format with microseconds YYYY-MM-DDTHH:MM:SS.mmmmmmZ.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
dt: A datetime object.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A string representing the datetime in ISO 8601 format with microseconds.
|
|
54
|
+
"""
|
|
55
|
+
return ensure_utc(dt).strftime(ISO8601_MICRO)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def from_utc_iso_format(dt_str: str) -> datetime:
|
|
59
|
+
"""
|
|
60
|
+
Parse a datetime string in ISO 8601 format to a datetime object.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
dt_str: A string representing the datetime in ISO 8601 format.
|
|
64
|
+
Returns:
|
|
65
|
+
A datetime object in UTC timezone.
|
|
66
|
+
"""
|
|
67
|
+
return ensure_utc(datetime.fromisoformat(dt_str))
|
|
@@ -26,7 +26,7 @@ class Snitch(BaseModel):
|
|
|
26
26
|
interval: str
|
|
27
27
|
alert_type: str
|
|
28
28
|
alert_email: list[str]
|
|
29
|
-
vault_data: str | None
|
|
29
|
+
vault_data: str | None = None
|
|
30
30
|
|
|
31
31
|
def needs_vault_update(self) -> bool:
|
|
32
32
|
return self.vault_data is not None and self.check_in_url != self.vault_data
|
reconcile/utils/differ.py
CHANGED
|
@@ -7,7 +7,6 @@ from collections.abc import (
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import (
|
|
9
9
|
Any,
|
|
10
|
-
Generic,
|
|
11
10
|
TypeVar,
|
|
12
11
|
)
|
|
13
12
|
|
|
@@ -18,13 +17,13 @@ Key = TypeVar("Key")
|
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
@dataclass(frozen=True, eq=True)
|
|
21
|
-
class DiffPair
|
|
20
|
+
class DiffPair[Current, Desired]:
|
|
22
21
|
current: Current
|
|
23
22
|
desired: Desired
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
@dataclass(frozen=True, eq=True)
|
|
27
|
-
class DiffResult
|
|
26
|
+
class DiffResult[Current, Desired, Key]:
|
|
28
27
|
add: dict[Key, Desired]
|
|
29
28
|
delete: dict[Key, Current]
|
|
30
29
|
change: dict[Key, DiffPair[Current, Desired]]
|
|
@@ -5,8 +5,9 @@ from functools import cached_property
|
|
|
5
5
|
from typing import Any, Self
|
|
6
6
|
|
|
7
7
|
from deepdiff import DeepHash
|
|
8
|
-
from pydantic import BaseModel
|
|
8
|
+
from pydantic import BaseModel, ConfigDict
|
|
9
9
|
|
|
10
|
+
from reconcile.utils.datetime_util import utc_now
|
|
10
11
|
from reconcile.utils.secret_reader import SecretReaderBase
|
|
11
12
|
from reconcile.utils.state import State, init_state
|
|
12
13
|
|
|
@@ -16,7 +17,7 @@ CACHE_SOURCE_DIGEST_METADATA_KEY = "cache-source-digest"
|
|
|
16
17
|
LATEST_CACHE_SOURCE_DIGEST_METADATA_KEY = "latest-cache-source-digest"
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class CacheKeyWithDigest(BaseModel):
|
|
20
|
+
class CacheKeyWithDigest(BaseModel, frozen=True):
|
|
20
21
|
integration: str
|
|
21
22
|
integration_version: str
|
|
22
23
|
dry_run: bool
|
|
@@ -69,9 +70,6 @@ class CacheKeyWithDigest(BaseModel):
|
|
|
69
70
|
args.append(f"--shard {self.shard}")
|
|
70
71
|
return " ".join(args)
|
|
71
72
|
|
|
72
|
-
class Config:
|
|
73
|
-
frozen = True
|
|
74
|
-
|
|
75
73
|
|
|
76
74
|
class CacheKey(BaseModel):
|
|
77
75
|
integration: str
|
|
@@ -121,9 +119,10 @@ class CacheKey(BaseModel):
|
|
|
121
119
|
"""
|
|
122
120
|
return self.cache_key_with_digest.build_cli_delete_args()
|
|
123
121
|
|
|
124
|
-
|
|
125
|
-
frozen
|
|
126
|
-
|
|
122
|
+
model_config = ConfigDict(
|
|
123
|
+
frozen=True,
|
|
124
|
+
ignored_types=(cached_property,),
|
|
125
|
+
)
|
|
127
126
|
|
|
128
127
|
|
|
129
128
|
class CacheValue(BaseModel):
|
|
@@ -167,7 +166,7 @@ class EarlyExitCache:
|
|
|
167
166
|
|
|
168
167
|
def get(self, key: CacheKey) -> CacheValue:
|
|
169
168
|
value = self.state.get(str(key))
|
|
170
|
-
return CacheValue.
|
|
169
|
+
return CacheValue.model_validate(value)
|
|
171
170
|
|
|
172
171
|
def set(
|
|
173
172
|
self,
|
|
@@ -185,7 +184,7 @@ class EarlyExitCache:
|
|
|
185
184
|
:param latest_cache_source_digest: latest cache source digest, used to check stale for dry run cache
|
|
186
185
|
:return: None
|
|
187
186
|
"""
|
|
188
|
-
expire_at =
|
|
187
|
+
expire_at = utc_now() + timedelta(seconds=ttl_seconds)
|
|
189
188
|
metadata = {
|
|
190
189
|
EXPIRE_AT_METADATA_KEY: str(int(expire_at.timestamp())),
|
|
191
190
|
CACHE_SOURCE_DIGEST_METADATA_KEY: key.cache_source_digest,
|
|
@@ -193,7 +192,7 @@ class EarlyExitCache:
|
|
|
193
192
|
}
|
|
194
193
|
self.state.add(
|
|
195
194
|
str(key),
|
|
196
|
-
value.
|
|
195
|
+
value.model_dump(),
|
|
197
196
|
metadata=metadata,
|
|
198
197
|
force=True,
|
|
199
198
|
)
|
|
@@ -233,7 +232,7 @@ class EarlyExitCache:
|
|
|
233
232
|
int(metadata[EXPIRE_AT_METADATA_KEY]),
|
|
234
233
|
tz=UTC,
|
|
235
234
|
)
|
|
236
|
-
now =
|
|
235
|
+
now = utc_now()
|
|
237
236
|
return now >= expire_at
|
|
238
237
|
|
|
239
238
|
def _head_dry_run_status(
|
reconcile/utils/expiration.py
CHANGED
|
@@ -6,6 +6,8 @@ from typing import (
|
|
|
6
6
|
cast,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
+
from reconcile.utils.datetime_util import ensure_utc, utc_now
|
|
10
|
+
|
|
9
11
|
DATE_FORMAT = "%Y-%m-%d"
|
|
10
12
|
|
|
11
13
|
|
|
@@ -17,12 +19,14 @@ DictsOrRoles = TypeVar("DictsOrRoles", bound=Iterable[FilterableRole] | Iterable
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def date_expired(date: str) -> bool:
|
|
20
|
-
exp_date = datetime.datetime.strptime(date, DATE_FORMAT).date()
|
|
21
|
-
current_date =
|
|
22
|
+
exp_date = ensure_utc(datetime.datetime.strptime(date, DATE_FORMAT)).date() # noqa: DTZ007
|
|
23
|
+
current_date = utc_now().date()
|
|
22
24
|
return current_date >= exp_date
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
def filter
|
|
27
|
+
def filter[DictsOrRoles: Iterable[FilterableRole] | Iterable[dict]](
|
|
28
|
+
roles: DictsOrRoles | None,
|
|
29
|
+
) -> DictsOrRoles:
|
|
26
30
|
"""Filters roles and returns the ones which are not yet expired."""
|
|
27
31
|
filtered = []
|
|
28
32
|
for r in roles or []:
|
|
@@ -144,13 +144,36 @@ class ExternalResourceSpec:
|
|
|
144
144
|
return {}
|
|
145
145
|
|
|
146
146
|
def tags(self, integration: str) -> dict[str, str]:
|
|
147
|
-
|
|
147
|
+
tags = {
|
|
148
148
|
"managed_by_integration": integration,
|
|
149
149
|
"cluster": self.cluster_name,
|
|
150
150
|
"namespace": self.namespace_name,
|
|
151
151
|
"environment": self.namespace["environment"]["name"],
|
|
152
152
|
"app": self.namespace["app"]["name"],
|
|
153
153
|
}
|
|
154
|
+
if app_code := self.namespace["app"].get("appCode"):
|
|
155
|
+
tags["app-code"] = app_code
|
|
156
|
+
if cost_center := self.namespace["app"].get("costCenter"):
|
|
157
|
+
tags["cost-center"] = cost_center
|
|
158
|
+
if service_phase := self.namespace["environment"].get("servicePhase"):
|
|
159
|
+
tags["service-phase"] = service_phase
|
|
160
|
+
|
|
161
|
+
resource_tags_str = self.resource.get("tags")
|
|
162
|
+
if resource_tags_str:
|
|
163
|
+
resource_tags = json.loads(resource_tags_str)
|
|
164
|
+
# normalize camelCase keys to kebab-case
|
|
165
|
+
key_mapping = {
|
|
166
|
+
"appCode": "app-code",
|
|
167
|
+
"costCenter": "cost-center",
|
|
168
|
+
"servicePhase": "service-phase",
|
|
169
|
+
}
|
|
170
|
+
normalized_tags = {}
|
|
171
|
+
for key, value in resource_tags.items():
|
|
172
|
+
normalized_key = key_mapping.get(key, key)
|
|
173
|
+
normalized_tags[normalized_key] = value
|
|
174
|
+
tags.update(normalized_tags)
|
|
175
|
+
|
|
176
|
+
return tags
|
|
154
177
|
|
|
155
178
|
def get_secret_field(self, field: str) -> str | None:
|
|
156
179
|
return self.secret.get(field)
|
reconcile/utils/filtering.py
CHANGED
reconcile/utils/gitlab_api.py
CHANGED
|
@@ -263,13 +263,13 @@ class GitLabApi:
|
|
|
263
263
|
# we can determine if a pending MR exists based on the title
|
|
264
264
|
return any(mr.title == title for mr in mrs)
|
|
265
265
|
|
|
266
|
-
@retry()
|
|
266
|
+
@retry(no_retry_exceptions=(RuntimeError,))
|
|
267
267
|
def get_project_maintainers(
|
|
268
268
|
self, repo_url: str | None = None, query: dict | None = None
|
|
269
|
-
) -> list[str]
|
|
269
|
+
) -> list[str]:
|
|
270
270
|
project = self.project if repo_url is None else self.get_project(repo_url)
|
|
271
271
|
if project is None:
|
|
272
|
-
|
|
272
|
+
raise RuntimeError("project not found")
|
|
273
273
|
members = project.members_all.list(iterator=True, query_parameters=query or {})
|
|
274
274
|
return [m.username for m in members if m.access_level >= 40]
|
|
275
275
|
|
|
@@ -826,14 +826,16 @@ class GitLabApi:
|
|
|
826
826
|
)
|
|
827
827
|
|
|
828
828
|
def get_commit_sha(self, ref: str, repo_url: str) -> str:
|
|
829
|
-
project
|
|
829
|
+
if not (project := self.get_project(repo_url)):
|
|
830
|
+
raise ValueError(f"Project not found for repo_url: {repo_url}")
|
|
830
831
|
commits = project.commits.list(ref_name=ref, per_page=1, page=1)
|
|
831
832
|
return commits[0].id
|
|
832
833
|
|
|
833
834
|
def repository_compare(
|
|
834
835
|
self, repo_url: str, ref_from: str, ref_to: str
|
|
835
836
|
) -> list[dict[str, Any]]:
|
|
836
|
-
project
|
|
837
|
+
if not (project := self.get_project(repo_url)):
|
|
838
|
+
raise ValueError(f"Project not found for repo_url: {repo_url}")
|
|
837
839
|
response: Any = project.repository_compare(ref_from, ref_to)
|
|
838
840
|
return response.get("commits", [])
|
|
839
841
|
|