qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__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.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/aus_sts_gate_handler.py +59 -0
- reconcile/aus/base.py +137 -34
- 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_gate_approver.py +1 -16
- reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +21 -9
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_account_manager/utils.py +1 -1
- 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 +1 -1
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +30 -23
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/README.md +1 -1
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +108 -42
- reconcile/change_owners/decision.py +1 -1
- reconcile/change_owners/diff.py +0 -2
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +94 -11
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +123 -117
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -1
- reconcile/endpoints_discovery/merge_request_manager.py +8 -8
- reconcile/external_resources/factories.py +4 -6
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -6
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +19 -15
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +6 -10
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +5 -2
- 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_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +5 -5
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +46 -7
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
- reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +5 -5
- reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +7 -5
- reconcile/gql_definitions/common/clusters_minimal.py +5 -5
- reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
- reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
- reconcile/gql_definitions/common/github_orgs.py +5 -5
- reconcile/gql_definitions/common/jira_settings.py +5 -5
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +5 -5
- reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
- reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
- reconcile/gql_definitions/common/ocm_environments.py +5 -5
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
- reconcile/gql_definitions/common/pipeline_providers.py +5 -5
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +5 -5
- reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
- reconcile/gql_definitions/common/state_aws_account.py +5 -5
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +5 -5
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +5 -5
- reconcile/gql_definitions/email_sender/users.py +5 -5
- reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
- reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
- reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/aws_organization.py +33 -0
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +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 +5 -5
- reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
- reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
- reconcile/gql_definitions/integrations/integrations.py +5 -5
- reconcile/gql_definitions/introspection.json +775 -136
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
- reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +5 -5
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
- reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
- reconcile/gql_definitions/rhcs/certs.py +25 -79
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +5 -5
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
- reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
- reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +5 -5
- reconcile/gql_definitions/statuspage/statuspages.py +5 -5
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
- reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
- reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
- reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +10 -8
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -57
- 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 +113 -4
- reconcile/openshift_cluster_bots.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +96 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +74 -37
- reconcile/openshift_rolebindings.py +7 -11
- reconcile/openshift_saas_deploy.py +4 -5
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_base.py +25 -6
- reconcile/quay_membership.py +55 -29
- reconcile/quay_mirror.py +1 -1
- reconcile/quay_mirror_org.py +6 -4
- reconcile/quay_permissions.py +81 -75
- reconcile/quay_repos.py +35 -37
- reconcile/queries.py +132 -1
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +16 -5
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +2 -2
- reconcile/templating/validator.py +4 -4
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +5 -5
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +6 -6
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +20 -8
- reconcile/terraform_vpc_resources/merge_request.py +12 -2
- reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
- 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 +20 -15
- 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/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/environ.py +5 -0
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +2 -0
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +19 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jinja2/utils.py +6 -101
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +26 -13
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +43 -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/app_interface_reporter.py +2 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +252 -201
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +17 -20
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/products.py +8 -8
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/openshift_resource.py +8 -3
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/quay_api.py +74 -87
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +86 -23
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +23 -20
- reconcile/utils/saasherder/saasherder.py +50 -27
- reconcile/utils/slack_api.py +2 -2
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +274 -124
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +107 -42
- reconcile/vpc_peerings_validator.py +13 -0
- 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/qontract_cli.py +28 -17
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
reconcile/ocm_machine_pools.py
CHANGED
|
@@ -7,11 +7,7 @@ from collections.abc import Iterable, Mapping
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from typing import Any, Self
|
|
9
9
|
|
|
10
|
-
from pydantic import
|
|
11
|
-
BaseModel,
|
|
12
|
-
Field,
|
|
13
|
-
root_validator,
|
|
14
|
-
)
|
|
10
|
+
from pydantic import BaseModel, Field, SerializeAsAny, model_validator
|
|
15
11
|
|
|
16
12
|
from reconcile import queries
|
|
17
13
|
from reconcile.gql_definitions.common.clusters import (
|
|
@@ -22,6 +18,7 @@ from reconcile.gql_definitions.common.clusters import (
|
|
|
22
18
|
from reconcile.typed_queries.clusters import get_clusters
|
|
23
19
|
from reconcile.utils.differ import diff_mappings
|
|
24
20
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
|
21
|
+
from reconcile.utils.json import json_dumps
|
|
25
22
|
from reconcile.utils.ocm import (
|
|
26
23
|
DEFAULT_OCM_MACHINE_POOL_ID,
|
|
27
24
|
OCM,
|
|
@@ -61,7 +58,8 @@ class MachinePoolAutoscaling(AbstractAutoscaling):
|
|
|
61
58
|
min_replicas: int
|
|
62
59
|
max_replicas: int
|
|
63
60
|
|
|
64
|
-
@
|
|
61
|
+
@model_validator(mode="before")
|
|
62
|
+
@classmethod
|
|
65
63
|
def max_greater_min(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
66
64
|
min_replicas = field_values.get("min_replicas")
|
|
67
65
|
max_replicas = field_values.get("max_replicas")
|
|
@@ -82,7 +80,8 @@ class NodePoolAutoscaling(AbstractAutoscaling):
|
|
|
82
80
|
min_replica: int
|
|
83
81
|
max_replica: int
|
|
84
82
|
|
|
85
|
-
@
|
|
83
|
+
@model_validator(mode="before")
|
|
84
|
+
@classmethod
|
|
86
85
|
def max_greater_min(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
87
86
|
min_replica = field_values.get("min_replica")
|
|
88
87
|
max_replica = field_values.get("max_replica")
|
|
@@ -103,14 +102,15 @@ class AbstractPool(ABC, BaseModel):
|
|
|
103
102
|
# Abstract class for machine pools, to be implemented by OSD/HyperShift classes
|
|
104
103
|
|
|
105
104
|
id: str
|
|
106
|
-
replicas: int | None
|
|
107
|
-
taints: list[Mapping[str, str]] | None
|
|
108
|
-
labels: Mapping[str, str] | None
|
|
105
|
+
replicas: int | None = None
|
|
106
|
+
taints: list[Mapping[str, str]] | None = None
|
|
107
|
+
labels: Mapping[str, str] | None = None
|
|
109
108
|
cluster: str
|
|
110
109
|
cluster_type: ClusterType = Field(..., exclude=True)
|
|
111
|
-
autoscaling: AbstractAutoscaling | None
|
|
110
|
+
autoscaling: SerializeAsAny[AbstractAutoscaling] | None = None
|
|
112
111
|
|
|
113
|
-
@
|
|
112
|
+
@model_validator(mode="before")
|
|
113
|
+
@classmethod
|
|
114
114
|
def validate_scaling(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
115
115
|
if field_values.get("autoscaling") and field_values.get("replicas"):
|
|
116
116
|
raise ValueError("autoscaling and replicas are mutually exclusive")
|
|
@@ -154,13 +154,13 @@ class MachinePool(AbstractPool):
|
|
|
154
154
|
instance_type: str
|
|
155
155
|
|
|
156
156
|
def delete(self, ocm: OCM) -> None:
|
|
157
|
-
ocm.delete_machine_pool(self.cluster, self.
|
|
157
|
+
ocm.delete_machine_pool(self.cluster, self.model_dump(by_alias=True))
|
|
158
158
|
|
|
159
159
|
def create(self, ocm: OCM) -> None:
|
|
160
|
-
ocm.create_machine_pool(self.cluster, self.
|
|
160
|
+
ocm.create_machine_pool(self.cluster, self.model_dump(by_alias=True))
|
|
161
161
|
|
|
162
162
|
def update(self, ocm: OCM) -> None:
|
|
163
|
-
update_dict = self.
|
|
163
|
+
update_dict = self.model_dump(by_alias=True)
|
|
164
164
|
# can not update instance_type
|
|
165
165
|
del update_dict["instance_type"]
|
|
166
166
|
if not update_dict["labels"]:
|
|
@@ -170,7 +170,10 @@ class MachinePool(AbstractPool):
|
|
|
170
170
|
ocm.update_machine_pool(self.cluster, update_dict)
|
|
171
171
|
|
|
172
172
|
def has_diff(self, pool: ClusterMachinePoolV1) -> bool:
|
|
173
|
-
|
|
173
|
+
pool_taints = (
|
|
174
|
+
[p.model_dump(by_alias=True) for p in pool.taints] if pool.taints else None
|
|
175
|
+
)
|
|
176
|
+
if self.taints != pool_taints or self.labels != pool.labels:
|
|
174
177
|
logging.warning(
|
|
175
178
|
f"updating labels or taints for machine pool {pool.q_id} "
|
|
176
179
|
f"will only be applied to new Nodes"
|
|
@@ -178,7 +181,7 @@ class MachinePool(AbstractPool):
|
|
|
178
181
|
|
|
179
182
|
return (
|
|
180
183
|
self.replicas != pool.replicas
|
|
181
|
-
or self.taints !=
|
|
184
|
+
or self.taints != pool_taints
|
|
182
185
|
or self.labels != pool.labels
|
|
183
186
|
or self.instance_type != pool.instance_type
|
|
184
187
|
or self._has_diff_autoscale(pool)
|
|
@@ -214,7 +217,7 @@ class MachinePool(AbstractPool):
|
|
|
214
217
|
replicas=pool.replicas,
|
|
215
218
|
autoscaling=autoscaling,
|
|
216
219
|
instance_type=pool.instance_type,
|
|
217
|
-
taints=[p.
|
|
220
|
+
taints=[p.model_dump(by_alias=True) for p in pool.taints or []],
|
|
218
221
|
labels=pool.labels,
|
|
219
222
|
cluster=cluster,
|
|
220
223
|
cluster_type=cluster_type,
|
|
@@ -232,14 +235,14 @@ class NodePool(AbstractPool):
|
|
|
232
235
|
subnet: str | None
|
|
233
236
|
|
|
234
237
|
def delete(self, ocm: OCM) -> None:
|
|
235
|
-
ocm.delete_node_pool(self.cluster, self.
|
|
238
|
+
ocm.delete_node_pool(self.cluster, self.model_dump(by_alias=True))
|
|
236
239
|
|
|
237
240
|
def create(self, ocm: OCM) -> None:
|
|
238
|
-
spec = self.
|
|
241
|
+
spec = self.model_dump(by_alias=True)
|
|
239
242
|
ocm.create_node_pool(self.cluster, spec)
|
|
240
243
|
|
|
241
244
|
def update(self, ocm: OCM) -> None:
|
|
242
|
-
update_dict = self.
|
|
245
|
+
update_dict = self.model_dump(by_alias=True)
|
|
243
246
|
# can not update instance_type
|
|
244
247
|
del update_dict["aws_node_pool"]
|
|
245
248
|
# can not update subnet
|
|
@@ -251,7 +254,10 @@ class NodePool(AbstractPool):
|
|
|
251
254
|
ocm.update_node_pool(self.cluster, update_dict)
|
|
252
255
|
|
|
253
256
|
def has_diff(self, pool: ClusterMachinePoolV1) -> bool:
|
|
254
|
-
|
|
257
|
+
pool_taints = (
|
|
258
|
+
[p.model_dump(by_alias=True) for p in pool.taints] if pool.taints else None
|
|
259
|
+
)
|
|
260
|
+
if self.taints != pool_taints or self.labels != pool.labels:
|
|
255
261
|
logging.warning(
|
|
256
262
|
f"updating labels or taints for node pool {pool.q_id} "
|
|
257
263
|
f"will only be applied to new Nodes"
|
|
@@ -259,7 +265,7 @@ class NodePool(AbstractPool):
|
|
|
259
265
|
|
|
260
266
|
return (
|
|
261
267
|
self.replicas != pool.replicas
|
|
262
|
-
or self.taints !=
|
|
268
|
+
or self.taints != pool_taints
|
|
263
269
|
or self.labels != pool.labels
|
|
264
270
|
or self.aws_node_pool.instance_type != pool.instance_type
|
|
265
271
|
or self.subnet != pool.subnet
|
|
@@ -297,7 +303,7 @@ class NodePool(AbstractPool):
|
|
|
297
303
|
aws_node_pool=AWSNodePool(
|
|
298
304
|
instance_type=pool.instance_type,
|
|
299
305
|
),
|
|
300
|
-
taints=[p.
|
|
306
|
+
taints=[p.model_dump(by_alias=True) for p in pool.taints or []],
|
|
301
307
|
labels=pool.labels,
|
|
302
308
|
subnet=pool.subnet,
|
|
303
309
|
cluster=cluster,
|
|
@@ -312,7 +318,7 @@ class PoolHandler(BaseModel):
|
|
|
312
318
|
pool: AbstractPool
|
|
313
319
|
|
|
314
320
|
def act(self, dry_run: bool, ocm: OCM) -> None:
|
|
315
|
-
logging.info(f"{self.action} {self.pool.
|
|
321
|
+
logging.info(f"{self.action} {self.pool.model_dump(by_alias=True)}")
|
|
316
322
|
if dry_run:
|
|
317
323
|
return
|
|
318
324
|
|
|
@@ -469,7 +475,7 @@ def calculate_diff(
|
|
|
469
475
|
if invalid_diff:
|
|
470
476
|
errors.append(
|
|
471
477
|
InvalidUpdateError(
|
|
472
|
-
f"can not update {invalid_diff} for existing machine pool on cluster {cluster_name}, CURRENT: {diff_pair.current
|
|
478
|
+
f"can not update {invalid_diff} for existing machine pool on cluster {cluster_name}, CURRENT: {json_dumps(diff_pair.current)}, DESIRED: {json_dumps(diff_pair.desired)}"
|
|
473
479
|
)
|
|
474
480
|
)
|
|
475
481
|
else:
|
|
@@ -531,7 +537,7 @@ def run(dry_run: bool) -> None:
|
|
|
531
537
|
|
|
532
538
|
settings = queries.get_app_interface_settings()
|
|
533
539
|
cluster_like_objects = [
|
|
534
|
-
cluster.
|
|
540
|
+
cluster.model_dump(by_alias=True) for cluster in filtered_clusters
|
|
535
541
|
]
|
|
536
542
|
ocm_map = OCMMap(
|
|
537
543
|
clusters=cluster_like_objects,
|
reconcile/openshift_base.py
CHANGED
|
@@ -29,10 +29,14 @@ from reconcile.utils import (
|
|
|
29
29
|
metrics,
|
|
30
30
|
)
|
|
31
31
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
32
|
+
from reconcile.utils.differ import DiffPair
|
|
32
33
|
from reconcile.utils.oc import (
|
|
34
|
+
POD_RECYCLE_SUPPORTED_OWNER_KINDS,
|
|
35
|
+
AmbiguousResourceTypeError,
|
|
33
36
|
DeploymentFieldIsImmutableError,
|
|
34
37
|
FieldIsImmutableError,
|
|
35
38
|
InvalidValueApplyError,
|
|
39
|
+
KindNotFoundError,
|
|
36
40
|
MayNotChangeOnceSetError,
|
|
37
41
|
MetaDataAnnotationsTooLongApplyError,
|
|
38
42
|
OC_Map,
|
|
@@ -60,6 +64,10 @@ AUTH_METHOD_USER_KEY = {
|
|
|
60
64
|
"oidc": "org_username",
|
|
61
65
|
"rhidp": "org_username",
|
|
62
66
|
}
|
|
67
|
+
RECYCLE_POD_ANNOTATIONS = [
|
|
68
|
+
"kubectl.kubernetes.io/restartedAt",
|
|
69
|
+
"openshift.openshift.io/restartedAt",
|
|
70
|
+
]
|
|
63
71
|
|
|
64
72
|
|
|
65
73
|
class ValidationError(Exception):
|
|
@@ -128,6 +136,29 @@ class ClusterMap(Protocol):
|
|
|
128
136
|
) -> list[str]: ...
|
|
129
137
|
|
|
130
138
|
|
|
139
|
+
def validate_managed_resource_types(
|
|
140
|
+
oc: OCCli,
|
|
141
|
+
managed_resource_types: Iterable[str],
|
|
142
|
+
managed_resource_names: Iterable[Mapping[str, Any]],
|
|
143
|
+
cluster_scope_resource_validation: bool,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Validate the managed resource types."""
|
|
146
|
+
managed_resources = [
|
|
147
|
+
managed_resource_name["resource"]
|
|
148
|
+
for managed_resource_name in managed_resource_names
|
|
149
|
+
]
|
|
150
|
+
for managed_resource_type in managed_resource_types:
|
|
151
|
+
# The k8s kind must be supported by the cluster
|
|
152
|
+
resource = oc.get_api_resource(managed_resource_type)
|
|
153
|
+
|
|
154
|
+
if cluster_scope_resource_validation and not resource.namespaced:
|
|
155
|
+
# cluster-scoped resources must be use managedResourceNames!
|
|
156
|
+
if managed_resource_type not in managed_resources:
|
|
157
|
+
raise ValidationError(
|
|
158
|
+
f"Cluster-scoped resource {managed_resource_type} must be managed by name only. Please use 'managedResourceNames' field to specify the names of the resources to manage."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
131
162
|
def init_specs_to_fetch(
|
|
132
163
|
ri: ResourceInventory,
|
|
133
164
|
oc_map: ClusterMap,
|
|
@@ -136,6 +167,7 @@ def init_specs_to_fetch(
|
|
|
136
167
|
override_managed_types: Iterable[str] | None = None,
|
|
137
168
|
managed_types_key: str = "managedResourceTypes",
|
|
138
169
|
cluster_admin: bool = False,
|
|
170
|
+
cluster_scope_resource_validation: bool = False,
|
|
139
171
|
) -> list[StateSpec]:
|
|
140
172
|
state_specs: list[StateSpec] = []
|
|
141
173
|
|
|
@@ -163,9 +195,27 @@ def init_specs_to_fetch(
|
|
|
163
195
|
logging.log(level=ex.log_level, msg=ex.message)
|
|
164
196
|
continue
|
|
165
197
|
|
|
198
|
+
managed_resource_names = namespace_info.get("managedResourceNames") or []
|
|
199
|
+
try:
|
|
200
|
+
validate_managed_resource_types(
|
|
201
|
+
oc,
|
|
202
|
+
managed_types,
|
|
203
|
+
managed_resource_names,
|
|
204
|
+
cluster_scope_resource_validation=cluster_scope_resource_validation,
|
|
205
|
+
)
|
|
206
|
+
except KindNotFoundError:
|
|
207
|
+
# We must allow kinds that are not supported by the cluster because:
|
|
208
|
+
# 1. We install CRD with an operator in the same MR
|
|
209
|
+
# 2. SAAS files initialize the namespace objects with managedResourceTypes from the SAAS file
|
|
210
|
+
# and we can't expect that all of those are valid for all clusters
|
|
211
|
+
pass
|
|
212
|
+
except (AmbiguousResourceTypeError, ValidationError) as e:
|
|
213
|
+
ri.register_error()
|
|
214
|
+
logging.error(f"[{cluster}/{namespace_info['name']}] {e}")
|
|
215
|
+
continue
|
|
216
|
+
|
|
166
217
|
namespace = namespace_info["name"]
|
|
167
218
|
# These may exit but have a value of None
|
|
168
|
-
managed_resource_names = namespace_info.get("managedResourceNames") or []
|
|
169
219
|
managed_resource_type_overrides = (
|
|
170
220
|
namespace_info.get("managedResourceTypeOverrides") or []
|
|
171
221
|
)
|
|
@@ -340,6 +390,7 @@ def fetch_current_state(
|
|
|
340
390
|
cluster_admin: bool = False,
|
|
341
391
|
caller: str | None = None,
|
|
342
392
|
init_projects: bool = False,
|
|
393
|
+
cluster_scope_resource_validation: bool = False,
|
|
343
394
|
) -> tuple[ResourceInventory, OC_Map]:
|
|
344
395
|
ri = ResourceInventory()
|
|
345
396
|
settings = queries.get_app_interface_settings()
|
|
@@ -362,6 +413,7 @@ def fetch_current_state(
|
|
|
362
413
|
clusters=clusters,
|
|
363
414
|
override_managed_types=override_managed_types,
|
|
364
415
|
cluster_admin=cluster_admin,
|
|
416
|
+
cluster_scope_resource_validation=cluster_scope_resource_validation,
|
|
365
417
|
)
|
|
366
418
|
threaded.run(
|
|
367
419
|
populate_current_state,
|
|
@@ -542,7 +594,7 @@ def apply(
|
|
|
542
594
|
oc.resize_pvcs(namespace, owned_pvc_names, desired_storage)
|
|
543
595
|
|
|
544
596
|
if recycle_pods:
|
|
545
|
-
oc.recycle_pods(dry_run, namespace,
|
|
597
|
+
oc.recycle_pods(dry_run, namespace, resource)
|
|
546
598
|
|
|
547
599
|
|
|
548
600
|
def create(
|
|
@@ -786,10 +838,56 @@ def handle_identical_resources(
|
|
|
786
838
|
return actions
|
|
787
839
|
|
|
788
840
|
|
|
841
|
+
def patch_desired_resource_for_recycle_annotations(
|
|
842
|
+
desired: OR,
|
|
843
|
+
current: OR,
|
|
844
|
+
) -> OR:
|
|
845
|
+
"""
|
|
846
|
+
Patch desired resource with recycle annotations to pod template from current resource.
|
|
847
|
+
This is to avoid full pods recycle when changes are not affecting pod template.
|
|
848
|
+
Note desired annotations can override current annotations.
|
|
849
|
+
For example, if desired resource has kubectl.kubernetes.io/restartedAt defined,
|
|
850
|
+
it will be used instead of current resource annotation.
|
|
851
|
+
|
|
852
|
+
Args:
|
|
853
|
+
desired: desired resource
|
|
854
|
+
current: current resource
|
|
855
|
+
|
|
856
|
+
Returns:
|
|
857
|
+
patched desired resource
|
|
858
|
+
"""
|
|
859
|
+
if current.kind not in POD_RECYCLE_SUPPORTED_OWNER_KINDS:
|
|
860
|
+
return desired
|
|
861
|
+
|
|
862
|
+
current_annotations = (
|
|
863
|
+
current.body.get("spec", {})
|
|
864
|
+
.get("template", {})
|
|
865
|
+
.get("metadata", {})
|
|
866
|
+
.get("annotations")
|
|
867
|
+
or {}
|
|
868
|
+
)
|
|
869
|
+
patch_annotations = {
|
|
870
|
+
k: value
|
|
871
|
+
for k in RECYCLE_POD_ANNOTATIONS
|
|
872
|
+
if (value := current_annotations.get(k))
|
|
873
|
+
}
|
|
874
|
+
if patch_annotations:
|
|
875
|
+
desired_annotations = (
|
|
876
|
+
desired.body.setdefault("spec", {})
|
|
877
|
+
.setdefault("template", {})
|
|
878
|
+
.setdefault("metadata", {})
|
|
879
|
+
.setdefault("annotations", {})
|
|
880
|
+
)
|
|
881
|
+
desired.body["spec"]["template"]["metadata"]["annotations"] = (
|
|
882
|
+
patch_annotations | desired_annotations
|
|
883
|
+
)
|
|
884
|
+
return desired
|
|
885
|
+
|
|
886
|
+
|
|
789
887
|
def handle_modified_resources(
|
|
790
888
|
oc_map: ClusterMap,
|
|
791
889
|
ri: ResourceInventory,
|
|
792
|
-
modified_resources: Mapping[
|
|
890
|
+
modified_resources: Mapping[str, DiffPair[OR, OR]],
|
|
793
891
|
cluster: str,
|
|
794
892
|
namespace: str,
|
|
795
893
|
resource_type: str,
|
|
@@ -985,6 +1083,12 @@ def _realize_resource_data_3way_diff(
|
|
|
985
1083
|
if options.enable_deletion and options.override_enable_deletion is False:
|
|
986
1084
|
options.enable_deletion = False
|
|
987
1085
|
|
|
1086
|
+
for k in data["current"].keys() & data["desired"].keys():
|
|
1087
|
+
patch_desired_resource_for_recycle_annotations(
|
|
1088
|
+
desired=data["desired"][k],
|
|
1089
|
+
current=data["current"][k],
|
|
1090
|
+
)
|
|
1091
|
+
|
|
988
1092
|
diff_result = differ.diff_mappings(
|
|
989
1093
|
data["current"], data["desired"], equal=three_way_diff_using_hash
|
|
990
1094
|
)
|
|
@@ -1363,6 +1467,11 @@ class HasOpenShiftResources(Protocol):
|
|
|
1363
1467
|
openshift_resources: list | None
|
|
1364
1468
|
|
|
1365
1469
|
|
|
1470
|
+
@runtime_checkable
|
|
1471
|
+
class HasOpenShiftResourcesRequired(Protocol):
|
|
1472
|
+
openshift_resources: list
|
|
1473
|
+
|
|
1474
|
+
|
|
1366
1475
|
@runtime_checkable
|
|
1367
1476
|
class HasOpenshiftServiceAccountTokens(Protocol):
|
|
1368
1477
|
openshift_service_account_tokens: list | None
|
|
@@ -1371,7 +1480,7 @@ class HasOpenshiftServiceAccountTokens(Protocol):
|
|
|
1371
1480
|
@runtime_checkable
|
|
1372
1481
|
class HasSharedResourcesOpenShiftResources(Protocol):
|
|
1373
1482
|
@property
|
|
1374
|
-
def shared_resources(self) -> Sequence[
|
|
1483
|
+
def shared_resources(self) -> Sequence[HasOpenShiftResourcesRequired] | None: ...
|
|
1375
1484
|
|
|
1376
1485
|
|
|
1377
1486
|
@runtime_checkable
|
|
@@ -301,7 +301,7 @@ def filter_clusters(clusters: list[ClusterV1]) -> list[ClusterV1]:
|
|
|
301
301
|
|
|
302
302
|
def get_ocm_map(clusters: list[ClusterV1]) -> OCMMap:
|
|
303
303
|
settings = queries.get_app_interface_settings()
|
|
304
|
-
clusters_info = [c.
|
|
304
|
+
clusters_info = [c.model_dump(by_alias=True) for c in clusters]
|
|
305
305
|
return OCMMap(
|
|
306
306
|
settings=settings,
|
|
307
307
|
clusters=clusters_info,
|
|
@@ -229,7 +229,7 @@ def get_gql_namespaces_in_shard() -> list[NamespaceV1]:
|
|
|
229
229
|
return [
|
|
230
230
|
ns
|
|
231
231
|
for ns in all_namespaces
|
|
232
|
-
if not ob.is_namespace_deleted(ns.
|
|
232
|
+
if not ob.is_namespace_deleted(ns.model_dump(by_alias=True))
|
|
233
233
|
and is_in_shard(f"{ns.cluster.name}/{ns.name}")
|
|
234
234
|
]
|
|
235
235
|
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import
|
|
2
|
+
from collections import defaultdict
|
|
3
3
|
from collections.abc import (
|
|
4
4
|
Callable,
|
|
5
5
|
Iterable,
|
|
6
|
-
Mapping,
|
|
7
6
|
Sequence,
|
|
8
7
|
)
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import StrEnum
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from sretoolbox.utils import threaded
|
|
12
13
|
|
|
13
14
|
import reconcile.openshift_base as ob
|
|
14
15
|
from reconcile.gql_definitions.common.namespaces_minimal import NamespaceV1
|
|
15
|
-
from reconcile.status import ExitCodes
|
|
16
16
|
from reconcile.typed_queries.app_interface_vault_settings import (
|
|
17
17
|
get_app_interface_vault_settings,
|
|
18
18
|
)
|
|
19
19
|
from reconcile.typed_queries.namespaces_minimal import get_namespaces_minimal
|
|
20
20
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
21
21
|
from reconcile.utils.defer import defer
|
|
22
|
-
from reconcile.utils.oc_filters import
|
|
22
|
+
from reconcile.utils.oc_filters import (
|
|
23
|
+
filter_namespaces_by_cluster_and_namespace,
|
|
24
|
+
)
|
|
23
25
|
from reconcile.utils.oc_map import (
|
|
24
26
|
OCLogMsg,
|
|
25
27
|
OCMap,
|
|
@@ -30,113 +32,111 @@ from reconcile.utils.sharding import is_in_shard
|
|
|
30
32
|
|
|
31
33
|
QONTRACT_INTEGRATION = "openshift-namespaces"
|
|
32
34
|
|
|
33
|
-
NS_STATE_PRESENT = "present"
|
|
34
|
-
NS_STATE_ABSENT = "absent"
|
|
35
|
-
|
|
36
|
-
NS_ACTION_CREATE = "create"
|
|
37
|
-
NS_ACTION_DELETE = "delete"
|
|
38
35
|
|
|
36
|
+
class Action(StrEnum):
|
|
37
|
+
CREATE = "create"
|
|
38
|
+
DELETE = "delete"
|
|
39
39
|
|
|
40
|
-
DUPLICATES_LOG_MSG = "Found multiple definitions for the namespace {key}"
|
|
41
40
|
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class DesiredState:
|
|
43
|
+
cluster: str
|
|
44
|
+
namespace: str
|
|
45
|
+
delete: bool
|
|
46
|
+
cluster_admin: bool
|
|
42
47
|
|
|
43
|
-
def get_desired_state(namespaces: Iterable[NamespaceV1]) -> list[dict[str, str]]:
|
|
44
|
-
desired_state: list[dict[str, str]] = []
|
|
45
|
-
for ns in namespaces:
|
|
46
|
-
state = NS_STATE_PRESENT
|
|
47
|
-
if ns.delete:
|
|
48
|
-
state = NS_STATE_ABSENT
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"namespace": ns.name,
|
|
53
|
-
"desired_state": state,
|
|
54
|
-
})
|
|
49
|
+
class NamespaceDuplicateError(Exception):
|
|
50
|
+
pass
|
|
55
51
|
|
|
56
|
-
return desired_state
|
|
57
52
|
|
|
53
|
+
def get_namespaces(
|
|
54
|
+
cluster_name: Sequence[str] | None,
|
|
55
|
+
namespace_name: Sequence[str] | None,
|
|
56
|
+
) -> tuple[list[NamespaceV1], list[NamespaceDuplicateError]]:
|
|
57
|
+
all_namespaces = get_namespaces_minimal()
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
namespaces: Iterable[NamespaceV1],
|
|
61
|
-
) -> tuple[list[NamespaceV1], bool]:
|
|
62
|
-
# Structure holding duplicates by namespace key
|
|
63
|
-
duplicates: dict[str, list[NamespaceV1]] = {}
|
|
64
|
-
# namespace filtered list without duplicates
|
|
65
|
-
filtered_ns: dict[str, NamespaceV1] = {}
|
|
66
|
-
|
|
67
|
-
err = False
|
|
68
|
-
for ns in namespaces:
|
|
69
|
-
key = f"{ns.cluster.name}/{ns.name}"
|
|
59
|
+
namespaces_by_shard_key = defaultdict(list)
|
|
70
60
|
|
|
61
|
+
for namespace in all_namespaces:
|
|
62
|
+
key = f"{namespace.cluster.name}/{namespace.name}"
|
|
71
63
|
if is_in_shard(key):
|
|
72
|
-
|
|
73
|
-
filtered_ns[key] = ns
|
|
74
|
-
else:
|
|
75
|
-
# Duplicated NS
|
|
76
|
-
dupe_list_by_key = duplicates.setdefault(key, [])
|
|
77
|
-
dupe_list_by_key.append(ns)
|
|
78
|
-
|
|
79
|
-
for key, dupe_list in duplicates.items():
|
|
80
|
-
dupe_list.append(filtered_ns[key])
|
|
81
|
-
delete_flags = (
|
|
82
|
-
[ns.delete for ns in dupe_list_by_key] if dupe_list_by_key else []
|
|
83
|
-
)
|
|
64
|
+
namespaces_by_shard_key[key].append(namespace)
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
66
|
+
managed_namespaces = []
|
|
67
|
+
duplicate_errors = []
|
|
68
|
+
|
|
69
|
+
for key, namespaces in namespaces_by_shard_key.items():
|
|
70
|
+
if len(namespaces) == 1:
|
|
71
|
+
namespace = namespaces[0]
|
|
72
|
+
if not namespace.managed_by_external:
|
|
73
|
+
managed_namespaces.append(namespace)
|
|
92
74
|
else:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
logging.
|
|
75
|
+
msg = f"Found multiple definitions for the namespace {key}"
|
|
76
|
+
duplicate_errors.append(NamespaceDuplicateError(msg))
|
|
77
|
+
logging.error(msg)
|
|
96
78
|
|
|
97
|
-
|
|
79
|
+
namespaces = filter_namespaces_by_cluster_and_namespace(
|
|
80
|
+
namespaces=managed_namespaces,
|
|
81
|
+
cluster_names=cluster_name,
|
|
82
|
+
namespace_names=namespace_name,
|
|
83
|
+
)
|
|
98
84
|
|
|
85
|
+
return namespaces, duplicate_errors
|
|
99
86
|
|
|
100
|
-
def manage_namespaces(spec: Mapping[str, str], oc_map: OCMap, dry_run: bool) -> None:
|
|
101
|
-
cluster = spec["cluster"]
|
|
102
|
-
namespace = spec["namespace"]
|
|
103
|
-
desired_state = spec["desired_state"]
|
|
104
87
|
|
|
105
|
-
|
|
88
|
+
def build_desired_state(
|
|
89
|
+
namespaces: Iterable[NamespaceV1],
|
|
90
|
+
) -> list[DesiredState]:
|
|
91
|
+
return [
|
|
92
|
+
DesiredState(
|
|
93
|
+
cluster=namespace.cluster.name,
|
|
94
|
+
namespace=namespace.name,
|
|
95
|
+
delete=namespace.delete or False,
|
|
96
|
+
cluster_admin=namespace.cluster_admin or False,
|
|
97
|
+
)
|
|
98
|
+
for namespace in namespaces
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def manage_namespace(
|
|
103
|
+
desired_state: DesiredState,
|
|
104
|
+
oc_map: OCMap,
|
|
105
|
+
dry_run: bool,
|
|
106
|
+
) -> None:
|
|
107
|
+
namespace = desired_state.namespace
|
|
108
|
+
|
|
109
|
+
oc = oc_map.get(desired_state.cluster, privileged=desired_state.cluster_admin)
|
|
106
110
|
if isinstance(oc, OCLogMsg):
|
|
107
111
|
logging.log(level=oc.log_level, msg=oc.message)
|
|
108
|
-
return
|
|
112
|
+
return
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
current_delete = not oc.project_exists(namespace)
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if not exists and desired_state == NS_STATE_PRESENT:
|
|
115
|
-
if namespace.startswith("openshift-"):
|
|
116
|
-
raise ValueError('cannot request a project starting with "openshift-"')
|
|
117
|
-
action = NS_ACTION_CREATE
|
|
118
|
-
elif exists and desired_state == NS_STATE_ABSENT:
|
|
119
|
-
action = NS_ACTION_DELETE
|
|
116
|
+
if desired_state.delete == current_delete:
|
|
117
|
+
return
|
|
120
118
|
|
|
121
|
-
if
|
|
122
|
-
logging.info([action, cluster, namespace])
|
|
123
|
-
if not dry_run:
|
|
124
|
-
act[action](namespace)
|
|
119
|
+
action = Action.DELETE if desired_state.delete else Action.CREATE
|
|
125
120
|
|
|
121
|
+
logging.info([str(action), desired_state.cluster, namespace])
|
|
122
|
+
if not dry_run:
|
|
123
|
+
match action:
|
|
124
|
+
case Action.CREATE:
|
|
125
|
+
oc.new_project(namespace)
|
|
126
|
+
case Action.DELETE:
|
|
127
|
+
oc.delete_project(namespace)
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
|
|
130
|
+
def build_runtime_errors(
|
|
131
|
+
desired_state: Iterable[DesiredState],
|
|
132
|
+
results: Iterable[Any],
|
|
133
|
+
) -> list[Exception]:
|
|
134
|
+
exceptions = []
|
|
131
135
|
for s, e in zip(desired_state, results, strict=False):
|
|
132
136
|
if isinstance(e, Exception):
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
f"exception: {e!s}"
|
|
137
|
-
)
|
|
138
|
-
logging.error(msg)
|
|
139
|
-
return err
|
|
137
|
+
e.add_note(f"cluster: {s.cluster}, namespace: {s.namespace}")
|
|
138
|
+
exceptions.append(e)
|
|
139
|
+
return exceptions
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
@defer
|
|
@@ -149,15 +149,8 @@ def run(
|
|
|
149
149
|
namespace_name: Sequence[str] | None = None,
|
|
150
150
|
defer: Callable | None = None,
|
|
151
151
|
) -> None:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
namespaces = filter_namespaces_by_cluster_and_namespace(
|
|
155
|
-
namespaces=shard_namespaces,
|
|
156
|
-
cluster_names=cluster_name,
|
|
157
|
-
namespace_names=namespace_name,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
desired_state = get_desired_state(namespaces)
|
|
152
|
+
namespaces, duplicate_errors = get_namespaces(cluster_name, namespace_name)
|
|
153
|
+
desired_state = build_desired_state(namespaces)
|
|
161
154
|
|
|
162
155
|
vault_settings = get_app_interface_vault_settings()
|
|
163
156
|
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
|
@@ -171,16 +164,17 @@ def run(
|
|
|
171
164
|
thread_pool_size=thread_pool_size,
|
|
172
165
|
init_projects=True,
|
|
173
166
|
)
|
|
174
|
-
|
|
175
167
|
if defer:
|
|
176
168
|
defer(oc_map.cleanup)
|
|
177
169
|
|
|
178
170
|
ob.publish_cluster_desired_metrics_from_state(
|
|
179
|
-
desired_state,
|
|
171
|
+
state=({"cluster": s.cluster} for s in desired_state),
|
|
172
|
+
integration=QONTRACT_INTEGRATION,
|
|
173
|
+
kind="Namespace",
|
|
180
174
|
)
|
|
181
175
|
|
|
182
176
|
results = threaded.run(
|
|
183
|
-
|
|
177
|
+
manage_namespace,
|
|
184
178
|
desired_state,
|
|
185
179
|
thread_pool_size,
|
|
186
180
|
return_exceptions=True,
|
|
@@ -188,6 +182,7 @@ def run(
|
|
|
188
182
|
oc_map=oc_map,
|
|
189
183
|
)
|
|
190
184
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
185
|
+
runtime_errors = build_runtime_errors(desired_state, results)
|
|
186
|
+
errors = runtime_errors + duplicate_errors
|
|
187
|
+
if errors:
|
|
188
|
+
raise ExceptionGroup("Reconcile errors occurred", errors)
|