Предлагаю готовый шаблон для Zabbix и скрипт на Python, для автообнаружения новых номеров провайдера Zadarma(новых, всмысле купленных вами номеров).

В шаблоне есть несколько триггерров и основная информация о номерах. Скрипт работатет с API Zadarma или с базой данных MySQL.

Для работы с MySQL, необходимо заранее по крону или прямо из диалплана с помощью отдельного скрипта положить туда данные.

Оба скрипта под катом.

zadarma_number.py
Скрипт необходимо положить в директорию со скриптами для zabbiz агента, у меня это

/etc/zabbix/scripts/

Так-же дайте ему права на запуск и на всякий случай измените владельца и группу zabbix:


chmod +x /etc/zabbix/scripts/zadarma_number.py
chown zabbix:zabbix /etc/zabbix/scripts/zadarma_number.py

#! /usr/bin/env python3
import json
import pymysql
import argparse
import sys
import base64
import hmac
import requests
from hashlib import sha1, md5
from collections import OrderedDict
from urllib.parse import urlencode

api_key      = '222c2fd22c2d2222a2b2'
secret_key   = '2a2f2222cee2222f2222'

database_address = '192.168.10.18'
database_name = 'asterisk'
database_user = 'asterisk'
database_password = 'dFgXuasfvgas45hAkEb2'
zadarma_api_url = 'http://api.zadarma.com'
monthly_fee_all = 0

def sql_result(sql):
    cursor.execute(sql)
    data = cursor.fetchall()
    return data

def str_for_json(data):
    json_data = []
    for result in data:
        json_data.append(dict(zip(['{#NUMBER}'], result)))
    json_data = ({"data":json_data})
    return json.dumps(json_data)
    # return json.dumps(json_data)

def createParser():
    parser = argparse.ArgumentParser(
        prog='Zadarma for Zabbix',
        description='''Это программа предназначена для мониторинга номеров провайдера Zadarma
        и изначально задумывалась как скрипт для zabbiz агента.''',
        epilog='''(c) Июль 2018. Автор программы, как всегда,
    не несет никакой ответственности ни за что.'''
    )

    parser.add_argument('-l', '--allnumbers', action='store_true',
                        help='"Show all found phone numbers in a format suitable for zabbix. '
                             ' Running a script without parameters (or with the -f option) leads to the same result."')
    parser.add_argument('-n', '--number', help = '"Phone number and -s or -S or -g or -e or -d or -m or -a "', metavar = 'phone number')
    parser.add_argument('-S', '--status', action='store_true')
    parser.add_argument('-g', '--start_date', action='store_true')
    parser.add_argument('-e', '--stop_date', action='store_true')
    parser.add_argument('-d', '--description', action='store_true')
    parser.add_argument('-s', '--sip', action='store_true', help='"can be used in combination with "-c""')

    parser.add_argument('-m', '--monthly_fee', action='store_true', help='"The amount required to renew a phone number or all phone numbers"')
    parser.add_argument('-a', '--autorenew', action='store_true')
    parser.add_argument('-b', '--balance', action='store_true', help='All balance numbers')
    parser.add_argument('-f', '--force_API', action='store_true',default=False, help = '"Force the use of api, the database is ignored"')

    parser.add_argument('-c', '--cut', type=int, default=0, help='''Used only in conjunction with "-s"
                                                                     0 - The whole line,
                                                                     1- Part of the string before "@",
                                                                     2 - Part of the line after "@"''', metavar='[0,1,2] or none')
    return parser

def api_zapros(method,data):
    if not data:
        data = {'format': 'json'}
    sorted_dict_params = OrderedDict(sorted(data.items()))
    query_string = urlencode(sorted_dict_params)
    h = md5(query_string.encode('utf8')).hexdigest()
    data = method + query_string + h
    hashed = hmac.new(secret_key.encode('utf8'), data.encode('utf8'), sha1)
    auth = api_key + ':' + base64.b64encode(bytes(hashed.hexdigest(), 'utf8')).decode()
    headers = {'User-Agent': '-', 'Authorization': auth}
    url = zadarma_api_url + method + '?' + query_string;
    r = requests.get(url, headers=headers)
    r.raise_for_status()
    return json.loads(r.text)

