qontract-reconcile 0.10.2.dev394__py3-none-any.whl → 0.10.2.dev427__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.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/METADATA +5 -4
- {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/RECORD +316 -315
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/base.py +117 -18
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +6 -6
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_ami_cleanup/integration.py +2 -5
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_saml_idp/integration.py +5 -3
- reconcile/aws_saml_roles/integration.py +23 -22
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +1 -1
- reconcile/cli.py +62 -4
- reconcile/dashdotdb_dora.py +1 -1
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +8 -9
- 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 +1 -1
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +3 -2
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +13 -13
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +2 -2
- reconcile/external_resources/state.py +22 -13
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +5 -5
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +46 -7
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +5 -5
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +5 -5
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +5 -5
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +5 -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 +5 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +5 -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 +5 -5
- 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 +5 -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 +5 -5
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -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 +231 -0
- 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 +5 -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 +24 -79
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +42 -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 +5 -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 +5 -5
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +5 -5
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
- reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
- reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +9 -8
- reconcile/jira_permissions_validator.py +2 -2
- 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 +97 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +27 -29
- reconcile/openshift_rolebindings.py +7 -11
- reconcile/openshift_saas_deploy.py +4 -5
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +1 -1
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_mirror.py +1 -1
- reconcile/queries.py +6 -0
- 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/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +31 -11
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/page.py +4 -9
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +2 -2
- 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 +2 -2
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +6 -6
- reconcile/terraform_tgw_attachments.py +20 -18
- reconcile/terraform_vpc_resources/integration.py +3 -1
- 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 +11 -11
- 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/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/early_exit_cache.py +8 -10
- reconcile/utils/gitlab_api.py +7 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/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 +2 -5
- 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 +1 -1
- reconcile/utils/merge_request_manager/parser.py +4 -4
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/notificator.py +1 -1
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +246 -201
- 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/service_log.py +1 -1
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/openshift_resource.py +5 -0
- reconcile/utils/pagerduty_api.py +5 -2
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +1 -4
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +1 -1
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +23 -20
- reconcile/utils/saasherder/saasherder.py +46 -24
- reconcile/utils/slack_api.py +2 -2
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +1 -1
- reconcile/utils/terrascript_aws_client.py +47 -43
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +1 -1
- 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 +6 -5
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev427.dist-info}/entry_points.txt +0 -0
reconcile/utils/oc.py
CHANGED
|
@@ -10,6 +10,7 @@ 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
16
|
from functools import cache, wraps
|
|
@@ -18,7 +19,7 @@ from threading import Lock
|
|
|
18
19
|
from typing import TYPE_CHECKING, Any, TextIO, cast
|
|
19
20
|
|
|
20
21
|
import urllib3
|
|
21
|
-
from kubernetes.client import (
|
|
22
|
+
from kubernetes.client import (
|
|
22
23
|
ApiClient,
|
|
23
24
|
Configuration,
|
|
24
25
|
)
|
|
@@ -46,7 +47,6 @@ from sretoolbox.utils import (
|
|
|
46
47
|
)
|
|
47
48
|
|
|
48
49
|
from reconcile.status import RunningState
|
|
49
|
-
from reconcile.utils.datetime_util import utc_now
|
|
50
50
|
from reconcile.utils.json import json_dumps
|
|
51
51
|
from reconcile.utils.jump_host import (
|
|
52
52
|
JumphostParameters,
|
|
@@ -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,11 +642,9 @@ 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
|
|
@@ -650,7 +653,7 @@ class OCCli:
|
|
|
650
653
|
|
|
651
654
|
@OCDecorators.process_reconcile_time
|
|
652
655
|
def new_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
|
|
653
|
-
if self.is_kind_supported(
|
|
656
|
+
if self.is_kind_supported(PROJECT_KIND):
|
|
654
657
|
cmd = ["new-project", namespace]
|
|
655
658
|
else:
|
|
656
659
|
cmd = ["create", "namespace", namespace]
|
|
@@ -666,7 +669,7 @@ class OCCli:
|
|
|
666
669
|
|
|
667
670
|
@OCDecorators.process_reconcile_time
|
|
668
671
|
def delete_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
|
|
669
|
-
if self.is_kind_supported(
|
|
672
|
+
if self.is_kind_supported(PROJECT_KIND):
|
|
670
673
|
cmd = ["delete", "project", namespace]
|
|
671
674
|
else:
|
|
672
675
|
cmd = ["delete", "namespace", namespace]
|
|
@@ -715,9 +718,9 @@ class OCCli:
|
|
|
715
718
|
|
|
716
719
|
def sa_get_token(self, namespace: str, name: str) -> str:
|
|
717
720
|
cmd = ["sa", "-n", namespace, "get-token", name]
|
|
718
|
-
return self._run(cmd)
|
|
721
|
+
return self._run(cmd).decode("utf-8")
|
|
719
722
|
|
|
720
|
-
def get_api_resources(self) -> dict[str,
|
|
723
|
+
def get_api_resources(self) -> dict[str, list[OCCliApiResource]]:
|
|
721
724
|
with self.api_resources_lock:
|
|
722
725
|
if not self.api_resources:
|
|
723
726
|
cmd = ["api-resources", "--no-headers"]
|
|
@@ -921,108 +924,105 @@ class OCCli:
|
|
|
921
924
|
if not status["ready"]:
|
|
922
925
|
raise PodNotReadyError(name)
|
|
923
926
|
|
|
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:
|
|
927
|
+
def _is_resource_supported_to_trigger_recycle(
|
|
928
|
+
self,
|
|
929
|
+
namespace: str,
|
|
930
|
+
resource: OR,
|
|
931
|
+
) -> bool:
|
|
932
|
+
if resource.kind not in POD_RECYCLE_SUPPORTED_TRIGGER_KINDS:
|
|
936
933
|
logging.debug([
|
|
937
934
|
"skipping_pod_recycle_unsupported",
|
|
938
935
|
self.cluster_name,
|
|
939
936
|
namespace,
|
|
940
|
-
|
|
937
|
+
resource.kind,
|
|
938
|
+
resource.name,
|
|
941
939
|
])
|
|
942
|
-
return
|
|
940
|
+
return False
|
|
943
941
|
|
|
944
|
-
dep_annotations = dep_resource.body["metadata"].get("annotations", {})
|
|
945
942
|
# 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"')
|
|
943
|
+
annotations = resource.body["metadata"].get("annotations") or {}
|
|
944
|
+
qontract_recycle = annotations.get("qontract.recycle")
|
|
950
945
|
if qontract_recycle != "true":
|
|
951
946
|
logging.debug([
|
|
952
947
|
"skipping_pod_recycle_no_annotation",
|
|
953
948
|
self.cluster_name,
|
|
954
949
|
namespace,
|
|
955
|
-
|
|
950
|
+
resource.kind,
|
|
951
|
+
resource.name,
|
|
956
952
|
])
|
|
953
|
+
return False
|
|
954
|
+
return True
|
|
955
|
+
|
|
956
|
+
def recycle_pods(
|
|
957
|
+
self,
|
|
958
|
+
dry_run: bool,
|
|
959
|
+
namespace: str,
|
|
960
|
+
resource: OR,
|
|
961
|
+
) -> None:
|
|
962
|
+
"""
|
|
963
|
+
recycles pods which are using the specified resources.
|
|
964
|
+
will only act on Secret or ConfigMap containing the 'qontract.recycle' annotation.
|
|
965
|
+
|
|
966
|
+
Args:
|
|
967
|
+
dry_run (bool): if True, will only log the recycle action without executing it
|
|
968
|
+
namespace (str): namespace of the resource
|
|
969
|
+
resource (OR): resource object (Secret or ConfigMap) to check for pod usage
|
|
970
|
+
"""
|
|
971
|
+
|
|
972
|
+
if not self._is_resource_supported_to_trigger_recycle(namespace, resource):
|
|
957
973
|
return
|
|
958
974
|
|
|
959
|
-
dep_name = dep_resource.name
|
|
960
975
|
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",
|
|
976
|
+
pods_to_recycle = [
|
|
977
|
+
pod
|
|
978
|
+
for pod in pods
|
|
979
|
+
if self.is_resource_used_in_pod(
|
|
980
|
+
name=resource.name,
|
|
981
|
+
kind=resource.kind,
|
|
982
|
+
pod=pod,
|
|
983
|
+
)
|
|
979
984
|
]
|
|
985
|
+
|
|
986
|
+
recycle_names_by_kind = defaultdict(set)
|
|
980
987
|
for pod in pods_to_recycle:
|
|
981
988
|
owner = self.get_obj_root_owner(namespace, pod, allow_not_found=True)
|
|
982
989
|
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)
|
|
990
|
+
if kind in POD_RECYCLE_SUPPORTED_OWNER_KINDS:
|
|
991
|
+
recycle_names_by_kind[kind].add(owner["metadata"]["name"])
|
|
992
|
+
|
|
993
|
+
for kind, names in recycle_names_by_kind.items():
|
|
994
|
+
for name in names:
|
|
995
|
+
self.recycle(
|
|
996
|
+
dry_run=dry_run,
|
|
997
|
+
namespace=namespace,
|
|
998
|
+
kind=kind,
|
|
999
|
+
name=name,
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1000
1002
|
def recycle(
|
|
1001
|
-
self,
|
|
1003
|
+
self,
|
|
1004
|
+
dry_run: bool,
|
|
1005
|
+
namespace: str,
|
|
1006
|
+
kind: str,
|
|
1007
|
+
name: str,
|
|
1002
1008
|
) -> None:
|
|
1003
|
-
"""
|
|
1009
|
+
"""
|
|
1010
|
+
Recycles an object using oc rollout restart, which will add an annotation
|
|
1011
|
+
kubectl.kubernetes.io/restartedAt with the current timestamp to the pod
|
|
1012
|
+
template, triggering a rolling restart.
|
|
1004
1013
|
|
|
1005
|
-
:
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1014
|
+
Args:
|
|
1015
|
+
dry_run (bool): if True, will only log the recycle action without executing it
|
|
1016
|
+
namespace (str): namespace of the object to recycle
|
|
1017
|
+
kind (str): kind of the object to recycle
|
|
1018
|
+
name (str): name of the object to recycle
|
|
1009
1019
|
"""
|
|
1010
|
-
name = obj["metadata"]["name"]
|
|
1011
1020
|
logging.info([f"recycle_{kind.lower()}", self.cluster_name, namespace, name])
|
|
1012
1021
|
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)
|
|
1022
|
+
self._run(
|
|
1023
|
+
["rollout", "restart", f"{kind}/{name}", "-n", namespace],
|
|
1024
|
+
apply=True,
|
|
1025
|
+
)
|
|
1026
1026
|
|
|
1027
1027
|
def get_obj_root_owner(
|
|
1028
1028
|
self,
|
|
@@ -1064,12 +1064,24 @@ class OCCli:
|
|
|
1064
1064
|
)
|
|
1065
1065
|
return obj
|
|
1066
1066
|
|
|
1067
|
-
def
|
|
1068
|
-
|
|
1069
|
-
|
|
1067
|
+
def is_resource_used_in_pod(
|
|
1068
|
+
self,
|
|
1069
|
+
name: str,
|
|
1070
|
+
kind: str,
|
|
1071
|
+
pod: Mapping[str, Any],
|
|
1072
|
+
) -> bool:
|
|
1073
|
+
"""
|
|
1074
|
+
Check if a resource (Secret or ConfigMap) is used in a Pod.
|
|
1075
|
+
|
|
1076
|
+
Args:
|
|
1077
|
+
name: Name of the resource
|
|
1078
|
+
kind: "Secret" or "ConfigMap"
|
|
1079
|
+
pod: Pod object
|
|
1070
1080
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1081
|
+
Returns:
|
|
1082
|
+
True if the resource is used in the Pod, False otherwise.
|
|
1083
|
+
"""
|
|
1084
|
+
used_resources = self.get_resources_used_in_pod_spec(pod["spec"], kind)
|
|
1073
1085
|
return name in used_resources
|
|
1074
1086
|
|
|
1075
1087
|
@staticmethod
|
|
@@ -1078,25 +1090,39 @@ class OCCli:
|
|
|
1078
1090
|
kind: str,
|
|
1079
1091
|
include_optional: bool = True,
|
|
1080
1092
|
) -> dict[str, set[str]]:
|
|
1081
|
-
|
|
1082
|
-
|
|
1093
|
+
"""
|
|
1094
|
+
Get resources (Secrets or ConfigMaps) used in a Pod spec.
|
|
1095
|
+
Returns a dictionary where keys are resource names and values are sets of keys used from that resource.
|
|
1096
|
+
|
|
1097
|
+
Args:
|
|
1098
|
+
spec: Pod spec
|
|
1099
|
+
kind: "Secret" or "ConfigMap"
|
|
1100
|
+
include_optional: Whether to include optional resources
|
|
1101
|
+
|
|
1102
|
+
Returns:
|
|
1103
|
+
A dictionary mapping resource names to sets of keys used.
|
|
1104
|
+
"""
|
|
1105
|
+
match kind:
|
|
1106
|
+
case "Secret":
|
|
1107
|
+
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1108
|
+
"secret",
|
|
1109
|
+
"secretName",
|
|
1110
|
+
"secretRef",
|
|
1111
|
+
"secretKeyRef",
|
|
1112
|
+
"name",
|
|
1113
|
+
)
|
|
1114
|
+
case "ConfigMap":
|
|
1115
|
+
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1116
|
+
"configMap",
|
|
1117
|
+
"name",
|
|
1118
|
+
"configMapRef",
|
|
1119
|
+
"configMapKeyRef",
|
|
1120
|
+
"name",
|
|
1121
|
+
)
|
|
1122
|
+
case _:
|
|
1123
|
+
raise KeyError(f"unsupported resource kind: {kind}")
|
|
1124
|
+
|
|
1083
1125
|
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
1126
|
|
|
1101
1127
|
resources: dict[str, set[str]] = {}
|
|
1102
1128
|
for v in spec.get("volumes") or []:
|
|
@@ -1125,8 +1151,8 @@ class OCCli:
|
|
|
1125
1151
|
continue
|
|
1126
1152
|
resource_name = resource_ref[env_ref]
|
|
1127
1153
|
resources.setdefault(resource_name, set())
|
|
1128
|
-
|
|
1129
|
-
resources[resource_name].add(
|
|
1154
|
+
key = resource_ref["key"]
|
|
1155
|
+
resources[resource_name].add(key)
|
|
1130
1156
|
except (KeyError, TypeError):
|
|
1131
1157
|
continue
|
|
1132
1158
|
|
|
@@ -1187,7 +1213,7 @@ class OCCli:
|
|
|
1187
1213
|
def _run_json(
|
|
1188
1214
|
self, cmd: list[str], allow_not_found: bool = False
|
|
1189
1215
|
) -> dict[str, Any]:
|
|
1190
|
-
out = self._run(cmd, allow_not_found=allow_not_found)
|
|
1216
|
+
out = self._run(cmd, allow_not_found=allow_not_found).decode("utf-8")
|
|
1191
1217
|
|
|
1192
1218
|
try:
|
|
1193
1219
|
out_json = json.loads(out)
|
|
@@ -1196,76 +1222,90 @@ class OCCli:
|
|
|
1196
1222
|
|
|
1197
1223
|
return out_json
|
|
1198
1224
|
|
|
1199
|
-
def
|
|
1200
|
-
|
|
1201
|
-
# the api resources initialization.
|
|
1202
|
-
if not self.api_resources:
|
|
1203
|
-
self.get_api_resources()
|
|
1225
|
+
def parse_kind(self, kind: str) -> tuple[str, str, str]:
|
|
1226
|
+
"""Parse a Kubernetes kind string into its components.
|
|
1204
1227
|
|
|
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
|
-
|
|
1228
|
+
Supports three formats:
|
|
1229
|
+
- kind
|
|
1230
|
+
- kind.group.whatever
|
|
1231
|
+
- kind.group.whatever/version
|
|
1232
|
+
|
|
1233
|
+
Args:
|
|
1234
|
+
kind: A Kubernetes kind string in one of the supported formats
|
|
1235
|
+
|
|
1236
|
+
Returns:
|
|
1237
|
+
Tuple of (kind, group, version) where missing parts are empty strings
|
|
1238
|
+
|
|
1239
|
+
Raises:
|
|
1240
|
+
ValueError: If the kind string format is invalid
|
|
1241
|
+
|
|
1242
|
+
Examples:
|
|
1243
|
+
>>> parse_kind_string("Deployment")
|
|
1244
|
+
('Deployment', '', '')
|
|
1245
|
+
>>> parse_kind_string("ClusterRoleBinding.rbac.authorization.k8s.io")
|
|
1246
|
+
('ClusterRoleBinding', 'rbac.authorization.k8s.io', '')
|
|
1247
|
+
>>> parse_kind_string("CustomResource.mygroup.example.com/v1")
|
|
1248
|
+
('CustomResource', 'mygroup.example.com', 'v1')
|
|
1249
|
+
"""
|
|
1250
|
+
pattern = r"^(?P<kind>[^./]+)(?:\.(?P<group>[^/]+))?(?:/(?P<version>.+))?$"
|
|
1251
|
+
match = re.match(pattern, kind)
|
|
1252
|
+
if not match:
|
|
1253
|
+
raise ValueError(f"Invalid kind string: {kind}")
|
|
1254
|
+
|
|
1255
|
+
kind = match.group("kind") or ""
|
|
1256
|
+
group = match.group("group") or DEFAULT_GROUP
|
|
1257
|
+
version = match.group("version") or ""
|
|
1258
|
+
|
|
1259
|
+
return kind, group, version
|
|
1232
1260
|
|
|
1233
1261
|
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()
|
|
1262
|
+
"""Returns True if the given kind is supported by the cluster, False otherwise.
|
|
1238
1263
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
else:
|
|
1246
|
-
return kind in self.api_resources
|
|
1264
|
+
Kind can be either kind, kind.group or kind.group/version."""
|
|
1265
|
+
try:
|
|
1266
|
+
self.get_api_resource(kind)
|
|
1267
|
+
return True
|
|
1268
|
+
except KindNotFoundError:
|
|
1269
|
+
return False
|
|
1247
1270
|
|
|
1248
1271
|
def is_kind_namespaced(self, kind: str) -> bool:
|
|
1249
|
-
|
|
1250
|
-
|
|
1272
|
+
"""Returns True if the given kind is namespaced, False if it's cluster scoped.
|
|
1273
|
+
|
|
1274
|
+
Kind can be either kind, kind.group or kind.group/version."""
|
|
1275
|
+
return self.get_api_resource(kind).namespaced
|
|
1276
|
+
|
|
1277
|
+
def get_api_resource(self, kind: str) -> OCCliApiResource:
|
|
1278
|
+
"""Return the OCCliApiResource for the given resource type.
|
|
1279
|
+
|
|
1280
|
+
Resource type can be either kind, kind.group or kind.group/version.
|
|
1281
|
+
If kind is not unique, group must be specified."""
|
|
1282
|
+
|
|
1251
1283
|
if not self.api_resources:
|
|
1252
|
-
|
|
1284
|
+
raise RuntimeError("API resources not initialized")
|
|
1253
1285
|
|
|
1254
|
-
|
|
1255
|
-
kind = kg[0]
|
|
1286
|
+
kind, group, _ = self.parse_kind(kind)
|
|
1256
1287
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
raise StatusCodeError(f"Kind {kind} does not exist in the ApiServer")
|
|
1288
|
+
if not (resources := self.api_resources.get(kind)):
|
|
1289
|
+
# the kind not found at all
|
|
1290
|
+
raise KindNotFoundError(f"Unsupported resource type: {kind}")
|
|
1261
1291
|
|
|
1262
|
-
if len(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1292
|
+
if len(resources) == 1 and group == DEFAULT_GROUP:
|
|
1293
|
+
return resources[0]
|
|
1294
|
+
|
|
1295
|
+
# get the resource with the specified group
|
|
1296
|
+
if resource := next((r for r in resources if r.group == group), None):
|
|
1297
|
+
return resource
|
|
1298
|
+
|
|
1299
|
+
# no resource with the specified group found
|
|
1300
|
+
if group == DEFAULT_GROUP:
|
|
1301
|
+
message = (
|
|
1302
|
+
f"Ambiguous resource type: {kind}. "
|
|
1303
|
+
"Please fully qualify it with its API group. E.g., ClusterRoleBinding -> ClusterRoleBinding.rbac.authorization.k8s.io"
|
|
1304
|
+
)
|
|
1305
|
+
raise AmbiguousResourceTypeError(message)
|
|
1306
|
+
|
|
1307
|
+
# group was specified but no matching resource found
|
|
1308
|
+
raise KindNotFoundError(f"Unsupported resource type: {kind}")
|
|
1269
1309
|
|
|
1270
1310
|
|
|
1271
1311
|
REQUEST_TIMEOUT = 60
|
|
@@ -1305,20 +1345,19 @@ class OCNative(OCCli):
|
|
|
1305
1345
|
|
|
1306
1346
|
server = connection_parameters.server_url
|
|
1307
1347
|
|
|
1308
|
-
if server:
|
|
1309
|
-
|
|
1310
|
-
self.api_resources = self.get_api_resources()
|
|
1348
|
+
if not server:
|
|
1349
|
+
raise Exception("Server name is required!")
|
|
1311
1350
|
|
|
1312
|
-
|
|
1313
|
-
raise Exception("
|
|
1351
|
+
if not token:
|
|
1352
|
+
raise Exception("Token is required!")
|
|
1353
|
+
|
|
1354
|
+
self.client = self._get_client(server, token)
|
|
1355
|
+
self.api_resources = self.get_api_resources()
|
|
1314
1356
|
|
|
1315
1357
|
self.projects = set()
|
|
1316
1358
|
self.init_projects = init_projects
|
|
1317
1359
|
if self.init_projects:
|
|
1318
|
-
if self.is_kind_supported("
|
|
1319
|
-
kind = "Project.project.openshift.io"
|
|
1320
|
-
else:
|
|
1321
|
-
kind = "Namespace"
|
|
1360
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
1322
1361
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
1323
1362
|
|
|
1324
1363
|
def __enter__(self) -> OCNative:
|
|
@@ -1368,8 +1407,10 @@ class OCNative(OCCli):
|
|
|
1368
1407
|
|
|
1369
1408
|
@retry(max_attempts=5, exceptions=(ServerTimeoutError))
|
|
1370
1409
|
def get_items(self, kind: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
1371
|
-
|
|
1372
|
-
obj_client = self._get_obj_client(
|
|
1410
|
+
resource = self.get_api_resource(kind)
|
|
1411
|
+
obj_client = self._get_obj_client(
|
|
1412
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1413
|
+
)
|
|
1373
1414
|
|
|
1374
1415
|
namespace = ""
|
|
1375
1416
|
if "namespace" in kwargs:
|
|
@@ -1421,8 +1462,10 @@ class OCNative(OCCli):
|
|
|
1421
1462
|
name: str | None = None,
|
|
1422
1463
|
allow_not_found: bool = False,
|
|
1423
1464
|
) -> dict[str, Any]:
|
|
1424
|
-
|
|
1425
|
-
obj_client = self._get_obj_client(
|
|
1465
|
+
resource = self.get_api_resource(kind)
|
|
1466
|
+
obj_client = self._get_obj_client(
|
|
1467
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1468
|
+
)
|
|
1426
1469
|
try:
|
|
1427
1470
|
obj = obj_client.get(
|
|
1428
1471
|
name=name,
|
|
@@ -1436,8 +1479,10 @@ class OCNative(OCCli):
|
|
|
1436
1479
|
raise StatusCodeError(f"[{self.server}]: {e}") from None
|
|
1437
1480
|
|
|
1438
1481
|
def get_all(self, kind: str, all_namespaces: bool = False) -> dict[str, Any]:
|
|
1439
|
-
|
|
1440
|
-
obj_client = self._get_obj_client(
|
|
1482
|
+
resource = self.get_api_resource(kind)
|
|
1483
|
+
obj_client = self._get_obj_client(
|
|
1484
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1485
|
+
)
|
|
1441
1486
|
try:
|
|
1442
1487
|
return obj_client.get(_request_timeout=REQUEST_TIMEOUT).to_dict()
|
|
1443
1488
|
except NotFoundError as e:
|