易陆发现互联网技术论坛

 找回密码
 开始注册
查看: 431|回复: 1
收起左侧

jumpserver二次开发模块

[复制链接]
发表于 2023-3-23 17:00:05 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?开始注册

x
Jumpserver是全球首款完全开源的堡垒机,是符合 4A 的专业运维审计系统。 http://www.jumpserver.org0 Y1 e" Y) O5 ]) f9 P

" @) v: B( M  F/ b! P0 `* |+ _jms很好用,但是引入公司内部,也会有一些问题,比如资产导入,资产和项目关联,如何和现有cmdb打通等问题。我这里只是抛砖引玉,介绍下jms如何二次开发,做企业定制化版的jumpserver,打通DevOps系统的方法。
% t8 `6 B; f8 C0 l首先,以增加项目管理 这个功能为例,说说堡垒机如何二次开发的。
& i, d9 t3 \5 k. [% K; w. m" {) B! J6 `7 S5 F- C" [: j
基于版本 Version 1.4.10-2 GPLv2.
$ m  }+ x: a; W& `& r
* Y, a0 j/ r$ J6 j) T1 n9 W一、概述
4 D" x  Y& c% R6 b# N$ ]Jumpserver用的是python的django框架开发,API 采用了DRF(Django REST framework),基于CBV(Class-based views)开发模式,因此,二次开发也是比较简单的,一般先定义model,然后写api,再使用template渲染,前端基本用datatables较多。
# n$ J) P; m( P2 }+ ]& [9 |
& ^/ N* P( c% y) f/ J# d8 A( w实现效果:
& B" L4 B) [2 V! I( t5 R6 v7 f项目管理
9 ~* Z7 x# B  L0 a. m$ Q项目详情
/ C% F# Q7 c/ m* Z" F- O项目详情2
: ^9 {; H9 Y# {/ X4 W
' O: Z+ q, L0 M  a  M. R二、实战
7 h) H) ?0 p5 ?) H项目(project)管理或者说是服务(service)管理,服务和主机是有关系的,我这里一个主机可以部署多个服务,同时一个服务也可以部署在多台主机上,所以是多堆多的关系' D( M/ _+ b* W  }# {
, r. G" F' ]* F1 r
2.1 定义project model$ C3 R+ S4 K+ h& v% D+ \
jumpserver/apps/assets/models/project.py9 i- C4 z2 @$ P. i7 k
& h9 `" `5 G: i2 w( ^

* L! j  G7 @) U  D2 e+ Z. ?# \2 W, G: r" b$ t. L1 P
#!/usr/bin/env python
) K$ M0 R8 M6 o$ A# -*- coding: utf-8 -*-, E- e) L# [% ?+ n8 D+ o" p0 A

