Skip to content

Commit 2b3551f

Browse files
committed
[Feature] 修改ansible和ops
1 parent e57121a commit 2b3551f

13 files changed

Lines changed: 233 additions & 197 deletions

File tree

apps/assets/models/asset.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,18 @@ class Asset(models.Model):
4444
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
4545
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
4646
port = models.IntegerField(default=22, verbose_name=_('Port'))
47-
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets',
48-
verbose_name=_('Asset groups'))
49-
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
50-
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
51-
system_users = models.ManyToManyField(SystemUser, blank=True,
52-
related_name='assets',
53-
verbose_name=_("System User"))
54-
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets',
55-
on_delete=models.SET_NULL, verbose_name=_('IDC'),)
47+
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets', verbose_name=_('Asset groups'))
48+
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_("Admin user"))
49+
system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User"))
50+
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('IDC'),)
5651
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
57-
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
58-
default='Server', verbose_name=_('Asset type'),)
59-
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
60-
default='Prod', verbose_name=_('Asset environment'),)
61-
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True,
62-
default='In use', verbose_name=_('Asset status'))
52+
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True, default='Server', verbose_name=_('Asset type'),)
53+
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True, default='Prod', verbose_name=_('Asset environment'),)
54+
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True, default='In use', verbose_name=_('Asset status'))
6355

6456
# Some information
65-
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
66-
null=True, verbose_name=_('Public IP'))
67-
remote_card_ip = models.CharField(max_length=16, null=True, blank=True,
68-
verbose_name=_('Remote control card IP'))
57+
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
58+
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote control card IP'))
6959
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
7060
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
7161
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
@@ -114,22 +104,27 @@ def to_json(self):
114104

115105
def _to_secret_json(self):
116106
"""Ansible use it create inventory"""
117-
return {
107+
data = {
118108
'id': self.id,
119109
'hostname': self.hostname,
120110
'ip': self.ip,
121111
'port': self.port,
122112
'groups': [group.name for group in self.groups.all()],
123-
'username': self.admin_user.username if self.admin_user else '',
124-
'password': self.admin_user.password if self.admin_user else '',
125-
'private_key': self.admin_user.private_key_file if self.admin_user else None,
126-
'become': {
127-
'method': self.admin_user.become_method,
128-
'user': self.admin_user.become_user,
129-
'pass': self.admin_user.become_pass,
130-
} if self.admin_user and self.admin_user.become else {},
131113
}
132114

115+
if self.admin_user:
116+
data.update({
117+
'username': self.admin_user.username,
118+
'password': self.admin_user.password,
119+
'private_key': self.admin_user.private_key_file,
120+
'become': {
121+
'method': self.admin_user.become_method,
122+
'user': self.admin_user.become_user,
123+
'pass': self.admin_user.become_pass,
124+
}
125+
})
126+
return data
127+
133128
class Meta:
134129
unique_together = ('ip', 'port')
135130

apps/assets/models/user.py

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,29 @@ def private_key_validator(value):
2828

2929

3030
class AdminUser(models.Model):
31+
"""
32+
Ansible use admin user as devops user to run adHoc and Playbook
33+
"""
3134
BECOME_METHOD_CHOICES = (
3235
('sudo', 'sudo'),
3336
('su', 'su'),
3437
)
3538
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
3639
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
3740
username = models.CharField(max_length=16, verbose_name=_('Username'))
38-
_password = models.CharField(
39-
max_length=256, blank=True, null=True, verbose_name=_('Password'))
40-
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'),
41-
validators=[private_key_validator,])
41+
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
42+
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator,])
4243
become = models.BooleanField(default=True)
4344
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
4445
become_user = models.CharField(default='root', max_length=64)
4546
become_pass = models.CharField(default='', max_length=128)
46-
_public_key = models.TextField(
47-
max_length=4096, blank=True, verbose_name=_('SSH public key'))
47+
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
4848
comment = models.TextField(blank=True, verbose_name=_('Comment'))
4949
date_created = models.DateTimeField(auto_now_add=True, null=True)
50-
created_by = models.CharField(
51-
max_length=32, null=True, verbose_name=_('Created by'))
50+
created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by'))
5251

53-
def __unicode__(self):
52+
def __str__(self):
5453
return self.name
55-
__str__ = __unicode__
5654

