- 积分
- 16843
在线时间 小时
最后登录1970-1-1
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?开始注册
x
Jumpserver是全球首款完全开源的堡垒机,是符合 4A 的专业运维审计系统。 http://www.jumpserver.org @; C: I% U& B/ |$ E5 t8 p# S" T
9 ?! L* i# `7 T8 n: F$ x/ njms很好用,但是引入公司内部,也会有一些问题,比如资产导入,资产和项目关联,如何和现有cmdb打通等问题。我这里只是抛砖引玉,介绍下jms如何二次开发,做企业定制化版的jumpserver,打通DevOps系统的方法。
; j9 V9 P- z E( s首先,以增加项目管理 这个功能为例,说说堡垒机如何二次开发的。
1 v, h: N' M/ P. T
4 A Q6 X7 s- J, h0 `# z基于版本 Version 1.4.10-2 GPLv2.
( L9 |) D- B# r: Z1 K0 z+ C) w) Z; ^; A7 J, Z& F5 B
一、概述$ G' J2 q" J6 q* Y" Q' c5 P5 c, ~
Jumpserver用的是python的django框架开发,API 采用了DRF(Django REST framework),基于CBV(Class-based views)开发模式,因此,二次开发也是比较简单的,一般先定义model,然后写api,再使用template渲染,前端基本用datatables较多。5 o3 j. }6 |0 {& N% k+ B
. h6 x. R d& k
实现效果:
' r$ }9 Z. v+ A4 F1 ?: |+ k1 s项目管理
2 a* @" i+ g2 G. Q" g! e7 ~项目详情3 Z" S4 I9 ^3 ]+ R/ U9 `
项目详情2
5 d B7 u4 R S# ?( x
) b) `: ]/ \- I7 U" M; J二、实战) H1 y Q5 V+ l# d: H# n
项目(project)管理或者说是服务(service)管理,服务和主机是有关系的,我这里一个主机可以部署多个服务,同时一个服务也可以部署在多台主机上,所以是多堆多的关系
Y, h7 M5 r6 o) |6 N
: @* W7 R% Z0 i2.1 定义project model
2 D; o+ M8 i1 D' Mjumpserver/apps/assets/models/project.py
8 Q% R" c+ g, m% W8 x1 x* T+ `/ _0 b) L6 y4 T$ u
% f/ a( M5 `0 V- G* ]8 C' j9 Z) s
#!/usr/bin/env python
$ U& I" y- c0 _% l; y0 n& l: \# -*- coding: utf-8 -*-
) e& d) \6 u+ U1 O7 _$ Q" u( Z8 Q& l- \9 I1 P0 p7 {; c L' X
from __future__ import unicode_literals
6 m B/ e7 x0 Y7 C6 i2 q' l$ q8 p' `
from django.db import models+ ?% f2 p: n2 c5 ~
import logging
; e. A' s8 O( j0 Vfrom django.utils.translation import ugettext_lazy as _
( K7 K d) t( @! t U$ H4 |2 z. Limport uuid8 r1 l# }8 l6 \* p0 e- b
from django.core.cache import cache
: H$ g/ ~* l7 i9 Y* Y
. A! o* t$ P% D7 F! g( h__all__ = ['Project', 'TYPE_CHOICES']$ ?6 P3 S' ~- j- K% l" X
logger = logging.getLogger(__name__)# Z, d1 ~- a& U5 I7 q0 |8 e( h
% Q4 S( B+ ]$ L( z8 M) g
TYPE_CHOICES = (
+ i! O* `2 Q) i4 l0 ^7 r ('Java', 'java'),
. f- O3 t" v) m6 O# g ('React', 'react')," h2 S+ |: d5 b$ _7 C5 y# l& G
('Cpp', 'c++'),
/ R! L2 ~8 E& P+ _4 ?, b* } I+ L7 V ('Python', 'python')," o9 j2 ~: R1 E( v4 b, B, I
('Middleware', 'Middleware'),4 ^5 D" N' y* j- l
('Other', 'Other'),; z8 `: B4 D5 |$ K2 V" g ^ d5 x
)
& G; Y; a- Q) ]2 x" F
2 ]; l' L% b4 `+ t% ?
! @% |0 v- q/ X4 V; w6 D9 w! pclass Project(models.Model):
0 m: E" U' f: U2 F TYPE_CHOICES =TYPE_CHOICES) z3 t9 Q. }" W# |% F7 c
! p) N7 o$ o, o: M" ^; h7 I id = models.UUIDField(default=uuid.uuid4, primary_key=True)
" v. l7 N# m9 v4 }6 k7 h name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))7 o# C& Z0 }+ B3 s( p1 ] K) w
9 \& D! f ?% ~/ e$ b8 S2 E8 }2 O
parent = models.ForeignKey('self', null=True, blank=True, related_name="children", on_delete=models.SET_NULL)
( O! s* t2 y9 s8 v8 p! @
7 f! Z7 K: Y1 D1 t, `& \ domain_name = models.CharField(max_length=64, blank=True, verbose_name=_('Domain Name'))4 r& G# z) h8 w0 r' n8 ?" B. I
git_address = models.CharField(max_length=128, blank=True, verbose_name=_('Git Address'))
, m; Z' C8 g1 u1 K$ h" e L port = models.IntegerField(blank=True, null=True, verbose_name=_('Open port'))
0 ~* f& [1 ^1 N O0 e! `1 ] type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
/ |5 Z; r4 {# p( p7 S default='Java', verbose_name=_('Project type'), )
2 k* ^4 z) v" C% v; ^! | dev_users = models.ManyToManyField(( e& Y( S2 J) f6 `
'users.User', related_name='users1',
9 J* e, w6 P3 p, { blank=True, verbose_name=_('Dev Users')
9 m9 u3 w2 o/ Y )/ h. n h9 b# p- A0 u( V9 P3 E
sa_users = models.ManyToManyField(
7 U6 H- s& |; j: B 'users.User', related_name='users2',0 b x+ [9 C' r( E
blank=True, verbose_name=_('Sa Users')
9 u! g! d9 Z, y/ c8 ^( v& A, T )
% S8 T" u9 {* p0 @+ }2 X scrum_master = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Scrum Master'))
: z, f$ N) q) ^- V" A/ ` created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
[; K3 p2 ~* q3 b+ p updated_by = models.CharField(max_length=32, blank=True, verbose_name=_('Updated by'))+ A/ n* g0 g! u/ |) S% A! a3 ]% W
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))1 L& o# T8 u0 p# Y
date_updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date updated'))! r6 s- Z4 H. D- `) B" K' U
comment = models.TextField(blank=True, verbose_name=_('Comment'))
. J1 _' Q1 w3 ^+ M- B0 l; L level = models.IntegerField(default=0, verbose_name=_('Level'))5 X4 B5 e, V5 L/ A" c
7 K% g# A$ J- l( E% L5 w def __unicode__(self):2 q4 F$ Z3 Z/ b. s8 T$ O" h
return self.name
8 [7 A( p* e5 W __str__ = __unicode__
8 f$ Z/ Z: v6 t2 }6 @) f! i, r/ A% j3 B& M% l' I" ~( s: P( S9 [* ?
@property: M, k: o3 w$ A9 h
def all_sa_users(self):
/ f4 ~" m: M) C" I8 l g0 d* k return [user.name for user in self.sa_users.all()]
* X! i [1 V5 J+ q' X/ D2 [# X0 h6 X. S R
@property
) u) P U0 _' S. u1 [5 C6 L def all_dev_users(self):: d2 e2 i5 }& D
return [user.name for user in self.dev_users.all()]# a( H8 ?( C6 O' B- v/ V4 N
$ N$ v% X% |" U& L0 a) A
def get_related_assets(self):
5 h! }" D$ q2 Z' | assets = self.assets.all()' N2 k6 g& c* b7 P1 K K
return assets7 a) Z! T' {' N: v
# n/ |( {" s& o* V3 k7 Y2 `& B* V/ B
def get_related_assets_ip(self):9 r. |" p( P% `; B
assets = self.assets.all()
: k, M* \" K9 K& ~ return [{'ip': asset.ip, 'environment': asset.environment} for asset in assets]
; a6 ^* ]3 `7 V2 t4 E
, C# s% t% b u* l+ a+ e @property
. k! C( R! V8 n% a+ M0 N def all_assets_ip(self):) S' m/ a2 Y- g& u2 X8 O, u. p
return self.get_related_assets_ip()
: z% e' W2 P; Z' T5 d2 [! ^ r& l: @9 G' B& i! q8 {
class Meta:
! G" |# r8 s7 Q1 U ordering = ['name']
+ Q/ S( b5 o c% N2.2 修改asset model
+ ?9 K' l( K( v+ R9 ^; a' Ijumpserver/apps/assets/models/asset.py
/ e# B9 S# p. r# x& H7 @% n! S2 j2 [0 |5 P; O$ F9 d& _8 I) ?
) d( _5 }7 Q+ t/ b/ m0 q/ K7 c' ~5 \! r
# 增加环境定义) b/ _ e1 C' O5 B- C* s1 D
ENV_CHOICES = (
, _9 ^6 G/ L) ] D& C( I ('DEV', 'dev'),( A" d2 [7 g9 C- N9 z
('TEST', 'test'),& @( J9 {/ B+ h+ j* f4 g @
('DEMO', 'demo'),
. U7 l! ?, t3 f ('TDEMO', 'tdemo'),* @! g3 K! V! K6 N! E2 t" ~' F
('PROD', 'prod'),4 x f+ K4 a9 Y" r& O+ F
('Other', 'other'),, z5 `4 o* J% ~6 [
)( x! e3 y3 F+ D9 i. q; [9 g2 Y
environment = models.CharField(max_length=32, choices=ENV_CHOICES, default='DEV', verbose_name=_('Environment')): r% g5 w6 g: o+ h3 r, A
# Project 和 asset 多对多关系
$ d" x/ Y" Q- B4 r' r* U7 N8 xprojects = models.ManyToManyField(Project, blank=True, verbose_name=_('Projects'), related_name='assets', ). }. p/ q* _6 E8 ?$ k+ `4 g5 U( S4 D$ g
2.3 加入全局变量中init* i4 f- u6 _4 G9 t" w' I) N
jumpserver/apps/assets/models/init.py 末尾增加
/ w0 F: V; x; z0 d! q i+ J; k" f$ L8 M% j6 {
1
% Q& |% h3 D3 }2 m0 `from .project import *
- ?6 E, d2 p/ S% ~) U2.4 定义DRF的serializers' ]% B n- m4 {8 L" m$ }: {3 v
jumpserver/apps/assets/serializers/project.py
- ~) w- A& D; x9 ?: _" q5 t6 t4 I! ]
1 a1 k! S7 E% B3 P- e! U% M( b" I/ Z% [+ L9 p- ]- n
# P2 F( ?9 z w3 [% n) Nfrom rest_framework_bulk import BulkListSerializer, BulkSerializerMixin4 @4 d0 q7 }6 K% R9 b. T3 B
from rest_framework import serializers& j( t0 a2 n4 v+ |' r. _
from ..models import Project, Asset
0 q6 o5 l: R( G+ k+ {8 y9 `# M2 q( u8 y0 V$ o. P( J
" i5 O; k: D+ n% ?) t! }' Iclass ProjectSerializer(serializers.ModelSerializer):
2 V1 b2 d# R1 r5 |/ d" { asset_count = serializers.SerializerMethodField()9 j C* S$ n) x1 u3 p8 r
0 e& s% {, ]. Z o" u
class Meta:& y7 O' E7 `2 k8 p; W
model = Project
* N1 E2 l% Y) k7 @0 F: R fields = '__all__'& _+ ?, Y+ o; E3 a8 n p
5 J8 e$ e8 g" @+ P: ]: ^( Z8 P
@staticmethod
- d' O7 S( z/ g9 ` def get_asset_count(obj):
/ ]2 S' y$ U4 l$ k/ y9 U return obj.assets.count()2 Y$ c3 `6 \ s3 j3 m
" l, k- U2 t4 b Z- ]) k' h: `) j# D/ O
class ProjectAssetsSerializer(serializers.ModelSerializer):$ C/ C" M4 s, _- ]) e
class Meta:0 X3 p, h+ l( m1 s* L
fields = ['id', 'name', 'port']. y6 y7 S+ u/ z; ^3 n- {3 y1 |
model = Project
9 L2 `) Q0 V5 f. j( ~- P( Y* |% A) B* V
def get_field_names(self, declared_fields, info):5 H- B, Y: c9 U- h
fields = super().get_field_names(declared_fields, info)
: w* U9 ?6 U. ~, [( n) I fields.extend([% [/ n' ^0 @! j: W
'all_assets_ip'! Q1 w( P% n7 p: _+ u; B8 v
])' Z$ X* C5 B- T7 d
return fields$ u9 ]# o% E: m# ]* x( M& W
% R1 t- w5 x$ M! @' y" c. n
; r* v# H6 N/ }8 c" Aclass ProjectUpdateSerializer(serializers.ModelSerializer): s* i m/ U. V# D
"""update the project, and add or delete the asset to the project"""
, j# {% n1 }& s1 b, u0 ] assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
% n! V4 }( ~$ {: F1 w$ V, c# j, S8 [! q' |9 k
class Meta:% H; }/ w$ ^1 |3 W
model = Project/ s6 M, R% `, P: b; u& z' z
fields = ['id', 'assets']0 d. _7 T! e: d2 M: a3 V
+ x- N6 t& f- T4 b2 K* a
2 v2 W: W! ]# y" o* yclass AssetUpdateProjectSerializer(serializers.ModelSerializer):
$ p. L2 r: `& j( S7 O* l2 v projects = serializers.PrimaryKeyRelatedField(many=True, queryset=Project.objects.all())
7 [4 a/ l$ x; F% Z" Z$ o1 I* N A) V$ ]+ R3 I, _, B( b
class Meta:9 h$ i3 v# g' y% R
model = Asset
" R4 |+ @) c) b6 p& g5 I8 [ fields = ['id', 'projects']
' {6 \! }4 C; i; Q2 B2.5 增加URl支持
7 ^, N3 y7 N* C+ N" v/ d2 o& N/jumpserver/apps/assets/urls/api_urls.py
! f& y; m2 o0 E, j; ]' A' e; i
5 W+ w3 b( B/ F. k7 S* c+ T5 \) [3 \5 n; c# U+ N: R
, h% R, V+ {# Z- j; h$ s, y
router.register(r'projects', api.ProjectViewSet, 'project'), X( i% ^2 t0 Z6 G+ u2 D
router.register(r'projects-assets', api.ProjectAssetsViewSet, 'project-assets-view')
9 D) g, b( a6 c
I+ |' h+ m8 V* k# urlpatterns 增加:
- n$ n& A7 B( I7 B- z8 S; I. S- a path(r'assets/<uuid:pk>/projects/', w* i) d' f/ f3 }
api.AssetUpdateProjectApi.as_view(), name='asset-update-project'),
6 T& [! Z- M5 P$ N path(r'projects/<uuid:pk>/assets/',) }+ c; m/ Y8 C/ I( m' Y+ M; Z
api.ProjectUpdateApi.as_view(), name='project-update-assets'),8 q5 L9 W7 `, X5 x
path('project/<uuid:pk>/assets/',8 Y) [0 ~" B$ \7 i5 R1 p% M
api.ProjectAssetsListView.as_view(), name='project-assets'),
' N9 Q$ Z3 Z, x7 B0 x* F1 OAPI到这里基本写完了,至于前端的一些,都是调用这些api,进行增删改查操作,详情见github/ C0 q7 c; {+ _$ ]9 }: i' T
, p" d" M- H2 @& s8 O# X3 i& l3 数据库迁移
, h- M/ b0 t. g3 v& ?0 J* A" e( [定义了模型以后,需要执行migrate命令进行数据库变更,省了自己执行sql: i4 d$ f$ {% {- Z
! d: f: z2 t |
9 x7 x9 J2 d* F# y2 x1 c
' u R: A2 |3 z8 `! s! D: [* [cd jumpserver/utils. v; x( P! V! \* i
bash make_migrations.sh
& C, T" P8 g3 Q3 a" v4 国际化
7 b9 W# J4 @# s9 t0 N9 g! R! |2 I因为我们定义的好多变量都是英文的,这里可以通过国际化手段支持中英文& f- T7 g! ?. S9 H% D5 U# \9 k' D, G
1 V+ n0 e G; j3 `( s- g5 \
' `; J: [& ~2 m9 ]4 o7 B) u, f; F& b7 {: f
# 先执行
1 X& }. h2 Y4 F) \- \cd jumpserver/apps2 S4 P, J. }7 u
python manage.py makemessages -l zh
& h3 b! h. L: j6 B! b" b* i# 修改jumpserver/apps/locale/zh/LC_MESSAGES/django.po 的中英文对应
: s; E! r6 d: z0 U+ a( kvim jumpserver/apps/locale/zh/LC_MESSAGES/django.po) ]. d R# A- a) K7 T' B& \
# 修改完成后生效,完全生效需要服务重启# V* }( E8 G* ^% T) @
python manage.py compilemessages
/ D1 o E$ |* Z, n) {1 u
1 z. V$ Z1 O; C/ z4 ^9 e6 x+ F8 s6 F5 d: e! D* k7 ]$ Q7 ~: R
* C9 p1 y5 g$ q, G* N! a! | |
|