- 积分
- 16843
在线时间 小时
最后登录1970-1-1
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?开始注册
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 |
|