5755
@property
5856
def password(self):
@@ -134,33 +132,22 @@ class SystemUser(models.Model):
134132
('K', 'Public key'),
135133
)
136134
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
137-
name = models.CharField(max_length=128, unique=True,
138-
verbose_name=_('Name'))
135+
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
139136
username = models.CharField(max_length=16, verbose_name=_('Username'))
140-
_password = models.CharField(
141-
max_length=256, blank=True, verbose_name=_('Password'))
142-
protocol = models.CharField(
143-
max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
144-
_private_key = models.TextField(
145-
max_length=8192, blank=True, verbose_name=_('SSH private key'))
146-
_public_key = models.TextField(
147-
max_length=8192, blank=True, verbose_name=_('SSH public key'))
148-
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K',
149-
max_length=1, verbose_name=_('Auth method'))
137+
_password = models.CharField(max_length=256, blank=True, verbose_name=_('Password'))
138+
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
139+
_private_key = models.TextField(max_length=8192, blank=True, verbose_name=_('SSH private key'))
140+
_public_key = models.TextField(max_length=8192, blank=True, verbose_name=_('SSH public key'))
141+
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K', max_length=1, verbose_name=_('Auth method'))
150142
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
151-
sudo = models.TextField(
152-
max_length=4096, default='/sbin/ifconfig', verbose_name=_('Sudo'))
153-
shell = models.CharField(
154-
max_length=64, default='/bin/bash', verbose_name=_('Shell'))
143+
sudo = models.TextField(default='/sbin/ifconfig', verbose_name=_('Sudo'))
144+
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
155145
date_created = models.DateTimeField(auto_now_add=True)
156-
created_by = models.CharField(
157-
max_length=32, blank=True, verbose_name=_('Created by'))
158-
comment = models.TextField(
159-
max_length=128, blank=True, verbose_name=_('Comment'))
146+
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
147+
comment = models.TextField(max_length=128, blank=True, verbose_name=_('Comment'))
160148

161-
def __unicode__(self):
149+
def __str__(self):
162150
return self.name
163-
__str__ = __unicode__
164151

165152
@property
166153
def password(self):
@@ -182,9 +169,24 @@ def private_key(self):
182169
def private_key(self, private_key_raw):
183170
self._private_key = signer.sign(private_key_raw)
184171

172+
@property
173+
def private_key_file(self):
174+
if not self.private_key:
175+
return None
176+
project_dir = settings.PROJECT_DIR
177+
tmp_dir = os.path.join(project_dir, 'tmp')
178+
key_name = md5(self._private_key.encode()).hexdigest()
179+
key_path = os.path.join(tmp_dir, key_name)
180+
if not os.path.exists(key_path):
181+
self.private_key.write_private_key_file(key_path)
182+
return key_path
183+
185184
@property
186185
def public_key(self):
187-
return signer.unsign(self._public_key)
186+
if self._public_key:
187+
return signer.unsign(self._public_key)
188+
else:
189+
return None
188190

189191
@public_key.setter
190192
def public_key(self, public_key_raw):
@@ -213,7 +215,8 @@ def _to_secret_json(self):
213215
'shell': self.shell,
214216
'sudo': self.sudo,
215217
'password': self.password,
216-
'public_key': self.public_key
218+
'public_key': self.public_key,
219+
'private_key_file': self.private_key_file,
217220
}
218221

219222
@property

apps/assets/utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,32 @@ def test_admin_user_connective_manual(asset):
1919

2020
def get_assets_by_id_list(id_list):
2121
return Asset.objects.filter(id__in=id_list)
22+
23+
24+
def get_assets_by_hostname_list(hostname_list):
25+
return Asset.objects.filter(hostname__in=hostname_list)
26+
27+
28+
def get_asset_admin_user(user, asset):
29+
if user.is_superuser:
30+
return asset.admin_user
31+
else:
32+
msg = "{} have no permission for admin user".format(user.username)
33+
raise PermissionError(msg)
34+
35+
36+
def get_asset_system_user(user, asset, system_user_name):
37+
from perms.utils import get_user_granted_assets
38+
assets = get_user_granted_assets(user)
39+
system_users = {system_user.name: system_user for system_user in assets.get(asset)}
40+
41+
if system_user_name in system_users:
42+
return system_users[system_user_name]
43+
else:
44+
msg = "{} have no permission for {}".format(user.name, system_user_name)
45+
raise PermissionError(msg)
46+
47+
48+
def get_assets_with_admin_by_hostname_list(hostname_list):
49+
assets = Asset.objects.filter(hostname__in=hostname_list)
50+
return [(asset, asset.admin_user) for asset in assets]

apps/common/exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+