def no_sql(*args):
    number = args[0]
    parametr = args[1]
    for number_string in numbers_result['info']:
        if number_string['number'] == number:
            if number_string[parametr] == 'true' or number_string[parametr] == 'on':
                return '0'
            elif number_string[parametr] == 'false' or number_string[parametr] == 'off':
                return '1'
            else:
                return number_string[parametr]

def zapros(*args):
    if namespace.force_API:
        return no_sql(namespace.number,args[0])
    else:
        return sql_result(sql)[0][0]

parser = createParser()
namespace = parser.parse_args(sys.argv[1:])

if not namespace.force_API:
    try:
        db = pymysql.connect(database_address, database_user, database_password, database_name)
        cursor = db.cursor()
    except pymysql.Error as e:
        print('\n\t!!!!!!!!!!!SQL no connect!!!!!!!!!!!\n')
        print(e)
        exit(1)
else:
    try:
        numbers_result = api_zapros('/v1/direct_numbers/', '')
        balance_info = api_zapros('/v1/info/balance/', '')
    except Exception as e:
        print('Error connect API zadarma')
        exit(1)

# print('namespace', namespace)


if namespace.number and namespace.status:
    sql = "SELECT status FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    print(zapros('status'))

elif namespace.number and namespace.start_date:
    sql = "SELECT start_date FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    print(zapros('start_date'))

elif namespace.number and namespace.stop_date:
    sql = "SELECT stop_date FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    print(zapros('stop_date'))

elif namespace.number and namespace.autorenew:
    sql = "SELECT autorenew FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    print(zapros('autorenew'))

elif namespace.number and namespace.description:
    sql = "SELECT description FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    print(zapros('description'))

elif namespace.number and namespace.sip:
    sql = "SELECT sip FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    if namespace.cut == 0:
        print(zapros('sip'))
    if namespace.cut == 1:
        try:
            print(zapros('sip').split('@')[0])
        except IndexError:
            print(zapros('sip'))
    if namespace.cut == 2:
        try:
            print(zapros('sip').split('@')[1])
        except  IndexError:
            print(zapros('sip'))

elif namespace.number and namespace.monthly_fee:
    sql = "SELECT monthly_fee FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
    print(zapros('monthly_fee'))

else:
    if namespace.number:
        # sql = "SELECT * FROM zadarma_numbers WHERE number = %s;" % (namespace.number)
        # print(zapros(namespace.number))
        print(namespace.number, 'there are not enough arguments or incorrect combination of arguments')
        sys.exit(1)

    elif namespace.balance:
        if namespace.force_API:
            balance = balance_info['balance']
            print(balance)
        else:
            print(sql_result('SELECT balance FROM zadarma_balance;')[0][0])

    elif namespace.monthly_fee:
        if namespace.force_API:
            for result in numbers_result['info']:
                monthly_fee_all = monthly_fee_all + result['monthly_fee']
            print(monthly_fee_all)
        else:
            print(sql_result('SELECT monthly_fee FROM zadarma_balance;')[0][0])

    elif namespace.allnumbers:
        if namespace.force_API:
            json_data = []
            for result in numbers_result['info']:
                print(result['number'])
                json_data.append(dict(zip(['{#NUMBER}'], [result['number']])))
            json_data = ({"data": json_data})
            print('jkgkgliuui',json.dumps(json_data))

        else:
            data = sql_result('SELECT number FROM zadarma_numbers  WHERE stop_date > date')
            print(str_for_json(data))

    else:
        if namespace.force_API:
            json_data = []
            for result in numbers_result['info']:
                json_data.append(dict(zip(['{#NUMBER}'], [result['number']])))
            json_data = ({"data": json_data})
            print(json.dumps(json_data))

        else:
            data = sql_result('SELECT number FROM zadarma_numbers  WHERE stop_date > date')
            print(str_for_json(data))
if not namespace.force_API:
    db.close()



Теоретически, работа с базой данный может оказаться более актуальной, так как скрипт для складывания туда информации можно запускать после каждого звонка. Таким образом инфо в базе возможно окажется чуточку актуальнее, чем прямая работа с API прям из Zabbix.

В шаблоне Zabbix макрос {$FORCE_API}, форсирует работу скрипта с API Zadarma.
Макрос {$CUT} служит для разделения строки запроса SIP-URI на часть до "@" и после.

У скрипта есть несколько ключей для запуска.

