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 *
|
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
|
import abc
|
||||||
from urllib.parse import parse_qsl
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import F, Value, CharField
|
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 ListAPIView
|
||||||
from rest_framework.generics import get_object_or_404
|
|
||||||
from rest_framework.request import Request
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from accounts.const import AliasAccount
|
|
||||||
from assets.api import SerializeToTreeNodeMixin
|
from assets.api import SerializeToTreeNodeMixin
|
||||||
from assets.models import Asset
|
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 import get_object_or_none, lazyproperty
|
||||||
from common.utils.common import timeit
|
from common.utils.common import timeit
|
||||||
from perms.hands import Node
|
from perms.hands import Node
|
||||||
from perms.models import PermNode
|
from perms.models import PermNode
|
||||||
from perms.utils import PermAssetDetailUtil, UserPermNodeUtil
|
|
||||||
from perms.utils import UserPermAssetUtil
|
from perms.utils import UserPermAssetUtil
|
||||||
|
from perms.utils import UserPermNodeUtil
|
||||||
from .mixin import RebuildTreeMixin
|
from .mixin import RebuildTreeMixin
|
||||||
from ..mixin import SelfOrPKUserMixin
|
from ..mixin import SelfOrPKUserMixin
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserGrantedK8sAsTreeApi',
|
|
||||||
'UserPermedNodesWithAssetsAsTreeApi',
|
'UserPermedNodesWithAssetsAsTreeApi',
|
||||||
'UserPermedNodeChildrenWithAssetsAsTreeApi',
|
'UserPermedNodeChildrenWithAssetsAsTreeApi',
|
||||||
'UserPermedNodeChildrenWithAssetsAsCategoryTreeApi',
|
'UserPermedNodeChildrenWithAssetsAsCategoryTreeApi',
|
||||||
|
@ -218,55 +209,3 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(BaseUserNodeWithAssetAsT
|
||||||
return self.get_assets()
|
return self.get_assets()
|
||||||
else:
|
else:
|
||||||
return []
|
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/',
|
path('<str:user>/nodes/all-with-assets/tree/',
|
||||||
api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
|
api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
|
||||||
name='user-nodes-with-assets-as-tree'),
|
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 = [
|
user_group_permission_urlpatterns = [
|
||||||
|
|
|
@ -3500,37 +3500,6 @@ type = "legacy"
|
||||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||||
reference = "aliyun"
|
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]]
|
[[package]]
|
||||||
name = "ldap3"
|
name = "ldap3"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
|
@ -7701,4 +7670,4 @@ reference = "aliyun"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "2dc429e66e78ab1e264e26da60df6a67cb8d5e501d80297332d673646bc0cf13"
|
content-hash = "9acfafd75bf7dbb7e0dffb54b7f11f6b09aa4ceff769d193a3906d03ae796ccc"
|
||||||
|
|
|
@ -126,7 +126,6 @@ django-auth-ldap = "4.4.0"
|
||||||
boto3 = "1.28.9"
|
boto3 = "1.28.9"
|
||||||
botocore = "1.31.9"
|
botocore = "1.31.9"
|
||||||
s3transfer = "0.6.1"
|
s3transfer = "0.6.1"
|
||||||
kubernetes = "27.2.0"
|
|
||||||
mysqlclient = "2.2.4"
|
mysqlclient = "2.2.4"
|
||||||
pymssql = "2.2.8"
|
pymssql = "2.2.8"
|
||||||
django-redis = "5.3.0"
|
django-redis = "5.3.0"
|
||||||
|
|
Loading…
Reference in New Issue