qontract-reconcile 0.10.2.dev299__py3-none-any.whl → 0.10.2.dev430__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +399 -394
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/base.py +134 -32
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +8 -8
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_ami_cleanup/integration.py +8 -12
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
- reconcile/aws_ecr_image_pull_secrets.py +4 -4
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +32 -25
- reconcile/aws_version_sync/integration.py +125 -84
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +1 -1
- reconcile/change_owners/diff.py +2 -4
- reconcile/checkpoint.py +12 -4
- reconcile/cli.py +111 -18
- reconcile/cluster_deployment_mapper.py +2 -3
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +125 -121
- reconcile/deadmanssnitch.py +1 -5
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -1
- reconcile/endpoints_discovery/merge_request_manager.py +9 -11
- reconcile/external_resources/factories.py +5 -12
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -5
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +20 -20
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +8 -11
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +8 -5
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_mr_sqs_consumer.py +2 -2
- reconcile/gitlab_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +10 -10
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +6 -6
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +51 -12
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
- reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +120 -0
- reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +23 -10
- reconcile/gql_definitions/common/aws_vpcs.py +11 -11
- reconcile/gql_definitions/common/clusters.py +37 -35
- reconcile/gql_definitions/common/clusters_minimal.py +14 -14
- reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
- reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
- reconcile/gql_definitions/common/github_orgs.py +10 -10
- reconcile/gql_definitions/common/jira_settings.py +10 -10
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +42 -44
- reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
- reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
- reconcile/gql_definitions/common/ocm_environments.py +19 -19
- reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
- reconcile/gql_definitions/common/pipeline_providers.py +29 -29
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +44 -44
- reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
- reconcile/gql_definitions/common/state_aws_account.py +7 -8
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +9 -9
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +8 -8
- reconcile/gql_definitions/email_sender/users.py +6 -6
- reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
- reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
- reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +492 -410
- reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +10 -5
- reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
- reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
- reconcile/gql_definitions/fragments/disable.py +5 -5
- reconcile/gql_definitions/fragments/email_service.py +5 -5
- reconcile/gql_definitions/fragments/email_user.py +5 -5
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
- reconcile/gql_definitions/fragments/membership_source.py +5 -5
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
- reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_values.py +5 -5
- reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
- reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
- reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
- reconcile/gql_definitions/fragments/terraform_state.py +5 -5
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
- reconcile/gql_definitions/fragments/user.py +5 -5
- reconcile/gql_definitions/fragments/vault_secret.py +5 -5
- reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
- reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
- reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
- reconcile/gql_definitions/integrations/integrations.py +48 -51
- reconcile/gql_definitions/introspection.json +3050 -1393
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
- reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +9 -9
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
- reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
- reconcile/gql_definitions/rhcs/certs.py +33 -87
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +18 -18
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
- reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
- reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +6 -7
- reconcile/gql_definitions/statuspage/statuspages.py +9 -9
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
- reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +448 -402
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
- reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
- reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_job_builder.py +1 -1
- reconcile/jenkins_worker_fleets.py +80 -11
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -56
- reconcile/ocm_aws_infrastructure_access.py +1 -1
- reconcile/ocm_clusters.py +4 -4
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/ocm_machine_pools.py +33 -27
- reconcile/openshift_base.py +122 -10
- reconcile/openshift_cluster_bots.py +5 -5
- reconcile/openshift_groups.py +5 -0
- reconcile/openshift_limitranges.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +97 -101
- reconcile/openshift_resources_base.py +10 -5
- reconcile/openshift_rhcs_certs.py +77 -40
- reconcile/openshift_rolebindings.py +230 -130
- reconcile/openshift_saas_deploy.py +6 -7
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +8 -7
- reconcile/openshift_tekton_resources.py +1 -1
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/openshift_users.py +5 -3
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/oum/providers.py +1 -1
- reconcile/prometheus_rules_tester/integration.py +4 -4
- reconcile/quay_mirror.py +1 -1
- reconcile/queries.py +131 -0
- reconcile/requests_sender.py +8 -3
- reconcile/resource_scraper.py +1 -5
- reconcile/rhidp/common.py +5 -5
- reconcile/rhidp/sso_client/base.py +19 -10
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/sendgrid_teammates.py +20 -9
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status.py +2 -2
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
- reconcile/templating/lib/merge_request_manager.py +2 -2
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +12 -13
- reconcile/terraform_aws_route53.py +18 -8
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +12 -13
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +18 -10
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +29 -21
- reconcile/terraform_vpc_peerings.py +16 -4
- reconcile/terraform_vpc_resources/integration.py +32 -2
- reconcile/typed_queries/app_interface_roles.py +10 -0
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/cost_report/app_names.py +1 -1
- reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
- reconcile/typed_queries/saas_files.py +13 -13
- reconcile/typed_queries/status_board.py +2 -2
- reconcile/unleash_feature_toggles/integration.py +4 -2
- reconcile/utils/acs/base.py +6 -3
- reconcile/utils/acs/policies.py +2 -2
- reconcile/utils/aggregated_list.py +4 -3
- reconcile/utils/aws_api.py +51 -20
- reconcile/utils/aws_api_typed/api.py +38 -9
- reconcile/utils/aws_api_typed/cloudformation.py +149 -0
- reconcile/utils/aws_api_typed/logs.py +73 -0
- reconcile/utils/aws_api_typed/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +11 -12
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +24 -1
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +7 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gpg.py +5 -3
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helm.py +2 -1
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/imap_client.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jenkins_api.py +24 -1
- reconcile/utils/jinja2/utils.py +6 -8
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +59 -43
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +74 -0
- reconcile/utils/ldap_client.py +4 -3
- reconcile/utils/lean_terraform_client.py +3 -1
- reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
- reconcile/utils/membershipsources/models.py +16 -23
- reconcile/utils/membershipsources/resolver.py +4 -2
- reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
- reconcile/utils/merge_request_manager/parser.py +6 -6
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/__init__.py +3 -1
- reconcile/utils/mr/app_interface_reporter.py +6 -3
- reconcile/utils/mr/aws_access.py +1 -1
- reconcile/utils/mr/base.py +7 -13
- reconcile/utils/mr/clusters_updates.py +4 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
- reconcile/utils/mr/promote_qontract.py +28 -12
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +7 -6
- reconcile/utils/oc.py +445 -336
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +27 -20
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/ocm.py +81 -71
- reconcile/utils/ocm/products.py +9 -3
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/ocm_base_client.py +4 -4
- reconcile/utils/openshift_resource.py +83 -52
- reconcile/utils/openssl.py +2 -2
- reconcile/utils/output.py +3 -2
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/raw_github_api.py +11 -8
- reconcile/utils/repo_owners.py +21 -29
- reconcile/utils/rhcsv2_certs.py +138 -35
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/meta.py +2 -1
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +25 -21
- reconcile/utils/saasherder/saasherder.py +60 -32
- reconcile/utils/secret_reader.py +6 -6
- reconcile/utils/sharding.py +1 -1
- reconcile/utils/slack_api.py +26 -4
- reconcile/utils/sloth.py +224 -0
- reconcile/utils/sqs_gateway.py +16 -11
- reconcile/utils/state.py +2 -1
- reconcile/utils/structs.py +4 -4
- reconcile/utils/terraform_client.py +32 -29
- reconcile/utils/terrascript_aws_client.py +658 -480
- reconcile/utils/three_way_diff_strategy.py +1 -1
- reconcile/utils/throughput.py +1 -1
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +44 -41
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +119 -58
- reconcile/vpc_peerings_validator.py +2 -2
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/cost_report/cost_management_api.py +3 -3
- tools/cli_commands/cost_report/view.py +7 -6
- tools/cli_commands/erv2.py +1 -1
- tools/cli_commands/gpg_encrypt.py +4 -1
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +36 -21
- tools/sre_checkpoints/util.py +5 -3
- tools/template_validation.py +3 -1
- reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
- reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
- reconcile/jenkins/__init__.py +0 -0
- reconcile/jenkins/types.py +0 -77
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
reconcile/utils/oc.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import copy
|
|
4
|
+
import itertools
|
|
2
5
|
import json
|
|
3
6
|
import logging
|
|
4
7
|
import os
|
|
@@ -7,20 +10,16 @@ import re
|
|
|
7
10
|
import subprocess
|
|
8
11
|
import threading
|
|
9
12
|
import time
|
|
10
|
-
from collections
|
|
11
|
-
Iterable,
|
|
12
|
-
Mapping,
|
|
13
|
-
)
|
|
13
|
+
from collections import defaultdict
|
|
14
14
|
from contextlib import suppress
|
|
15
15
|
from dataclasses import dataclass
|
|
16
|
-
from datetime import datetime
|
|
17
16
|
from functools import cache, wraps
|
|
18
17
|
from subprocess import Popen
|
|
19
18
|
from threading import Lock
|
|
20
|
-
from typing import Any
|
|
19
|
+
from typing import TYPE_CHECKING, Any, TextIO, cast
|
|
21
20
|
|
|
22
21
|
import urllib3
|
|
23
|
-
from kubernetes.client import (
|
|
22
|
+
from kubernetes.client import (
|
|
24
23
|
ApiClient,
|
|
25
24
|
Configuration,
|
|
26
25
|
)
|
|
@@ -48,12 +47,12 @@ from sretoolbox.utils import (
|
|
|
48
47
|
)
|
|
49
48
|
|
|
50
49
|
from reconcile.status import RunningState
|
|
50
|
+
from reconcile.utils.json import json_dumps
|
|
51
51
|
from reconcile.utils.jump_host import (
|
|
52
52
|
JumphostParameters,
|
|
53
53
|
JumpHostSSH,
|
|
54
54
|
)
|
|
55
55
|
from reconcile.utils.metrics import reconcile_time
|
|
56
|
-
from reconcile.utils.oc_connection_parameters import OCConnectionParameters
|
|
57
56
|
from reconcile.utils.openshift_resource import OpenshiftResource as OR
|
|
58
57
|
from reconcile.utils.secret_reader import (
|
|
59
58
|
SecretNotFoundError,
|
|
@@ -61,10 +60,26 @@ from reconcile.utils.secret_reader import (
|
|
|
61
60
|
)
|
|
62
61
|
from reconcile.utils.unleash import get_feature_toggle_state
|
|
63
62
|
|
|
63
|
+
if TYPE_CHECKING:
|
|
64
|
+
from collections.abc import Callable, Iterable, Mapping, MutableMapping
|
|
65
|
+
|
|
66
|
+
from reconcile.utils.oc_connection_parameters import OCConnectionParameters
|
|
67
|
+
|
|
64
68
|
urllib3.disable_warnings()
|
|
65
69
|
|
|
66
70
|
GET_REPLICASET_MAX_ATTEMPTS = 20
|
|
67
|
-
|
|
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
|
+
]
|
|
68
83
|
|
|
69
84
|
oc_run_execution_counter = Counter(
|
|
70
85
|
name="oc_run_execution_counter",
|
|
@@ -121,29 +136,29 @@ class JSONParsingError(Exception):
|
|
|
121
136
|
pass
|
|
122
137
|
|
|
123
138
|
|
|
124
|
-
class
|
|
139
|
+
class PodNotReadyError(Exception):
|
|
125
140
|
pass
|
|
126
141
|
|
|
127
142
|
|
|
128
|
-
class
|
|
143
|
+
class JobNotRunningError(Exception):
|
|
129
144
|
pass
|
|
130
145
|
|
|
131
146
|
|
|
132
|
-
class
|
|
147
|
+
class RequestEntityTooLargeError(Exception):
|
|
133
148
|
pass
|
|
134
149
|
|
|
135
150
|
|
|
136
|
-
class
|
|
151
|
+
class KindNotFoundError(Exception):
|
|
137
152
|
pass
|
|
138
153
|
|
|
139
154
|
|
|
140
|
-
class
|
|
155
|
+
class AmbiguousResourceTypeError(Exception):
|
|
141
156
|
pass
|
|
142
157
|
|
|
143
158
|
|
|
144
159
|
class OCDecorators:
|
|
145
160
|
@classmethod
|
|
146
|
-
def process_reconcile_time(cls, function):
|
|
161
|
+
def process_reconcile_time(cls, function: Callable) -> Callable:
|
|
147
162
|
"""
|
|
148
163
|
Compare current time against bundle commit time and create log
|
|
149
164
|
and metrics from it.
|
|
@@ -165,7 +180,7 @@ class OCDecorators:
|
|
|
165
180
|
"""
|
|
166
181
|
|
|
167
182
|
@wraps(function)
|
|
168
|
-
def wrapper(*args, **kwargs):
|
|
183
|
+
def wrapper(*args: Any, **kwargs: Any) -> list | tuple | Any:
|
|
169
184
|
result = function(*args, **kwargs)
|
|
170
185
|
msg = result[:-1] if isinstance(result, list | tuple) else result
|
|
171
186
|
|
|
@@ -173,6 +188,8 @@ class OCDecorators:
|
|
|
173
188
|
return result
|
|
174
189
|
|
|
175
190
|
running_state = RunningState()
|
|
191
|
+
if running_state.timestamp is None:
|
|
192
|
+
raise ValueError("Running state timestamp is None")
|
|
176
193
|
commit_time = float(running_state.timestamp)
|
|
177
194
|
time_spent = time.time() - commit_time
|
|
178
195
|
|
|
@@ -225,7 +242,9 @@ class OCProcessReconcileTimeDecoratorMsg:
|
|
|
225
242
|
is_log_slow_oc_reconcile: bool
|
|
226
243
|
|
|
227
244
|
|
|
228
|
-
def oc_process(
|
|
245
|
+
def oc_process(
|
|
246
|
+
template: Mapping[str, Any], parameters: Mapping[str, Any] | None = None
|
|
247
|
+
) -> Iterable[dict[str, Any]]:
|
|
229
248
|
oc = OCLocal(cluster_name="cluster", server=None, token=None, local=True)
|
|
230
249
|
return oc.process(template, parameters)
|
|
231
250
|
|
|
@@ -252,7 +271,7 @@ class OCCliApiResource:
|
|
|
252
271
|
namespaced: bool
|
|
253
272
|
|
|
254
273
|
@property
|
|
255
|
-
def group_version(self):
|
|
274
|
+
def group_version(self) -> str:
|
|
256
275
|
if self.group:
|
|
257
276
|
return f"{self.group}/{self.api_version}"
|
|
258
277
|
return self.api_version
|
|
@@ -271,7 +290,7 @@ class OCCli:
|
|
|
271
290
|
local: bool = False,
|
|
272
291
|
insecure_skip_tls_verify: bool = False,
|
|
273
292
|
connection_parameters: OCConnectionParameters | None = None,
|
|
274
|
-
):
|
|
293
|
+
) -> None:
|
|
275
294
|
"""
|
|
276
295
|
As of now we have to conform with 2 ways to initialize this client:
|
|
277
296
|
|
|
@@ -300,10 +319,10 @@ class OCCli:
|
|
|
300
319
|
insecure_skip_tls_verify=insecure_skip_tls_verify,
|
|
301
320
|
)
|
|
302
321
|
|
|
303
|
-
def __enter__(self):
|
|
322
|
+
def __enter__(self) -> OCCli:
|
|
304
323
|
return self
|
|
305
324
|
|
|
306
|
-
def __exit__(self, *exc):
|
|
325
|
+
def __exit__(self, *exc: Any) -> None:
|
|
307
326
|
self.cleanup()
|
|
308
327
|
|
|
309
328
|
def _init_old_without_types(
|
|
@@ -317,7 +336,7 @@ class OCCli:
|
|
|
317
336
|
init_api_resources: bool = False,
|
|
318
337
|
local: bool = False,
|
|
319
338
|
insecure_skip_tls_verify: bool = False,
|
|
320
|
-
):
|
|
339
|
+
) -> None:
|
|
321
340
|
"""Initiates an OC client
|
|
322
341
|
|
|
323
342
|
Args:
|
|
@@ -372,10 +391,7 @@ class OCCli:
|
|
|
372
391
|
|
|
373
392
|
self.init_projects = init_projects
|
|
374
393
|
if self.init_projects:
|
|
375
|
-
if self.is_kind_supported("
|
|
376
|
-
kind = "Project.project.openshift.io"
|
|
377
|
-
else:
|
|
378
|
-
kind = "Namespace"
|
|
394
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
379
395
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
380
396
|
|
|
381
397
|
self.slow_oc_reconcile_threshold = float(
|
|
@@ -392,7 +408,7 @@ class OCCli:
|
|
|
392
408
|
init_projects: bool = False,
|
|
393
409
|
init_api_resources: bool = False,
|
|
394
410
|
local: bool = False,
|
|
395
|
-
):
|
|
411
|
+
) -> None:
|
|
396
412
|
self.cluster_name = connection_parameters.cluster_name
|
|
397
413
|
self.server = connection_parameters.server_url
|
|
398
414
|
oc_base_cmd = ["oc", "--kubeconfig", "/dev/null"]
|
|
@@ -445,10 +461,7 @@ class OCCli:
|
|
|
445
461
|
|
|
446
462
|
self.init_projects = init_projects
|
|
447
463
|
if self.init_projects:
|
|
448
|
-
if self.is_kind_supported("
|
|
449
|
-
kind = "Project.project.openshift.io"
|
|
450
|
-
else:
|
|
451
|
-
kind = "Namespace"
|
|
464
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
452
465
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
453
466
|
|
|
454
467
|
self.slow_oc_reconcile_threshold = float(
|
|
@@ -459,14 +472,14 @@ class OCCli:
|
|
|
459
472
|
"LOG_SLOW_OC_RECONCILE", ""
|
|
460
473
|
).lower() in {"true", "yes"}
|
|
461
474
|
|
|
462
|
-
def whoami(self):
|
|
475
|
+
def whoami(self) -> bytes:
|
|
463
476
|
return self._run(["whoami"])
|
|
464
477
|
|
|
465
|
-
def cleanup(self):
|
|
478
|
+
def cleanup(self) -> None:
|
|
466
479
|
if hasattr(self, "jump_host") and isinstance(self.jump_host, JumpHostSSH):
|
|
467
480
|
self.jump_host.cleanup()
|
|
468
481
|
|
|
469
|
-
def get_items(self, kind, **kwargs):
|
|
482
|
+
def get_items(self, kind: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
470
483
|
cmd = ["get", kind, "-o", "json"]
|
|
471
484
|
|
|
472
485
|
if "namespace" in kwargs:
|
|
@@ -479,28 +492,33 @@ class OCCli:
|
|
|
479
492
|
cmd.extend(["-n", namespace])
|
|
480
493
|
|
|
481
494
|
if "labels" in kwargs:
|
|
482
|
-
labels_list = [f"{k}={v}" for k, v in kwargs.get("labels").items()]
|
|
495
|
+
labels_list = [f"{k}={v}" for k, v in kwargs.get("labels", {}).items()]
|
|
483
496
|
cmd += ["-l", ",".join(labels_list)]
|
|
484
497
|
|
|
485
498
|
resource_names = kwargs.get("resource_names")
|
|
486
499
|
if resource_names:
|
|
487
|
-
|
|
500
|
+
resource_items = []
|
|
488
501
|
for resource_name in resource_names:
|
|
489
502
|
resource_cmd = cmd + [resource_name]
|
|
490
503
|
item = self._run_json(resource_cmd, allow_not_found=True)
|
|
491
504
|
if item:
|
|
492
|
-
|
|
493
|
-
items_list = {"items":
|
|
505
|
+
resource_items.append(item)
|
|
506
|
+
items_list = {"items": resource_items}
|
|
494
507
|
else:
|
|
495
508
|
items_list = self._run_json(cmd)
|
|
496
509
|
|
|
497
510
|
items = items_list.get("items")
|
|
498
511
|
if items is None:
|
|
499
512
|
raise Exception("Expecting items")
|
|
500
|
-
|
|
501
513
|
return items
|
|
502
514
|
|
|
503
|
-
def get(
|
|
515
|
+
def get(
|
|
516
|
+
self,
|
|
517
|
+
namespace: str | None,
|
|
518
|
+
kind: str,
|
|
519
|
+
name: str | None = None,
|
|
520
|
+
allow_not_found: bool = False,
|
|
521
|
+
) -> dict[str, Any]:
|
|
504
522
|
cmd = ["get", "-o", "json", kind]
|
|
505
523
|
if name:
|
|
506
524
|
cmd.append(name)
|
|
@@ -508,13 +526,15 @@ class OCCli:
|
|
|
508
526
|
cmd.extend(["-n", namespace])
|
|
509
527
|
return self._run_json(cmd, allow_not_found=allow_not_found)
|
|
510
528
|
|
|
511
|
-
def get_all(self, kind, all_namespaces=False):
|
|
529
|
+
def get_all(self, kind: str, all_namespaces: bool = False) -> dict[str, Any]:
|
|
512
530
|
cmd = ["get", "-o", "json", kind]
|
|
513
531
|
if all_namespaces:
|
|
514
532
|
cmd.append("--all-namespaces")
|
|
515
533
|
return self._run_json(cmd)
|
|
516
534
|
|
|
517
|
-
def remove_last_applied_configuration(
|
|
535
|
+
def remove_last_applied_configuration(
|
|
536
|
+
self, namespace: str, kind: str, name: str
|
|
537
|
+
) -> None:
|
|
518
538
|
cmd = [
|
|
519
539
|
"annotate",
|
|
520
540
|
"-n",
|
|
@@ -525,7 +545,9 @@ class OCCli:
|
|
|
525
545
|
]
|
|
526
546
|
self._run(cmd)
|
|
527
547
|
|
|
528
|
-
def _msg_to_process_reconcile_time(
|
|
548
|
+
def _msg_to_process_reconcile_time(
|
|
549
|
+
self, namespace: str, resource: OR
|
|
550
|
+
) -> OCProcessReconcileTimeDecoratorMsg:
|
|
529
551
|
return OCProcessReconcileTimeDecoratorMsg(
|
|
530
552
|
namespace=namespace,
|
|
531
553
|
resource=resource,
|
|
@@ -534,7 +556,9 @@ class OCCli:
|
|
|
534
556
|
is_log_slow_oc_reconcile=self.is_log_slow_oc_reconcile,
|
|
535
557
|
)
|
|
536
558
|
|
|
537
|
-
def process(
|
|
559
|
+
def process(
|
|
560
|
+
self, template: Mapping[str, Any], parameters: Mapping[str, Any] | None = None
|
|
561
|
+
) -> Iterable[dict[str, Any]]:
|
|
538
562
|
if parameters is None:
|
|
539
563
|
parameters = {}
|
|
540
564
|
parameters_to_process = [f"{k}={v}" for k, v in parameters.items()]
|
|
@@ -545,36 +569,44 @@ class OCCli:
|
|
|
545
569
|
"-f",
|
|
546
570
|
"-",
|
|
547
571
|
] + parameters_to_process
|
|
548
|
-
result = self._run(cmd, stdin=
|
|
572
|
+
result = self._run(cmd, stdin=json_dumps(template))
|
|
549
573
|
return json.loads(result)["items"]
|
|
550
574
|
|
|
551
575
|
@OCDecorators.process_reconcile_time
|
|
552
|
-
def apply(self, namespace, resource):
|
|
576
|
+
def apply(self, namespace: str, resource: OR) -> OCProcessReconcileTimeDecoratorMsg:
|
|
553
577
|
cmd = ["apply", "-n", namespace, "-f", "-"]
|
|
554
578
|
self._run(cmd, stdin=resource.to_json(), apply=True)
|
|
555
579
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
556
580
|
|
|
557
581
|
@OCDecorators.process_reconcile_time
|
|
558
|
-
def create(
|
|
582
|
+
def create(
|
|
583
|
+
self, namespace: str, resource: OR
|
|
584
|
+
) -> OCProcessReconcileTimeDecoratorMsg:
|
|
559
585
|
cmd = ["create", "-n", namespace, "-f", "-"]
|
|
560
586
|
self._run(cmd, stdin=resource.to_json(), apply=True)
|
|
561
587
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
562
588
|
|
|
563
589
|
@OCDecorators.process_reconcile_time
|
|
564
|
-
def replace(
|
|
590
|
+
def replace(
|
|
591
|
+
self, namespace: str, resource: OR
|
|
592
|
+
) -> OCProcessReconcileTimeDecoratorMsg:
|
|
565
593
|
cmd = ["replace", "-n", namespace, "-f", "-"]
|
|
566
594
|
self._run(cmd, stdin=resource.to_json(), apply=True)
|
|
567
595
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
568
596
|
|
|
569
597
|
@OCDecorators.process_reconcile_time
|
|
570
|
-
def patch(
|
|
571
|
-
|
|
598
|
+
def patch(
|
|
599
|
+
self, namespace: str, kind: str, name: str, patch: Mapping[str, Any]
|
|
600
|
+
) -> OCProcessReconcileTimeDecoratorMsg:
|
|
601
|
+
cmd = ["patch", "-n", namespace, kind, name, "-p", json_dumps(patch)]
|
|
572
602
|
self._run(cmd)
|
|
573
603
|
resource = OR({"kind": kind, "metadata": {"name": name}}, "", "")
|
|
574
604
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
575
605
|
|
|
576
606
|
@OCDecorators.process_reconcile_time
|
|
577
|
-
def delete(
|
|
607
|
+
def delete(
|
|
608
|
+
self, namespace: str, kind: str, name: str, cascade: bool = True
|
|
609
|
+
) -> OCProcessReconcileTimeDecoratorMsg:
|
|
578
610
|
cmd = [
|
|
579
611
|
"delete",
|
|
580
612
|
"-n",
|
|
@@ -589,7 +621,14 @@ class OCCli:
|
|
|
589
621
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
590
622
|
|
|
591
623
|
@OCDecorators.process_reconcile_time
|
|
592
|
-
def label(
|
|
624
|
+
def label(
|
|
625
|
+
self,
|
|
626
|
+
namespace: str | None,
|
|
627
|
+
kind: str,
|
|
628
|
+
name: str,
|
|
629
|
+
labels: Mapping[str, str | None],
|
|
630
|
+
overwrite: bool = False,
|
|
631
|
+
) -> OCProcessReconcileTimeDecoratorMsg:
|
|
593
632
|
ns = ["-n", namespace] if namespace else []
|
|
594
633
|
added = [f"{k}={v}" for k, v in labels.items() if v is not None]
|
|
595
634
|
removed = [f"{k}-" for k, v in labels.items() if v is None]
|
|
@@ -598,16 +637,14 @@ class OCCli:
|
|
|
598
637
|
cmd.extend(added + removed)
|
|
599
638
|
self._run(cmd)
|
|
600
639
|
resource = OR({"kind": kind, "metadata": {"name": name}}, "", "")
|
|
601
|
-
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
640
|
+
return self._msg_to_process_reconcile_time(namespace or "", resource)
|
|
602
641
|
|
|
603
|
-
def project_exists(self, name):
|
|
642
|
+
def project_exists(self, name: str) -> bool:
|
|
604
643
|
if name in self.projects:
|
|
605
644
|
return True
|
|
645
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
606
646
|
try:
|
|
607
|
-
|
|
608
|
-
self.get(None, "Project.project.openshift.io", name)
|
|
609
|
-
else:
|
|
610
|
-
self.get(None, "Namespace", name)
|
|
647
|
+
self.get(None, kind, name)
|
|
611
648
|
except StatusCodeError as e:
|
|
612
649
|
if "NotFound" in str(e):
|
|
613
650
|
return False
|
|
@@ -615,8 +652,8 @@ class OCCli:
|
|
|
615
652
|
return True
|
|
616
653
|
|
|
617
654
|
@OCDecorators.process_reconcile_time
|
|
618
|
-
def new_project(self, namespace):
|
|
619
|
-
if self.is_kind_supported(
|
|
655
|
+
def new_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
|
|
656
|
+
if self.is_kind_supported(PROJECT_KIND):
|
|
620
657
|
cmd = ["new-project", namespace]
|
|
621
658
|
else:
|
|
622
659
|
cmd = ["create", "namespace", namespace]
|
|
@@ -631,8 +668,8 @@ class OCCli:
|
|
|
631
668
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
632
669
|
|
|
633
670
|
@OCDecorators.process_reconcile_time
|
|
634
|
-
def delete_project(self, namespace):
|
|
635
|
-
if self.is_kind_supported(
|
|
671
|
+
def delete_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
|
|
672
|
+
if self.is_kind_supported(PROJECT_KIND):
|
|
636
673
|
cmd = ["delete", "project", namespace]
|
|
637
674
|
else:
|
|
638
675
|
cmd = ["delete", "namespace", namespace]
|
|
@@ -642,7 +679,7 @@ class OCCli:
|
|
|
642
679
|
resource = OR({"kind": "Namespace", "metadata": {"name": namespace}}, "", "")
|
|
643
680
|
return self._msg_to_process_reconcile_time(namespace, resource)
|
|
644
681
|
|
|
645
|
-
def get_group_if_exists(self, name):
|
|
682
|
+
def get_group_if_exists(self, name: str) -> dict[str, Any] | None:
|
|
646
683
|
try:
|
|
647
684
|
return self.get(None, "Group", name)
|
|
648
685
|
except StatusCodeError as e:
|
|
@@ -650,20 +687,20 @@ class OCCli:
|
|
|
650
687
|
return None
|
|
651
688
|
raise e
|
|
652
689
|
|
|
653
|
-
def create_group(self, group):
|
|
690
|
+
def create_group(self, group: str) -> None:
|
|
654
691
|
if self.get_group_if_exists(group) is not None:
|
|
655
692
|
return
|
|
656
693
|
cmd = ["adm", "groups", "new", group]
|
|
657
694
|
self._run(cmd)
|
|
658
695
|
|
|
659
|
-
def delete_group(self, group):
|
|
696
|
+
def delete_group(self, group: str) -> None:
|
|
660
697
|
cmd = ["delete", "group", group]
|
|
661
698
|
self._run(cmd)
|
|
662
699
|
|
|
663
|
-
def get_users(self):
|
|
700
|
+
def get_users(self) -> Iterable[dict[str, Any]]:
|
|
664
701
|
return self.get_all("User")["items"]
|
|
665
702
|
|
|
666
|
-
def delete_user(self, user_name):
|
|
703
|
+
def delete_user(self, user_name: str) -> None:
|
|
667
704
|
user = self.get(None, "User", user_name)
|
|
668
705
|
cmd = ["delete", "user", user_name]
|
|
669
706
|
self._run(cmd)
|
|
@@ -671,19 +708,19 @@ class OCCli:
|
|
|
671
708
|
cmd = ["delete", "identity", identity]
|
|
672
709
|
self._run(cmd)
|
|
673
710
|
|
|
674
|
-
def add_user_to_group(self, group, user):
|
|
711
|
+
def add_user_to_group(self, group: str, user: str) -> None:
|
|
675
712
|
cmd = ["adm", "groups", "add-users", group, user]
|
|
676
713
|
self._run(cmd)
|
|
677
714
|
|
|
678
|
-
def del_user_from_group(self, group, user):
|
|
715
|
+
def del_user_from_group(self, group: str, user: str) -> None:
|
|
679
716
|
cmd = ["adm", "groups", "remove-users", group, user]
|
|
680
717
|
self._run(cmd)
|
|
681
718
|
|
|
682
|
-
def sa_get_token(self, namespace, name):
|
|
719
|
+
def sa_get_token(self, namespace: str, name: str) -> str:
|
|
683
720
|
cmd = ["sa", "-n", namespace, "get-token", name]
|
|
684
|
-
return self._run(cmd)
|
|
721
|
+
return self._run(cmd).decode("utf-8")
|
|
685
722
|
|
|
686
|
-
def get_api_resources(self):
|
|
723
|
+
def get_api_resources(self) -> dict[str, list[OCCliApiResource]]:
|
|
687
724
|
with self.api_resources_lock:
|
|
688
725
|
if not self.api_resources:
|
|
689
726
|
cmd = ["api-resources", "--no-headers"]
|
|
@@ -704,13 +741,13 @@ class OCCli:
|
|
|
704
741
|
|
|
705
742
|
return self.api_resources
|
|
706
743
|
|
|
707
|
-
def get_version(self):
|
|
744
|
+
def get_version(self) -> bytes:
|
|
708
745
|
# this is actually a 10 second timeout, because: oc reasons
|
|
709
746
|
cmd = ["version", "--request-timeout=5"]
|
|
710
747
|
return self._run(cmd)
|
|
711
748
|
|
|
712
749
|
@retry(exceptions=(JobNotRunningError), max_attempts=20)
|
|
713
|
-
def wait_for_job_running(self, namespace, name):
|
|
750
|
+
def wait_for_job_running(self, namespace: str, name: str) -> None:
|
|
714
751
|
logging.info("waiting for job to run: " + name)
|
|
715
752
|
pods = self.get_items("Pod", namespace=namespace, labels={"job-name": name})
|
|
716
753
|
|
|
@@ -725,13 +762,13 @@ class OCCli:
|
|
|
725
762
|
|
|
726
763
|
def job_logs(
|
|
727
764
|
self,
|
|
728
|
-
namespace,
|
|
729
|
-
name,
|
|
730
|
-
follow,
|
|
731
|
-
output,
|
|
732
|
-
wait_for_job_running=True,
|
|
733
|
-
wait_for_logs_process=False,
|
|
734
|
-
):
|
|
765
|
+
namespace: str,
|
|
766
|
+
name: str,
|
|
767
|
+
follow: bool,
|
|
768
|
+
output: str | pathlib.Path | TextIO,
|
|
769
|
+
wait_for_job_running: bool = True,
|
|
770
|
+
wait_for_logs_process: bool = False,
|
|
771
|
+
) -> None:
|
|
735
772
|
if wait_for_job_running:
|
|
736
773
|
self.wait_for_job_running(namespace, name)
|
|
737
774
|
|
|
@@ -739,6 +776,7 @@ class OCCli:
|
|
|
739
776
|
if follow:
|
|
740
777
|
cmd.append("-f")
|
|
741
778
|
|
|
779
|
+
output_file: TextIO
|
|
742
780
|
if isinstance(output, str | pathlib.Path):
|
|
743
781
|
output_file = open(os.path.join(output, name), "w", encoding="locale") # noqa: SIM115
|
|
744
782
|
else:
|
|
@@ -779,12 +817,11 @@ class OCCli:
|
|
|
779
817
|
return output_file_name
|
|
780
818
|
|
|
781
819
|
@staticmethod
|
|
782
|
-
def get_service_account_username(user):
|
|
783
|
-
namespace = user.split("/")
|
|
784
|
-
name = user.split("/")[1]
|
|
820
|
+
def get_service_account_username(user: str) -> str:
|
|
821
|
+
namespace, name, _ = user.split("/", maxsplit=2)
|
|
785
822
|
return f"system:serviceaccount:{namespace}:{name}"
|
|
786
823
|
|
|
787
|
-
def get_owned_pods(self, namespace, resource):
|
|
824
|
+
def get_owned_pods(self, namespace: str, resource: OR) -> list[dict[str, Any]]:
|
|
788
825
|
pods = self.get(namespace, "Pod")["items"]
|
|
789
826
|
owned_pods = []
|
|
790
827
|
for p in pods:
|
|
@@ -797,7 +834,9 @@ class OCCli:
|
|
|
797
834
|
|
|
798
835
|
return owned_pods
|
|
799
836
|
|
|
800
|
-
def get_owned_replicasets(
|
|
837
|
+
def get_owned_replicasets(
|
|
838
|
+
self, namespace: str, resource: Mapping[str, Any]
|
|
839
|
+
) -> list[dict[str, Any]]:
|
|
801
840
|
owned_replicasets = []
|
|
802
841
|
for rs in self.get(namespace, "ReplicaSet")["items"]:
|
|
803
842
|
owner = self.get_obj_root_owner(namespace, rs, allow_not_found=True)
|
|
@@ -814,8 +853,11 @@ class OCCli:
|
|
|
814
853
|
max_attempts=GET_REPLICASET_MAX_ATTEMPTS,
|
|
815
854
|
)
|
|
816
855
|
def get_replicaset(
|
|
817
|
-
self,
|
|
818
|
-
|
|
856
|
+
self,
|
|
857
|
+
namespace: str,
|
|
858
|
+
deployment_resource: Mapping[str, Any],
|
|
859
|
+
allow_empty: bool = False,
|
|
860
|
+
) -> dict[str, Any]:
|
|
819
861
|
"""Get last active ReplicaSet for given Deployment.
|
|
820
862
|
|
|
821
863
|
Implements similar logic like in kubectl describe deployment.
|
|
@@ -835,7 +877,7 @@ class OCCli:
|
|
|
835
877
|
raise ResourceNotFoundError("No ReplicaSet found")
|
|
836
878
|
|
|
837
879
|
@staticmethod
|
|
838
|
-
def get_pod_owned_pvc_names(pods: Iterable[
|
|
880
|
+
def get_pod_owned_pvc_names(pods: Iterable[Mapping[str, Any]]) -> set[str]:
|
|
839
881
|
owned_pvc_names = set()
|
|
840
882
|
for p in pods:
|
|
841
883
|
vols = p["spec"].get("volumes")
|
|
@@ -849,25 +891,28 @@ class OCCli:
|
|
|
849
891
|
return owned_pvc_names
|
|
850
892
|
|
|
851
893
|
@staticmethod
|
|
852
|
-
def get_storage(resource):
|
|
894
|
+
def get_storage(resource: Mapping[str, Any]) -> str | None:
|
|
853
895
|
# resources with volumeClaimTemplates
|
|
854
896
|
with suppress(KeyError, IndexError):
|
|
855
897
|
vct = resource["spec"]["volumeClaimTemplates"][0]
|
|
856
898
|
return vct["spec"]["resources"]["requests"]["storage"]
|
|
899
|
+
return None
|
|
857
900
|
|
|
858
|
-
def resize_pvcs(self, namespace, pvc_names, size):
|
|
901
|
+
def resize_pvcs(self, namespace: str, pvc_names: Iterable[str], size: str) -> None:
|
|
859
902
|
patch = {"spec": {"resources": {"requests": {"storage": size}}}}
|
|
860
903
|
for p in pvc_names:
|
|
861
904
|
self.patch(namespace, "PersistentVolumeClaim", p, patch)
|
|
862
905
|
|
|
863
|
-
def recycle_orphan_pods(
|
|
906
|
+
def recycle_orphan_pods(
|
|
907
|
+
self, namespace: str, pods: Iterable[Mapping[str, Any]]
|
|
908
|
+
) -> None:
|
|
864
909
|
for p in pods:
|
|
865
910
|
name = p["metadata"]["name"]
|
|
866
911
|
self.delete(namespace, "Pod", name)
|
|
867
912
|
self.validate_pod_ready(namespace, name)
|
|
868
913
|
|
|
869
914
|
@retry(max_attempts=20)
|
|
870
|
-
def validate_pod_ready(self, namespace, name):
|
|
915
|
+
def validate_pod_ready(self, namespace: str, name: str) -> None:
|
|
871
916
|
logging.info([
|
|
872
917
|
self.validate_pod_ready.__name__,
|
|
873
918
|
self.cluster_name,
|
|
@@ -879,108 +924,113 @@ class OCCli:
|
|
|
879
924
|
if not status["ready"]:
|
|
880
925
|
raise PodNotReadyError(name)
|
|
881
926
|
|
|
882
|
-
def
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
dep_resource: dependant resource."""
|
|
889
|
-
|
|
890
|
-
supported_kinds = ["Secret", "ConfigMap"]
|
|
891
|
-
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:
|
|
892
933
|
logging.debug([
|
|
893
934
|
"skipping_pod_recycle_unsupported",
|
|
894
935
|
self.cluster_name,
|
|
895
936
|
namespace,
|
|
896
|
-
|
|
937
|
+
resource.kind,
|
|
938
|
+
resource.name,
|
|
897
939
|
])
|
|
898
|
-
return
|
|
940
|
+
return False
|
|
899
941
|
|
|
900
|
-
dep_annotations = dep_resource.body["metadata"].get("annotations", {})
|
|
901
942
|
# Note, that annotations might have been set to None explicitly
|
|
902
|
-
|
|
903
|
-
qontract_recycle =
|
|
904
|
-
if qontract_recycle is True:
|
|
905
|
-
raise RecyclePodsInvalidAnnotationValueError('should be "true"')
|
|
943
|
+
annotations = resource.body["metadata"].get("annotations") or {}
|
|
944
|
+
qontract_recycle = annotations.get("qontract.recycle")
|
|
906
945
|
if qontract_recycle != "true":
|
|
907
946
|
logging.debug([
|
|
908
947
|
"skipping_pod_recycle_no_annotation",
|
|
909
948
|
self.cluster_name,
|
|
910
949
|
namespace,
|
|
911
|
-
|
|
950
|
+
resource.kind,
|
|
951
|
+
resource.name,
|
|
912
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):
|
|
913
973
|
return
|
|
914
974
|
|
|
915
|
-
dep_name = dep_resource.name
|
|
916
975
|
pods = self.get(namespace, "Pod")["items"]
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
]
|
|
926
|
-
else:
|
|
927
|
-
raise RecyclePodsUnsupportedKindError(dep_kind)
|
|
928
|
-
|
|
929
|
-
recyclables = {}
|
|
930
|
-
supported_recyclables = [
|
|
931
|
-
"Deployment",
|
|
932
|
-
"DeploymentConfig",
|
|
933
|
-
"StatefulSet",
|
|
934
|
-
"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
|
+
)
|
|
935
984
|
]
|
|
985
|
+
|
|
986
|
+
recycle_names_by_kind = defaultdict(set)
|
|
936
987
|
for pod in pods_to_recycle:
|
|
937
988
|
owner = self.get_obj_root_owner(namespace, pod, allow_not_found=True)
|
|
938
989
|
kind = owner["kind"]
|
|
939
|
-
if kind
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
for
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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
|
+
|
|
1002
|
+
def recycle(
|
|
1003
|
+
self,
|
|
1004
|
+
dry_run: bool,
|
|
1005
|
+
namespace: str,
|
|
1006
|
+
kind: str,
|
|
1007
|
+
name: str,
|
|
1008
|
+
) -> None:
|
|
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.
|
|
1013
|
+
|
|
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
|
|
963
1019
|
"""
|
|
964
|
-
name = obj["metadata"]["name"]
|
|
965
1020
|
logging.info([f"recycle_{kind.lower()}", self.cluster_name, namespace, name])
|
|
966
1021
|
if not dry_run:
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
obj = self.get(namespace, kind, name)
|
|
972
|
-
# honor update strategy by setting annotations to force
|
|
973
|
-
# a new rollout
|
|
974
|
-
a = obj["spec"]["template"]["metadata"].get("annotations", {})
|
|
975
|
-
a["recycle.time"] = recycle_time
|
|
976
|
-
obj["spec"]["template"]["metadata"]["annotations"] = a
|
|
977
|
-
cmd = ["apply", "-n", namespace, "-f", "-"]
|
|
978
|
-
stdin = json.dumps(obj, sort_keys=True)
|
|
979
|
-
self._run(cmd, stdin=stdin, apply=True)
|
|
1022
|
+
self._run(
|
|
1023
|
+
["rollout", "restart", f"{kind}/{name}", "-n", namespace],
|
|
1024
|
+
apply=True,
|
|
1025
|
+
)
|
|
980
1026
|
|
|
981
1027
|
def get_obj_root_owner(
|
|
982
|
-
self,
|
|
983
|
-
|
|
1028
|
+
self,
|
|
1029
|
+
ns: str,
|
|
1030
|
+
obj: dict[str, Any],
|
|
1031
|
+
allow_not_found: bool = False,
|
|
1032
|
+
allow_not_controller: bool = False,
|
|
1033
|
+
) -> dict[str, Any]:
|
|
984
1034
|
"""Get object root owner (recursively find the top level owner).
|
|
985
1035
|
- Returns obj if it has no ownerReferences
|
|
986
1036
|
- Returns obj if all ownerReferences have controller set to false
|
|
@@ -1014,39 +1064,65 @@ class OCCli:
|
|
|
1014
1064
|
)
|
|
1015
1065
|
return obj
|
|
1016
1066
|
|
|
1017
|
-
def
|
|
1018
|
-
|
|
1019
|
-
|
|
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.
|
|
1020
1075
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1076
|
+
Args:
|
|
1077
|
+
name: Name of the resource
|
|
1078
|
+
kind: "Secret" or "ConfigMap"
|
|
1079
|
+
pod: Pod object
|
|
1080
|
+
|
|
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)
|
|
1023
1085
|
return name in used_resources
|
|
1024
1086
|
|
|
1025
1087
|
@staticmethod
|
|
1026
1088
|
def get_resources_used_in_pod_spec(
|
|
1027
|
-
spec:
|
|
1089
|
+
spec: Mapping[str, Any],
|
|
1028
1090
|
kind: str,
|
|
1029
1091
|
include_optional: bool = True,
|
|
1030
1092
|
) -> dict[str, set[str]]:
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
+
|
|
1033
1125
|
optional = "optional"
|
|
1034
|
-
if kind == "Secret":
|
|
1035
|
-
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1036
|
-
"secret",
|
|
1037
|
-
"secretName",
|
|
1038
|
-
"secretRef",
|
|
1039
|
-
"secretKeyRef",
|
|
1040
|
-
"name",
|
|
1041
|
-
)
|
|
1042
|
-
elif kind == "ConfigMap":
|
|
1043
|
-
volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
|
|
1044
|
-
"configMap",
|
|
1045
|
-
"name",
|
|
1046
|
-
"configMapRef",
|
|
1047
|
-
"configMapKeyRef",
|
|
1048
|
-
"name",
|
|
1049
|
-
)
|
|
1050
1126
|
|
|
1051
1127
|
resources: dict[str, set[str]] = {}
|
|
1052
1128
|
for v in spec.get("volumes") or []:
|
|
@@ -1075,15 +1151,15 @@ class OCCli:
|
|
|
1075
1151
|
continue
|
|
1076
1152
|
resource_name = resource_ref[env_ref]
|
|
1077
1153
|
resources.setdefault(resource_name, set())
|
|
1078
|
-
|
|
1079
|
-
resources[resource_name].add(
|
|
1154
|
+
key = resource_ref["key"]
|
|
1155
|
+
resources[resource_name].add(key)
|
|
1080
1156
|
except (KeyError, TypeError):
|
|
1081
1157
|
continue
|
|
1082
1158
|
|
|
1083
1159
|
return resources
|
|
1084
1160
|
|
|
1085
1161
|
@retry(exceptions=(StatusCodeError, NoOutputError), max_attempts=10)
|
|
1086
|
-
def _run(self, cmd, **kwargs) -> bytes:
|
|
1162
|
+
def _run(self, cmd: list[str], **kwargs: Any) -> bytes:
|
|
1087
1163
|
oc_run_execution_counter.labels(integration=RunningState().integration).inc()
|
|
1088
1164
|
stdin = kwargs.get("stdin")
|
|
1089
1165
|
stdin_text = stdin.encode() if stdin else None
|
|
@@ -1134,8 +1210,10 @@ class OCCli:
|
|
|
1134
1210
|
|
|
1135
1211
|
return result.stdout.strip()
|
|
1136
1212
|
|
|
1137
|
-
def _run_json(
|
|
1138
|
-
|
|
1213
|
+
def _run_json(
|
|
1214
|
+
self, cmd: list[str], allow_not_found: bool = False
|
|
1215
|
+
) -> dict[str, Any]:
|
|
1216
|
+
out = self._run(cmd, allow_not_found=allow_not_found).decode("utf-8")
|
|
1139
1217
|
|
|
1140
1218
|
try:
|
|
1141
1219
|
out_json = json.loads(out)
|
|
@@ -1144,76 +1222,90 @@ class OCCli:
|
|
|
1144
1222
|
|
|
1145
1223
|
return out_json
|
|
1146
1224
|
|
|
1147
|
-
def
|
|
1148
|
-
|
|
1149
|
-
# the api resources initialization.
|
|
1150
|
-
if not self.api_resources:
|
|
1151
|
-
self.get_api_resources()
|
|
1225
|
+
def parse_kind(self, kind: str) -> tuple[str, str, str]:
|
|
1226
|
+
"""Parse a Kubernetes kind string into its components.
|
|
1152
1227
|
|
|
1153
|
-
|
|
1154
|
-
kind
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
|
1180
1260
|
|
|
1181
1261
|
def is_kind_supported(self, kind: str) -> bool:
|
|
1182
|
-
|
|
1183
|
-
# the api resources initialization.
|
|
1184
|
-
if not self.api_resources:
|
|
1185
|
-
self.get_api_resources()
|
|
1262
|
+
"""Returns True if the given kind is supported by the cluster, False otherwise.
|
|
1186
1263
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
else:
|
|
1194
|
-
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
|
|
1195
1270
|
|
|
1196
1271
|
def is_kind_namespaced(self, kind: str) -> bool:
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
+
|
|
1199
1283
|
if not self.api_resources:
|
|
1200
|
-
|
|
1284
|
+
raise RuntimeError("API resources not initialized")
|
|
1201
1285
|
|
|
1202
|
-
|
|
1203
|
-
kind = kg[0]
|
|
1286
|
+
kind, group, _ = self.parse_kind(kind)
|
|
1204
1287
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1288
|
+
if not (resources := self.api_resources.get(kind)):
|
|
1289
|
+
# the kind not found at all
|
|
1290
|
+
raise KindNotFoundError(f"Unsupported resource type: {kind}")
|
|
1291
|
+
|
|
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)
|
|
1209
1306
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
for r in kind_resources:
|
|
1213
|
-
if group == r.group:
|
|
1214
|
-
return r.namespaced
|
|
1215
|
-
raise StatusCodeError(f"Kind: {kind} does nod exist in the ApiServer")
|
|
1216
|
-
return kind_resources[0].namespaced
|
|
1307
|
+
# group was specified but no matching resource found
|
|
1308
|
+
raise KindNotFoundError(f"Unsupported resource type: {kind}")
|
|
1217
1309
|
|
|
1218
1310
|
|
|
1219
1311
|
REQUEST_TIMEOUT = 60
|
|
@@ -1231,7 +1323,7 @@ class OCNative(OCCli):
|
|
|
1231
1323
|
local: bool = False,
|
|
1232
1324
|
insecure_skip_tls_verify: bool = False,
|
|
1233
1325
|
connection_parameters: OCConnectionParameters | None = None,
|
|
1234
|
-
):
|
|
1326
|
+
) -> None:
|
|
1235
1327
|
super().__init__(
|
|
1236
1328
|
cluster_name,
|
|
1237
1329
|
server,
|
|
@@ -1253,35 +1345,34 @@ class OCNative(OCCli):
|
|
|
1253
1345
|
|
|
1254
1346
|
server = connection_parameters.server_url
|
|
1255
1347
|
|
|
1256
|
-
if server:
|
|
1257
|
-
|
|
1258
|
-
self.api_resources = self.get_api_resources()
|
|
1348
|
+
if not server:
|
|
1349
|
+
raise Exception("Server name is required!")
|
|
1259
1350
|
|
|
1260
|
-
|
|
1261
|
-
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()
|
|
1262
1356
|
|
|
1263
1357
|
self.projects = set()
|
|
1264
1358
|
self.init_projects = init_projects
|
|
1265
1359
|
if self.init_projects:
|
|
1266
|
-
if self.is_kind_supported("
|
|
1267
|
-
kind = "Project.project.openshift.io"
|
|
1268
|
-
else:
|
|
1269
|
-
kind = "Namespace"
|
|
1360
|
+
kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
|
|
1270
1361
|
self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
|
|
1271
1362
|
|
|
1272
|
-
def __enter__(self):
|
|
1363
|
+
def __enter__(self) -> OCNative:
|
|
1273
1364
|
return self
|
|
1274
1365
|
|
|
1275
|
-
def __exit__(self, *exc):
|
|
1366
|
+
def __exit__(self, *exc: Any) -> None:
|
|
1276
1367
|
self.cleanup()
|
|
1277
1368
|
|
|
1278
|
-
def cleanup(self):
|
|
1369
|
+
def cleanup(self) -> None:
|
|
1279
1370
|
super().cleanup()
|
|
1280
1371
|
if hasattr(self, "client") and self.client is not None:
|
|
1281
1372
|
self.client.client.close()
|
|
1282
1373
|
|
|
1283
1374
|
@retry(exceptions=(ServerTimeoutError, InternalServerError, ForbiddenError))
|
|
1284
|
-
def _get_client(self, server, token):
|
|
1375
|
+
def _get_client(self, server: str, token: str) -> DynamicClient:
|
|
1285
1376
|
opts = {
|
|
1286
1377
|
"api_key": {"authorization": f"Bearer {token}"},
|
|
1287
1378
|
"host": server,
|
|
@@ -1315,9 +1406,11 @@ class OCNative(OCCli):
|
|
|
1315
1406
|
return self.client.resources.get(api_version=group_version, kind=kind)
|
|
1316
1407
|
|
|
1317
1408
|
@retry(max_attempts=5, exceptions=(ServerTimeoutError))
|
|
1318
|
-
def get_items(self, kind, **kwargs):
|
|
1319
|
-
|
|
1320
|
-
obj_client = self._get_obj_client(
|
|
1409
|
+
def get_items(self, kind: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
1410
|
+
resource = self.get_api_resource(kind)
|
|
1411
|
+
obj_client = self._get_obj_client(
|
|
1412
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1413
|
+
)
|
|
1321
1414
|
|
|
1322
1415
|
namespace = ""
|
|
1323
1416
|
if "namespace" in kwargs:
|
|
@@ -1330,13 +1423,12 @@ class OCNative(OCCli):
|
|
|
1330
1423
|
|
|
1331
1424
|
labels = ""
|
|
1332
1425
|
if "labels" in kwargs:
|
|
1333
|
-
labels_list = [f"{k}={v}" for k, v in kwargs.get("labels").items()]
|
|
1334
|
-
|
|
1426
|
+
labels_list = [f"{k}={v}" for k, v in kwargs.get("labels", {}).items()]
|
|
1335
1427
|
labels = ",".join(labels_list)
|
|
1336
1428
|
|
|
1337
1429
|
resource_names = kwargs.get("resource_names")
|
|
1338
1430
|
if resource_names:
|
|
1339
|
-
|
|
1431
|
+
resource_items = []
|
|
1340
1432
|
for resource_name in resource_names:
|
|
1341
1433
|
try:
|
|
1342
1434
|
item = obj_client.get(
|
|
@@ -1346,10 +1438,10 @@ class OCNative(OCCli):
|
|
|
1346
1438
|
_request_timeout=REQUEST_TIMEOUT,
|
|
1347
1439
|
)
|
|
1348
1440
|
if item:
|
|
1349
|
-
|
|
1441
|
+
resource_items.append(item.to_dict())
|
|
1350
1442
|
except NotFoundError:
|
|
1351
1443
|
pass
|
|
1352
|
-
items_list = {"items":
|
|
1444
|
+
items_list = {"items": resource_items}
|
|
1353
1445
|
else:
|
|
1354
1446
|
items_list = obj_client.get(
|
|
1355
1447
|
namespace=namespace,
|
|
@@ -1363,9 +1455,17 @@ class OCNative(OCCli):
|
|
|
1363
1455
|
return items
|
|
1364
1456
|
|
|
1365
1457
|
@retry(max_attempts=5, exceptions=(ServerTimeoutError, ForbiddenError))
|
|
1366
|
-
def get(
|
|
1367
|
-
|
|
1368
|
-
|
|
1458
|
+
def get(
|
|
1459
|
+
self,
|
|
1460
|
+
namespace: str | None,
|
|
1461
|
+
kind: str,
|
|
1462
|
+
name: str | None = None,
|
|
1463
|
+
allow_not_found: bool = False,
|
|
1464
|
+
) -> dict[str, Any]:
|
|
1465
|
+
resource = self.get_api_resource(kind)
|
|
1466
|
+
obj_client = self._get_obj_client(
|
|
1467
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1468
|
+
)
|
|
1369
1469
|
try:
|
|
1370
1470
|
obj = obj_client.get(
|
|
1371
1471
|
name=name,
|
|
@@ -1378,9 +1478,11 @@ class OCNative(OCCli):
|
|
|
1378
1478
|
return {}
|
|
1379
1479
|
raise StatusCodeError(f"[{self.server}]: {e}") from None
|
|
1380
1480
|
|
|
1381
|
-
def get_all(self, kind, all_namespaces=False):
|
|
1382
|
-
|
|
1383
|
-
obj_client = self._get_obj_client(
|
|
1481
|
+
def get_all(self, kind: str, all_namespaces: bool = False) -> dict[str, Any]:
|
|
1482
|
+
resource = self.get_api_resource(kind)
|
|
1483
|
+
obj_client = self._get_obj_client(
|
|
1484
|
+
group_version=resource.group_version, kind=resource.kind
|
|
1485
|
+
)
|
|
1384
1486
|
try:
|
|
1385
1487
|
return obj_client.get(_request_timeout=REQUEST_TIMEOUT).to_dict()
|
|
1386
1488
|
except NotFoundError as e:
|
|
@@ -1393,11 +1495,11 @@ OCClient = OCNative | OCCli
|
|
|
1393
1495
|
class OCLocal(OCCli):
|
|
1394
1496
|
def __init__(
|
|
1395
1497
|
self,
|
|
1396
|
-
cluster_name,
|
|
1397
|
-
server,
|
|
1398
|
-
token,
|
|
1399
|
-
local=False,
|
|
1400
|
-
):
|
|
1498
|
+
cluster_name: str,
|
|
1499
|
+
server: str | None,
|
|
1500
|
+
token: str | None,
|
|
1501
|
+
local: bool = False,
|
|
1502
|
+
) -> None:
|
|
1401
1503
|
super().__init__(
|
|
1402
1504
|
cluster_name=cluster_name,
|
|
1403
1505
|
server=server,
|
|
@@ -1406,6 +1508,8 @@ class OCLocal(OCCli):
|
|
|
1406
1508
|
)
|
|
1407
1509
|
|
|
1408
1510
|
|
|
1511
|
+
# we should replace this class with a proper get_oc_client wrapper!
|
|
1512
|
+
# Or getting rid of one or the other OC implementations
|
|
1409
1513
|
class OC:
|
|
1410
1514
|
client_status = Counter(
|
|
1411
1515
|
name="qontract_reconcile_native_client",
|
|
@@ -1413,7 +1517,7 @@ class OC:
|
|
|
1413
1517
|
labelnames=["cluster_name", "native_client"],
|
|
1414
1518
|
)
|
|
1415
1519
|
|
|
1416
|
-
def __new__(
|
|
1520
|
+
def __new__( # type: ignore
|
|
1417
1521
|
cls,
|
|
1418
1522
|
cluster_name: str | None = None,
|
|
1419
1523
|
server: str | None = None,
|
|
@@ -1425,7 +1529,7 @@ class OC:
|
|
|
1425
1529
|
local: bool = False,
|
|
1426
1530
|
insecure_skip_tls_verify: bool = False,
|
|
1427
1531
|
connection_parameters: OCConnectionParameters | None = None,
|
|
1428
|
-
):
|
|
1532
|
+
) -> OCClient:
|
|
1429
1533
|
use_native_env = os.environ.get("USE_NATIVE_CLIENT", "")
|
|
1430
1534
|
use_native = True
|
|
1431
1535
|
if len(use_native_env) > 0:
|
|
@@ -1483,19 +1587,19 @@ class OC_Map: # noqa: N801
|
|
|
1483
1587
|
|
|
1484
1588
|
def __init__(
|
|
1485
1589
|
self,
|
|
1486
|
-
clusters=None,
|
|
1487
|
-
namespaces=None,
|
|
1488
|
-
integration="",
|
|
1489
|
-
settings=None,
|
|
1490
|
-
internal=None,
|
|
1491
|
-
use_jump_host=True,
|
|
1492
|
-
thread_pool_size=1,
|
|
1493
|
-
init_projects=False,
|
|
1494
|
-
init_api_resources=False,
|
|
1495
|
-
cluster_admin=False,
|
|
1496
|
-
):
|
|
1497
|
-
self.oc_map = {}
|
|
1498
|
-
self.privileged_oc_map = {}
|
|
1590
|
+
clusters: Iterable[Mapping[str, Any]] | None = None,
|
|
1591
|
+
namespaces: Iterable[Mapping[str, Any]] | None = None,
|
|
1592
|
+
integration: str = "",
|
|
1593
|
+
settings: Mapping[str, Any] | None = None,
|
|
1594
|
+
internal: bool | None = None,
|
|
1595
|
+
use_jump_host: bool = True,
|
|
1596
|
+
thread_pool_size: int = 1,
|
|
1597
|
+
init_projects: bool = False,
|
|
1598
|
+
init_api_resources: bool = False,
|
|
1599
|
+
cluster_admin: bool = False,
|
|
1600
|
+
) -> None:
|
|
1601
|
+
self.oc_map: dict[str, OCClient | OCLogMsg] = {}
|
|
1602
|
+
self.privileged_oc_map: dict[str, OCClient | OCLogMsg] = {}
|
|
1499
1603
|
self.calling_integration = integration
|
|
1500
1604
|
self.settings = settings
|
|
1501
1605
|
self.internal = internal
|
|
@@ -1504,7 +1608,7 @@ class OC_Map: # noqa: N801
|
|
|
1504
1608
|
self.init_projects = init_projects
|
|
1505
1609
|
self.init_api_resources = init_api_resources
|
|
1506
1610
|
self._lock = Lock()
|
|
1507
|
-
self.jh_ports = {}
|
|
1611
|
+
self.jh_ports: dict[str, str | int] = {}
|
|
1508
1612
|
|
|
1509
1613
|
if clusters and namespaces:
|
|
1510
1614
|
raise KeyError("expected only one of clusters or namespaces.")
|
|
@@ -1548,13 +1652,13 @@ class OC_Map: # noqa: N801
|
|
|
1548
1652
|
else:
|
|
1549
1653
|
raise KeyError("expected one of clusters or namespaces.")
|
|
1550
1654
|
|
|
1551
|
-
def __enter__(self):
|
|
1655
|
+
def __enter__(self) -> OC_Map:
|
|
1552
1656
|
return self
|
|
1553
1657
|
|
|
1554
|
-
def __exit__(self, *exc):
|
|
1658
|
+
def __exit__(self, *exc: Any) -> None:
|
|
1555
1659
|
self.cleanup()
|
|
1556
1660
|
|
|
1557
|
-
def set_jh_ports(self, jh):
|
|
1661
|
+
def set_jh_ports(self, jh: MutableMapping[str, str | int]) -> None:
|
|
1558
1662
|
# This will be replaced with getting the data from app-interface in
|
|
1559
1663
|
# a future PR.
|
|
1560
1664
|
jh["remotePort"] = 8888
|
|
@@ -1565,7 +1669,7 @@ class OC_Map: # noqa: N801
|
|
|
1565
1669
|
self.jh_ports[key] = port
|
|
1566
1670
|
jh["localPort"] = self.jh_ports[key]
|
|
1567
1671
|
|
|
1568
|
-
def init_oc_client(self, cluster_info, privileged: bool):
|
|
1672
|
+
def init_oc_client(self, cluster_info: Mapping[str, Any], privileged: bool) -> None:
|
|
1569
1673
|
cluster = cluster_info["name"]
|
|
1570
1674
|
if not privileged and self.oc_map.get(cluster):
|
|
1571
1675
|
return None
|
|
@@ -1640,15 +1744,18 @@ class OC_Map: # noqa: N801
|
|
|
1640
1744
|
if jump_host:
|
|
1641
1745
|
self.set_jh_ports(jump_host)
|
|
1642
1746
|
try:
|
|
1643
|
-
oc_client =
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1747
|
+
oc_client = cast(
|
|
1748
|
+
"OCClient",
|
|
1749
|
+
OC(
|
|
1750
|
+
cluster,
|
|
1751
|
+
server_url,
|
|
1752
|
+
token,
|
|
1753
|
+
jump_host,
|
|
1754
|
+
settings=self.settings,
|
|
1755
|
+
init_projects=self.init_projects,
|
|
1756
|
+
init_api_resources=self.init_api_resources,
|
|
1757
|
+
insecure_skip_tls_verify=bool(insecure_skip_tls_verify),
|
|
1758
|
+
),
|
|
1652
1759
|
)
|
|
1653
1760
|
self.set_oc(cluster, oc_client, privileged)
|
|
1654
1761
|
except StatusCodeError as e:
|
|
@@ -1661,14 +1768,16 @@ class OC_Map: # noqa: N801
|
|
|
1661
1768
|
privileged,
|
|
1662
1769
|
)
|
|
1663
1770
|
|
|
1664
|
-
def set_oc(
|
|
1771
|
+
def set_oc(
|
|
1772
|
+
self, cluster: str, value: OCClient | OCLogMsg, privileged: bool
|
|
1773
|
+
) -> None:
|
|
1665
1774
|
with self._lock:
|
|
1666
1775
|
if privileged:
|
|
1667
1776
|
self.privileged_oc_map[cluster] = value
|
|
1668
1777
|
else:
|
|
1669
1778
|
self.oc_map[cluster] = value
|
|
1670
1779
|
|
|
1671
|
-
def cluster_disabled(self, cluster_info):
|
|
1780
|
+
def cluster_disabled(self, cluster_info: Mapping[str, Any]) -> bool:
|
|
1672
1781
|
try:
|
|
1673
1782
|
integrations = cluster_info["disable"]["integrations"]
|
|
1674
1783
|
if self.calling_integration.replace("_", "-") in integrations:
|
|
@@ -1678,12 +1787,13 @@ class OC_Map: # noqa: N801
|
|
|
1678
1787
|
|
|
1679
1788
|
return False
|
|
1680
1789
|
|
|
1681
|
-
def get(self, cluster: str, privileged: bool = False):
|
|
1790
|
+
def get(self, cluster: str, privileged: bool = False) -> OCClient | OCLogMsg:
|
|
1682
1791
|
cluster_map = self.privileged_oc_map if privileged else self.oc_map
|
|
1683
|
-
|
|
1792
|
+
c = cluster_map.get(
|
|
1684
1793
|
cluster,
|
|
1685
1794
|
OCLogMsg(log_level=logging.DEBUG, message=f"[{cluster}] cluster skipped"),
|
|
1686
1795
|
)
|
|
1796
|
+
return c
|
|
1687
1797
|
|
|
1688
1798
|
def get_cluster(self, cluster: str, privileged: bool = False) -> OCClient:
|
|
1689
1799
|
result = self.get(cluster, privileged)
|
|
@@ -1705,12 +1815,11 @@ class OC_Map: # noqa: N801
|
|
|
1705
1815
|
return list(cluster_map.keys())
|
|
1706
1816
|
return [k for k, v in cluster_map.items() if v]
|
|
1707
1817
|
|
|
1708
|
-
def cleanup(self):
|
|
1709
|
-
for oc in
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
if oc:
|
|
1818
|
+
def cleanup(self) -> None:
|
|
1819
|
+
for oc in itertools.chain(
|
|
1820
|
+
self.oc_map.values(), self.privileged_oc_map.values()
|
|
1821
|
+
):
|
|
1822
|
+
if oc and isinstance(oc, OCClient):
|
|
1714
1823
|
oc.cleanup()
|
|
1715
1824
|
|
|
1716
1825
|
|
|
@@ -1719,12 +1828,12 @@ class OCLogMsg(Exception): # noqa: N818
|
|
|
1719
1828
|
Track log messages associated with initializing OC clients in OC_Map.
|
|
1720
1829
|
"""
|
|
1721
1830
|
|
|
1722
|
-
def __init__(self, log_level, message):
|
|
1831
|
+
def __init__(self, log_level: int, message: str) -> None:
|
|
1723
1832
|
super().__init__()
|
|
1724
1833
|
self.log_level = log_level
|
|
1725
1834
|
self.message = message
|
|
1726
1835
|
|
|
1727
|
-
def __bool__(self):
|
|
1836
|
+
def __bool__(self) -> bool:
|
|
1728
1837
|
"""
|
|
1729
1838
|
Returning False here makes this object falsy, which is used
|
|
1730
1839
|
elsewhere when differentiating between an OC client or a log
|
|
@@ -1741,7 +1850,7 @@ LABEL_MAX_KEY_NAME_LENGTH = 63
|
|
|
1741
1850
|
LABEL_MAX_KEY_PREFIX_LENGTH = 253
|
|
1742
1851
|
|
|
1743
1852
|
|
|
1744
|
-
def validate_labels(labels:
|
|
1853
|
+
def validate_labels(labels: Mapping[str, str]) -> list[str]:
|
|
1745
1854
|
"""
|
|
1746
1855
|
Validate a label key/value against some rules from
|
|
1747
1856
|
https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set
|
|
@@ -1803,7 +1912,7 @@ class OpenshiftLazyDiscoverer(LazyDiscoverer):
|
|
|
1803
1912
|
https://github.com/openshift/openshift-restclient-python/blob/master/openshift/dynamic/discovery.py
|
|
1804
1913
|
"""
|
|
1805
1914
|
|
|
1806
|
-
def default_groups(self, request_resources=False):
|
|
1915
|
+
def default_groups(self, request_resources: bool = False) -> dict[str, Any]:
|
|
1807
1916
|
groups = super().default_groups(request_resources)
|
|
1808
1917
|
if self.version.get("openshift"):
|
|
1809
1918
|
groups["oapi"] = {
|
|
@@ -1822,7 +1931,7 @@ class OpenshiftLazyDiscoverer(LazyDiscoverer):
|
|
|
1822
1931
|
}
|
|
1823
1932
|
return groups
|
|
1824
1933
|
|
|
1825
|
-
def get(self, **kwargs):
|
|
1934
|
+
def get(self, **kwargs: Any) -> Any:
|
|
1826
1935
|
"""Same as search, but will throw an error if there are multiple or no
|
|
1827
1936
|
results. If there are multiple results and only one is an exact match
|
|
1828
1937
|
on api_version, that resource will be returned.
|