Без ключей или с ключем -l скрипт отдаст список номеров в фрмате json, который подходит для автообнаружения номеров в zabbix.

Справка по ключам скрипта:
usage: Zadarma for Zabbix [-h] [-l] [-n phone number] [-S] [-g] [-e] [-d] [-s]
[-m] [-a] [-b] [-f] [-c [0,1,2] or none]

Это программа предназначена для мониторинга номеров провайдера Zadarma и
изначально задумывалась как скрипт для zabbiz агента.

optional arguments:
  -h, --help            show this help message and exit
  -l, --allnumbers      "Show all found phone numbers in a format suitable for
                        zabbix. Running a script without parameters (or with
                        the -f option) leads to the same result."
  -n phone number, --number phone number
                        "Phone number and -s or -S or -g or -e or -d or -m or
                        -a "
  -S, --status
  -g, --start_date
  -e, --stop_date
  -d, --description
  -s, --sip             "can be used in combination with "-c""
  -m, --monthly_fee     "The amount required to renew a phone number or all
                        phone numbers"
  -a, --autorenew
  -b, --balance         All balance numbers
  -f, --force_API       "Force the use of api, the database is ignored"
  -c [0,1,2] or none, --cut [0,1,2] or none
                        Used only in conjunction with "-s" 0 - The whole line,
                        1- Part of the string before "@", 2 - Part of the line
                        after "@"


Ключи "-S, -s, -g, -e, -d, -a" используются только в сочетании с номером
Ключ -m может использоваться как с номером, так и без него
Ключ -b, только без номера
Ключ -c только с ключем -s
Ключ -f форсирует использование API, без него идет запрос в базу данных и его можно использовать с любым ключом.

Второй скрипт:

Скрипт API_to_MySql
Так-же дайте ему права на запуск:


chmod +x ./main.py

Как обозвать скрипт и куда его положить, решайте сами.

#! /usr/bin/env python3
from hashlib import sha1, md5
from collections import OrderedDict
from urllib.parse import urlencode
import hmac
import requests
import base64
import json
import pymysql

api_key      = '222c2fd22c2d2222a2b2'
secret_key   = '2a2f2222cee2222f2222'

database_address = '192.168.10.18'
database_name = 'asterisk'
database_user = 'asterisk'
database_password = 'dFgXuasfvgas45hAkEb2'
zadarma_api_url = 'http://api.zadarma.com'

monthly_fee_all = 0

debug = True
# debug = False
db = pymysql.connect(database_address, database_user, database_password, database_name)
cursor = db.cursor()

def sql_insert():

    sql = "REPLACE zadarma_numbers (id, date, number, number_name, description, sip, start_date, stop_date, monthly_fee, status, channels, autorenew)           VALUES (NULL, UTC_TIMESTAMP(), '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');" %           (number,number_name, description, sip, start_date, stop_date, monthly_fee, status, channels, autorenew)
    try:
        # Execute the SQL command
        cursor.execute(sql)
        # Commit your changes in the database
        db.commit()
    except:
        # Rollback in case there is any error
        db.rollback()

def zapros(method,data):
    if not data:
        data = {'format': 'json'}
    sorted_dict_params = OrderedDict(sorted(data.items()))
    query_string = urlencode(sorted_dict_params)
    h = md5(query_string.encode('utf8')).hexdigest()
    data = method + query_string + h
    hashed = hmac.new(secret_key.encode('utf8'), data.encode('utf8'), sha1)
    auth = api_key + ':' + base64.b64encode(bytes(hashed.hexdigest(), 'utf8')).decode()

    headers = {'User-Agent': '-', 'Authorization': auth}
    url = zadarma_api_url + method + '?' + query_string;
    r = requests.get(url, headers=headers)
    return json.loads(r.text)
    # print(r.text)
numbers = zapros('/v1/direct_numbers/', '')
if debug:
    print(numbers['info'])
