mirror of https://github.com/jumpserver/jumpserver
perf: Remove kubernetes tree api (#13995)
* perf: Remove kubernetes tree api * perf: Update Dockerfile with new base image tag --------- Co-authored-by: feng <1304903146@qq.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>pull/14005/head
parent
828582333d
commit
181eb621c0
|
@ -1,2 +1 @@
|
|||
from .k8s import *
|
||||
from .node import *
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
from kubernetes import client
|
||||
from kubernetes.client import api_client
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..const import CloudTypes, Category
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class KubernetesClient:
|
||||
def __init__(self, asset, token):
|
||||
self.url = asset.address
|
||||
self.token = token or ''
|
||||
self.server = self.get_gateway_server(asset)
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
configuration = client.Configuration()
|
||||
scheme = urlparse(self.url).scheme
|
||||
if not self.server:
|
||||
host = self.url
|
||||
else:
|
||||
host = f'{scheme}://127.0.0.1:{self.server.local_bind_port}'
|
||||
configuration.host = host
|
||||
configuration.verify_ssl = False
|
||||
configuration.api_key = {"authorization": "Bearer " + self.token}
|
||||
c = api_client.ApiClient(configuration=configuration)
|
||||
api = core_v1_api.CoreV1Api(c)
|
||||
return api
|
||||
|
||||
def get_namespaces(self):
|
||||
namespaces = []
|
||||
resp = self.api.list_namespace()
|
||||
for ns in resp.items:
|
||||
namespaces.append(ns.metadata.name)
|
||||
return namespaces
|
||||
|
||||
def get_pods(self, namespace):
|
||||
pods = []
|
||||
resp = self.api.list_namespaced_pod(namespace)
|
||||
for pd in resp.items:
|
||||
pods.append(pd.metadata.name)
|
||||
return pods
|
||||
|
||||
def get_containers(self, namespace, pod_name):
|
||||
containers = []
|
||||
resp = self.api.read_namespaced_pod(pod_name, namespace)
|
||||
for container in resp.spec.containers:
|
||||
containers.append(container.name)
|
||||
return containers
|
||||
|
||||
@staticmethod
|
||||
def get_gateway_server(asset):
|
||||
gateway = None
|
||||
if not asset.is_gateway and asset.domain:
|
||||
gateway = asset.domain.select_gateway()
|
||||
|
||||
if not gateway:
|
||||
return
|
||||
|
||||
remote_bind_address = (
|
||||
urlparse(asset.address).hostname,
|
||||
urlparse(asset.address).port or 443
|
||||
)
|
||||
server = SSHTunnelForwarder(
|
||||
(gateway.address, gateway.port),
|
||||
ssh_username=gateway.username,
|
||||
ssh_password=gateway.password,
|
||||
ssh_pkey=gateway.private_key_path,
|
||||
remote_bind_address=remote_bind_address
|
||||
)
|
||||
try:
|
||||
server.start()
|
||||
except BaseSSHTunnelForwarderError:
|
||||
err_msg = 'Gateway is not active: %s' % asset.get('name', '')
|
||||
print('\033[31m %s \033[0m\n' % err_msg)
|
||||
return server
|
||||
|
||||
def run(self, tp, *args):
|
||||
func_name = f'get_{tp}s'
|
||||
data = []
|
||||
if hasattr(self, func_name):
|
||||
try:
|
||||
data = getattr(self, func_name)(*args)
|
||||
except Exception as e:
|
||||
logger.error(f'K8S tree get {tp} error: {e}')
|
||||
|
||||
if self.server:
|
||||
self.server.stop()
|
||||
return data
|
||||
|
||||
|
||||
class KubernetesTree:
|
||||
def __init__(self, asset, secret):
|
||||
self.asset = asset
|
||||
self.secret = secret
|
||||
|
||||
def as_asset_tree_node(self):
|
||||
i = str(self.asset.id)
|
||||
name = str(self.asset)
|
||||
node = self.create_tree_node(
|
||||
i, i, name, 'asset', icon='k8s', is_open=True,
|
||||
)
|
||||
return node
|
||||
|
||||
def as_namespace_node(self, name, tp):
|
||||
i = urlencode({'namespace': name})
|
||||
pid = str(self.asset.id)
|
||||
node = self.create_tree_node(i, pid, name, tp, icon='cloud')
|
||||
return node
|
||||
|
||||
def as_pod_tree_node(self, namespace, name, tp):
|
||||
pid = urlencode({'namespace': namespace})
|
||||
i = urlencode({'namespace': namespace, 'pod': name})
|
||||
node = self.create_tree_node(i, pid, name, tp, icon='cloud')
|
||||
return node
|
||||
|
||||
def as_container_tree_node(self, namespace, pod, name, tp):
|
||||
pid = urlencode({'namespace': namespace, 'pod': pod})
|
||||
i = urlencode({'namespace': namespace, 'pod': pod, 'container': name})
|
||||
node = self.create_tree_node(
|
||||
i, pid, name, tp, icon='cloud', is_container=True
|
||||
)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
def create_tree_node(id_, pid, name, identity, icon='', is_container=False, is_open=False):
|
||||
node = {
|
||||
'id': id_,
|
||||
'name': name,
|
||||
'title': name,
|
||||
'pId': pid,
|
||||
'isParent': not is_container,
|
||||
'open': is_open,
|
||||
'iconSkin': icon,
|
||||
'meta': {
|
||||
'type': 'k8s',
|
||||
'data': {
|
||||
'category': Category.CLOUD,
|
||||
'type': CloudTypes.K8S,
|
||||
'identity': identity
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
|
||||
def async_tree_node(self, namespace, pod):
|
||||
tree = []
|
||||
k8s_client = KubernetesClient(self.asset, self.secret)
|
||||
if pod:
|
||||
tp = 'container'
|
||||
containers = k8s_client.run(
|
||||
tp, namespace, pod
|
||||
)
|
||||
for container in containers:
|
||||
container_node = self.as_container_tree_node(
|
||||
namespace, pod, container, tp
|
||||
)
|
||||
tree.append(container_node)
|
||||
elif namespace:
|
||||
tp = 'pod'
|
||||
pods = k8s_client.run(tp, namespace)
|
||||
for pod in pods:
|
||||
pod_node = self.as_pod_tree_node(namespace, pod, tp)
|
||||
tree.append(pod_node)
|
||||
else:
|
||||
tp = 'namespace'
|
||||
namespaces = k8s_client.run(tp)
|
||||
for namespace in namespaces:
|
||||
namespace_node = self.as_namespace_node(namespace, tp)
|
||||
tree.append(namespace_node)
|
||||
return tree
|
|
@ -1,31 +1,22 @@
|
|||
import abc
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import F, Value, CharField
|
||||
from rest_framework.exceptions import PermissionDenied, NotFound
|
||||
from rest_framework.generics import ListAPIView
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from accounts.const import AliasAccount
|
||||
from assets.api import SerializeToTreeNodeMixin
|
||||
from assets.models import Asset
|
||||
from assets.utils import KubernetesTree
|
||||
from authentication.models import ConnectionToken
|
||||
from common.exceptions import JMSException
|
||||
from common.utils import get_object_or_none, lazyproperty
|
||||
from common.utils.common import timeit
|
||||
from perms.hands import Node
|
||||
from perms.models import PermNode
|
||||
from perms.utils import PermAssetDetailUtil, UserPermNodeUtil
|
||||
from perms.utils import UserPermAssetUtil
|
||||
from perms.utils import UserPermNodeUtil
|
||||
from .mixin import RebuildTreeMixin
|
||||
from ..mixin import SelfOrPKUserMixin
|
||||
|
||||
__all__ = [
|
||||
'UserGrantedK8sAsTreeApi',
|
||||
'UserPermedNodesWithAssetsAsTreeApi',
|
||||
'UserPermedNodeChildrenWithAssetsAsTreeApi',
|
||||
'UserPermedNodeChildrenWithAssetsAsCategoryTreeApi',
|
||||
|
@ -218,55 +209,3 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(BaseUserNodeWithAssetAsT
|
|||
return self.get_assets()
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):
|
||||
""" 用户授权的K8s树 """
|
||||
|
||||
def get_token(self):
|
||||
token_id = self.request.query_params.get('token')
|
||||
token = get_object_or_404(ConnectionToken, pk=token_id)
|
||||
if token.is_expired:
|
||||
raise PermissionDenied('Token is expired')
|
||||
token.renewal()
|
||||
return token
|
||||
|
||||
def get_account_secret(self, token: ConnectionToken):
|
||||
util = PermAssetDetailUtil(self.user, token.asset)
|
||||
accounts = util.get_permed_accounts_for_user()
|
||||
account_name = token.account
|
||||
|
||||
if account_name in [AliasAccount.INPUT, AliasAccount.USER]:
|
||||
return token.input_secret
|
||||
else:
|
||||
accounts = filter(lambda x: x.name == account_name, accounts)
|
||||
accounts = list(accounts)
|
||||
if not accounts:
|
||||
raise NotFound('Account is not found')
|
||||
account = accounts[0]
|
||||
return account.secret
|
||||
|
||||
@staticmethod
|
||||
def get_namespace_and_pod(key):
|
||||
namespace_and_pod = dict(parse_qsl(key))
|
||||
pod = namespace_and_pod.get('pod')
|
||||
namespace = namespace_and_pod.get('namespace')
|
||||
return namespace, pod
|
||||
|
||||
def list(self, request: Request, *args, **kwargs):
|
||||
token = self.get_token()
|
||||
asset = token.asset
|
||||
secret = self.get_account_secret(token)
|
||||
key = self.request.query_params.get('key')
|
||||
namespace, pod = self.get_namespace_and_pod(key)
|
||||
|
||||
tree = []
|
||||
k8s_tree_instance = KubernetesTree(asset, secret)
|
||||
if not any([namespace, pod]) and not key:
|
||||
asset_node = k8s_tree_instance.as_asset_tree_node()
|
||||
tree.append(asset_node)
|
||||
try:
|
||||
tree.extend(k8s_tree_instance.async_tree_node(namespace, pod))
|
||||
return Response(data=tree)
|
||||
except Exception as e:
|
||||
raise JMSException(e)
|
||||
|
|
|
@ -47,9 +47,6 @@ user_permission_urlpatterns = [
|
|||
path('<str:user>/nodes/all-with-assets/tree/',
|
||||
api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
|
||||
name='user-nodes-with-assets-as-tree'),
|
||||
path('<str:user>/nodes/children-with-k8s/tree/',
|
||||
api.UserGrantedK8sAsTreeApi.as_view(),
|
||||
name='user-nodes-children-with-k8s-as-tree'),
|
||||
]
|
||||
|
||||
user_group_permission_urlpatterns = [
|
||||
|
|
|
@ -3500,37 +3500,6 @@ type = "legacy"
|
|||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "aliyun"
|
||||
|
||||
[[package]]
|
||||
name = "kubernetes"
|
||||
version = "27.2.0"
|
||||
description = "Kubernetes python client"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "kubernetes-27.2.0-py2.py3-none-any.whl", hash = "sha256:0f9376329c85cf07615ed6886bf9bf21eb1cbfc05e14ec7b0f74ed8153cd2815"},
|
||||
{file = "kubernetes-27.2.0.tar.gz", hash = "sha256:d479931c6f37561dbfdf28fc5f46384b1cb8b28f9db344ed4a232ce91990825a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=14.05.14"
|
||||
google-auth = ">=1.0.1"
|
||||
oauthlib = ">=3.2.2"
|
||||
python-dateutil = ">=2.5.3"
|
||||
pyyaml = ">=5.4.1"
|
||||
requests = "*"
|
||||
requests-oauthlib = "*"
|
||||
six = ">=1.9.0"
|
||||
urllib3 = ">=1.24.2"
|
||||
websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0"
|
||||
|
||||
[package.extras]
|
||||
adal = ["adal (>=1.0.2)"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
reference = "aliyun"
|
||||
|
||||
[[package]]
|
||||
name = "ldap3"
|
||||
version = "2.9.1"
|
||||
|
@ -7701,4 +7670,4 @@ reference = "aliyun"
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "2dc429e66e78ab1e264e26da60df6a67cb8d5e501d80297332d673646bc0cf13"
|
||||
content-hash = "9acfafd75bf7dbb7e0dffb54b7f11f6b09aa4ceff769d193a3906d03ae796ccc"
|
||||
|
|
|
@ -126,7 +126,6 @@ django-auth-ldap = "4.4.0"
|
|||
boto3 = "1.28.9"
|
||||
botocore = "1.31.9"
|
||||
s3transfer = "0.6.1"
|
||||
kubernetes = "27.2.0"
|
||||
mysqlclient = "2.2.4"
|
||||
pymssql = "2.2.8"
|
||||
django-redis = "5.3.0"
|
||||
|
|
Loading…
Reference in New Issue