单爆手

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
一需求分析:
excel做公司内部资产记录如下:--但这样不方便
  资产编号, ip, 主机名, 使用者, os, mem, cpu, 上架时间, 过保时间 所以开发web版,可以主机进行增,删,改,查 1.添加:方法如下 (1)人手工录入: ssh ip hostname cat
/proc/cpuinfo | grep processor | wc -l 查到它是几核 cat /proc/meminfo | grep MemTotal 去内存文件看内存 (2)主机内部安装了程序 agent => server 定时上报数据(ip, hostname, os, mem, cpu) (3)写一程序扫描ip范围: ip, hostname, os
(4)手工变自动化---自动远程获取: 上述四种方式就是通过excel导入(把excel文件上传服务器进行解析入库)text
/csv/excel/xm => 导入 ip, hostname, os, mem, cpu (5)其他第三方系统(如阿里云机器) => 调用它们的API => 导入 2.删除: 人工删除 自动删除(与第三方系统结合) 3.改: 人工改 自动改 4.查: 自己查 提供数据 => API(别人把你当第三方系统,你给别人提供数据,别人调你的api) 删除, 查询 => 人工 添加, 修改 => 自动 修改 => 人工 定义model => table 资产编号, ip, 主机名, 使用者, os, arch, mem, cpu, disk, 上架时间, 过保时间, 在线状态

机制推: 每周三所有人把作业发给我 agent采集 => server 

拉: 每周三我找所有人要
  扫描(ansible)

 

 二.创建资产管理app

1. 创建app
(python36env) [vagrant@CentOS cmdb]$ python manage.py startapp asset
2. 启用app    
    settings INSTALLED_APPS asset.apps.AssetConfig
3. asset/urls.py
from django.urls import path
from . import views
app_name = 'asset'
urlpatterns = [
    path('', views.index, name='index'),
    path('list/ajax/', views.list_ajax, name='list_ajax'),
]
4. cmdb/urls.py根路由配置相关的url(/app名称/)分发给app
    path('asset/', include('asset.urls'))  把app的url映射包进来
5. asset/models.py
6. 迁移数据库
    python manage.py makemigrations
    python manage.py migrate

7.asset/views.py:

#encoding: utf-8
from django.http import JsonResponse
from django.shortcuts import render, redirect

from .models import Host

def index(request):
    if not request.session.get('user'):
        return redirect('user:login')
    return render(request, 'asset/index.html')


def list_ajax(request):
    if not request.session.get('user'):
        return JsonResponse({'code' : 403, 'result' : []})
    result= [host.as_dict() for host in Host.objects.all()]
    return JsonResponse({'code' : 200, 'result' : result})

8.asset/urls.py:

#encoding: utf-8
from django.urls import path
from . import views

app_name = 'asset'
urlpatterns = [
    #这里我是想访问根路径(http://127.0.0.1:9000/asset/)时直接请求此app的index,所以为空''
    path('', views.index, name='index'),
    path('list/ajax/', views.list_ajax, name='list_ajax'),
]

9.asset/models.py:

#encoding: utf-8

from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
import datetime

