Ansible API 2.10.5 运行pb产出报表的过程记录

前言

最近想着看看ansible的api,然后写一个日常报表的生成脚本,这样就不用每天人力一个个去检查了,毕竟涉及的服务器有上百台。

正文

版本准备

  • Python3.8.8
  • Ansible2.10.5 (这个版本API又和之前不一样)

搭建开发环境

Python的安装就不说了,这里使用的是python3.8版本,windows装ansible实在是麻烦,我直接在linux弄了,使用pip安装就好:

pip3 install ansible==2.10.5 -i https://pypi.tuna.tsinghua.edu.cn/simple

在这里插入图片描述
在这里插入图片描述
pip安装后,并不会创建ansible相关命令的软连接,如果需要可以手动创建,跑一个for就可以了,如下:

for i in `ls /usr/local/python3/bin/|grep ansible`;do ln -s /usr/local/python3/bin/$i /usr/bin/$i;done

在这里插入图片描述
一切就绪后,我们自己创建配置文件并测试一下,经典的ping模块和Hello World:
在这里插入图片描述

API核心类的介绍

导入类完整路径功能用途
from ansible.module_utils.common.collections import ImmutableDict用于添加选项。比如: 指定远程用户remote_user=None
from ansible.parsing.dataloader import DataLoader读取 json/ymal/ini 格式的文件的数据解析器
from ansible.vars.manager import VariableManager管理主机和主机组的变量管理器
from ansible.inventory.manager import InventoryManager管理资源库的,可以指定一个 inventory 文件等
from ansible.playbook.play import Play用于执行 Ad-hoc 的类 ,需要传入相应的参数
from ansible.executor.task_queue_manager import TaskQueueManageransible 底层用到的任务队列管理器
ansible.plugins.callback.CallbackBase处理任务执行后返回的状态
from ansible import context上下文管理器,他就是用来接收 ImmutableDict 的示例对象
import ansible.constants as C用于获取 ansible 产生的临时文档。
from ansible.executor.playbook_executor import PlaybookExecutor执行 playbook 的核心类
from ansible.inventory.host import Group对 主机组 执行操作 ,可以给组添加变量等操作,扩展
from ansible.inventory.host import Host对 主机 执行操作 ,可以给主机添加变量等操作,扩展

捕捉PB回显结果

借鉴案例

借鉴网上的样例,先写了一个回调函数:

class MyPlaybookCallback(CallbackBase):

    def __init__(self, *args):
        super(MyPlaybookCallback, self).__init__(display=None)
        self.status_ok = json.dumps({})
        self.status_fail = json.dumps({})
        self.status_unreachable = json.dumps({})
        self.status_playbook = ""
        self.status_no_hosts = False
        self.host_ok = {}
        self.host_failed = {}
        self.host_unreachable = {}

    def v2_runner_on_ok(self, result):
        host = result._host.get_name()
        self.runner_on_ok(host, result._result)
        self.host_ok[host] = result

    def v2_runner_on_failed(self, result, ignore_errors=False):
        host = result._host.get_name()
        self.runner_on_failed(host, result._result, ignore_errors)
        self.host_failed[host] = result

    def v2_runner_on_unreachable(self, result):
        host = result._host.get_name()
        self.runner_on_unreachable(host, result._result)
        self.host_unreachable[host] = result

    def v2_playbook_on_no_hosts_matched(self):
        self.playbook_on_no_hosts_matched()
        self.status_no_hosts = True

这个回显运行ad-hoc没有问题,但是如果运行playbook,只会显示最后一次运行任务结果,比如下面这个playbook:

- hosts: myhost
  remote_user: root
  gather_facts: false
  tasks:
     - name: 磁盘只读及坏盘巡检
       shell: touch /data*
       ignore_errors: true
     - name: 磁盘容量巡检
       shell: df -hP|awk 'NR>1 && int($5) > 80'
       ignore_errors: true
     - name: 内存巡检
       shell: export usag=`free | awk '/-\/+/{print $3}'`;free | awk -v test="$usag" '/Mem/{printf("%.2f%"), test/$2*100}'
       ignore_errors: true
     - name: CPU巡检
       shell: cat /proc/stat | awk '/cpu/{printf("%.2f%\n"), ($2+$4)*100/($2+$4+$5)}' | awk '{print $0}' | head -1
       ignore_errors: true

打印最终结果的TASK会发现只有最后一个TASK的结果,这显然不是我想要的:
在这里插入图片描述

适配每个任务结果获取

笔者代码能力一般,不知道这样的写法是否适用于项目,仅仅做个记录,根据ansible的源码可以知道这几个信息:

  • v2_playbook_on_start会在每个playbook运行前调用;
  • v2_playbook_on_task_start会在每个task运行前调用;
  • v2_runner_on_ok等会在每个task运行后调用,

因此,重写回调类的时候,可以直接在v2_runner_on_ok进行编写,failed状态的可以参考编写:

def v2_runner_on_ok(self, result):
    host = result._host.get_name()
    self.runner_on_ok(host, result._result)
    self.host_ok[host] = result
    t = {
        "hostname": host,
        "taskname": result._task,
        "result": result._result
    }
    self.result.append(t)

最终只需要实际的playbook执行类中获取结果并打印就好,比如:

def get_result(self):
    self.result_all = {'success': {}, 'fail': {}, 'unreachable': {}}
    print(self.results_callback.result)

在这里插入图片描述

执行任务时获取组名

因为需要出报表,所以对服务器ip进行了分组,包括了应用,gis等,为了能够在报表生成时自动对分组内容填写,找到一种方法可以直接打印出组名:

        pbex = PlaybookExecutor(playbooks=[self.playbook_path],
                                inventory=self.inv_obj,
                                variable_manager=self.variable_manager,
                                loader=self.loader,
                                passwords=self.passwords)
        pbex._tqm._stdout_callback = self.results_callback # 使用自己的回调函数
        print(pbex._tqm.get_variable_manager().get_vars()['groups'])

实际使用肯定还是需要进一步处理的,这样获取的返回值如下:
在这里插入图片描述
那么,能不能在Callback中返回结果的时候,进行组名的获取呢,答案是肯定的,v2_playbook_on_play_start方法会在每个剧本开始的时候执行,那么只需要在这个方法去写就好:

    def v2_playbook_on_play_start(self, play):
        self.playbook_on_play_start(play.name)
        self.playbook_path = play.name
        variable_manager = play.get_variable_manager()
        hostvars = variable_manager.get_vars()
        print(hostvars)

这里直接打印出来看看:
在这里插入图片描述
完美,再从中处理处自己要的信息就好,最终的产出的数据结构如下:
在这里插入图片描述

Excel生成

编写自己的操作类

首先自己根据需要使用openpyxl库写一个简单的类,主要用于创建sheet等基本操作:

# -*- coding:utf-8 -*-
#!/usr/bin/python3
import openpyxl
from openpyxl.styles import Alignment
from openpyxl.utils import get_column_letter


class ExcelMake():
    def __init__(self, name):
        self.name = name  # excel文件的名称

    def create_excel_obj(self):
        self.workbook = openpyxl.Workbook()  # 打开的工作簿
        self.worksheet = self.workbook.active

    def create_excel_sheet(self, sheet):
        # 创建一个工作表
        if sheet not in self.workbook.sheetnames:
            self.workbook.create_sheet(sheet)

    def rename_excel_sheet(self, oldname, newname):
        ws = self.workbook[oldname]  # 不需要用active和get_sheet_by_name
        ws.title = newname

    def remove_excel_sheet(self, sheet):
        ws = self.workbook[sheet]
        self.workbook.remove(ws)  # 删除sheet页

    def save_excel_file(self):
        self.workbook.save(self.name)

    def active_excel_sheet(self, sheet):
        self.worksheet = self.workbook[sheet]

这样已经具备基本的excel操作能力了,不过产出的表格很丑很丑,挤在一起,因此增加了两个用来调整表格的方法。

自动调整列宽和行高

首先是要自动调整列宽和行高,因为表格结构本身比较简单,而且生成数据的时候就处理了空值,所以这里不需要考虑空值的处理,设置行高相对简单点,因为没有对字号进行设置,所以每一个单元格的高度基本一致,只需要全局设置成想要的高度就可以,列宽的设置相对来说复杂一些,需要去判断每一列的最大文字长度,有意思的是,不能简单的用len来判断字符串,因为中文和英文的len都其取决于个数,所以我这里转成utf-8以后在判断长度

    def auto_set_height(self, height): # 设置行高,传入需要的行高值
        row = self.worksheet.max_row
        for r in range(1, row+1):
            self.worksheet.row_dimensions[r].height = height

    def auto_set_width(self): # 设置列宽,会根据本列内容自动设置
        row = self.worksheet.max_row
        for col in range(1, self.worksheet.max_column+1):
            width = 0  # 初始化宽度
            for i in range(1, row+1): # 遍历这一列的每个单元格
                v = self.worksheet.cell(row=i, column=col).value
                if '\n' in str(v):
                    for msg in v.split('\n'):
                        if len(msg.encode('utf-8')) > width:
                            width = len(msg.encode('utf-8'))
                elif len(str(v).encode('utf-8')) > width:
                    width = len(str(v).encode('utf-8')) # 找到最大的width
            self.worksheet.column_dimensions[get_column_letter(
                col)].width = width*1.2
       
    def auto_set_style(self):
        # 对单个sheet设计样式
        alignment_center = Alignment(horizontal='center', vertical='center') # 居中
        for row in range(1, self.worksheet.max_row+1):
            for col in range(1, self.worksheet.max_column+1):
                self.worksheet.cell(row, col).alignment =alignment_center

运行后确实美观一些:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Meepoljd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值