Привет. Совсем какой-то не сетевой топик, но что делать... жизнь она такая.
Сегодня пишем невероятно простую инвенторку с API и покуем все это дело в докер контейнер с использованием GitHub CI/CD.
Кому не хочется читать :)
https://github.com/MelHiour/simple_api_inventory
https://hub.docker.com/r/melhiour1/simple_inventory
С чего все началось?
Я в очередной раз пытался понять зачем лично мне нужен OOP. Но потом подумал, что если OOP не приходит ко мне, то я приду к нему. Решил я поэкспериментировать и написать что-то очень простое, но относительно полезное.
После недолгих раздумий решено было написать простенькую инвенторную системку для хранения данных о своем барахлишке. Я использовал для этого простую табличку, но это не всегда удобно. Дело в том, что я использую свои файерволы как некие "джамп" хосты. Сначала логинишься на них, а потом прыгаем по серверам. Было бы удобно смотреть инвенторку прям с них. Естественно на ум пришел CURL.
Запускать, понятное дело, такие вещи удобно в контейнере. Если так, то почему бы не паковать в контейнер сразу с Github через их CI/CD систему.
Итак, погнали по списку:
- Логика
- Web API
- Docker Image
- CI/CD
- Запускаем
Логика
Файл: https://github.com/MelHiour/simple_api_inventory/blob/main/src/helpers.py
Я по прежнему пишу отвратительный код и уже не особо переживаю на этот счет. На этот раз я попытался реализовать свои идеи в стиле Объектно Ориентированного Программирования.
Идея проста как и все в OOП мире, мгм...
- наша инвенторка это некий объект Inventory, в котором содержаться другие объекты Equipment.
- каждый объект Equipment содержит в себе инвентарные данные - ip, имя, ОС и т.д.
- над инвентори мы можем совершать разные действия, такие как посмотреть на определенный Equipment внутри или удалить его.
class Inventory:
def __init__(self, db_file):
def get_equipment(self):
def get_equipment_attr(self, name, attr):
def add_equipment(self, dictionary):
def del_equipment(self, name):
def update_equipment_attr(self, name, dictionary):
def sync(self):
def add_equipment(self, dictionary):
setattr(self, dictionary['name'], Equipment(dictionary))
self.sync()
return dictionary['name']
class Equipment:
def __init__(self, dictionary):
"""It expects a dictionary and create an instance from it"""
self.__dict__.update(dictionary)
def to_dict(self):
"""Can return instance as a dictionary"""
return self.__dict__
# python3
>>> from helpers import Inventory
>>> inventory = Inventory('db.json')
File not found. It will be created
>>> inventory.get_equipment()
[]
>>> inventory.add_equipment({'name':'device1','type':'Firewall'})
'device1'
>>> inventory.add_equipment({'name':'device2','type':'Router'})
'device2'
>>> inventory.get_equipment()
['device1', 'device2']
>>> inventory.get_equipment_attr('device1', 'type')
'Firewall'
>>> inventory.del_equipment('device2')
{'name': 'device2', 'type': 'Router'}
>>> inventory.get_equipment()
['device1']
>>> quit()
# cat db.json
{"device1": {"name": "device1", "type": "Firewall"}}
Web API
- /inventory/
- GET - возвращает список устройств
- POST - добавляет новое устройство
- /inventory/<name>
- GET - возвращает детали по одному устройству
- DELETE - удаляет устройство
- PUT - обновляет данные устройства
- /inventory/<name>/<attr>
- GET - возвращает атрибут устройства
@app.route('/inventory/', methods=['GET'])
def get_inventory():
@app.route('/inventory/<name>', methods=['GET'])
def get_equipment_by_name(name):
@app.route('/inventory/<name>/<attr>', methods=['GET'])
def get_equipment_by_attr(name, attr):
@app.route('/inventory/', methods=['POST'])
def add_equipment():
@app.route('/inventory/<name>', methods=['DELETE'])
def del_equipment(name):
@app.route('/inventory/<name>', methods=['PUT'])
def update_equipment_attr(name):
if __name__ == '__main__':
inventory = Inventory('data/db.json')
app.run(host='0.0.0.0')
# python3 simple_inventory.py
File not found. It will be created
* Serving Flask app 'simple_inventory' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on all addresses.
WARNING: This is a development server. Do not use it in a production deployment.
* Running on http://10.20.30.12:5000/ (Press CTRL+C to quit)
#### Что в инвентори? Ничего.
# curl http://10.20.30.12:5000/inventory/
{"equipment":[[]]}
#### Добавим три устройства.
# curl --header "Content-Type: application/json" \
> --request POST \
> --data '{"name":"FRW1","address":"10.0.0.1","location":"Lipetsk"}' \
> http://10.20.30.12:5000/inventory/
{"name":"FRW1"}
# curl --header "Content-Type: application/json" \
> --request POST \
> --data '{"name":"FRW2","address":"192.168.0.1","location":"Voronezh","OS":"JunOS"}' \
> http://10.20.30.12:5000/inventory/
{"name":"FRW2"}
# curl --header "Content-Type: application/json" \
> --request POST \
> --data '{"name":"FRW3","address":"172.16.0.1","location":"Dublin","OS":"VyOS","type":"VM"}'\
> http://10.20.30.12:5000/inventory/
{"name":"FRW3"}
#### Так гораздо лучше
# curl http://10.20.30.12:5000/inventory/
{"equipment":[["FRW1","FRW2","FRW3"]]}
#### Можно посмотреть детальней
# curl http://10.20.30.12:5000/inventory/FRW1
{"address":"10.0.0.1","location":"Lipetsk","name":"FRW1"}
#### Или еще детальней
# curl http://10.20.30.12:5000/inventory/FRW1/address
"10.0.0.1"
#### Можно поменять что-то
# curl --header "Content-Type: application/json" \
> --request PUT \
> --data '{"address":"10.10.10.10"}' \
> http://10.20.30.12:5000/inventory/FRW1
{"address":"10.10.10.10","location":"Lipetsk","name":"FRW1"}
# curl http://10.20.30.12:5000/inventory/FRW1/address
"10.10.10.10"
#### А можно и удалить совсем
# curl --request DELETE http://10.20.30.12:5000/inventory/FRW2
{"OS":"JunOS","address":"192.168.0.1","location":"Voronezh","name":"FRW2"}
# curl http://10.20.30.12:5000/inventory/
{"equipment":[["FRW1","FRW3"]]}
Docker Image
# syntax=docker/dockerfile:1
FROM python:3.8-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY helpers.py helpers.py
COPY simple_inventory.py simple_inventory.py
RUN mkdir data
CMD [ "python3", "simple_inventory.py"]
CI/CD
Запускаем
/srv/simple_inventory/
├── data
└── run
# cat simple_inventory/run
docker run --name simple_inventory \
-p 5000:5000/tcp \
-v /srv/simple_inventory/data:/app/data \
--restart always \
melhiour1/simple_inventory
# bash simple_inventory/run
Unable to find image 'melhiour1/simple_inventory:latest' locally
latest: Pulling from melhiour1/simple_inventory
33847f680f63: Already exists
e8124950597e: Pull complete
cc636c24d49d: Downloading [======================> ] 4.934MB/10.73MB
1fbf3ac5d4b6: Download complete
937cec37db4e: Download complete
c492c4ebd07b: Verifying Checksum
7232db23c932: Download complete
e02af92bd35d: Downloading [======================> ] 1.897MB/4.285MB
98c19ae47b01: Download complete
368280d54981: Waiting
517871cb6a3c: Waiting
# curl http://localhost:5000/inventory/
{"equipment":[[]]}
# curl --header "Content-Type: application/json" \
> --request POST \
> --data '{"name":"FRW1","address":"10.0.0.1","location":"Lipetsk"}' \
> http://localhost:5000/inventory/
{"name":"FRW1"}
# curl http://localhost:5000/inventory/
{"equipment":[["FRW1"]]}
# cat simple_inventory/data/db.json
{"FRW1": {"name": "FRW1", "address": "10.0.0.1", "location": "Lipetsk"}}
Комментариев нет:
Отправить комментарий