#要继承自models.Model
class Host(models.Model):
    #这一块是我收集的:
    name = models.CharField(max_length=128, null=False, default='')
    ip = models.GenericIPAddressField(null=False, default='0.0.0.0')
    mac = models.CharField(max_length=32, null=False, default='')
    os = models.CharField(max_length=64, null=False, default='')
    arch = models.CharField(max_length=16, null=False, default='')
    mem = models.BigIntegerField(null=False, default=0)
    cpu = models.IntegerField(null=False, default=0)
    disk = models.CharField(max_length=512, null=False, default='{}')

    #这一块是要进行输入的:
    sn = models.CharField(max_length=128, null=False, default='')
    user = models.CharField(max_length=128, null=False, default='')
    remark = models.TextField(null=False, default='')
    purchase_time = models.DateTimeField(null=False)
    over_insurance_time = models.DateTimeField(null=False)

    #这一块是我应用程序用的:
    created_time = models.DateTimeField(null=False, auto_now_add=True)
    last_time = models.DateTimeField(null=False)

    @classmethod
    def create_or_replace(cls, ip, name, mac, os, arch, mem, cpu, disk):
        obj = None
        try:
            obj = cls.objects.get(ip=ip)
        except ObjectDoesNotExist as e:
            obj = cls()
            obj.ip = ip
            obj.purchase_time = timezone.now()
            obj.over_insurance_time = timezone.now()

        obj.name = name
        obj.mac = mac
        obj.os = os
        obj.arch = arch
        obj.mem = mem
        obj.cpu = cpu
        obj.disk = disk

        obj.last_time = timezone.now()
        obj.save()
        return obj

    def as_dict(self):
        rt = {}
        for k, v in self.__dict__.items():
            if isinstance(v, (int, float, bool, str, datetime.datetime)):
                rt[k] = v
        return rt

如下图迁移后就有了

三.自定义command

我的扫描进程去掉用此方法更新=>models.create_or_replace,但这是models方法,我的脚本不能随便去调用model,因为这个脚本没有初始化django的环境,
那我就想在一个进程里去调用models方法---用django中的comands后台命令,我可以把脚本定义成commands,这样django在启动时,就会初始化自己的环境,然后我就可以调用models方法了
这个commands这个就跟python脚本一样,不过只是定义在django中(如runserver,makemigrations,check,migrate等这些都是djnago的内置commands)
我们也可自定义commandds,如下方法:
(1)app/management包/commands包/
(2)在上述目录下定义我的脚本script.py:
    class Command(BaseCommand)必须有Command类:
        def handle(self,*args,**options):这个options就是kwargs
(python36env) [vagrant@CentOS asset]$ mkdir -p management/commands
(python36env) [vagrant@CentOS management]$ touch __init__.py
(python36env) [vagrant@CentOS management]$ cd commands/
(python36env) [vagrant@CentOS commands]$ touch __init__.py
(python36env) [vagrant@CentOS commands]$ touch collect_host.py

 ()asset/management/commands/collect_host.py:

class Command(BaseCommand):
    def handle(self, *args, **options):
        print('test')

此时执行python manage.py,就会如下图出现collect_host命令了--这就是我自定义的commands了,可以pthon manage.py collect_host就执行此脚本了。

 四.ansible安装配置

  http://www.ansible.com.cn/docs/intro.html

  ansible:它是运维工具,可以用来做比如你要远程执行东西,你在一台控制机上对你的受控机器执行任何操作,它的执行是基于ssh.

(1)python36env) [vagrant@CentOS cmdb]$ pip install ansible
注:即使安装成功也找不到配置文件,所以先使用yum 安装一次ansibel再使用pip安装,查看/etc/ansible就有配置文件了
[root@CentOS etc]# cp -r ansible ansible_bak  备份再卸载yum安装的ansible
(python36env) [vagrant@CentOS cmdb]$ cat /etc/ansible/ansible.cfg
    它控制机器 =>(ssh)=> 受控机器,得告诉ansible哪些机是受控机
    用inventory = 剧本配置
(2)(python36env) [vagrant@CentOS cmdb]$ mkdir etc  建此文件夹放剧本
(3)(python36env) [vagrant@CentOS etc]$ touch hosts
localhost ansible_connection=local
txy ansible_connection=smart ansible_host=120.53.122.78 ansible_user=lizhihua ansible_ssh_pass=******
(4)给哪些机器去运行:查看匹配哪些机 
(python36env) [vagrant@CentOS cmdb]$ ansible all
-i etc/hosts --list-hosts hosts (2): localhost txy (python36env) [vagrant@CentOS cmdb]$ ansible txy -i etc/hosts --list-hosts 效果如下图看到ping命令执行成功 hosts (1): txy [root@txy ~]# useradd lizhihua 这是我的腾讯云服务器 [root@txy ~]# passwd lizhihua [vagrant@CentOS ~]$ sudo vi /etc/ansible/ansible.cfg host_key_checking = False 否则会连接不上腾讯云

 (5)免密连接ssh key:

(python36env) [vagrant@CentOS cmdb]$ ssh-keygen -t rsa -b 4096 生成密钥并把公钥拷贝到别人机上即可
(python36env) [vagrant@CentOS cmdb]$ cat /home/vagrant/.ssh/id_rsa.pub
(python36env) [vagrant@CentOS cmdb]$ ssh-copy-id -i /home/vagrant/.ssh/id_rsa.pub lizhihua@120.53.122.78
(python36env) [vagrant@CentOS cmdb]$ ssh 'lizhihua@120.53.122.78'
[lizhihua@txy ~]$ 此时在我本机就登录上我腾讯云机了
那此时我hosts文件中就可不用密码了:
localhost ansible_connection=local
txy ansible_connection=smart ansible_host=120.53.122.78 ansible_user=lizhihua
(python36env) [vagrant@CentOS cmdb]$ ansible all -m ping -i etc/hosts

 五.ansible python api

  要收集主机信息,主要使用到一个模块setup(ansible提供)

(python36env) [vagrant@CentOS cmdb]$ ansible all -m setup -i etc/hosts

结果如图:这就是采集到主机信息json:

1.获取数据两种方法:
(1. 调用系统命令,它会输出output--那我直接把结果拿出来
(2. 有没有写了python系统命令工具 => 并python 封装了 => python 3rd(第三方库)
(3. python 3rd  --系统命令本身就是python的,那我直接用python调用即可

2.ansible: 
(1)应该有hosts => 剧本有这些变量:ansible_connection ansible_host ansible_user ansbile_ssh_pass
(2)执行ansible命令时 => -m module(模块/单个任务)
(3)playbook  剧本(多个任务串成一个工作流)

3.ansible python api:
https://docs.ansible.com/ansible/latest/dev_guide/developing_api.html 开发者手册
测试使用官方提供的实例:
/etc/test.py:
#encoding: utf-8
import json
import shutil
from collections import namedtuple
from ansible.module_utils.common.collections import ImmutableDict from ansible.parsing.dataloader import DataLoader from ansible.vars.manager import VariableManager from ansible.inventory.manager import InventoryManager from ansible.playbook.play import Play from ansible.executor.task_queue_manager import TaskQueueManager from ansible.plugins.callback import CallbackBase from ansible import context import ansible.constants as C ''' 回调 ''' class ResultCallback(CallbackBase): def v2_runner_on_ok(self, result, **kwargs): host = result._host print(result.task_name) print(json.dumps({host.name: result._result}, indent=4)) Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff']) options = Options(connection='smart', module_path=[], forks=10, become=None, become_method=None, become_user=None, check=False, diff=False) loader = DataLoader() passwords = {} results_callback = ResultCallback() inventory = InventoryManager(loader=loader, sources='hosts') # 剧本的位置 variable_manager = VariableManager(loader=loader, inventory=inventory) # ansible all -i etc/hosts -m setup play_source = { 'name' : "test", 'hosts' : 'all', #在哪些主机上执行 'gather_facts' : 'no', 'tasks' : [ #执行的任务列表 { 'name' : 'fact', #任务名称 'setup' : '' #执行任务模块 } ] } play = Play().load(play_source, variable_manager=variable_manager, loader=loader) tqm = None try: tqm = TaskQueueManager( inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=passwords, stdout_callback=results_callback, ) result = tqm.run(play) finally: if tqm is not None: tqm.cleanup() shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

(python36env) [vagrant@CentOS etc]$ python test.py

效果如图:

六.commands和ansible api和models连起来:
command的handle方法 => 调用ansible api => 调用Callback(且在回调中调用models) => models =>存储数据
asset/management/commands/collect_host.py:
#encoding: utf-8
import os
import shutil
from collections import namedtuple
import json

from django.core.management import BaseCommand
from django.conf import settings

from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
import ansible.constants as C

from asset.models import Host

class ResultCallback(CallbackBase):
    def v2_runner_on_ok(self, result, **kwargs):
        if result.task_name == 'collect_host':
            self.collect_host(result._result)
        print(result.task_name)
        print(result._host.name)

    
    def collect_host(self, result):
        facts = result.get('ansible_facts', {})

        ip = facts.get('ansible_default_ipv4', {}).get('address', '')
        name = facts.get('ansible_nodename', '')
        mac = facts.get('ansible_default_ipv4', {}).get('macaddress', '')
        os = facts.get('ansible_os_family', '')
        arch = facts.get('ansible_architecture', '')
        mem = facts.get('ansible_memtotal_mb', '')
        cpu = facts.get('ansible_processor_vcpus', '')

        disk = [{'name' : i.get('device'), 'total' : int(i.get('size_total')) / 1024 / 1024 } for i in facts.get('ansible_mounts', [])]
        #disk = {i.get('device') : int(i.get('size_total')) / 1024 / 1024 for i in facts.get('ansible_mounts', [])}
        Host.create_or_replace(ip, name, mac, os, arch, mem, cpu, json.dumps(disk))
        

class Command(BaseCommand):
    def handle(self, *args, **options):
        AnsibleOptions = namedtuple('AnsibleOptions', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff'])
        ansible_options = AnsibleOptions(connection='smart', module_path=[], forks=10, become=None, become_method=None, become_user=None, check=False, diff=False)
        loader = DataLoader()
        passwords = {}
        results_callback = ResultCallback()
        inventory = InventoryManager(loader=loader, sources=os.path.join(settings.BASE_DIR, 'etc', 'hosts'))
        variable_manager = VariableManager(loader=loader, inventory=inventory)
        play_source =  {
                'name' : "cmdb",
                'hosts' : 'all',
                'gather_facts' : 'no',
                'tasks' : [
                    {
                      'name' : 'collect_host',
                      'setup' : ''
                    }
                 ]
            }

        play = Play().load(play_source, variable_manager=variable_manager, loader=loader)

        tqm = None
        try:
            tqm = TaskQueueManager(
                      inventory=inventory,
                      variable_manager=variable_manager,
                      loader=loader,
                      options=ansible_options,
                      passwords=passwords,
                      stdout_callback=results_callback,
                      )
            result = tqm.run(play)
        finally:
            if tqm is not None:
                tqm.cleanup()

            shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

(python36env) [vagrant@CentOS cmdb]$ python manage.py collect_host
报错:
stdout_callback=results_callback TypeError:: __init__() got an unexpected options
原因是我的ansible版本不对太高了,是2.9版本的,解决:所以我把它降级为pip install ansible==2.4.2.0
结果如图:中可看到

 六.模版继承

模版继承:
python函数:把函数体中变的东西=>取到函数参数上,  不变=>函数体
变的东西: 菜单的谁选中 显示的内容(body中)
不变的东西: 页面结构,菜单项

定一html页面,把不变的放到html中来,把变的内容,通过某占位进行占位--block和endbloak变html中变的内容包起来并起一名
使用html时,就把变的内容传递进去


template.html:--我定义一模版放项目templates中
<html>
    <body>{% block body %} {% endblock %}</body>
</html>

a.html:--别人来调用(extends)我模版(要给我的block传值),这样它只要把变的都放他block中,不变的就放我模版即可
{% extends "template.html" %}

{% block body %} A {% endblock %}

b.html:
{% extends "template.html" %}

{% block body %} B {% endblock %}

(1)cmdb/templates/base.html

{% load static %}

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" />
    <link rel="stylesheet" type="text/css" href="{% static 'sweetalert-1.0.1/dist/sweetalert.css' %}" />
    <link rel="stylesheet" type="text/css" href="{% static 'DataTables-1.10.15/media/css/dataTables.bootstrap.min.css' %}" />
    {% block links %}{% endblock %}
    <style type="text/css">
      body {
        padding-top: 70px;
      }
      {% block style %}{% endblock %}
    </style>
  </head>
  <body>
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">CMDB</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
            <li class="{% block nav_asset %}{% endblock %}"><a href="{% url 'asset:index' %}">资产管理</a></li>
          </ul>
          <ul class="nav navbar-nav navbar-right">
          </ul>
        </div>
      </div>
    </nav>

    <div class="container">
        {% block container %}{% endblock %}
    </div>
    {% block dialogs %}{% endblock %}
    <script type="text/javascript" src="{% static 'jquery/jquery-1.12.4.js'%}"></script>
    <script type="text/javascript" src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'sweetalert-1.0.1/dist/sweetalert.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'DataTables-1.10.15/media/js/jquery.dataTables.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'DataTables-1.10.15/media/js/dataTables.bootstrap.min.js' %}"></script>
    {% block scripts %}{% endblock %}
    <script type="text/javascript">
      jQuery(document).ready(function() {
        {% block js %}{% endblock %}
      });
    </script>
  </body>
</html>

 七.datatable ajax

  不用页面直接加载数据,因为用页面刷新一次才能把对应数据更新了。ajax获取数据--因为如果页面有了数据,那每次只用去调ajax重新去渲染table就ok了。

用ajax获取数据:
(1). template
    不需要去查询数据,只要load到页面上来就行
(2). datatable发起ajax => 获取数据
    datatable要使用的,先定义table的结构。
    {
        'data': [
            {}
        ]
    }
        result

    ajax : {
        url : ,
        dataSrc: 'result'
    }
    columns: [
        
    ]
(3). 通过datatable提供的接口 => 渲染table

参考http://datatables.club/  ---用ajax获取数据并展示

http://datatables.club/example/ajax/objects.html

(1)asset/templates/asset/index.html

{% extends "base.html" %}

{% block title %}资产管理{% endblock %}
{% block nav_asset %}active{% endblock %}
{% block container %}
<table id="table_asset" class="table table-striped table-bordered table-hover table-condensed">
    <thead>
        <tr>
            <th>名称(IP)</th>
            <th>操作系统</th>
            <th>架构</th>
            <th>内存</th>
            <th>CPU</th>
            <th>磁盘</th>
            <th>发现时间</th>
            <th>最有发现时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
    </tbody>
</table>
{% endblock %}

{% block js %}

var table = jQuery('#table_asset').DataTable({
    "language": {
        "processing": "处理中...",
        "lengthMenu": "显示 _MENU_ 项结果",
        "zeroRecords": "没有匹配结果",
        "info": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
        "infoEmpty": "显示第 0 至 0 项结果,共 0 项",
        "infoFiltered": "(由 _MAX_ 项结果过滤)",
        "infoPostFix": "",
        "search": "搜索:",
        "searchPlaceholder": "搜索...",
        "url": "",
        "emptyTable": "表中数据为空",
        "loadingRecords": "载入中...",
        "infoThousands": ",",
        "paginate": {
            "first": "首页",
            "previous": "上页",
            "next": "下页",
            "last": "末页"
        },
        "aria": {
            paginate: {
                first: '首页',
                previous: '上页',
                next: '下页',
                last: '末页'
            },
            "sortAscending": ": 以升序排列此列",
            "sortDescending": ": 以降序排列此列"
        },
        "decimal": "-",
        "thousands": "."
    },
    ajax : {
        url : '{% url "asset:list_ajax" %}',
        dataSrc: 'result',
    },
    columns : [
        {
            "data" : function(row, type, set, meta) {
                return row['name'] + '(' + row['ip'] + ')';
            }
        },
        {
            "data" : "os"
        },
        {
            "data" : "arch"
        },
        {
            "data" : "mem"
        },
        {
            "data" : "cpu"
        },
        {
            "data" : "disk"
        },
        {
            "data" : "created_time"
        },
        {
            "data" : "last_time"
        },
        {
            "data" : function(row) {
                return '<a class="btn btn-success btn-xs btn-edit-asset" href="javascript:void(0)" data-id="' + row['id'] + '">编辑</a>' + 
                '<a class="btn btn-danger btn-xs btn-delete-asset" data-id="' + row['id'] + '" href="javascript:void(0)">删除</a>';
            }
        },
    ]
});

jQuery('#table_asset').on('click', '.btn-edit-asset', function() {
    console.log('编辑' + jQuery(this).attr('data-id')); 
    table.ajax.reload(null, false);
});

jQuery('#table_asset').on('click', '.btn-delete-asset', function() {
    console.log('删除' + jQuery(this).attr('data-id')); 
    table.ajax.reload(null, false);
});
{% endblock %}

(2)asset/urls.py:

#encoding: utf-8
from django.urls import path
from . import views

app_name = 'asset'
urlpatterns = [
    #这里我是想访问根路径(http://127.0.0.1:9000/asset/)时直接请求此app的index,所以为空''
    path('', views.index, name='index'),
    path('list/ajax/', views.list_ajax, name='list_ajax'),
]

(3)asset/views.py:

#encoding: utf-8
from django.http import JsonResponse
from django.shortcuts import render, redirect

from .models import Host

def index(request):
    return render(request, 'asset/index.html')


def list_ajax(request):
    result= [host.as_dict() for host in Host.objects.all()]
    return JsonResponse({'code' : 200, 'result' : result})

(4)asset/models.py:

#encoding: utf-8

from django.db import models
from django.core.exceptions import ObjectDoesNotExist
#此模块可获取当前时间
from django.utils import timezone
import datetime

#要继承自models.Model
class Host(models.Model):
    #这一块是我收集的:
    name = models.CharField(max_length=128, null=False, default='')
    ip = models.GenericIPAddressField(null=False, default='0.0.0.0')
    mac = models.CharField(max_length=32, null=False, default='')
    os = models.CharField(max_length=64, null=False, default='')
    arch = models.CharField(max_length=16, null=False, default='')
    mem = models.BigIntegerField(null=False, default=0)
    cpu = models.IntegerField(null=False, default=0)
    disk = models.CharField(max_length=512, null=False, default='{}')

    #这一块是要进行输入的:
    sn = models.CharField(max_length=128, null=False, default='')
    user = models.CharField(max_length=128, null=False, default='')
    remark = models.TextField(null=False, default='')
    purchase_time = models.DateTimeField(null=False)
    over_insurance_time = models.DateTimeField(null=False)

    #这一块是我应用程序用的:
    created_time = models.DateTimeField(null=False, auto_now_add=True)
    last_time = models.DateTimeField(null=False)
    #它是类方法所以要用@classmethod来修饰
    @classmethod
    #这函数是给后台更新用的,它的输入是上述那采集的信息(更新和创建用id判断)
    def create_or_replace(cls, ip, name, mac, os, arch, mem, cpu, disk):
        obj = None
        try:
            obj = cls.objects.get(ip=ip)
        except ObjectDoesNotExist as e:
            obj = cls()
            obj.ip = ip
            obj.purchase_time = timezone.now()
            obj.over_insurance_time = timezone.now()

        obj.name = name
        obj.mac = mac
        obj.os = os
        obj.arch = arch
        obj.mem = mem
        obj.cpu = cpu
        obj.disk = disk

        obj.last_time = timezone.now()
        obj.save()
        return obj

    def as_dict(self):
        rt = {}
        for k, v in self.__dict__.items():
            if isinstance(v, (int, float, bool, str, datetime.datetime)):
                rt[k] = v
        return rt

 

 

 

 

 

 

 MariaDB [cmdb]> delete from asset_host;  ---从数据库中删除后如下图。

(python36env) [vagrant@CentOS cmdb]$ python manage.py collect_host
collect_host
txy
collect_host
localhost

posted on 2020-07-29 23:16  单爆手  阅读(418)  评论(0编辑  收藏  举报