apps/ops/ansible/callback.py

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,65 +9,85 @@ class AdHocResultCallback(CallbackBase):
99
"""
1010
def __init__(self, display=None):
1111
# result_raw example: {
12-
# "ok": {"hostname": []},
13-
# "failed": {"hostname": []},
14-
# "unreachable: {"hostname": []},
15-
# "skipped": {"hostname": []},
12+
# "ok": {"hostname": [{"task_name": {},...],..},
13+
# "failed": {"hostname": ["task_name": {}..], ..},
14+
# "unreachable: {"hostname": ["task_name": {}, ..]},
15+
# "skipped": {"hostname": ["task_name": {}, ..], ..},
1616
# }
1717
# results_summary example: {
1818
# "contacted": {"hostname",...},
19-
# "dark": {"hostname": ["error",...],},
19+
# "dark": {"hostname": [{"task_name": "error"},...],},
2020
# }
2121
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
2222
self.results_summary = dict(contacted=set(), dark={})
2323
super().__init__(display)
2424

25-
def gather_result(self, t, host, res):
25+
def gather_result(self, t, res):
26+
host = res._host.get_name()
27+
task_name = res.task_name
28+
task_result = res._result
29+
2630
if self.results_raw[t].get(host):
27-
self.results_raw[t][host].append(res)
31+
self.results_raw[t][host].append({task_name: task_result})
2832
else:
29-
self.results_raw[t][host] = [res]
30-
self.clean_result(t, host, res)
33+
self.results_raw[t][host] = [{task_name: task_result}]
34+
self.clean_result(t, host, task_name, task_result)
3135

32-
def clean_result(self, t, host, res):
36+
def clean_result(self, t, host, task_name, task_result):
3337
contacted = self.results_summary["contacted"]
3438
dark = self.results_summary["dark"]
3539
if t in ("ok", "skipped") and host not in dark:
3640
contacted.add(host)
3741
else:
38-
dark[host].append(res)
42+
if dark.get(host):
43+
dark[host].append({task_name: task_result})
44+
else:
45+
dark[host] = [{task_name: task_result}]
3946
if host in contacted:
4047
contacted.remove(dark)
4148

42-
def runner_on_ok(self, host, res):
43-
self.gather_result("ok", host, res)
49+
def v2_runner_on_failed(self, result, ignore_errors=False):
50+
self.gather_result("failed", result)
4451

45-
def runner_on_failed(self, host, res, ignore_errors=False):
46-
self.gather_result("failed", host, res)
52+
def v2_runner_on_ok(self, result):
53+
self.gather_result("ok", result)
4754

48-
def runner_on_unreachable(self, host, res):
49-
self.gather_result("unreachable", host, res)
55+
def v2_runner_on_skipped(self, result):
56+
self.gather_result("skipped", result)
5057

51-
def runner_on_skipped(self, host, item=None):
52-
self.gather_result("skipped", host, item)
58+
def v2_runner_on_unreachable(self, result):
59+
self.gather_result("unreachable", result)
5360

5461

5562
class CommandResultCallback(AdHocResultCallback):
63+
"""
64+
Command result callback
65+
"""
5666
def __init__(self, display=None):
67+
# results_command: {
68+
# "cmd": "",
69+
# "stderr": "",
70+
# "stdout": "",
71+
# "rc": 0,
72+
# "delta": 0:0:0.123
73+
# }
74+
#
5775
self.results_command = dict()
5876
super().__init__(display)
5977

60-
def gather_result(self, t, host, res):
61-
super().gather_result(t, host, res)
62-
self.gather_cmd(t, host, res)
78+
def gather_result(self, t, res):
79+
super().gather_result(t, res)
80+
self.gather_cmd(t, res)
6381

64-
def gather_cmd(self, t, host, res):
82+
def gather_cmd(self, t, res):
83+
host = res._host.get_name()
6584
cmd = {}
6685
if t == "ok":
67-
cmd['cmd'] = res.get('cmd')
68-
cmd['stderr'] = res.get('stderr')
69-
cmd['stdout'] = res.get('stdout')
70-
cmd['rc'] = res.get('rc')
86+
cmd['cmd'] = res._result.get('cmd')
87+
cmd['stderr'] = res._result.get('stderr')
88+
cmd['stdout'] = res._result.get('stdout')
89+
cmd['rc'] = res._result.get('rc')
90+
cmd['delta'] = res._result.get('delta')
7191
else:
7292
cmd['err'] = "Error: {}".format(res)
7393
self.results_command[host] = cmd

apps/ops/ansible/runner.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ def check_pattern(self, pattern):
170170
"pattern: %s dose not match any hosts." % pattern
171171
)
172172

173+
def set_option(self, k, v):
174+
kwargs = {k: v}
175+
self.options = self.options._replace(**kwargs)
176+
173177
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'):
174178
"""
175179
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]

apps/ops/ansible/test_runner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ def setUp(self):
2424

2525
def test_run(self):
2626
tasks = [
27-
{"action": {"module": "shell", "args": "ls"}},
28-
{"action": {"module": "shell", "args": "whoami"}},
27+
{"action": {"module": "shell", "args": "ls"}, "name": "run_cmd"},
28+
{"action": {"module": "shell", "args": "whoami"}, "name": "run_whoami"},
2929
]
3030
ret = self.runner.run(tasks, "all")
3131
print(ret.results_summary)
@@ -48,6 +48,7 @@ def setUp(self):
4848
def test_execute(self):
4949
res = self.runner.execute('ls', 'all')
5050
print(res.results_command)
51+
print(res.results_raw)
5152

5253

5354
if __name__ == "__main__":

apps/ops/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from rest_framework import viewsets
55

66
from .hands import IsSuperUser
7-
from .models import Playbook
7+
from .models import AdHoc
88
from .serializers import TaskSerializer
99

1010

1111
class TaskViewSet(viewsets.ModelViewSet):
12-
queryset = Playbook.objects.all()
12+
queryset = AdHoc.objects.all()
1313
serializer_class = TaskSerializer
1414
permission_classes = (IsSuperUser,)
1515

0 commit comments

Comments
 (0)