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/utils/oc.py
CHANGED
|
@@ -10,16 +10,16 @@ import re
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import threading
|
|
12
12
|
import time
|
|
13
|
+
from collections import defaultdict
|
|
13
14
|
from contextlib import suppress
|
|
14
15
|
from dataclasses import dataclass
|
|
15
|
-
from datetime import datetime
|
|
16
16
|
from functools import cache, wraps
|
|
17
17
|
from subprocess import Popen
|
|
18
18
|
from threading import Lock
|
|
19
19
|
from typing import TYPE_CHECKING, Any, TextIO, cast
|
|
20
20
|
|
|
21
21
|
import urllib3
|
|
22
|
-
from kubernetes.client import (
|
|
22
|
+
from kubernetes.client import (
|
|
23
23
|
ApiClient,
|
|
24
24
|
Configuration,
|
|
25
25
|
)
|
|
@@ -68,7 +68,18 @@ if TYPE_CHECKING:
|
|
|
68
68
|
urllib3.disable_warnings()
|
|
69
69
|
|
|
70
70
|
GET_REPLICASET_MAX_ATTEMPTS = 20
|
|
71
|
-
|
|
71
|
+
DEFAULT_GROUP = ""
|
|
72
|
+
PROJECT_KIND = "Project.project.openshift.io"
|
|
73
|
+
POD_RECYCLE_SUPPORTED_TRIGGER_KINDS = [
|
|
74
|
+
"ConfigMap",
|
|
75
|
+
"Secret",
|
|
76
|
+
]
|
|
77
|
+
POD_RECYCLE_SUPPORTED_OWNER_KINDS = [
|
|
78
|
+
"DaemonSet",
|
|
79
|
+
"Deployment",
|
|
80
|
+
"DeploymentConfig",
|
|
81
|
+
"StatefulSet",
|
|
82
|
+
]
|
|
72
83
|
|
|
73
84
|
oc_run_execution_counter = Counter(
|
|
74
85
|
name="oc_run_execution_counter",
|
|
@@ -125,23 +136,23 @@ class JSONParsingError(Exception):
|
|
|
125
136
|
pass
|
|
126
137
|
|
|
127
138
|
|
|
128
|
-
class
|
|
139
|
+
class PodNotReadyError(Exception):
|
|
129
140
|
pass
|
|
130
141
|
|
|
131
142
|
|
|
132
|
-
class
|
|
143
|
+
class JobNotRunningError(Exception):
|
|
133
144
|
pass
|
|
134
145
|
|
|
135
146
|
|
|
136
|
-
class
|
|
147
|
+
class RequestEntityTooLargeError(Exception):
|
|
137
148
|
pass
|
|
138
149
|
|
|
139
150
|
|
|
140
|
-
class
|
|
151
|
+
class KindNotFoundError(Exception):
|
|
141
152
|
pass
|
|
142
153
|
|
|
143
154
|
|
|
144
|
-
class
|
|
155
|
+
class AmbiguousResourceTypeError(Exception):
|
|
145
156
|
pass
|
|
146
157
|
|
|
147
158
|
|
|
@@ -380,10 +391,7 @@ class OCCli:
|
|
|
380
391
|
|
|
381
392
|
self.init_projects = init_projects
|
|
382
393
|
if self.init_projects:
|
|
383
|
-
if self.is_kind_supported("
|
|
384
|
-
kind = "Project.project.openshift.io"
|
|
385
|
-
else:
|
|
386
|
-
kind = "Namespace"
|
|
394
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
387
395
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
388
396
|
|
|
389
397
|
self.slow_oc_reconcile_threshold = float(
|
|
@@ -453,10 +461,7 @@ class OCCli:
|
|
|
453
461
|
|
|
454
462
|
self.init_projects = init_projects
|
|
455
463
|
if self.init_projects:
|
|
456
|
-
if self.is_kind_supported("
|
|
457
|
-
kind = "Project.project.openshift.io"
|
|
458
|
-
else:
|
|
459
|
-
kind = "Namespace"
|
|
464
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
460
465
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
461
466
|
|
|
462
467
|
self.slow_oc_reconcile_threshold = float(
|
|
@@ -637,20 +642,24 @@ class OCCli:
|
|
|
637
642
|
def project_exists(self, name: str) -> bool:
|
|
638
643
|
if name in self.projects:
|
|
639
644
|
return True
|
|
645
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
640
646
|
try:
|
|
641
|
-
|
|
642
|
-
self.get(None, "Project.project.openshift.io", name)
|
|
643
|
-
else:
|
|
644
|
-
self.get(None, "Namespace", name)
|
|
647
|
+
self.get(None, kind, name)
|
|
645
648
|
except StatusCodeError as e:
|
|
646
649
|
if "NotFound" in str(e):
|
|
647
650
|
return False
|
|
648
651
|
raise e
|
|
649
652
|
return True
|
|
650
653
|
|
|
654
|
+
def _use_oc_project(self, namespace: str) -> bool:
|
|
655
|
+
# Note, that openshift-* namespaces cannot be created via new-project
|
|
656
|
+
return self.is_kind_supported(PROJECT_KIND) and not namespace.startswith(
|
|
657
|
+
"openshift-"
|
|
658
|
+
)
|
|
659
|
+
|
|
651
660
|
@OCDecorators.process_reconcile_time
|
|
652
661
|
def new_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
|
|
653
|
-
if self.
|
|
662
|
+
if self._use_oc_project(namespace=namespace):
|
|
654
663
|
cmd = ["new-project", namespace]
|
|
655
664
|
else:
|
|
656
665
|
cmd = ["create", "namespace", namespace]
|
|
@@ -666,7 +675,7 @@ class OCCli:
|
|
|
666
675
|
|
|
667
676
|
@OCDecorators.process_reconcile_time
|
|
668
677
|
def delete_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
|
|
669
|
-
if self.
|
|
678
|
+
if self._use_oc_project(namespace=namespace):
|
|
670
679
|
cmd = ["delete", "project", namespace]
|
|
671
680
|
else:
|
|
672
681
|
cmd = ["delete", "namespace", namespace]
|
|
@@ -715,9 +724,9 @@ class OCCli:
|
|
|
715
724
|
|
|
716
725
|
def sa_get_token(self, namespace: str, name: str) -> str:
|
|
717
726
|
cmd = ["sa", "-n", namespace, "get-token", name]
|
|
718
|
-
return self._run(cmd)
|
|
727
|
+
return self._run(cmd).decode("utf-8")
|
|
719
728
|
|
|
720
|
-
def get_api_resources(self) -> dict[str,
|
|
729
|
+
def get_api_resources(self) -> dict[str, list[OCCliApiResource]]:
|
|
721
730
|
with self.api_resources_lock:
|
|
722
731
|
if not self.api_resources:
|
|
723
732
|
cmd = ["api-resources", "--no-headers"]
|
|
@@ -921,108 +930,105 @@ class OCCli:
|
|
|
921
930
|
if not status["ready"]:
|
|
922
931
|
raise PodNotReadyError(name)
|
|
923
932
|
|
|
924
|
-
def
|
|
925
|
-
self,
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
namespace: namespace in which dependant resource is applied.
|
|
931
|
-
dep_kind: dependant resource kind. currently only supports Secret.
|
|
932
|
-
dep_resource: dependant resource."""
|
|
933
|
-
|
|
934
|
-
supported_kinds = ["Secret", "ConfigMap"]
|
|
935
|
-
if dep_kind not in supported_kinds:
|
|
933
|
+
def _is_resource_supported_to_trigger_recycle(
|
|
934
|
+
self,
|
|
935
|
+
namespace: str,
|
|
936
|
+
resource: OR,
|
|
937
|
+
) -> bool:
|
|
938
|
+
if resource.kind not in POD_RECYCLE_SUPPORTED_TRIGGER_KINDS:
|
|
936
939
|
logging.debug([
|
|
937
940
|
"skipping_pod_recycle_unsupported",
|
|
938
941
|
self.cluster_name,
|
|
939
942
|
namespace,
|
|
940
|
-
|
|
943
|
+
resource.kind,
|
|
944
|
+
resource.name,
|
|
941
945
|
])
|
|
942
|
-
return
|
|
946
|
+
return False
|
|
943
947
|
|
|
944
|
-
dep_annotations = dep_resource.body["metadata"].get("annotations", {})
|
|
945
948
|
# Note, that annotations might have been set to None explicitly
|
|
946
|
-
|
|
947
|
-
qontract_recycle =
|
|
948
|
-
if qontract_recycle is True:
|
|
949
|
-
raise RecyclePodsInvalidAnnotationValueError('should be "true"')
|
|
949
|
+
annotations = resource.body["metadata"].get("annotations") or {}
|
|
950
|
+
qontract_recycle = annotations.get("qontract.recycle")
|
|
950
951
|
if qontract_recycle != "true":
|
|
951
952
|
logging.debug([
|
|
952
953
|
"skipping_pod_recycle_no_annotation",
|
|
953
954
|
self.cluster_name,
|
|
954
955
|
namespace,
|
|
955
|
-
|
|
956
|
+
resource.kind,
|
|
957
|
+
resource.name,
|
|
956
958
|
])
|
|
959
|
+
return False
|
|
960
|
+
return True
|
|
961
|
+
|
|
962
|
+
def recycle_pods(
|
|
963
|
+
self,
|
|
964
|
+
dry_run: bool,
|
|
965
|
+
namespace: str,
|
|
966
|
+
resource: OR,
|
|
967
|
+
) -> None:
|
|
968
|
+
"""
|
|
969
|
+
recycles pods which are using the specified resources.
|
|
970
|
+
will only act on Secret or ConfigMap containing the 'qontract.recycle' annotation.
|
|
971
|
+
|
|
972
|
+
Args:
|
|
973
|
+
dry_run (bool): if True, will only log the recycle action without executing it
|
|
974
|
+
namespace (str): namespace of the resource
|
|
975
|
+
resource (OR): resource object (Secret or ConfigMap) to check for pod usage
|
|
976
|
+
"""
|
|
977
|
+
|
|
978
|
+
if not self._is_resource_supported_to_trigger_recycle(namespace, resource):
|
|
957
979
|
return
|
|
958
980
|
|
|
959
|
-
dep_name = dep_resource.name
|
|
960
981
|
pods = self.get(namespace, "Pod")["items"]
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
]
|
|
970
|
-
else:
|
|
971
|
-
raise RecyclePodsUnsupportedKindError(dep_kind)
|
|
972
|
-
|
|
973
|
-
recyclables: dict[str, list[dict[str, Any]]] = {}
|
|
974
|
-
supported_recyclables = [
|
|
975
|
-
"Deployment",
|
|
976
|
-
"DeploymentConfig",
|
|
977
|
-
"StatefulSet",
|
|
978
|
-
"DaemonSet",
|
|
982
|
+
pods_to_recycle = [
|
|
983
|
+
pod
|
|
984
|
+
for pod in pods
|
|
985
|
+
if self.is_resource_used_in_pod(
|
|
986
|
+
name=resource.name,
|
|
987
|
+
kind=resource.kind,
|
|
988
|
+
pod=pod,
|
|
989
|
+
)
|
|
979
990
|
]
|
|
991
|
+
|
|
992
|
+
recycle_names_by_kind = defaultdict(set)
|
|
980
993
|
for pod in pods_to_recycle:
|
|
981
994
|
owner = self.get_obj_root_owner(namespace, pod, allow_not_found=True)
|
|
982
995
|
kind = owner["kind"]
|
|
983
|
-
if kind
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
for
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
for kind, objs in recyclables.items():
|
|
996
|
-
for obj in objs:
|
|
997
|
-
self.recycle(dry_run, namespace, kind, obj)
|
|
998
|
-
|
|
999
|
-
@retry(exceptions=ObjectHasBeenModifiedError)
|
|
996
|
+
if kind in POD_RECYCLE_SUPPORTED_OWNER_KINDS:
|
|
997
|
+
recycle_names_by_kind[kind].add(owner["metadata"]["name"])
|
|
998
|
+
|
|
999
|
+
for kind, names in recycle_names_by_kind.items():
|
|
1000
|
+
for name in names:
|
|
1001
|
+
self.recycle(
|
|
1002
|
+
dry_run=dry_run,
|
|
1003
|
+
namespace=namespace,
|
|
1004
|
+
kind=kind,
|
|
1005
|
+
name=name,
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1000
1008
|
def recycle(
|
|
1001
|
-
self,
|
|
1009
|
+
self,
|
|
1010
|
+
dry_run: bool,
|
|
1011
|
+
namespace: str,
|
|
1012
|
+
kind: str,
|
|
1013
|
+
name: str,
|
|
1002
1014
|
) -> None:
|
|
1003
|
-
"""
|
|
1015
|
+
"""
|
|
1016
|
+
Recycles an object using oc rollout restart, which will add an annotation
|
|
1017
|
+
kubectl.kubernetes.io/restartedAt with the current timestamp to the pod
|
|
1018
|
+
template, triggering a rolling restart.
|
|
1004
1019
|
|
|
1005
|
-
:
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1020
|
+
Args:
|
|
1021
|
+
dry_run (bool): if True, will only log the recycle action without executing it
|
|
1022
|
+
namespace (str): namespace of the object to recycle
|
|
1023
|
+
kind (str): kind of the object to recycle
|
|
1024
|
+
name (str): name of the object to recycle
|
|
1009
1025
|
"""
|
|
1010
|
-
name = obj["metadata"]["name"]
|
|
1011
1026
|
logging.info([f"recycle_{kind.lower()}", self.cluster_name, namespace, name])
|
|
1012
1027
|
if not dry_run:
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
obj = self.get(namespace, kind, name)
|
|
1018
|
-
# honor update strategy by setting annotations to force
|
|
1019
|
-
# a new rollout
|
|
1020
|
-
a = obj["spec"]["template"]["metadata"].get("annotations", {})
|
|
1021
|
-
a["recycle.time"] = recycle_time
|
|
1022
|
-
obj["spec"]["template"]["metadata"]["annotations"] = a
|
|
1023
|
-
cmd = ["apply", "-n", namespace, "-f", "-"]
|
|
1024
|
-
stdin = json_dumps(obj)
|
|
1025
|
-
self._run(cmd, stdin=stdin, apply=True)
|
|
1028
|
+
self._run(
|
|
1029
|
+
["rollout", "restart", f"{kind}/{name}", "-n", namespace],
|
|
1030
|
+
apply=True,
|
|
1031
|
+
)
|
|
1026
1032
|
|
|
1027
1033
|
def get_obj_root_owner(
|
|
1028
1034
|
self,
|
|
@@ -1064,12 +1070,24 @@ class OCCli:
|
|
|
1064
1070
|
)
|
|
1065
1071
|
return obj
|
|
1066
1072
|
|
|
1067
|
-
def
|
|
1068
|
-
|
|
1069
|
-
|
|
1073
|
+
def is_resource_used_in_pod(
|
|
1074
|
+
self,
|
|
1075
|
+
name: str,
|
|
1076
|
+
kind: str,
|
|
1077
|
+
pod: Mapping[str, Any],
|
|
1078
|
+
) -> bool:
|
|
1079
|
+
"""
|
|
1080
|
+
Check if a resource (Secret or ConfigMap) is used in a Pod.
|
|
1081
|
+
|
|
1082
|
+
Args:
|
|
1083
|
+
name: Name of the resource
|
|
1084
|
+
kind: "Secret" or "ConfigMap"
|
|
1085
|
+
pod: Pod object
|
|
1070
1086
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1087
|
+
Returns:
|
|
1088
|
+
True if the resource is used in the Pod, False otherwise.
|
|
1089
|
+
"""
|
|
1090
|
+
used_resources = self.get_resources_used_in_pod_spec(pod["spec"], kind)
|
|
1073
1091
|
return name in used_resources
|
|
1074
1092
|
|
|
1075
1093
|
@staticmethod
|
|
@@ -1078,25 +1096,39 @@ class OCCli:
|
|
|
1078
1096
|
kind: str,
|
|
1079
1097
|
include_optional: bool = True,
|
|
1080
1098
|
) -> dict[str, set[str]]:
|
|
1081
|
-
|
|
1082
|
-
|
|
1099
|
+
"""
|
|
1100
|
+
Get resources (Secrets or ConfigMaps) used in a Pod spec.
|
|
1101
|
+
Returns a dictionary where keys are resource names and values are sets of keys used from that resource.
|
|
1102
|
+
|
|
1103
|
+
Args:
|
|
1104
|
+
spec: Pod spec
|
|
1105
|
+
kind: "Secret" or "ConfigMap"
|
|
1106
|
+
include_optional: Whether to include optional resources
|
|
1107
|
+
|
|
1108
|
+
Returns:
|
|
1109
|
+
A dictionary mapping resource names to sets of keys used.
|
|
1110
|
+
"""
|
|
1111
|
+
match kind:
|
|
1112
|
+
case "Secret":
|
|
1113
|
+
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1114
|
+
"secret",
|
|
1115
|
+
"secretName",
|
|
1116
|
+
"secretRef",
|
|
1117
|
+
"secretKeyRef",
|
|
1118
|
+
"name",
|
|
1119
|
+
)
|
|
1120
|
+
case "ConfigMap":
|
|
1121
|
+
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1122
|
+
"configMap",
|
|
1123
|
+
"name",
|
|
1124
|
+
"configMapRef",
|
|
1125
|
+
"configMapKeyRef",
|
|
1126
|
+
"name",
|
|
1127
|
+
)
|
|
1128
|
+
case _:
|
|
1129
|
+
raise KeyError(f"unsupported resource kind: {kind}")
|
|
1130
|
+
|
|
1083
1131
|
optional = "optional"
|
|
1084
|
-
if kind == "Secret":
|
|
1085
|
-
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1086
|
-
"secret",
|
|
1087
|
-
"secretName",
|
|
1088
|
-
"secretRef",
|
|
1089
|
-
"secretKeyRef",
|
|
1090
|
-
"name",
|
|
1091
|
-
)
|
|
1092
|
-
elif kind == "ConfigMap":
|
|
1093
|
-
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1094
|
-
"configMap",
|
|
1095
|
-
"name",
|
|
1096
|
-
"configMapRef",
|
|
1097
|
-
"configMapKeyRef",
|
|
1098
|
-
"name",
|
|
1099
|
-
)
|
|
1100
1132
|
|
|
1101
1133
|
resources: dict[str, set[str]] = {}
|
|
1102
1134
|
for v in spec.get("volumes") or []:
|
|
@@ -1125,8 +1157,8 @@ class OCCli:
|
|
|
1125
1157
|
continue
|
|
1126
1158
|
resource_name = resource_ref[env_ref]
|
|
1127
1159
|
resources.setdefault(resource_name, set())
|
|
1128
|
-
|
|
1129
|
-
resources[resource_name].add(
|
|
1160
|
+
key = resource_ref["key"]
|
|
1161
|
+
resources[resource_name].add(key)
|
|
1130
1162
|
except (KeyError, TypeError):
|
|
1131
1163
|
continue
|
|
1132
1164
|
|
|
@@ -1187,7 +1219,7 @@ class OCCli:
|
|
|
1187
1219
|
def _run_json(
|
|
1188
1220
|
self, cmd: list[str], allow_not_found: bool = False
|
|
1189
1221
|
) -> dict[str, Any]:
|
|
1190
|
-
out = self._run(cmd, allow_not_found=allow_not_found)
|
|
1222
|
+
out = self._run(cmd, allow_not_found=allow_not_found).decode("utf-8")
|
|
1191
1223
|
|
|
1192
1224
|
try:
|
|
1193
1225
|
out_json = json.loads(out)
|
|
@@ -1196,76 +1228,90 @@ class OCCli:
|
|
|
1196
1228
|
|
|
1197
1229
|
return out_json
|
|
1198
1230
|
|
|
1199
|
-
def
|
|
1200
|
-
|
|
1201
|
-
# the api resources initialization.
|
|
1202
|
-
if not self.api_resources:
|
|
1203
|
-
self.get_api_resources()
|
|
1231
|
+
def parse_kind(self, kind: str) -> tuple[str, str, str]:
|
|
1232
|
+
"""Parse a Kubernetes kind string into its components.
|
|
1204
1233
|
|
|
1205
|
-
|
|
1206
|
-
kind
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1234
|
+
Supports three formats:
|
|
1235
|
+
- kind
|
|
1236
|
+
- kind.group.whatever
|
|
1237
|
+
- kind.group.whatever/version
|
|
1238
|
+
|
|
1239
|
+
Args:
|
|
1240
|
+
kind: A Kubernetes kind string in one of the supported formats
|
|
1241
|
+
|
|
1242
|
+
Returns:
|
|
1243
|
+
Tuple of (kind, group, version) where missing parts are empty strings
|
|
1244
|
+
|
|
1245
|
+
Raises:
|
|
1246
|
+
ValueError: If the kind string format is invalid
|
|
1247
|
+
|
|
1248
|
+
Examples:
|
|
1249
|
+
>>> parse_kind_string("Deployment")
|
|
1250
|
+
('Deployment', '', '')
|
|
1251
|
+
>>> parse_kind_string("ClusterRoleBinding.rbac.authorization.k8s.io")
|
|
1252
|
+
('ClusterRoleBinding', 'rbac.authorization.k8s.io', '')
|
|
1253
|
+
>>> parse_kind_string("CustomResource.mygroup.example.com/v1")
|
|
1254
|
+
('CustomResource', 'mygroup.example.com', 'v1')
|
|
1255
|
+
"""
|
|
1256
|
+
pattern = r"^(?P<kind>[^./]+)(?:\.(?P<group>[^/]+))?(?:/(?P<version>.+))?$"
|
|
1257
|
+
match = re.match(pattern, kind)
|
|
1258
|
+
if not match:
|
|
1259
|
+
raise ValueError(f"Invalid kind string: {kind}")
|
|
1260
|
+
|
|
1261
|
+
kind = match.group("kind") or ""
|
|
1262
|
+
group = match.group("group") or DEFAULT_GROUP
|
|
1263
|
+
version = match.group("version") or ""
|
|
1264
|
+
|
|
1265
|
+
return kind, group, version
|
|
1232
1266
|
|
|
1233
1267
|
def is_kind_supported(self, kind: str) -> bool:
|
|
1234
|
-
|
|
1235
|
-
# the api resources initialization.
|
|
1236
|
-
if not self.api_resources:
|
|
1237
|
-
self.get_api_resources()
|
|
1268
|
+
"""Returns True if the given kind is supported by the cluster, False otherwise.
|
|
1238
1269
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
else:
|
|
1246
|
-
return kind in self.api_resources
|
|
1270
|
+
Kind can be either kind, kind.group or kind.group/version."""
|
|
1271
|
+
try:
|
|
1272
|
+
self.get_api_resource(kind)
|
|
1273
|
+
return True
|
|
1274
|
+
except KindNotFoundError:
|
|
1275
|
+
return False
|
|
1247
1276
|
|
|
1248
1277
|
def is_kind_namespaced(self, kind: str) -> bool:
|
|
1249
|
-
|
|
1250
|
-
|
|
1278
|
+
"""Returns True if the given kind is namespaced, False if it's cluster scoped.
|
|
1279
|
+
|
|
1280
|
+
Kind can be either kind, kind.group or kind.group/version."""
|
|
1281
|
+
return self.get_api_resource(kind).namespaced
|
|
1282
|
+
|
|
1283
|
+
def get_api_resource(self, kind: str) -> OCCliApiResource:
|
|
1284
|
+
"""Return the OCCliApiResource for the given resource type.
|
|
1285
|
+
|
|
1286
|
+
Resource type can be either kind, kind.group or kind.group/version.
|
|
1287
|
+
If kind is not unique, group must be specified."""
|
|
1288
|
+
|
|
1251
1289
|
if not self.api_resources:
|
|
1252
|
-
|
|
1290
|
+
raise RuntimeError("API resources not initialized")
|
|
1253
1291
|
|
|
1254
|
-
|
|
1255
|
-
kind = kg[0]
|
|
1292
|
+
kind, group, _ = self.parse_kind(kind)
|
|
1256
1293
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
raise StatusCodeError(f"Kind {kind} does not exist in the ApiServer")
|
|
1294
|
+
if not (resources := self.api_resources.get(kind)):
|
|
1295
|
+
# the kind not found at all
|
|
1296
|
+
raise KindNotFoundError(f"Unsupported resource type: {kind}")
|
|
1261
1297
|
|
|
1262
|
-
if len(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1298
|
+
if len(resources) == 1 and group == DEFAULT_GROUP:
|
|
1299
|
+
return resources[0]
|
|
1300
|
+
|
|
1301
|
+
# get the resource with the specified group
|
|
1302
|
+
if resource := next((r for r in resources if r.group == group), None):
|
|
1303
|
+
return resource
|
|
1304
|
+
|
|
1305
|
+
# no resource with the specified group found
|
|
1306
|
+
if group == DEFAULT_GROUP:
|
|
1307
|
+
message = (
|
|
1308
|
+
f"Ambiguous resource type: {kind}. "
|
|
1309
|
+
"Please fully qualify it with its API group. E.g., ClusterRoleBinding -> ClusterRoleBinding.rbac.authorization.k8s.io"
|
|
1310
|
+
)
|
|
1311
|
+
raise AmbiguousResourceTypeError(message)
|
|
1312
|
+
|
|
1313
|
+
# group was specified but no matching resource found
|
|
1314
|
+
raise KindNotFoundError(f"Unsupported resource type: {kind}")
|
|
1269
1315
|
|
|
1270
1316
|
|
|
1271
1317
|
REQUEST_TIMEOUT = 60
|
|
@@ -1305,20 +1351,19 @@ class OCNative(OCCli):
|
|
|
1305
1351
|
|
|
1306
1352
|
server = connection_parameters.server_url
|
|
1307
1353
|
|
|
1308
|
-
if server:
|
|
1309
|
-
|
|
1310
|
-
self.api_resources = self.get_api_resources()
|
|
1354
|
+
if not server:
|
|
1355
|
+
raise Exception("Server name is required!")
|
|
1311
1356
|
|
|
1312
|
-
|
|
1313
|
-
raise Exception("
|
|
1357
|
+
if not token:
|
|
1358
|
+
raise Exception("Token is required!")
|
|
1359
|
+
|
|
1360
|
+
self.client = self._get_client(server, token)
|
|
1361
|
+
self.api_resources = self.get_api_resources()
|
|
1314
1362
|
|
|
1315
1363
|
self.projects = set()
|
|
1316
1364
|
self.init_projects = init_projects
|
|
1317
1365
|
if self.init_projects:
|
|
1318
|
-
if self.is_kind_supported("
|
|
1319
|
-
kind = "Project.project.openshift.io"
|
|
1320
|
-
else:
|
|
1321
|
-
kind = "Namespace"
|
|
1366
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
1322
1367
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
1323
1368
|
|
|
1324
1369
|
def __enter__(self) -> OCNative:
|
|
@@ -1368,8 +1413,10 @@ class OCNative(OCCli):
|
|
|
1368
1413
|
|
|
1369
1414
|
@retry(max_attempts=5, exceptions=(ServerTimeoutError))
|
|
1370
1415
|
def get_items(self, kind: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
1371
|
-
|
|
1372
|
-
obj_client = self._get_obj_client(
|
|
1416
|
+
resource = self.get_api_resource(kind)
|
|
1417
|
+
obj_client = self._get_obj_client(
|
|
1418
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1419
|
+
)
|
|
1373
1420
|
|
|
1374
1421
|
namespace = ""
|
|
1375
1422
|
if "namespace" in kwargs:
|
|
@@ -1421,8 +1468,10 @@ class OCNative(OCCli):
|
|
|
1421
1468
|
name: str | None = None,
|
|
1422
1469
|
allow_not_found: bool = False,
|
|
1423
1470
|
) -> dict[str, Any]:
|
|
1424
|
-
|
|
1425
|
-
obj_client = self._get_obj_client(
|
|
1471
|
+
resource = self.get_api_resource(kind)
|
|
1472
|
+
obj_client = self._get_obj_client(
|
|
1473
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1474
|
+
)
|
|
1426
1475
|
try:
|
|
1427
1476
|
obj = obj_client.get(
|
|
1428
1477
|
name=name,
|
|
@@ -1436,8 +1485,10 @@ class OCNative(OCCli):
|
|
|
1436
1485
|
raise StatusCodeError(f"[{self.server}]: {e}") from None
|
|
1437
1486
|
|
|
1438
1487
|
def get_all(self, kind: str, all_namespaces: bool = False) -> dict[str, Any]:
|
|
1439
|
-
|
|
1440
|
-
obj_client = self._get_obj_client(
|
|
1488
|
+
resource = self.get_api_resource(kind)
|
|
1489
|
+
obj_client = self._get_obj_client(
|
|
1490
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1491
|
+
)
|
|
1441
1492
|
try:
|
|
1442
1493
|
return obj_client.get(_request_timeout=REQUEST_TIMEOUT).to_dict()
|
|
1443
1494
|
except NotFoundError as e:
|
reconcile/utils/oc_filters.py
CHANGED
|
@@ -19,19 +19,19 @@ class Namespace(Protocol):
|
|
|
19
19
|
NS = TypeVar("NS", bound=Namespace)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def filter_namespaces_by_cluster(
|
|
22
|
+
def filter_namespaces_by_cluster[NS: Namespace](
|
|
23
23
|
namespaces: Iterable[NS], cluster_names: Iterable[str]
|
|
24
24
|
) -> list[NS]:
|
|
25
25
|
return [n for n in namespaces if n.cluster.name in cluster_names]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def filter_namespaces_by_name(
|
|
28
|
+
def filter_namespaces_by_name[NS: Namespace](
|
|
29
29
|
namespaces: Iterable[NS], namespace_names: Iterable[str]
|
|
30
30
|
) -> list[NS]:
|
|
31
31
|
return [n for n in namespaces if n.name in namespace_names]
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def filter_namespaces_by_cluster_and_namespace(
|
|
34
|
+
def filter_namespaces_by_cluster_and_namespace[NS: Namespace](
|
|
35
35
|
namespaces: Iterable[NS],
|
|
36
36
|
cluster_names: Iterable[str] | None,
|
|
37
37
|
namespace_names: Iterable[str] | None,
|