Представим что нам нужно что-то скопировать ансиблом на сервер, а SSH нет. Ситуация абсурдная, но все же. Пошарившишь на гитхабе, не нашёл ничего достаточно простого и в тоже время работающего, поэтому решил сделать все своими силами.
План такой:
— понять как сделать свой модуль
— понять как копировать Питоном по FTP.
Что касается пункта 2 нашего списка, все просто. Есть дефолтная библиотека на этот случай. Её и будем использовать. А вот с модулем ансибла чуть сложнее, но дока тоже есть.
Хочется чтобы данные отправлялись на сервер вот такой таской:
- name: Upload file
local_action:
module: ftp
host: IP
user: username
password: password
src: file.txt
dest: file.txt
command: put
changed_when: false
Для этого примем параметры как указанно в доке:
module = AnsibleModule(
argument_spec=dict(
host=dict(type='str', required=True),
port=dict(type='int', default=21),
user=dict(type='str', required=True),
password=dict(type='str', required=True),
src=dict(type='str'),
dest=dict(type='str'),
command=dict(type='str', choices=["get", "put"], required=True)
),
supports_check_mode=True
)
Тут я думаю все ясно. Если в command указанно get, то скачиваем файл, put — загружаем на сервер.
Далее напишем часть, которая будет отвечать за подключение по FTP и загрузку/скачивание файла.
ftp = FTP()
ftp.connect(module.params["host"], module.params["port"])
ftp.login(module.params["user"], module.params["password"])
if module.params["command"] == "put":
with open(module.params["src"], 'rb') as file:
out = ftp.storbinary("STOR " + module.params["dest"], file)
elif module.params["command"] == "get":
with open(module.params["dest"], 'wb') as file:
out = ftp.retrbinary('RETR ' + module.params["src"], file.write)
if "226" in out:
result = dict(res=out, changed=True, failed=False)
else:
result = dict(res=out, changed=False, failed=True)
Итого имеем такой модуль:
from ftplib import FTP
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(
host=dict(type='str', required=True),
port=dict(type='int', default=21),
user=dict(type='str', required=True),
password=dict(type='str', required=True),
src=dict(type='str'),
dest=dict(type='str'),
command=dict(type='str', choices=["get", "put"], required=True)
),
supports_check_mode=True
)
ftp = FTP()
ftp.connect(module.params["host"], module.params["port"])
ftp.login(module.params["user"], module.params["password"])
if module.params["command"] == "put":
with open(module.params["src"], 'rb') as file:
out = ftp.storbinary("STOR " + module.params["dest"], file)
elif module.params["command"] == "get":
with open(module.params["dest"], 'wb') as file:
out = ftp.retrbinary('RETR ' + module.params["src"], file.write)
if "226" in out:
result = dict(res=out, changed=True, failed=False)
else:
result = dict(res=out, changed=False, failed=True)
module.exit_json(**result)
if __name__ == '__main__':
main()
Этот файл я назову ftp.py и положу в папку library.
Делаю инвентарь и плейбук
ftp.yml:
---
- hosts: all
gather_facts: no
connection: local
tasks:
- name: Upload file
local_action:
module: ftp
host: 77.222.61.40
user: ***_test
password: ***
src: file.txt
dest: file.txt
command: put
register: out
- debug:
msg: "{{ out }}"
Сразу добавлю дебаг, чтобы видеть ошибки, если будут.
Инвентарь любой, так как хост определяется в самом модуле.
inventory.ini:
localhost
Итого получили:
tree
.
├── file.txt
├── ftp.yml
├── inventory.ini
└── library
└── ftp.py
Ну и запускаем.
ansible-playbook -i inventory.ini ftp.yml
PLAY [all] *************************************************************************************************************
TASK [Upload file] *****************************************************************************************************
[WARNING]: Module did not set no_log for password
ok: [localhost]
TASK [debug] ***********************************************************************************************************
ok: [localhost] => {
"msg": {
"changed": false,
"failed": false,
"res": "226-File successfully transferred\n226 0.061 seconds (measured here), 361.21 bytes per second",
"warnings": [
"Module did not set no_log for password"
]
}
}
PLAY RECAP *************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
На мой взгляд все ок. Проверим на сервере, что файл file.txt появился и готово.
Итого: мы узнали как работает библиотека python FTP и написали модуль, который может загрузить файлик на сервер не используя SSH.
На гитхабе это лежит тут: https://github.com/ATPstealer/Scripts/tree/master/ansible_ftp