for res in numbers['info']:

    description = res['description']
    number = res['number']
    number_name = res['number_name']
    start_date = res['start_date']
    stop_date = res['stop_date']
    sip = res['sip']

    if res['autorenew'] ==  'true':
        autorenew = 0
    else:
        autorenew = 1

    monthly_fee = res['monthly_fee']

    if res['status'] == 'on':
        status = 0
    else:
        status = 1
    channels = res['channels']
    monthly_fee_all = monthly_fee_all + monthly_fee
    if debug:
        print('number', number)
        print('description', description)
        print('sip',sip)
        print('number_name', number_name)
        print('start_date', start_date)
        print('stop_date', stop_date)
        print('monthly_fee',monthly_fee)
        print('status', status)
    # print('monthly_fee_all',monthly_fee_all)
    if autorenew != 0:
        if debug:
            print('!!!!!!!!!!!!!!!\nautorenew OFF\n!!!!!!!!!!!!!!!\n')
    if debug:
        print()

    sql_insert()


balance_info = zapros('/v1/info/balance/','')
balance = balance_info['balance']

if debug:
    print('balance', balance)
    print('monthly_fee_all',monthly_fee_all)

sql = "REPLACE zadarma_balance (id, date, balance, monthly_fee) VALUES (NULL, UTC_TIMESTAMP(), '%s', '%s');" % (balance, monthly_fee_all)
try:

    cursor.execute(sql)
    db.commit()
except:
    db.rollback()
if debug:
    print('doplata -->', balance - monthly_fee_all )
    print('monthly_fee_all', monthly_fee_all)
db.close()

<spoiler title="Создадим таблюцу MySQL для номеров">

Для отключения вывода информации в консоль необходимо раскоментировать строчку
debug = False


Для работы обоих скриптов скорее всего понадобится доустановить «requests»

pip install requests

Таблица для номеров
CREATE TABLE IF NOT EXISTS `zadarma_numbers` (
  `id` int(10) NOT NULL,
  `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `number` varchar(12) DEFAULT NULL,
  `number_name` varchar(60) DEFAULT NULL,
  `description` varchar(60) NOT NULL DEFAULT '',
  `sip` varchar(80) DEFAULT NULL,
  `start_date` datetime NOT NULL,
  `stop_date` datetime NOT NULL,
  `monthly_fee` int(5) DEFAULT NULL,
  `status` int(1) DEFAULT NULL,
  `channels` int(3) DEFAULT NULL,
  `autorenew` int(1) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=205 DEFAULT CHARSET=utf8;

ALTER TABLE `zadarma_numbers`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `number` (`number`),
  ADD KEY `calldate` (`date`),
  ADD KEY `accountcode` (`monthly_fee`);
ALTER TABLE `zadarma_numbers`
  MODIFY `id` int(10) NOT NULL AUTO_INCREMENT;



Таблица для баланса
CREATE TABLE IF NOT EXISTS `zadarma_balance` (
  `id` int(10) NOT NULL,
  `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `balance` int(10) DEFAULT NULL,
  `monthly_fee` int(10) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;

ALTER TABLE `zadarma_balance`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `balance` (`balance`),
  ADD KEY `calldate` (`date`);

ALTER TABLE `zadarma_balance`
  MODIFY `id` int(10) NOT NULL AUTO_INCREMENT;


Репозиторий с шаблоном, скриптами и дампами mysql.

Комментарии (4)


  1. Magnum_M
    11.07.2018 19:53

    «Справка»


    1. Ash666 Автор
      11.07.2018 19:58

      Спасибо! Исправил.


      1. Magnum_M
        11.07.2018 20:11

        Я конечно извиняюсь, но реализована немного надуманная потребность на мой взгляд.

        Смутно представляю, зачем мне в Заббиксе нужны графики баланса, если уведомления о низком балансе со счетом настраиваются в панели управления самого Задарма. И зачем список номеров. Может какой-то редкий паттерн когда очень часто меняются номера?

        Но за движуху и оформление до этапа использования плюс. )


        1. Ash666 Автор
          11.07.2018 20:24

          Вынужден с Вами согласиться в плане надуманности, хотя с другой стороны удобно, когда мониторинг в одном месте, уведомления теперь в телеграмм сыпятся.
          В Заббиксе три триггера, первый срабатывает, когда текущий баланс ниже суммы необходимой для продления всех номеров. Второй, когда на номере отключено авто-продление. Третий, когда статус номера отключен.
          Ещё можно сделать триггер на изменение SIP-URI, что теоретически может свидетельствовать о несанкционированном доступе Л.К. Задарма.
          Графики я кстати не рисовал.
          Есть немного и юмора в этом всем — теперь исправить проблему можно потратив энную сумму денег)