' a8 [  h# G7 g# W3 W4 Z  @  Tfrom __future__ import unicode_literals+ `2 J1 l; w7 R: p* l
- T  P1 b4 C/ Z2 f
from django.db import models
& d9 n, M, G; _( b+ Jimport logging
+ L5 z' T# d8 W: n* ~from django.utils.translation import ugettext_lazy as _$ |& U, }- }- D- O- X8 E1 _
import uuid
' f6 j/ J2 k- M! nfrom django.core.cache import cache8 l- J& w( ^5 o; H" x  f: e$ ]
, K# h% z5 u# K2 d* E
__all__ = ['Project', 'TYPE_CHOICES']: p+ p" h7 j7 ~0 `$ c: r
logger = logging.getLogger(__name__)$ n$ X/ T6 L0 u- U2 _' ?( \7 a4 b

( _7 i5 S& D7 n0 y. s% S5 `3 QTYPE_CHOICES = (& l5 f! }, S" W3 r; q  z$ t
        ('Java', 'java'),
. [' x' _% O& f" y" L$ ?        ('React', 'react'),
1 I8 ^' |, r; O( ]+ ~        ('Cpp', 'c++'),! g5 M' V1 Q) V1 M. E* U/ J2 ]
        ('Python', 'python'),  e) _  s5 Q! i5 a: P! w
        ('Middleware', 'Middleware'),2 e# R+ V# L6 b) I2 d) z7 {, K
        ('Other', 'Other'),
. v; B3 K3 h7 Q. J    )
# U4 E0 y( u2 x& r' }4 ?% W! e( g4 G: j& B
6 P$ `8 p8 a6 p% k: k4 ~3 Q5 f
class Project(models.Model):
- k6 P- J+ L2 j0 U    TYPE_CHOICES =TYPE_CHOICES1 y* j* }& L1 ^# W

0 E- W1 c0 l; u- i( p' T    id = models.UUIDField(default=uuid.uuid4, primary_key=True), M! X. V  @1 B- |
    name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
+ U- b* N5 ]0 m2 E( ]/ o% r9 S7 h  M9 L* s8 a9 r
    parent = models.ForeignKey('self', null=True, blank=True, related_name="children", on_delete=models.SET_NULL)8 X9 S# E! y/ U

9 H1 _% p% ~* N& P    domain_name = models.CharField(max_length=64, blank=True, verbose_name=_('Domain Name'))5 G( Q" x* _! {+ J
    git_address = models.CharField(max_length=128, blank=True, verbose_name=_('Git Address'))9 T( V& @2 {$ U/ K8 r! z8 r! z
    port = models.IntegerField(blank=True, null=True, verbose_name=_('Open port'))
( j' |  ~8 r9 T- k6 }    type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
4 |, W5 ?; N& D* e; b# O5 q                            default='Java', verbose_name=_('Project type'), )
3 |6 Y4 \4 H9 E0 O    dev_users = models.ManyToManyField(
$ N+ L7 F. \$ ]5 ]        'users.User', related_name='users1',
" w9 G: f1 K/ C7 u        blank=True, verbose_name=_('Dev Users')
/ }1 _1 n7 M$ k  {" @    )
8 Z. g( c& n2 N* w6 ?$ c    sa_users = models.ManyToManyField(7 a& S, c5 W* M. q; ]
        'users.User', related_name='users2',( K' Z* K* A) N5 v' L3 d! `
        blank=True, verbose_name=_('Sa Users')* d/ G/ Y, r6 n
    )1 e$ T3 m+ s  y. ~' [. y
    scrum_master = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Scrum Master'))
3 V3 Q9 R8 Y0 o" g    created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
* t, a7 F8 V4 u% ?; C6 ^    updated_by = models.CharField(max_length=32, blank=True, verbose_name=_('Updated by')). d' Z7 Y2 @6 E. X$ y" T" u
    date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
7 v3 d' O2 p) E7 p8 w    date_updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date updated'))
3 J8 L0 A/ v/ j) e    comment = models.TextField(blank=True, verbose_name=_('Comment'))" E; x! U1 F8 j  W
    level = models.IntegerField(default=0, verbose_name=_('Level'))
2 n9 v( [# j/ w
* ]! h) M, f! l1 M8 y! T    def __unicode__(self):
* Z3 j" J' R, d, w        return self.name
8 D# v; `: ^& R; V: b5 _! y; S) ~    __str__ = __unicode__
! H& R' {. z$ |( a) u  {/ {- n$ E; _0 ?" N* _6 S
    @property/ v( T0 }/ w! h
    def all_sa_users(self):$ K, S0 t9 N1 }8 y$ }7 J/ P" A
        return [user.name for user in self.sa_users.all()]
* l5 d, G( Q" m( n5 R
0 x# s. W5 j! A6 n2 l2 @    @property
! X" `5 a" D; d% V3 p    def all_dev_users(self):
& h1 |+ w  E% z  N9 L- K        return [user.name for user in self.dev_users.all()]
3 A. b4 g1 k$ y
, i$ |4 s( W: x7 \1 q* `    def get_related_assets(self):- A, B5 Y; ]) f
        assets = self.assets.all()
* \: y. O- ^' i8 `5 I        return assets
0 o; N) N8 s$ J
" Z) N( U  r3 t    def get_related_assets_ip(self):. {0 c" O/ c6 c7 Q5 B8 D3 T9 c0 L
        assets = self.assets.all()
5 c, B* w5 m' o  M0 I8 z        return [{'ip': asset.ip, 'environment': asset.environment} for asset in assets]
$ Z" T) B/ Y$ b
5 U( X/ G& P2 N) q0 c8 ~8 y    @property5 k- o. p/ D6 t6 p
    def all_assets_ip(self):) e+ W- {: o% n+ X
        return self.get_related_assets_ip()
1 ~& C- h3 T; H- V+ ?0 a8 v, U% n
7 G8 I4 q; _( r- [    class Meta:
5 U# U% M# v, d        ordering = ['name']. ~. X# x( V# `1 z
2.2 修改asset model; v: z# t9 e/ F' @
jumpserver/apps/assets/models/asset.py) F8 ?( E( @% i- F( _
: l) \7 y, x2 o9 y2 A

! `7 l7 V2 b1 s" V( Z4 d& a: N
8 E! X9 F7 @- K6 C# 增加环境定义" b( J6 C# F, `' v
ENV_CHOICES = (
) h1 g# g5 v# i$ r7 x. T    ('DEV', 'dev'),
% F0 O" C) ^  ~0 [    ('TEST', 'test'),) Y4 e3 ^$ g. n
    ('DEMO', 'demo'),
- z' ~0 V9 D& X9 O    ('TDEMO', 'tdemo'),3 {: g8 [) y; d( d+ S5 m; t' s! h
    ('PROD', 'prod'),2 o3 [+ X! i$ c# Y  X; g
    ('Other', 'other'),
6 h4 G5 O8 I3 p1 |% @)
4 P  B& ^2 K8 }. t6 oenvironment = models.CharField(max_length=32, choices=ENV_CHOICES, default='DEV', verbose_name=_('Environment'))
2 X* s) |& t$ b* [# Project 和 asset 多对多关系# f4 L* I! s8 x/ |
projects = models.ManyToManyField(Project, blank=True, verbose_name=_('Projects'), related_name='assets', ). B  G% H: ]+ ^3 N  a; a
2.3 加入全局变量中init' |6 f0 T8 b3 J: Y) L" o
jumpserver/apps/assets/models/init.py 末尾增加
' ]5 C0 t+ p) W  v  I& M; d: e/ B! s. Z% g4 R- F" X' A( R  I
1
& t+ q+ s( n3 k3 a1 ~& efrom .project import *
0 ~  H# ^. ]& [7 l9 _+ o2.4 定义DRF的serializers" P$ K3 ]' `8 e4 ?& T* ~- K2 v
jumpserver/apps/assets/serializers/project.py; [. ^+ K6 z+ t, k' F' w
6 l7 J3 C+ B" D1 O) ]  `. [, ^

+ i2 u+ I" j. L/ \7 q8 M' t/ l+ H% c
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin$ }( d; k% w, i6 X5 X! Z& K
from rest_framework import serializers
/ a: A8 {- Q; ~; nfrom ..models import Project, Asset- D8 D* b1 q5 d8 L. K
8 ]' x" B9 R+ U$ v, I) A' V

( J6 _; O8 O9 U# _$ aclass ProjectSerializer(serializers.ModelSerializer):" B/ B1 @' ^( K# U- g) t
    asset_count = serializers.SerializerMethodField()
9 y/ T6 W. e' B9 e+ A" W( q: ?9 {& N" m8 s! G9 |* ^' j& {
    class Meta:
; a8 l2 n5 v) Z- K4 Q        model = Project
$ n7 z5 ?% k# d+ [        fields = '__all__'2 ]0 v3 O; ?( w1 R
. z/ w5 N* R+ C
    @staticmethod/ r7 |) d. N8 Q8 Y  Q: Z( Q
    def get_asset_count(obj):$ q+ V# q( k, B5 r) o  [
        return obj.assets.count()
! h: v" x+ K" r; p+ Z2 ~2 j
3 i# K+ F) t$ ?+ b
7 T' L& J- p, Cclass ProjectAssetsSerializer(serializers.ModelSerializer):2 a9 ?6 ?5 a/ W/ }8 i1 H% W" ]
    class Meta:
' {" ^( W. F& V4 ~- L& c/ m3 e- [! {' u        fields = ['id', 'name', 'port']# l4 @4 `  e. S
        model = Project" u+ l2 ~' e  I5 @' Y
+ M" A7 E' @' {
    def get_field_names(self, declared_fields, info):% o; H# K$ L# l- U/ T! r, _
        fields = super().get_field_names(declared_fields, info)4 h3 k* ^! a4 }; t* j
        fields.extend([
- i; u- f, r% g: W8 \            'all_assets_ip'/ i$ J7 F# D& `1 {
        ])" |6 }8 W4 l; L$ o$ m! v
        return fields3 h! A. ?* ~2 I# r) u0 j% T
3 ?3 w8 h9 ~9 M$ H
' t- w  D2 E+ `" C$ a# m( {
class ProjectUpdateSerializer(serializers.ModelSerializer):; {- U7 K2 v* m* @# f2 ^8 ?$ Q
    """update the project, and add or delete the asset to the project""": ]5 m/ B+ B5 k) h& t
    assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())  p; w9 F" A# f6 s$ k- a) c& m. y

, M. K& T: w$ [9 t- }) y    class Meta:7 w3 s) E4 @7 }& t
        model = Project
3 g0 Z) G6 ^. d9 p3 ^. N        fields = ['id', 'assets']; m' Y8 j  F1 C
2 W% z: o. \2 L+ L$ q4 t5 ^9 m

1 _8 p! {# l/ I1 E* o1 i5 U/ j3 @class AssetUpdateProjectSerializer(serializers.ModelSerializer):
" e8 Y2 Q; ?' `# f  X    projects = serializers.PrimaryKeyRelatedField(many=True, queryset=Project.objects.all())( L! F/ {, V! [) O( n% c& B
/ A( D( r6 i$ t
    class Meta:
4 t. M9 [# h6 N! v9 c        model = Asset4 C& s$ W6 n7 ?8 p; B
        fields = ['id', 'projects']- C# N, v# z' x# h
2.5 增加URl支持
4 _3 {& X1 H# z  K; j+ M* I4 q/ |/jumpserver/apps/assets/urls/api_urls.py
0 q/ ?. @1 O- j+ |' F* M& i8 q* g) k0 H0 }

  G" {) M+ C+ Q+ Z& b1 K, U) H, C/ k7 F
router.register(r'projects', api.ProjectViewSet, 'project')) ~; P9 ~! U3 I% U: o5 w
router.register(r'projects-assets', api.ProjectAssetsViewSet, 'project-assets-view')
; p1 D, B8 y# q; b
5 O- C( e8 R- n  r# urlpatterns 增加:
2 N' `2 L8 `+ P- H    path(r'assets/<uuid:pk>/projects/',
  Z4 m8 g- g. p6 d/ ?5 W/ ^+ _; ?            api.AssetUpdateProjectApi.as_view(), name='asset-update-project'),
" L" [6 N4 D) v' m! s4 j$ P; q    path(r'projects/<uuid:pk>/assets/',  j6 G* D7 U, ]- B6 _, [
            api.ProjectUpdateApi.as_view(), name='project-update-assets'),
. C0 p2 J1 M8 G) w3 P    path('project/<uuid:pk>/assets/',
$ R9 V+ v, a2 O2 R  f* N            api.ProjectAssetsListView.as_view(), name='project-assets'),0 B: n/ r% u& i7 y0 t
API到这里基本写完了,至于前端的一些,都是调用这些api,进行增删改查操作,详情见github
% k7 |0 {: r$ m3 A+ c! }9 a$ [4 i1 Q* \7 x0 ~! }) \) f  r* H) C2 B
3 数据库迁移' G) J1 H3 ]8 G3 x# ?3 U8 `
定义了模型以后,需要执行migrate命令进行数据库变更,省了自己执行sql' X% n& l/ E) H9 ^: e0 }% h
1 n9 c: G% i3 E
& ?  T$ q# p+ o4 X. i0 b; P7 i9 S4 S

  e% K  y$ J6 X9 V- m4 _9 a0 [& ]cd jumpserver/utils
& H; h, @/ j( Jbash make_migrations.sh
" L3 a# n  P$ ?7 G5 n1 V: d7 t4 国际化2 v! }4 B& t% n5 e- y
因为我们定义的好多变量都是英文的,这里可以通过国际化手段支持中英文2 B) Z( G# d1 y; f5 E

) N3 Y' c1 |0 I8 D/ B
! z3 ^0 A' x% H
1 f8 q! W4 {# t4 z- R+ G3 p. }6 P# 先执行
/ l0 U- X8 a& J5 C0 r7 B8 m$ acd jumpserver/apps
4 X/ V2 P4 m- d0 ~$ |" r% X) `python manage.py makemessages -l zh
/ `4 }( {/ G7 d5 H: L# 修改jumpserver/apps/locale/zh/LC_MESSAGES/django.po 的中英文对应
: D" f$ q, @8 S6 Yvim jumpserver/apps/locale/zh/LC_MESSAGES/django.po7 o9 ?7 ?  E- \+ y  R: {) m
# 修改完成后生效,完全生效需要服务重启' P5 [2 o" W/ O" y: j: C# h* ~
python manage.py compilemessages1 y" i; i' I3 Q/ S
. T! t) H! f! V- c2 r

- H5 `$ Y" x# V6 I! F
4 Q7 s6 M) }4 a) n* z' _, k
 楼主| 发表于 2023-3-23 17:00:06 | 显示全部楼层
您需要登录后才可以回帖 登录 | 开始注册

本版积分规则

关闭

站长推荐上一条 /4 下一条

北京云银创陇科技有限公司以云计算运维,代码开发

QQ|返回首页|Archiver|小黑屋|易陆发现技术论坛 ( 蜀ICP备2026014127号-1 )点击这里给我发消息

GMT+8, 2026-4-8 21:37 , Processed in 0.053968 second(s), 21 queries .

Powered by Discuz! X3.4 Licensed

© 2012-2025 Discuz! Team.

快速回复 返回顶部 返回列表