Compare commits
291 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06336697a6 | |||
| 4113719334 | |||
| f8c32d609b | |||
| e537eadda6 | |||
| 5f3df12742 | |||
| 6b102946de | |||
| 45355b3dd3 | |||
| 54c306b13b | |||
| dd2fca15f3 | |||
| aa3f84095f | |||
| ba305a0ccb | |||
|
|
7d60e5f97b | ||
| bbc352715d | |||
| c34f02f7bd | |||
| 3c6ce85dba | |||
| fe1e8a32f9 | |||
| 0ca3399026 | |||
| ce54341a64 | |||
| 7422464109 | |||
| 0c0fde8470 | |||
| aefc920e1a | |||
| b73a57dd0d | |||
| cd663a4a01 | |||
| 0cdfa6a0ec | |||
| 53938d9d94 | |||
| 8e4ce18d8a | |||
| ced031bba8 | |||
| 25795e9fe9 | |||
| a46ed24168 | |||
| 53ba9a4f02 | |||
| 682f19c4ce | |||
| 3516843c41 | |||
| 94586345a1 | |||
| c1dfaccb61 | |||
| d99af4498a | |||
| a0805d45b3 | |||
| 3b2be18346 | |||
| f230325968 | |||
| 1d56940e7e | |||
| 10dba22834 | |||
| b6bbc31961 | |||
| 412bd704f1 | |||
| 77a2b6a182 | |||
| f00055e009 | |||
| 37de8afbc1 | |||
| 53e30105b1 | |||
| 734408ab10 | |||
| 0fcde3f4bd | |||
| 32324ee184 | |||
| 5161649fe9 | |||
| ce4ac4630c | |||
| 8ee5bcce52 | |||
| 1d6141117d | |||
| 5d6a278b8f | |||
| cf4d43d23e | |||
| 11168827e6 | |||
| 2507a65d89 | |||
| 22f487b626 | |||
| 89b03213df | |||
| 96b163ba00 | |||
| 21470542ea | |||
| 7eed30d2ab | |||
| efa60a5caf | |||
| b7b02fdb85 | |||
| c5f2e143ba | |||
| 68f4bcbcc6 | |||
| a94a5a2e62 | |||
| 3b84a5f633 | |||
| 2d04cad3d4 | |||
| f12e6fc941 | |||
| 02e55e77f4 | |||
| dac66fac77 | |||
| 3ce022a800 | |||
| 59000feb00 | |||
| 614c7d98d9 | |||
| 3428b74b00 | |||
| 36930bda0d | |||
|
|
eb91d5200f | ||
| d7711e58ca | |||
| d287c88293 | |||
| 309d5b91eb | |||
| db854acc11 | |||
| 7b682f0340 | |||
| b51136b711 | |||
|
|
daa91bee95 | ||
|
|
5227e2be0b | ||
| 9ccf2de256 | |||
| 825ce068c8 | |||
| efb51ab8b4 | |||
| 190a1d302c | |||
| 840e4bec21 | |||
| f22be4fe08 | |||
| 4b1a7d9d9e | |||
| 3a2d9904cf | |||
| 9393bb76cc | |||
| 27e7c2749f | |||
| e578742bb2 | |||
| 7998046cfb | |||
| 06b4686e18 | |||
| cb13649586 | |||
| 7e372511bc | |||
| bb8e9ad6f7 | |||
| 3fec6f014b | |||
| 4a08cd86f6 | |||
| 3dc9cbcbd8 | |||
| 0008372e6b | |||
| 5c3194b94d | |||
| 20b9228351 | |||
| 9449afa2ac | |||
| b2477d2035 | |||
| ea63959289 | |||
| b3b47c57a1 | |||
| bb6394873b | |||
| ff1c43e8de | |||
| 6b4957f8aa | |||
| d824e599b9 | |||
| b1f2167d00 | |||
| aad2f89e6d | |||
| 4311eee435 | |||
| 3bdac96760 | |||
| a6a6954d46 | |||
| 6d003e4541 | |||
| 0ae16ddc1d | |||
| c5f68fea38 | |||
| 23ad78b1cf | |||
| 93a929aff5 | |||
| 90ae212d0c | |||
| 421d19bfa8 | |||
| 41731c3dd7 | |||
| 034aeca3f0 | |||
| db67a3b2d2 | |||
| af6eff33f7 | |||
| ca45f49c2e | |||
| 88d9ddcdbe | |||
| 876ff5b98d | |||
| c46edd4f86 | |||
| 9ce5a3d711 | |||
| 38757aa902 | |||
| 56f7f354c1 | |||
| 2b09b9449a | |||
| eb0766b15f | |||
| d7a32376db | |||
| 8a9c354f20 | |||
| 1e994410fe | |||
| e2bbeb7ffb | |||
| 2777059b6b | |||
| 38f11ee480 | |||
| dc24c0e7ee | |||
| 9a5fd176f0 | |||
| 279d60083d | |||
| 1d53fd3213 | |||
| a0da034499 | |||
| e5504b3ac9 | |||
| 2eb43815e7 | |||
| 2f9716a51d | |||
| aa96af1455 | |||
| a39ab95c1f | |||
| 76a1ff1788 | |||
| 9dc3a5e780 | |||
| ff32b509f8 | |||
| 5b731d009b | |||
| 57dba89b5a | |||
| 007c7f4bad | |||
|
|
7f87642b47 | ||
| 2873e4da82 | |||
| 9c97f4b645 | |||
| 772f389d98 | |||
| 36e1383c7e | |||
| 35fe2bdf0e | |||
| 7e7a4898ec | |||
| fde986bb42 | |||
| 538c7e6f47 | |||
| 53634b7fa5 | |||
| b68e562fc1 | |||
| df619e7998 | |||
|
|
877b84d650 | ||
| 10b98b7c4a | |||
| 51707cbb69 | |||
| 3f4cfd40d6 | |||
|
|
4cf0316dad | ||
| abcb7d52f8 | |||
|
|
8a6d624069 | ||
|
|
2b96881849 | ||
| 6725e4342e | |||
| 5003991cf5 | |||
| 8a99097b6c | |||
| 6a99ea4d85 | |||
| 236c3b6d26 | |||
| bcf384a910 | |||
| 8cf3d05d0b | |||
| 5da120f2d3 | |||
| 6abd78882c | |||
| 7fa2d07ab0 | |||
| 8b407c6f69 | |||
| 4c4be07550 | |||
|
|
eb542fa46c | ||
| 240e2ce2df | |||
|
|
f4fcf5b7b7 | ||
|
|
d2dabdb194 | ||
| 694e68fe22 | |||
| 9e7a33a44a | |||
|
|
31b72b3806 | ||
|
|
fa79b90269 | ||
|
|
aa9f26bf1a | ||
| 6cd828d031 | |||
| d50509e0c3 | |||
| a45ab19d38 | |||
|
|
a3c1dcad7a | ||
|
|
9bf50c958f | ||
|
|
d769119ade | ||
| 200e8f7151 | |||
| f11ec8fefb | |||
| 19534384a8 | |||
| 1304a0fcbf | |||
|
|
a5f03389f2 | ||
|
|
6760167e4e | ||
|
|
2b4ba7ee55 | ||
|
|
5b66c98cc6 | ||
|
|
55268beaad | ||
| b18bdef269 | |||
| 21e853c1f2 | |||
| 1c6b46c535 | |||
| 058de4884f | |||
|
|
d622dd8453 | ||
|
|
e26011ab20 | ||
|
|
f2e9b550bb | ||
|
|
a8f50bfb6b | ||
|
|
9222fdd866 | ||
|
|
e94faef2ad | ||
|
|
66e72a3d3b | ||
|
|
740271b2f2 | ||
|
|
207c04bb77 | ||
|
|
5be2e850ce | ||
|
|
7fffdd0ab5 | ||
|
|
ae85826129 | ||
|
|
b03b82e492 | ||
|
|
15893f58bb | ||
|
|
3681949ea6 | ||
|
|
8713ebe099 | ||
|
|
87ecb762aa | ||
|
|
b30d8f2b3c | ||
|
|
36d6fe092d | ||
|
|
8f737397de | ||
|
|
72d9da0234 | ||
|
|
4b3f34376b | ||
| 72b591723f | |||
| f0e9ac10cc | |||
|
|
82eef4492d | ||
|
|
889618f843 | ||
|
|
ef33532a83 | ||
|
|
8f7c933fb8 | ||
|
|
dd28084b76 | ||
|
|
8d6ed618ab | ||
|
|
e2c9f591b8 | ||
|
|
ebdf9aa012 | ||
|
|
31d6774475 | ||
|
|
6148e67dd1 | ||
|
|
4c7922cb55 | ||
|
|
8d51555db1 | ||
|
|
ebfb465531 | ||
|
|
f3504a3d50 | ||
|
|
1c3558d6f3 | ||
|
|
12a80e7cf4 | ||
|
|
38d24cfa0a | ||
|
|
ebe1c76a43 | ||
|
|
224ac59779 | ||
|
|
ff4273cbce | ||
|
|
5e5b6f6b8a | ||
|
|
c497979efa | ||
|
|
28876ae38d | ||
|
|
ebe1a9194f | ||
|
|
d9ad1fee21 | ||
|
|
4b23cf8bae | ||
|
|
5b809dee4f | ||
|
|
ac93e76005 | ||
|
|
aae60a46b9 | ||
|
|
25af1b4428 | ||
|
|
e058925f9b | ||
|
|
f6b0da976b | ||
|
|
9b9b92ff07 | ||
|
|
498b46b4f2 | ||
|
|
85da544614 | ||
|
|
be7841ba38 | ||
|
|
aabd5dab6e | ||
|
|
4a9d181782 | ||
|
|
ec7d1fd181 | ||
|
|
4f151e174b | ||
|
|
0237c469c5 | ||
|
|
ab44d18d21 | ||
|
|
75bda6d76d | ||
|
|
41ca42dcab |
30
.pre-commit-config.yaml
Normal file
30
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v5.0.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: no-commit-to-branch
|
||||||
|
name: no-commit-to-branch (main, master, dev_*)
|
||||||
|
args:
|
||||||
|
- --pattern
|
||||||
|
- dev_.*
|
||||||
|
- repo: https://github.com/pycqa/flake8
|
||||||
|
rev: 7.2.0
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
module_utils/decort_utils.py |
|
||||||
|
library/decort_bservice.py |
|
||||||
|
library/decort_disk.py |
|
||||||
|
library/decort_group.py |
|
||||||
|
library/decort_k8s.py |
|
||||||
|
library/decort_kvmvm.py |
|
||||||
|
library/decort_lb.py |
|
||||||
|
library/decort_osimage.py |
|
||||||
|
library/decort_pfw.py |
|
||||||
|
library/decort_rg.py |
|
||||||
|
library/decort_vins.py
|
||||||
|
)$
|
||||||
|
args:
|
||||||
|
- --extend-ignore=E402
|
||||||
122
CHANGELOG.md
Normal file
122
CHANGELOG.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# Список изменений в версии 9.0.0
|
||||||
|
|
||||||
|
## Добавлено
|
||||||
|
### Глобально
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-798 | Обновлены системные требования: версия интерпретатора Python обновлена до 3.12, версия Python-библиотеки ansible обновлена до 11.6.0 |
|
||||||
|
|
||||||
|
### Модуль decort_kvmvm
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-790 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
|
||||||
|
| BANS-810 | Добавлен параметр `guest_agent` и возвращаемое значение `guest_agent`. |
|
||||||
|
| BANS-806 | Добавлен параметр `get_snapshot_merge_status` и возвращаемое значение `snapshot_merge_status`. |
|
||||||
|
| BANS-823 | Добавлено значение `TRUNK` для параметра `networks.type`. |
|
||||||
|
| BANS-813 | Добавлено значение `SDN` для параметра `networks.type`. |
|
||||||
|
| BANS-835 | Добавлена возможность использования параметра `networks.mtu` для внешней сети. |
|
||||||
|
|
||||||
|
### Модуль decort_lb
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-793 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
|
||||||
|
| BANS-819 | Добавлено возвращаемое значение `account_id`. |
|
||||||
|
| BANS-800 | Добавлены значения `stopped` и `started` для параметра `state` и возвращаемое значение `tech_status`. |
|
||||||
|
|
||||||
|
### Модуль decort_k8s
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-794 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
|
||||||
|
| BANS-804 | Добавлены значения `stopped` и `started` для параметра `state`. |
|
||||||
|
|
||||||
|
### Модуль decort_vins
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-791 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
|
||||||
|
|
||||||
|
### Модуль decort_bservice
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-792 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
|
||||||
|
| BANS-805 | Добавлены значения `stopped` и `started` для параметра `state`. |
|
||||||
|
|
||||||
|
### Модуль decort_user_info
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-796 | Добавлен параметр `zones` и возвращаемое значение `zones`. |
|
||||||
|
| BANS-826 | Добавлен параметр `trunks` и возвращаемое значение `trunks`. |
|
||||||
|
|
||||||
|
### Модуль decort_account
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-789 | Добавлен параметр `default_zone_id` и возвращаемые значение `zoneIds`, `defaultZoneId`. |
|
||||||
|
|
||||||
|
### Модуль decort_account_info
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-809 | Добавлено значение `MERGE` для параметра `computes.filter.tech_status`. |
|
||||||
|
| BANS-855 | Добавлены значения `SNAPCREATE`, `CLONING`, `ROLLBACK` для параметра `computes.filter.tech_status`. |
|
||||||
|
|
||||||
|
### Модуль decort_rg
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-812 | Добавлен параметр `sdn_access_group_id` и возвращаемое значение `sdn_access_group_id`. |
|
||||||
|
|
||||||
|
### Модуль decort_zone
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-795 | Добавлен модуль `decort_zone` для получения информации о зонах. |
|
||||||
|
|
||||||
|
### Модуль decort_trunk
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-825 | Добавлен модуль `decort_trunk` для получения информации о транковых портах. |
|
||||||
|
|
||||||
|
### Модуль decort_snapshot
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-808 | Добавлено значение `merge_aborted` для параметра `state`. |
|
||||||
|
|
||||||
|
### Модуль decort_osimage
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-849 | Добавлен параметр `account_id`, используемый при создании шаблонных и виртуальных образов. |
|
||||||
|
|
||||||
|
## Удалено
|
||||||
|
### Модуль decort_disk
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-815 | Удалено значение по умолчанию для параметра `description`. |
|
||||||
|
|
||||||
|
### Модуль decort_lb
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-815 | Удалено значение по умолчанию для параметра `description`. |
|
||||||
|
|
||||||
|
### Модуль decort_k8s
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-804 | Удален параметр `started` в связи с переносом логики в параметр `state` (значение `started`). |
|
||||||
|
|
||||||
|
### Модуль decort_bservice
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-805 | Удален параметр `started` в связи с переносом логики в параметр `state` (значение `started`). |
|
||||||
|
|
||||||
|
### Модуль decort_osimage
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-849 | Удален параметр `account_Id` в связи с переименованием в `account_id`. |
|
||||||
|
|
||||||
|
## Исправлено
|
||||||
|
### Модуль decort_lb
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-803 | Модуль завершал работу ошибкой Python при создании балансировщика с указанием параметра `backends` или `frontends`. |
|
||||||
|
| BANS-820 | Выполнение модуля с указанием параметра `vins_id` и без указания параметра `ext_net_id` вызывало создание балансировщика с некорректной сетевой конфигурацией, дальнейшее добавление конфигурации backend к которому завершалось ошибкой платформы. |
|
||||||
|
| BANS-799 | Скорректирована логика параметра целевого состояния `present`. Теперь состояние `present` соответствует тому, что балансировщик нагрузки существует, и не приводит к изменению состояния существующего балансировщика нагрузки. Также для параметра `state` значение по умолчанию `present` теперь только при создании балансировщика нагрузки. |
|
||||||
|
|
||||||
|
### Модуль decort_account
|
||||||
|
| Идентификатор<br>задачи | Описание |
|
||||||
|
| --- | --- |
|
||||||
|
| BANS-817 | Модуль некорректно отслеживал завершение удаления и восстановления аккаунта. |
|
||||||
3
Makefile
Normal file
3
Makefile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dev:
|
||||||
|
pip install -r requirements-dev.txt
|
||||||
|
pre-commit install
|
||||||
27
README.md
27
README.md
@@ -1,12 +1,21 @@
|
|||||||
# decort-ansible
|
# decort-ansible
|
||||||
Ansible modules for Digital Energy Orchestration Technology (DECORT) platform v3.6.1 and above.
|
Модули Ansible для платформы Digital Energy Orchestration Technology (DECORT).
|
||||||
|
|
||||||
Note that this module may produce unreliable results when used with older DECORT API versions.
|
## Соответствие версий платформы версиям модулей Ansible
|
||||||
|
|
||||||
Requirements:
|
| Версия платформы | Версия модулей Ansible |
|
||||||
* Ansible 2.7 or higher
|
|:----------------:|:--------------------------:|
|
||||||
* Python 2.6 or higher
|
| 4.4.0 build 963 | 9.0.x |
|
||||||
* PyJWT 1.7.1 Python module
|
| 4.3.0 | 8.0.x |
|
||||||
* requests Python module
|
| 4.2.0 | 7.0.x, 7.1.x, 7.2.x |
|
||||||
* netaddr Python module
|
| 4.1.0 | 6.0.x, 6.1.x |
|
||||||
* DECORT cloud platform version 3.5.0 or higher
|
| 4.0.0 | 5.6.x, 5.5.x, 5.4.x, 5.3.x |
|
||||||
|
| 3.8.8, 3.8.9 | 5.2.6 |
|
||||||
|
| 3.8.7 | 5.2.5 |
|
||||||
|
| 3.8.6 | 5.2.4 |
|
||||||
|
|
||||||
|
## Ссылки
|
||||||
|
|
||||||
|
- [Документация](https://repository.basistech.ru/BASIS/wiki-decort-ansible/src/branch/main/Home.md)
|
||||||
|
|
||||||
|
- [Список изменений](./CHANGELOG.md)
|
||||||
|
|||||||
0
examples/.gitkeep
Normal file
0
examples/.gitkeep
Normal file
40
examples/VINS.yaml
Normal file
40
examples/VINS.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT vins module example
|
||||||
|
#
|
||||||
|
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: obtain JWT
|
||||||
|
decort_jwt:
|
||||||
|
oauth2_url: "https://sso.digitalenergy.online"
|
||||||
|
validity: 1200
|
||||||
|
register: my_jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: print out JWT
|
||||||
|
debug:
|
||||||
|
var: my_jwt.jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Manage ViNS at resource group level
|
||||||
|
decort_vins:
|
||||||
|
authenticator: jwt
|
||||||
|
jwt: "{{ my_jwt.jwt }}"
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
vins_name: "vins_created_by_decort_VINS_module"
|
||||||
|
state: present
|
||||||
|
rg_id: 198
|
||||||
|
ext_net_id: -1
|
||||||
|
ipcidr: "10.20.30.0/24"
|
||||||
|
mgmtaddr: "10.20.30.1"
|
||||||
|
custom_config: false
|
||||||
|
config_save: false
|
||||||
|
verify_ssl: false
|
||||||
|
|
||||||
|
register: managed_vins
|
||||||
|
|
||||||
|
- name: print VINS facter
|
||||||
|
debug:
|
||||||
|
msg: "{{managed_vins.facts.password}}"
|
||||||
|
when: managed_vins.facts.password is defined
|
||||||
40
examples/annotations.yaml
Normal file
40
examples/annotations.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT k8s module labels, taints, annotations example
|
||||||
|
#
|
||||||
|
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: obtain JWT
|
||||||
|
decort_jwt:
|
||||||
|
oauth2_url: "https://sso.digitalenergy.online"
|
||||||
|
validity: 1200
|
||||||
|
register: my_jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: print out JWT
|
||||||
|
debug:
|
||||||
|
var: my_jwt.jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create k8s cluster
|
||||||
|
decort_k8s:
|
||||||
|
authenticator: jwt
|
||||||
|
jwt: "{{ my_jwt.jwt }}"
|
||||||
|
controller_url: "https://mr4.digitalenergy.online"
|
||||||
|
name: "example_kubernetes"
|
||||||
|
rg_id: 199
|
||||||
|
k8ci_id: 4
|
||||||
|
state: present
|
||||||
|
workers:
|
||||||
|
- name: workgroup1
|
||||||
|
labels:
|
||||||
|
- disktype1=ssd1
|
||||||
|
- disktype2=ssd2
|
||||||
|
taints:
|
||||||
|
- key1=value1:NoSchedule
|
||||||
|
- key2=value2:NoSchedule
|
||||||
|
annotations:
|
||||||
|
- node.deckhouse.io/group1=g1
|
||||||
|
- node.deckhouse.io/group2=g2
|
||||||
|
register: kube
|
||||||
31
examples/basicservices.yaml
Normal file
31
examples/basicservices.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT bservice module example
|
||||||
|
#
|
||||||
|
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: obtain JWT
|
||||||
|
decort_jwt:
|
||||||
|
oauth2_url: "https://sso.digitalenergy.online"
|
||||||
|
validity: 1200
|
||||||
|
register: my_jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: print out JWT
|
||||||
|
debug:
|
||||||
|
var: my_jwt.jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Manage bservice at RG
|
||||||
|
decort_bservice:
|
||||||
|
account_id: 98
|
||||||
|
verify_ssl: false
|
||||||
|
authenticator: jwt
|
||||||
|
jwt: "{{ my_jwt.jwt }}"
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
rg_id: 1629
|
||||||
|
state: present
|
||||||
|
name: databases
|
||||||
|
started: True
|
||||||
|
register: db_bservice
|
||||||
22
examples/decort_disk/disk_create.yaml
Normal file
22
examples/decort_disk/disk_create.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: manage data disk 01
|
||||||
|
decort_disk:
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: #Application id from SSO DigitalEnergy
|
||||||
|
app_secret: #Application secret from SSO DigitalEnergy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
account_name: "account_name"
|
||||||
|
name: "example_disk"
|
||||||
|
sep_id: 1
|
||||||
|
pool: 0
|
||||||
|
gid: 0
|
||||||
|
size: 2
|
||||||
|
type: "D"
|
||||||
|
description: "Disk created by decort_disk module"
|
||||||
|
iops: 2000
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
|
||||||
|
delegate_to: localhost
|
||||||
18
examples/decort_disk/disk_delete.yaml
Normal file
18
examples/decort_disk/disk_delete.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: manage data disk 01
|
||||||
|
decort_disk:
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: #Application id from SSO DigitalEnergy
|
||||||
|
app_secret: #Application secret from SSO DigitalEnergy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
account_name: "account_name"
|
||||||
|
name: "example_disk"
|
||||||
|
permanently: False
|
||||||
|
force_detach: True
|
||||||
|
reason: "Just to test module decort_disk"
|
||||||
|
state: absent
|
||||||
|
verify_ssl: false
|
||||||
|
|
||||||
|
delegate_to: localhost
|
||||||
28
examples/decort_disk/disk_limitIO.yaml
Normal file
28
examples/decort_disk/disk_limitIO.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: manage data disk 01
|
||||||
|
decort_disk:
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: #Application id from SSO DigitalEnergy
|
||||||
|
app_secret: #Application secret from SSO DigitalEnergy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
account_name: "account_name"
|
||||||
|
id: 111
|
||||||
|
limitIO:
|
||||||
|
read_bytes_sec: 100
|
||||||
|
read_bytes_sec_max: 100
|
||||||
|
read_iops_sec: 100
|
||||||
|
read_iops_sec_max: 100
|
||||||
|
size_iops_sec: 100
|
||||||
|
write_bytes_sec: 100
|
||||||
|
write_bytes_sec_max: 100
|
||||||
|
write_iops_sec: 100
|
||||||
|
write_iops_sec_max: 100
|
||||||
|
total_bytes_sec: 0
|
||||||
|
total_iops_sec: 0
|
||||||
|
total_bytes_sec_max: 0
|
||||||
|
total_iops_sec_max: 0
|
||||||
|
verify_ssl: false
|
||||||
|
|
||||||
|
delegate_to: localhost
|
||||||
15
examples/decort_disk/disk_rename.yaml
Normal file
15
examples/decort_disk/disk_rename.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: manage data disk 01
|
||||||
|
decort_disk:
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: #Application id from SSO DigitalEnergy
|
||||||
|
app_secret: #Application secret from SSO DigitalEnergy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
account_name: "account_name"
|
||||||
|
id: 111
|
||||||
|
name: "example_disk2"
|
||||||
|
verify_ssl: false
|
||||||
|
|
||||||
|
delegate_to: localhost
|
||||||
15
examples/decort_disk/disk_restore.yaml
Normal file
15
examples/decort_disk/disk_restore.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: manage data disk 01
|
||||||
|
decort_disk:
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: #Application id from SSO DigitalEnergy
|
||||||
|
app_secret: #Application secret from SSO DigitalEnergy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
account_name: "account_name"
|
||||||
|
id: 111
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
|
||||||
|
delegate_to: localhost
|
||||||
27
examples/decort_osimage/create-osimage.yaml
Normal file
27
examples/decort_osimage/create-osimage.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT osimage module example
|
||||||
|
#
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_osimage:
|
||||||
|
authenticator: oauth2
|
||||||
|
verify_ssl: False
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
state: present
|
||||||
|
image_name: "alpine_linux3.14.0"
|
||||||
|
account_Id: 12345
|
||||||
|
url: "https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-virt-3.14.0-x86_64.iso"
|
||||||
|
boottype: "uefi"
|
||||||
|
imagetype: "linux"
|
||||||
|
hotresize: False
|
||||||
|
image_username: "test"
|
||||||
|
image_password: "p@ssword"
|
||||||
|
usernameDL: "testDL"
|
||||||
|
passwordDL: "p@sswordDL"
|
||||||
|
architecture: "X86_64"
|
||||||
|
drivers: "KVM_X86"
|
||||||
|
|
||||||
|
delegate_to: localhost
|
||||||
|
register: simple_vm
|
||||||
14
examples/decort_osimage/create-virtual-osimage.yaml
Normal file
14
examples/decort_osimage/create-virtual-osimage.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT osimage module example
|
||||||
|
#
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create_virtual_osimage
|
||||||
|
decort_osimage:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
image_name: "alpine_linux_3.14.0"
|
||||||
|
virt_name: "alpine_last"
|
||||||
|
delegate_to: localhost
|
||||||
|
register: osimage
|
||||||
14
examples/decort_osimage/get-osimage.yaml
Normal file
14
examples/decort_osimage/get-osimage.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT osimage module example
|
||||||
|
#
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: get_osimage
|
||||||
|
decort_osimage:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
image_name: "alpine_linux_3.14.0"
|
||||||
|
account_Id: 79349
|
||||||
|
delegate_to: localhost
|
||||||
|
register: simple_vm
|
||||||
14
examples/decort_osimage/rename-osimage.yaml
Normal file
14
examples/decort_osimage/rename-osimage.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT osimage module example
|
||||||
|
#
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: rename_osimage
|
||||||
|
decort_osimage:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
image_name: "alpine_linux_3.14.0v2.0"
|
||||||
|
image_id: 54321
|
||||||
|
delegate_to: localhost
|
||||||
|
register: osimage
|
||||||
20
examples/decort_rg/changeLimits_rg.yaml
Normal file
20
examples/decort_rg/changeLimits_rg.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
quotas:
|
||||||
|
cpu: 8
|
||||||
|
ram: 4096
|
||||||
|
disk: 20
|
||||||
|
ext_ips: 10
|
||||||
|
net_transfer: 200
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
21
examples/decort_rg/changeResTypes_rg.yaml
Normal file
21
examples/decort_rg/changeResTypes_rg.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
resType:
|
||||||
|
- vins
|
||||||
|
- compute
|
||||||
|
- k8s
|
||||||
|
- openshift
|
||||||
|
- lb
|
||||||
|
- flipgroup
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
30
examples/decort_rg/create_rg.yaml
Normal file
30
examples/decort_rg/create_rg.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
account_id: 99
|
||||||
|
owner: "user_1" #Leave blank to set current user as owner.
|
||||||
|
quotas:
|
||||||
|
cpu: 8
|
||||||
|
ram: 4096
|
||||||
|
disk: 20
|
||||||
|
ext_ips: 10
|
||||||
|
net_transfer: 200
|
||||||
|
access:
|
||||||
|
action: "grant"
|
||||||
|
user: "user_2"
|
||||||
|
right: "RCX"
|
||||||
|
def_netType: "PRIVATE"
|
||||||
|
ipcidr: "" "192.168.1.1"
|
||||||
|
extNetId: 0
|
||||||
|
extNetIp: "" "10.100.1.10"
|
||||||
|
resType:
|
||||||
|
- vins
|
||||||
|
- compute
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
15
examples/decort_rg/delete_rg.yaml
Normal file
15
examples/decort_rg/delete_rg.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "test_rg"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
state: present
|
||||||
|
permanently: True
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
12
examples/decort_rg/disable_rg.yaml
Normal file
12
examples/decort_rg/disable_rg.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_id: 999 # rg can be restored only by rg id
|
||||||
|
account_id: 99
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
14
examples/decort_rg/enable_rg.yaml
Normal file
14
examples/decort_rg/enable_rg.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
state: enabled
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
18
examples/decort_rg/grantAccess_rg.yaml
Normal file
18
examples/decort_rg/grantAccess_rg.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
access:
|
||||||
|
action: "grant"
|
||||||
|
user: "new_user"
|
||||||
|
right: "R"
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
15
examples/decort_rg/rename_rg.yaml
Normal file
15
examples/decort_rg/rename_rg.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "old_rg_name"
|
||||||
|
# or
|
||||||
|
#rg_id: 1737
|
||||||
|
account_id: 99
|
||||||
|
rename: "new_rg_name"
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
17
examples/decort_rg/revokeAccess_rg.yaml
Normal file
17
examples/decort_rg/revokeAccess_rg.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
access:
|
||||||
|
action: "revoke"
|
||||||
|
user: "old_user"
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
16
examples/decort_rg/setDefNet_rg.yaml
Normal file
16
examples/decort_rg/setDefNet_rg.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: create
|
||||||
|
decort_rg:
|
||||||
|
authenticator: oauth2
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
rg_name: "rg_created_by_module"
|
||||||
|
# or
|
||||||
|
#rg_id: 999
|
||||||
|
account_id: 99
|
||||||
|
def_netType: "PRIVATE"
|
||||||
|
def_netId: 199
|
||||||
|
state: present
|
||||||
|
verify_ssl: false
|
||||||
|
register: my_rg
|
||||||
|
delegate_to: localhost
|
||||||
14
examples/hashivault/hashivault_create_engine.yaml
Normal file
14
examples/hashivault/hashivault_create_engine.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# This playbook create engine "test".
|
||||||
|
#
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- hashivault_secret_engine:
|
||||||
|
url: "https://vault.domain.local"
|
||||||
|
authtype: ldap
|
||||||
|
username: "user"
|
||||||
|
password: "p@ssword"
|
||||||
|
state: present
|
||||||
|
name: test
|
||||||
|
backend: generic
|
||||||
17
examples/hashivault/hashivault_create_secret.yaml
Normal file
17
examples/hashivault/hashivault_create_secret.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# This playbook create secret "secret" with data foo:foe. If secret "secret" exists - add data foo:foe.
|
||||||
|
#
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- hashivault_secret:
|
||||||
|
url: "https://vault.domain.local"
|
||||||
|
authtype: ldap
|
||||||
|
username: "user"
|
||||||
|
password: "p@ssword"
|
||||||
|
mount_point: "kv"
|
||||||
|
state: present
|
||||||
|
permanent: true
|
||||||
|
secret: secret
|
||||||
|
data:
|
||||||
|
foo: foe
|
||||||
35
examples/hashivault/hashivault_example.yaml
Normal file
35
examples/hashivault/hashivault_example.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- hashivault_read:
|
||||||
|
url: "https://vault.domain.local"
|
||||||
|
authtype: ldap
|
||||||
|
username: "user"
|
||||||
|
password: "p@ssword"
|
||||||
|
mount_point: kv
|
||||||
|
secret: secrets/myaccount
|
||||||
|
key: app_secret
|
||||||
|
version: 2
|
||||||
|
register: key
|
||||||
|
|
||||||
|
- name: create a VM using app_secret from hashicorp vault
|
||||||
|
decort_kvmvm:
|
||||||
|
annotation: "VM managed by decort_kvmvm module"
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: "" # Application id from SSO Digital Energy
|
||||||
|
app_secret: "{{ key }}" # API key from SSO Digital Energy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
name: hashivault_read_example
|
||||||
|
cpu: 2
|
||||||
|
ram: 2048
|
||||||
|
boot_disk: 10
|
||||||
|
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
|
||||||
|
networks:
|
||||||
|
- type: VINS
|
||||||
|
id: 99 #VINS id
|
||||||
|
tags: "Ansible hashivault_read example"
|
||||||
|
state: present
|
||||||
|
rg_id: 99 #Resource group id
|
||||||
|
delegate_to: localhost
|
||||||
|
register: simple_vm
|
||||||
31
examples/hashivault/hashivault_plugin_example.yaml
Normal file
31
examples/hashivault/hashivault_plugin_example.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: Read a kv2 secret with kv mount point
|
||||||
|
vars:
|
||||||
|
ansible_hashi_vault_auth_method: ldap
|
||||||
|
ansible_hashi_vault_username: username
|
||||||
|
ansible_hashi_vault_password: pwd
|
||||||
|
ansible_hashi_vault_engine_mount_point: kv
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'secret', url='https://vault.domain.local') }}"
|
||||||
|
|
||||||
|
- name: create a VM using app_secret from hashicorp vault
|
||||||
|
decort_kvmvm:
|
||||||
|
annotation: "VM managed by decort_kvmvm module"
|
||||||
|
authenticator: oauth2
|
||||||
|
app_id: "" # Application id from SSO Digital Energy
|
||||||
|
app_secret: "{{ response.data.password }}" # API key from SSO Digital Energy
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
name: hashivault_read_example
|
||||||
|
cpu: 2
|
||||||
|
ram: 2048
|
||||||
|
boot_disk: 10
|
||||||
|
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
|
||||||
|
networks:
|
||||||
|
- type: VINS
|
||||||
|
id: 99 #VINS id
|
||||||
|
tags: "Ansible hashivault_read example"
|
||||||
|
state: present
|
||||||
|
rg_id: 99 #Resource group id
|
||||||
|
delegate_to: localhost
|
||||||
|
register: simple_vm
|
||||||
16
examples/hashivault/hashivault_plugin_login.yaml
Normal file
16
examples/hashivault/hashivault_plugin_login.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: Get auth token from vault
|
||||||
|
set_fact:
|
||||||
|
login_data: "{{ lookup('community.hashi_vault.vault_login', url='https://vault.domain.local', auth_method='ldap', username='username', password='pwd') }}"
|
||||||
|
|
||||||
|
- name: Perform multiple kv2 reads with a single Vault login, showing the secrets
|
||||||
|
vars:
|
||||||
|
ansible_hashi_vault_auth_method: token
|
||||||
|
ansible_hashi_vault_token: '{{ login_data | community.hashi_vault.vault_login_token }}'
|
||||||
|
ansible_hashi_vault_engine_mount_point: kv
|
||||||
|
paths:
|
||||||
|
- secret
|
||||||
|
- secret2
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ lookup('community.hashi_vault.vault_kv2_get', *paths, auth_method='token', url='https://vault.domain.local') }}"
|
||||||
18
examples/hashivault/hashivault_plugin_read_secret.yaml
Normal file
18
examples/hashivault/hashivault_plugin_read_secret.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: Read a kv2 secret with the default mount point
|
||||||
|
vars:
|
||||||
|
ansible_hashi_vault_auth_method: ldap
|
||||||
|
ansible_hashi_vault_username: username
|
||||||
|
ansible_hashi_vault_password: pwd
|
||||||
|
ansible_hashi_vault_engine_mount_point: kv
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'secret', url='https://vault.domain.local') }}"
|
||||||
|
|
||||||
|
- name: Display the results
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Secret: {{ response.secret }}"
|
||||||
|
- "Data: {{ response.data }} (contains secret data & metadata in kv2)"
|
||||||
|
- "Metadata: {{ response.metadata }}"
|
||||||
|
- "Full response: {{ response.raw }}"
|
||||||
13
examples/hashivault/hashivault_read_secret.yaml
Normal file
13
examples/hashivault/hashivault_read_secret.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- hashivault_read:
|
||||||
|
url: "https://vault.domain.local"
|
||||||
|
authtype: ldap
|
||||||
|
username: "uset"
|
||||||
|
password: "p@ssword"
|
||||||
|
mount_point: kv
|
||||||
|
secret: secret
|
||||||
|
key: foo
|
||||||
|
version: 2
|
||||||
|
register: key
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[all]
|
|
||||||
ansible_master ansible_host=<ansible host IP address> ansible_port=<SSH port on ansible host> ansible_user=root
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#
|
|
||||||
# More details on how to use DECORT Ansible module can be found at:
|
|
||||||
# https://github.com/rudecs/decort-ansible/wiki
|
|
||||||
#
|
|
||||||
|
|
||||||
- hosts: ansible_master
|
|
||||||
tasks:
|
|
||||||
- name: obtain JWT
|
|
||||||
decort_jwt:
|
|
||||||
oauth2_url: "{{ decort_sso }}" # "https://sso.digitalenergy.online"
|
|
||||||
validity: 1200
|
|
||||||
register: my_jwt
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out JWT
|
|
||||||
debug:
|
|
||||||
var: my_jwt.jwt
|
|
||||||
delegate_to: localhost
|
|
||||||
39
examples/kubernetes.yaml
Normal file
39
examples/kubernetes.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT k8s module example
|
||||||
|
#
|
||||||
|
- hosts: ansible_master
|
||||||
|
tasks:
|
||||||
|
- name: obtain JWT
|
||||||
|
decort_jwt:
|
||||||
|
oauth2_url: "https://sso.digitalenergy.online"
|
||||||
|
validity: 1200
|
||||||
|
verify_ssl: false
|
||||||
|
register: token
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: create a VM named cluster-test
|
||||||
|
decort_k8s:
|
||||||
|
state: present
|
||||||
|
started: True
|
||||||
|
getConfig: True
|
||||||
|
authenticator: jwt
|
||||||
|
jwt: "{{ token.jwt }}"
|
||||||
|
controller_url: "https://ds1.digitalenergy.online"
|
||||||
|
name: "cluster-test"
|
||||||
|
rg_id: 125
|
||||||
|
k8ci_id: 18
|
||||||
|
workers:
|
||||||
|
- name: wg1
|
||||||
|
ram: 1024
|
||||||
|
cpu: 10
|
||||||
|
disk: 10
|
||||||
|
num: 1
|
||||||
|
- name: wg2
|
||||||
|
ram: 1024
|
||||||
|
cpu: 10
|
||||||
|
disk: 10
|
||||||
|
num: 2
|
||||||
|
verify_ssl: false
|
||||||
|
delegate_to: localhost
|
||||||
|
register: kube
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
#
|
|
||||||
# More details on how to use DECORT Ansible module can be found at:
|
|
||||||
# https://github.com/rudecs/decort-ansible/wiki
|
|
||||||
#
|
|
||||||
|
|
||||||
- hosts: ansible_master
|
|
||||||
vars_files:
|
|
||||||
- vars.yaml
|
|
||||||
tasks:
|
|
||||||
- name: obtain JWT
|
|
||||||
decort_jwt:
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
validity: 1200
|
|
||||||
register: token
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: obtain OS image
|
|
||||||
decort_osimage:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
image_name: "{{ os_image_name }}"
|
|
||||||
account_name: "{{ target_account_name }}"
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_image
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_image.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage RG
|
|
||||||
decort_rg:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
account_id: 32
|
|
||||||
rg_name: "{{ target_rg_name }}"
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_rg
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_rg.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage ViNS 01
|
|
||||||
decort_vins:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
vins_name: "{{ vins01_name }}"
|
|
||||||
rg_id: "{{ my_rg.facts.id }}"
|
|
||||||
ext_net_id: "{{ target_ext_net_id }}"
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_vins01
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_vins01.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage ViNS 02
|
|
||||||
decort_vins:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
vins_name: "{{ vins02_name }}"
|
|
||||||
rg_id: "{{ my_rg.facts.id }}"
|
|
||||||
ext_net_id: -1
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_vins02
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_vins02.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage data disk 01
|
|
||||||
decort_disk:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
name: "{{ datadisk01_name }}"
|
|
||||||
size: "{{ datadisk01_size }}"
|
|
||||||
account_name: "{{ target_account_name }}"
|
|
||||||
pool: data01
|
|
||||||
place_with: "{{ my_image.facts.id }}"
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_disk01
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_disk01.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage data disk 02
|
|
||||||
decort_disk:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
name: "{{ datadisk02_name }}"
|
|
||||||
size: "{{ datadisk02_size }}"
|
|
||||||
account_name: "{{ target_account_name }}"
|
|
||||||
pool: data01
|
|
||||||
place_with: "{{ my_image.facts.id }}"
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_disk02
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_disk02.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage KVM X86 VM
|
|
||||||
decort_kvmvm:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
name: "{{ vm01_name }}"
|
|
||||||
arch: KVM_X86
|
|
||||||
ram: "{{ vm01_ram }}"
|
|
||||||
cpu: "{{ vm01_cpu }}"
|
|
||||||
image_id: "{{ my_image.facts.id }}"
|
|
||||||
boot_disk: "{{ vm01_boot_disk }}"
|
|
||||||
data_disks:
|
|
||||||
- "{{ my_disk01.facts.id }}"
|
|
||||||
- "{{ my_disk02.facts.id }}"
|
|
||||||
networks:
|
|
||||||
- type: VINS
|
|
||||||
id: "{{ my_vins01.facts.id }}"
|
|
||||||
- type: VINS
|
|
||||||
id: "{{ my_vins02.facts.id }}"
|
|
||||||
- type: EXTNET
|
|
||||||
id: "{{ target_ext_net_id }}"
|
|
||||||
rg_id: "{{ my_rg.facts.id }}"
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_kvmvm
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_kvmvm.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: manage PFW rules on Compute
|
|
||||||
decort_pfw:
|
|
||||||
authenticator: jwt
|
|
||||||
jwt: "{{ token.jwt }}"
|
|
||||||
oauth2_url: "{{ decort_sso }}"
|
|
||||||
controller_url: "{{ decort_ctrl }}"
|
|
||||||
compute_id: "{{ my_kvmvm.facts.id }}"
|
|
||||||
vins_id: "{{ my_vins01.facts.id }}"
|
|
||||||
rules:
|
|
||||||
- public_port_start: 30022
|
|
||||||
local_port: 22
|
|
||||||
proto: tcp
|
|
||||||
- public_port_start: 30080
|
|
||||||
public_port_end: 30085
|
|
||||||
local_port: 30080
|
|
||||||
proto: tcp
|
|
||||||
state: present
|
|
||||||
verify_ssl: false
|
|
||||||
register: my_pfw
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: print out the result
|
|
||||||
debug:
|
|
||||||
var: my_pfw.facts
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 1. As this file will contain sensitive data (application ID & Secret pair) put this file
|
|
||||||
# in a directory, where only you will have access to it, e.g. your ~/.ssh directory.
|
|
||||||
# 2. Make sure this file is not readable by anybody else (chmod 400).
|
|
||||||
# 3. Paste your Application ID (obtained from DECORT SSO application) to DECORT_APP_ID.
|
|
||||||
# 4. Paste your Application Secret (obtained from DECORT SSO application) to DECORT_APP_SECRET.
|
|
||||||
# 5. Paste DECORT SSO application URL to DECORT_OAUTH2_URL.
|
|
||||||
#
|
|
||||||
# Source this file into shell to prepare environment for running DECORT Ansible module, e.g.
|
|
||||||
# . ~/.ssh/prepenv.sh
|
|
||||||
#
|
|
||||||
# More informaiton on DECORT Ansible module can be found at:
|
|
||||||
# https://github.com/rudecs/decort-ansible/wiki
|
|
||||||
#
|
|
||||||
|
|
||||||
export DECORT_APP_ID="put your application ID here"
|
|
||||||
export DECORT_APP_SECRET="put your application secret here"
|
|
||||||
export DECORT_OAUTH2_URL="put DECORT SSO URL here" # "https://sso.digitalenergy.online"
|
|
||||||
|
|
||||||
export ANSIBLE_HOST_KEY_CHEKCING=False
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#
|
|
||||||
# More details on how to use DECORT Ansible module can be found at:
|
|
||||||
# https://github.com/rudecs/decort-ansible/wiki
|
|
||||||
#
|
|
||||||
|
|
||||||
decort_sso: "put DECORT SSO application URL here" # "https://sso.digitalenergy.online"
|
|
||||||
decort_ctrl: "put DECORT controller URL here" # "https://ds1.digitalenergy.online"
|
|
||||||
|
|
||||||
target_account_name: "your account name"
|
|
||||||
target_rg_name: "target resource group name"
|
|
||||||
os_image_name: "OS image name"
|
|
||||||
|
|
||||||
vins01_name: "Vins01-ansible"
|
|
||||||
vins02_name: "Vins02-ansible"
|
|
||||||
target_ext_net_id: 0
|
|
||||||
|
|
||||||
datadisk01_name: "Data01-ansible"
|
|
||||||
datadisk01_size: 5
|
|
||||||
|
|
||||||
datadisk02_name: "Data02-ansible"
|
|
||||||
datadisk02_size: 5
|
|
||||||
|
|
||||||
vm01_name: "Vm01-ansible"
|
|
||||||
vm01_cpu: 1
|
|
||||||
vm01_ram: 1024
|
|
||||||
vm01_boot_disk: 10
|
|
||||||
42
examples/vins_connect.yaml
Normal file
42
examples/vins_connect.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
#
|
||||||
|
# DECORT vins module example
|
||||||
|
#
|
||||||
|
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: obtain JWT
|
||||||
|
decort_jwt:
|
||||||
|
oauth2_url: "https://sso.digitalenergy.online"
|
||||||
|
validity: 1200
|
||||||
|
register: my_jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: print out JWT
|
||||||
|
debug:
|
||||||
|
var: my_jwt.jwt
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Manage ViNS at resource group level
|
||||||
|
decort_vins:
|
||||||
|
authenticator: jwt
|
||||||
|
jwt: "{{ my_jwt.jwt }}"
|
||||||
|
controller_url: "https://cloud.digitalenergy.online"
|
||||||
|
vins_name: "vins_connected_by_decort_vins_module"
|
||||||
|
state: present
|
||||||
|
rg_id: 98
|
||||||
|
connect_to:
|
||||||
|
- type: VINS
|
||||||
|
id: 864
|
||||||
|
ipaddr: 192.168.5.66
|
||||||
|
netmask: 24
|
||||||
|
- type: VINS
|
||||||
|
id: 196
|
||||||
|
ipaddr: 192.168.9.133
|
||||||
|
netmask: 24
|
||||||
|
register: managed_vins
|
||||||
|
|
||||||
|
- name: print VINS facter
|
||||||
|
debug:
|
||||||
|
msg: "{{managed_vins.facts.password}}"
|
||||||
|
when: managed_vins.facts.password is defined
|
||||||
421
library/decort_account.py
Normal file
421
library/decort_account.py
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_account
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
|
'''
|
||||||
|
|
||||||
|
from typing import Iterable
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
|
|
||||||
|
class DecortAccount(DecortController):
|
||||||
|
OBJ = 'account'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
self.check_amodule_args()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
access_emails=dict(
|
||||||
|
type='bool',
|
||||||
|
),
|
||||||
|
acl=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
mode=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'match',
|
||||||
|
'revoke',
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
default='update',
|
||||||
|
),
|
||||||
|
users=dict(
|
||||||
|
type='list',
|
||||||
|
required=True,
|
||||||
|
elements='dict',
|
||||||
|
options=dict(
|
||||||
|
rights=dict(
|
||||||
|
type='str',
|
||||||
|
choices=['R', 'RCX', 'ARCXDU'],
|
||||||
|
default='R',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
quotas=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
cpu=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
disks_size=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ext_traffic=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
gpu=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
public_ip=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ram=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'absent_permanently',
|
||||||
|
'confirmed',
|
||||||
|
'disabled',
|
||||||
|
'present',
|
||||||
|
],
|
||||||
|
default='present',
|
||||||
|
),
|
||||||
|
sep_pools=dict(
|
||||||
|
type='list',
|
||||||
|
elements='dict',
|
||||||
|
options=dict(
|
||||||
|
sep_id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
pool_names=dict(
|
||||||
|
type='list',
|
||||||
|
required=True,
|
||||||
|
elements='str',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
description=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
default_zone_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
required_one_of=[
|
||||||
|
('id', 'name')
|
||||||
|
],
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args(self):
|
||||||
|
"""
|
||||||
|
Additional Ansible Module arguments validation that
|
||||||
|
cannot be implemented using Ansible Argument spec.
|
||||||
|
"""
|
||||||
|
|
||||||
|
arg_state = self.aparams['state']
|
||||||
|
if 'absent' in arg_state:
|
||||||
|
# Parameters or combinations of parameters that can
|
||||||
|
# cause changing the object.
|
||||||
|
changing_params = [
|
||||||
|
'access_emails',
|
||||||
|
'acl',
|
||||||
|
['id', 'name'],
|
||||||
|
'quotas',
|
||||||
|
]
|
||||||
|
check_error = False
|
||||||
|
for elem in changing_params:
|
||||||
|
if isinstance(elem, str):
|
||||||
|
param = elem
|
||||||
|
if self.aparams[elem] is not None:
|
||||||
|
self.message(
|
||||||
|
f'If the parameter "state" is set to'
|
||||||
|
f' "{arg_state}", then using the parameter'
|
||||||
|
f' "{param}" is not allowed.'
|
||||||
|
)
|
||||||
|
check_error = True
|
||||||
|
elif isinstance(elem, Iterable):
|
||||||
|
params = elem
|
||||||
|
params_using = map(
|
||||||
|
lambda x: self.aparams[x] is not None, params
|
||||||
|
)
|
||||||
|
if all(params_using):
|
||||||
|
params_str = ', '.join(f'"{p}"' for p in params)
|
||||||
|
self.message(
|
||||||
|
f'If the parameter "state" is set to'
|
||||||
|
f' "{arg_state}", then using the combination'
|
||||||
|
f' of parameters {params_str} are not allowed.'
|
||||||
|
)
|
||||||
|
check_error = True
|
||||||
|
if check_error:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_error = False
|
||||||
|
if self.check_aparam_default_zone_id() is False:
|
||||||
|
check_error = True
|
||||||
|
|
||||||
|
if check_error:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_info()
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
self.change()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
# If this is the first getting info
|
||||||
|
if self._acc_info is None:
|
||||||
|
self.acc_id, self._acc_info = self.account_find(
|
||||||
|
account_name=self.aparams['name'],
|
||||||
|
account_id=self.aparams['id'],
|
||||||
|
)
|
||||||
|
# If this is a repeated getting info
|
||||||
|
else:
|
||||||
|
# If check mode is enabled, there is no needed to
|
||||||
|
# request info again
|
||||||
|
if not self.amodule.check_mode:
|
||||||
|
self.acc_id, self._acc_info = self.account_find(
|
||||||
|
account_id=self.acc_id,
|
||||||
|
)
|
||||||
|
self.facts = self.acc_info
|
||||||
|
|
||||||
|
def change(self):
|
||||||
|
self.change_state()
|
||||||
|
|
||||||
|
self.change_acl()
|
||||||
|
|
||||||
|
if self.account_update_args:
|
||||||
|
self.account_update(account_id=self.acc_id,
|
||||||
|
**self.account_update_args)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def change_state(self):
|
||||||
|
match self._acc_info:
|
||||||
|
case None:
|
||||||
|
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
|
||||||
|
match self.aparams:
|
||||||
|
case {'state': 'absent' | 'absent_permanently'}:
|
||||||
|
pass
|
||||||
|
case {'state': 'confirmed' | 'disabled' | 'present'}:
|
||||||
|
self.exit(fail=True)
|
||||||
|
case {'status': 'DESTROYED'}:
|
||||||
|
match self.aparams:
|
||||||
|
case {'state': 'absent' | 'absent_permanently'}:
|
||||||
|
self.message(
|
||||||
|
self.MESSAGES.obj_deleted(
|
||||||
|
obj=self.OBJ,
|
||||||
|
id=self.acc_id,
|
||||||
|
permanently=True,
|
||||||
|
already=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case {'state': 'confirmed' | 'disabled' | 'present'}:
|
||||||
|
self.message(
|
||||||
|
self.MESSAGES.obj_not_restored(obj=self.OBJ,
|
||||||
|
id=self.acc_id)
|
||||||
|
)
|
||||||
|
self.exit(fail=True)
|
||||||
|
case {'status': 'DELETED'}:
|
||||||
|
match self.aparams:
|
||||||
|
case {'state': 'absent'}:
|
||||||
|
self.message(
|
||||||
|
self.MESSAGES.obj_deleted(
|
||||||
|
obj=self.OBJ,
|
||||||
|
id=self.acc_id,
|
||||||
|
permanently=False,
|
||||||
|
already=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case {'state': 'absent_permanently'}:
|
||||||
|
self.delete(permanently=True)
|
||||||
|
case {'state': 'confirmed' | 'present'}:
|
||||||
|
self.restore()
|
||||||
|
case {'state': 'disabled'}:
|
||||||
|
self.restore()
|
||||||
|
self.disable()
|
||||||
|
case {'status': 'CONFIRMED'}:
|
||||||
|
match self.aparams:
|
||||||
|
case {'state': 'absent'}:
|
||||||
|
self.delete()
|
||||||
|
case {'state': 'absent_permanently'}:
|
||||||
|
self.delete(permanently=True)
|
||||||
|
case {'state': 'confirmed' | 'present'}:
|
||||||
|
pass
|
||||||
|
case {'state': 'disabled'}:
|
||||||
|
self.disable()
|
||||||
|
case {'status': 'DISABLED'}:
|
||||||
|
match self.aparams:
|
||||||
|
case {'state': 'absent'}:
|
||||||
|
self.delete()
|
||||||
|
case {'state': 'absent_permanently'}:
|
||||||
|
self.delete(permanently=True)
|
||||||
|
case {'state': 'confirmed'}:
|
||||||
|
self.enable()
|
||||||
|
case {'state': 'present' | 'disabled'}:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, permanently=False):
|
||||||
|
self.account_delete(account_id=self.acc_id, permanently=permanently)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
self.account_disable(account_id=self.acc_id)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
self.account_enable(account_id=self.acc_id)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
self.account_restore(account_id=self.acc_id)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def change_acl(self):
|
||||||
|
if not self.aparams['acl']:
|
||||||
|
return
|
||||||
|
|
||||||
|
actual_users = {
|
||||||
|
u['userGroupId']: u['right'] for u in self.acc_info['acl']
|
||||||
|
}
|
||||||
|
actual_users_ids = set(actual_users.keys())
|
||||||
|
|
||||||
|
aparams_acl = self.aparams['acl']
|
||||||
|
aparams_users = {u['id']: u['rights'] for u in aparams_acl['users']}
|
||||||
|
aparams_users_ids = set(aparams_users.keys())
|
||||||
|
|
||||||
|
del_users_ids = None
|
||||||
|
upd_users = None
|
||||||
|
new_users = None
|
||||||
|
|
||||||
|
match aparams_acl:
|
||||||
|
case {'mode': 'revoke'}:
|
||||||
|
del_users_ids = aparams_users_ids.intersection(
|
||||||
|
actual_users_ids,
|
||||||
|
)
|
||||||
|
case {'mode': 'update' | 'match' as mode}:
|
||||||
|
new_users_ids = aparams_users_ids.difference(
|
||||||
|
actual_users_ids,
|
||||||
|
)
|
||||||
|
new_users = dict(
|
||||||
|
u for u in aparams_users.items() if u[0] in new_users_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
upd_users_ids =\
|
||||||
|
aparams_users_ids.intersection(actual_users_ids)
|
||||||
|
upd_users = dict()
|
||||||
|
for id in upd_users_ids:
|
||||||
|
if actual_users[id] == 'CXDRAU':
|
||||||
|
actual_user_rights = 'ARCXDU'
|
||||||
|
else:
|
||||||
|
actual_user_rights = actual_users[id]
|
||||||
|
|
||||||
|
if actual_user_rights != aparams_users[id]:
|
||||||
|
upd_users[id] = aparams_users[id]
|
||||||
|
|
||||||
|
if mode == 'match':
|
||||||
|
del_users_ids =\
|
||||||
|
actual_users_ids.difference(aparams_users_ids)
|
||||||
|
|
||||||
|
if del_users_ids or new_users or upd_users:
|
||||||
|
self.account_change_acl(account_id=self.acc_id,
|
||||||
|
del_users=del_users_ids,
|
||||||
|
add_users=new_users,
|
||||||
|
upd_users=upd_users)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def account_update_args(self) -> dict:
|
||||||
|
result_args = dict()
|
||||||
|
|
||||||
|
aparam_access_emails = self.aparams['access_emails']
|
||||||
|
if (aparam_access_emails is not None
|
||||||
|
and self.acc_info['sendAccessEmails'] != aparam_access_emails):
|
||||||
|
result_args['access_emails'] = aparam_access_emails
|
||||||
|
|
||||||
|
aparam_name = self.aparams['name']
|
||||||
|
if (self.aparams['id'] and aparam_name
|
||||||
|
and self.acc_info['name'] != aparam_name):
|
||||||
|
result_args['name'] = aparam_name
|
||||||
|
|
||||||
|
aparam_quotas = self.aparams['quotas']
|
||||||
|
if aparam_quotas:
|
||||||
|
quotas_naming = [
|
||||||
|
['cpu', 'CU_C', 'cpu_quota'],
|
||||||
|
['disks_size', 'CU_DM', 'disks_size_quota'],
|
||||||
|
['ext_traffic', 'CU_NP', 'ext_traffic_quota'],
|
||||||
|
['gpu', 'gpu_units', 'gpu_quota'],
|
||||||
|
['public_ip', 'CU_I', 'public_ip_quota'],
|
||||||
|
['ram', 'CU_M', 'ram_quota'],
|
||||||
|
]
|
||||||
|
for aparam, info_key, result_arg in quotas_naming:
|
||||||
|
current_value = int(self.acc_info['resourceLimits'][info_key])
|
||||||
|
if (aparam_quotas[aparam] is not None
|
||||||
|
and current_value != aparam_quotas[aparam]):
|
||||||
|
result_args[result_arg] = aparam_quotas[aparam]
|
||||||
|
|
||||||
|
aparam_sep_pools = self.aparams['sep_pools']
|
||||||
|
if aparam_sep_pools is not None:
|
||||||
|
sep_pools = set()
|
||||||
|
for sep in aparam_sep_pools:
|
||||||
|
for pool_name in sep['pool_names']:
|
||||||
|
sep_pools.add(
|
||||||
|
f'{sep["sep_id"]}_{pool_name}'
|
||||||
|
)
|
||||||
|
if set(self.acc_info['uniqPools']) != sep_pools:
|
||||||
|
result_args['sep_pools'] = sep_pools
|
||||||
|
|
||||||
|
aparam_desc = self.aparams['description']
|
||||||
|
if (
|
||||||
|
aparam_desc is not None
|
||||||
|
and self.acc_info['description'] != aparam_desc
|
||||||
|
):
|
||||||
|
result_args['description'] = aparam_desc
|
||||||
|
|
||||||
|
aparam_default_zone_id = self.aparams['default_zone_id']
|
||||||
|
if (
|
||||||
|
aparam_default_zone_id is not None
|
||||||
|
and self.acc_info['defaultZoneId'] != aparam_default_zone_id
|
||||||
|
):
|
||||||
|
result_args['default_zone_id'] = aparam_default_zone_id
|
||||||
|
|
||||||
|
return result_args
|
||||||
|
|
||||||
|
def check_aparam_default_zone_id(self) -> bool | None:
|
||||||
|
aparam_default_zone_id = self.aparams['default_zone_id']
|
||||||
|
if aparam_default_zone_id is not None:
|
||||||
|
if aparam_default_zone_id in self.acc_zone_ids:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "default_zone_id" failed: '
|
||||||
|
f'zone ID {aparam_default_zone_id} not available '
|
||||||
|
f'for account ID {self.acc_id}.'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DecortAccount().run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
573
library/decort_account_info.py
Normal file
573
library/decort_account_info.py
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_account_info
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
|
|
||||||
|
class DecortAccountInfo(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
audits=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False
|
||||||
|
),
|
||||||
|
computes=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
ext_net_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ext_net_name=dict(
|
||||||
|
type='str'
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ip=dict(
|
||||||
|
type='str'
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str'
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
rg_name=dict(
|
||||||
|
type='str'
|
||||||
|
),
|
||||||
|
tech_status=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.COMPUTE_TECH_STATUSES,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.FIELDS_FOR_SORTING_ACCOUNT_COMPUTE_LIST, # noqa: E501
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
disks=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
type=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.DISK_TYPES,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.FIELDS_FOR_SORTING_ACCOUNT_DISK_LIST, # noqa: E501
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
flip_groups=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
ext_net_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ip=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
vins_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
vins_name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
images=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
type=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.IMAGE_TYPES,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.FIELDS_FOR_SORTING_ACCOUNT_IMAGE_LIST, # noqa: E501
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
resource_groups=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str'
|
||||||
|
),
|
||||||
|
status=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.RESOURCE_GROUP_STATUSES,
|
||||||
|
),
|
||||||
|
vins_id=dict(
|
||||||
|
type='int'
|
||||||
|
),
|
||||||
|
vm_id=dict(
|
||||||
|
type='int'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.FIELDS_FOR_SORTING_ACCOUNT_RG_LIST, # noqa: E501
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
resource_consumption=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False
|
||||||
|
),
|
||||||
|
vinses=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
ext_ip=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str'
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.FIELDS_FOR_SORTING_ACCOUNT_VINS_LIST, # noqa: E501
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mutually_exclusive=[
|
||||||
|
('id', 'name')
|
||||||
|
],
|
||||||
|
required_one_of=[
|
||||||
|
('id', 'name')
|
||||||
|
],
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_computes_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `computes` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.account_computes`
|
||||||
|
(excluding for `account_id`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['computes']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
if input_args['filter']:
|
||||||
|
mapped_args['compute_id'] = input_args['filter']['id']
|
||||||
|
mapped_args['compute_ip'] = input_args['filter']['ip']
|
||||||
|
mapped_args['compute_name'] = input_args['filter']['name']
|
||||||
|
mapped_args['compute_tech_status'] =\
|
||||||
|
input_args['filter']['tech_status']
|
||||||
|
mapped_args['ext_net_id'] = input_args['filter']['ext_net_id']
|
||||||
|
mapped_args['ext_net_name'] =\
|
||||||
|
input_args['filter']['ext_net_name']
|
||||||
|
mapped_args['rg_id'] = input_args['filter']['rg_id']
|
||||||
|
mapped_args['rg_name'] = input_args['filter']['rg_name']
|
||||||
|
if input_args['pagination']:
|
||||||
|
mapped_args['page_number'] =\
|
||||||
|
input_args['pagination']['number']
|
||||||
|
mapped_args['page_size'] =\
|
||||||
|
input_args['pagination']['size']
|
||||||
|
if input_args['sorting']:
|
||||||
|
mapped_args['sort_by_asc'] =\
|
||||||
|
input_args['sorting']['asc']
|
||||||
|
mapped_args['sort_by_field'] =\
|
||||||
|
input_args['sorting']['field']
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_disks_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `disks` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.account_disks`
|
||||||
|
(excluding for `account_id`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['disks']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
if input_args['filter']:
|
||||||
|
mapped_args['disk_id'] = input_args['filter']['id']
|
||||||
|
mapped_args['disk_name'] = input_args['filter']['name']
|
||||||
|
mapped_args['disk_size'] = input_args['filter']['size']
|
||||||
|
mapped_args['disk_type'] = input_args['filter']['type']
|
||||||
|
if input_args['pagination']:
|
||||||
|
mapped_args['page_number'] =\
|
||||||
|
input_args['pagination']['number']
|
||||||
|
mapped_args['page_size'] =\
|
||||||
|
input_args['pagination']['size']
|
||||||
|
if input_args['sorting']:
|
||||||
|
mapped_args['sort_by_asc'] =\
|
||||||
|
input_args['sorting']['asc']
|
||||||
|
mapped_args['sort_by_field'] =\
|
||||||
|
input_args['sorting']['field']
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_flip_groups_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `flip_groups` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.account_flip_groups`
|
||||||
|
(excluding for `account_id`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['flip_groups']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
if input_args['filter']:
|
||||||
|
mapped_args['ext_net_id'] = input_args['filter']['ext_net_id']
|
||||||
|
mapped_args['flig_group_id'] = input_args['filter']['id']
|
||||||
|
mapped_args['flig_group_ip'] = input_args['filter']['ip']
|
||||||
|
mapped_args['flig_group_name'] = input_args['filter']['name']
|
||||||
|
mapped_args['vins_id'] = input_args['filter']['vins_id']
|
||||||
|
mapped_args['vins_name'] = input_args['filter']['vins_name']
|
||||||
|
if input_args['pagination']:
|
||||||
|
mapped_args['page_number'] =\
|
||||||
|
input_args['pagination']['number']
|
||||||
|
mapped_args['page_size'] =\
|
||||||
|
input_args['pagination']['size']
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_images_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `images` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.account_images`
|
||||||
|
(excluding for `account_id`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['images']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
if input_args['filter']:
|
||||||
|
mapped_args['image_id'] = input_args['filter']['id']
|
||||||
|
mapped_args['image_name'] = input_args['filter']['name']
|
||||||
|
mapped_args['image_type'] = input_args['filter']['type']
|
||||||
|
if input_args['pagination']:
|
||||||
|
mapped_args['page_number'] =\
|
||||||
|
input_args['pagination']['number']
|
||||||
|
mapped_args['page_size'] =\
|
||||||
|
input_args['pagination']['size']
|
||||||
|
if input_args['sorting']:
|
||||||
|
mapped_args['sort_by_asc'] =\
|
||||||
|
input_args['sorting']['asc']
|
||||||
|
mapped_args['sort_by_field'] =\
|
||||||
|
input_args['sorting']['field']
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_rg_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `resource_groups` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.account_resource_groups`
|
||||||
|
(excluding for `account_id`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['resource_groups']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
if input_args['filter']:
|
||||||
|
mapped_args['rg_id'] =\
|
||||||
|
input_args['filter']['id']
|
||||||
|
mapped_args['rg_name'] =\
|
||||||
|
input_args['filter']['name']
|
||||||
|
mapped_args['rg_status'] =\
|
||||||
|
input_args['filter']['status']
|
||||||
|
mapped_args['vins_id'] =\
|
||||||
|
input_args['filter']['vins_id']
|
||||||
|
mapped_args['vm_id'] =\
|
||||||
|
input_args['filter']['vm_id']
|
||||||
|
if input_args['pagination']:
|
||||||
|
mapped_args['page_number'] =\
|
||||||
|
input_args['pagination']['number']
|
||||||
|
mapped_args['page_size'] =\
|
||||||
|
input_args['pagination']['size']
|
||||||
|
if input_args['sorting']:
|
||||||
|
mapped_args['sort_by_asc'] =\
|
||||||
|
input_args['sorting']['asc']
|
||||||
|
mapped_args['sort_by_field'] =\
|
||||||
|
input_args['sorting']['field']
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_vinses_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `vinses` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.account_vinses`
|
||||||
|
(excluding for `account_id`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['vinses']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
if input_args['filter']:
|
||||||
|
mapped_args['vins_id'] = input_args['filter']['id']
|
||||||
|
mapped_args['vins_name'] = input_args['filter']['name']
|
||||||
|
mapped_args['ext_ip'] = input_args['filter']['ext_ip']
|
||||||
|
mapped_args['rg_id'] = input_args['filter']['rg_id']
|
||||||
|
if input_args['pagination']:
|
||||||
|
mapped_args['page_number'] =\
|
||||||
|
input_args['pagination']['number']
|
||||||
|
mapped_args['page_size'] =\
|
||||||
|
input_args['pagination']['size']
|
||||||
|
if input_args['sorting']:
|
||||||
|
mapped_args['sort_by_asc'] =\
|
||||||
|
input_args['sorting']['asc']
|
||||||
|
mapped_args['sort_by_field'] =\
|
||||||
|
input_args['sorting']['field']
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_info()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
self.id, self.facts = self.account_find(
|
||||||
|
account_name=self.aparams['name'],
|
||||||
|
account_id=self.aparams['id'],
|
||||||
|
audits=self.aparams['audits'],
|
||||||
|
computes_args=self.mapped_computes_args,
|
||||||
|
disks_args=self.mapped_disks_args,
|
||||||
|
flip_groups_args=self.mapped_flip_groups_args,
|
||||||
|
images_args=self.mapped_images_args,
|
||||||
|
resource_consumption=self.aparams['resource_consumption'],
|
||||||
|
resource_groups_args=self.mapped_rg_args,
|
||||||
|
vinses_args=self.mapped_vinses_args,
|
||||||
|
fail_if_not_found=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DecortAccountInfo().run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
330
library/decort_bservice.py
Normal file
330
library/decort_bservice.py
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_bservice
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class decort_bservice(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super(decort_bservice, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
arg_amodule = self.amodule
|
||||||
|
|
||||||
|
validated_acc_id = 0
|
||||||
|
validated_rg_id = 0
|
||||||
|
self.bservice_info = None
|
||||||
|
self.is_bservice_stopped_or_will_be_stopped: None | bool = None
|
||||||
|
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = "Cannot manage Basic Services when its ID is 0 and name is empty."
|
||||||
|
self.fail_json(**self.result)
|
||||||
|
if not arg_amodule.params['id']:
|
||||||
|
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
|
||||||
|
validated_acc_id, self.acc_info = self.account_find(arg_amodule.params['account_name'],
|
||||||
|
arg_amodule.params['account_id'])
|
||||||
|
if not validated_acc_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Current user does not have access to the account ID {} / "
|
||||||
|
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
|
||||||
|
arg_amodule.params['account_name'])
|
||||||
|
self.fail_json(**self.result)
|
||||||
|
# fail the module -> exit
|
||||||
|
# now validate RG
|
||||||
|
validated_rg_id, validated_rg_facts = self.rg_find(
|
||||||
|
arg_account_id=validated_acc_id,
|
||||||
|
arg_rg_id=arg_amodule.params['rg_id'],
|
||||||
|
arg_rg_name=arg_amodule.params['rg_name']
|
||||||
|
)
|
||||||
|
if not validated_rg_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
|
||||||
|
arg_amodule.params['rg_name'])
|
||||||
|
self.fail_json(**self.result)
|
||||||
|
|
||||||
|
arg_amodule.params['rg_id'] = validated_rg_id
|
||||||
|
arg_amodule.params['rg_name'] = validated_rg_facts['name']
|
||||||
|
validated_acc_id = validated_rg_facts['accountId']
|
||||||
|
|
||||||
|
self.bservice_id, self.bservice_info = self.bservice_find(
|
||||||
|
validated_acc_id,
|
||||||
|
validated_rg_id,
|
||||||
|
arg_amodule.params['name'],
|
||||||
|
arg_amodule.params['id']
|
||||||
|
)
|
||||||
|
|
||||||
|
self.acc_id = validated_acc_id or self.bservice_info['accountId']
|
||||||
|
|
||||||
|
if self.bservice_id and self.bservice_info['status'] != 'DESTROYED':
|
||||||
|
self.bservice_should_exist = True
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
else:
|
||||||
|
self.bservice_should_exist = False
|
||||||
|
self.check_amodule_args_for_create()
|
||||||
|
|
||||||
|
def nop(self):
|
||||||
|
"""No operation (NOP) handler for B-service.
|
||||||
|
This function is intended to be called from the main switch construct of the module
|
||||||
|
when current state -> desired state change logic does not require any changes to
|
||||||
|
the actual Compute state.
|
||||||
|
"""
|
||||||
|
self.result['failed'] = False
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.bservice_id:
|
||||||
|
self.result['msg'] = ("No state change required for B-service ID {} because of its "
|
||||||
|
"current status '{}'.").format(self.bservice_id, self.bservice_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("No state change to '{}' can be done for "
|
||||||
|
"non-existent B-service instance.").format(self.amodule.params['state'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.bservice_id:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for B-service ID {} in the "
|
||||||
|
"current status '{}'.").format(self.bservice_id,
|
||||||
|
self.amodule.params['state'],
|
||||||
|
self.bservice_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for non-existent B-service name '{}' "
|
||||||
|
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
|
||||||
|
self.amodule.params['name'],
|
||||||
|
self.amodule.params['rg_id'],
|
||||||
|
self.amodule.params['rg_name'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.bservice_id = self.bservice_id = self.bservice_provision(
|
||||||
|
self.amodule.params['name'],
|
||||||
|
self.amodule.params['rg_id'],
|
||||||
|
self.amodule.params['sshuser'],
|
||||||
|
self.amodule.params['sshkey'],
|
||||||
|
zone_id=self.aparams['zone_id'],
|
||||||
|
)
|
||||||
|
if self.bservice_id:
|
||||||
|
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
|
||||||
|
self.bservice_state(self.bservice_info,'enabled')
|
||||||
|
return
|
||||||
|
|
||||||
|
def action(self,d_state):
|
||||||
|
self.bservice_state(self.bservice_info,d_state)
|
||||||
|
|
||||||
|
aparam_zone_id = self.aparams['zone_id']
|
||||||
|
if aparam_zone_id is not None and aparam_zone_id != self.bservice_info['zoneId']:
|
||||||
|
self.bservice_migrate_to_zone(
|
||||||
|
bs_id=self.bservice_id,
|
||||||
|
zone_id=aparam_zone_id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = "Restore B-Service ID {} manualy.".format(self.bservice_id)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self.bservice_delete(self.bservice_id)
|
||||||
|
self.bservice_info['status'] = 'DELETED'
|
||||||
|
self.bservice_should_exist = False
|
||||||
|
return
|
||||||
|
|
||||||
|
def package_facts(self,check_mode=False):
|
||||||
|
|
||||||
|
ret_dict = dict(
|
||||||
|
name="",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
account_id=0,
|
||||||
|
rg_id=0,
|
||||||
|
config=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
ret_dict['id'] = self.bservice_info['id']
|
||||||
|
ret_dict['name'] = self.bservice_info['name']
|
||||||
|
ret_dict['techStatus'] = self.bservice_info['techStatus']
|
||||||
|
ret_dict['state'] = self.bservice_info['status']
|
||||||
|
ret_dict['rg_id'] = self.bservice_info['rgId']
|
||||||
|
ret_dict['account_id'] = self.bservice_info['accountId']
|
||||||
|
ret_dict['groups'] = self.bservice_info['groups']
|
||||||
|
ret_dict['zone_id'] = self.bservice_info['zoneId']
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'disabled',
|
||||||
|
'enabled',
|
||||||
|
'present',
|
||||||
|
'started',
|
||||||
|
'stopped',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
sshuser=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
sshkey=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
rg_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
zone_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=[
|
||||||
|
('id', 'name'),
|
||||||
|
('rg_id', 'rg_name'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_errors = False
|
||||||
|
|
||||||
|
self.is_bservice_stopped_or_will_be_stopped = (
|
||||||
|
(
|
||||||
|
self.bservice_info['techStatus'] == 'STOPPED'
|
||||||
|
and (
|
||||||
|
self.aparams['state'] is None
|
||||||
|
or self.aparams['state'] in ('present', 'stopped')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
self.bservice_info['techStatus'] != 'STOPPED'
|
||||||
|
and self.aparams['state'] == 'stopped'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
if (
|
||||||
|
self.aparams['zone_id'] is not None
|
||||||
|
and self.aparams['zone_id'] != self.bservice_info['zoneId']
|
||||||
|
and not self.is_bservice_stopped_or_will_be_stopped
|
||||||
|
):
|
||||||
|
check_errors = True
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "zone_id" failed: '
|
||||||
|
'Basic Service must be stopped to migrate to a zone.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def check_amodule_args_for_create(self):
|
||||||
|
check_errors = False
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
subj = decort_bservice()
|
||||||
|
amodule = subj.amodule
|
||||||
|
|
||||||
|
if subj.amodule.check_mode:
|
||||||
|
subj.result['changed'] = False
|
||||||
|
if subj.bservice_id:
|
||||||
|
subj.result['failed'] = False
|
||||||
|
subj.result['facts'] = subj.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
# we exit the module at this point
|
||||||
|
else:
|
||||||
|
subj.result['failed'] = True
|
||||||
|
subj.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, "
|
||||||
|
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
|
||||||
|
amodule.params['id'],
|
||||||
|
amodule.params['rg_name'],
|
||||||
|
amodule.params['rg_id'],
|
||||||
|
amodule.params['account_name'])
|
||||||
|
amodule.fail_json(**subj.result)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
#MAIN MANAGE PART
|
||||||
|
|
||||||
|
if subj.bservice_id:
|
||||||
|
if subj.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
|
||||||
|
"ENABLING","DISABLING","RESTORING","MODELED"):
|
||||||
|
subj.error()
|
||||||
|
elif subj.bservice_info['status'] == "DELETED":
|
||||||
|
if amodule.params['state'] in (
|
||||||
|
'disabled', 'enabled', 'present', 'started', 'stopped'
|
||||||
|
):
|
||||||
|
subj.restore(subj.bservice_id)
|
||||||
|
subj.action(amodule.params['state'])
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
elif subj.bservice_info['status'] in ('ENABLED', 'DISABLED'):
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.destroy()
|
||||||
|
else:
|
||||||
|
subj.action(amodule.params['state'])
|
||||||
|
elif subj.bservice_info['status'] == "DESTROED":
|
||||||
|
if amodule.params['state'] in ('present','enabled'):
|
||||||
|
subj.create()
|
||||||
|
subj.action(amodule.params['state'])
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
else:
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
if amodule.params['state'] in ('present','started'):
|
||||||
|
subj.create()
|
||||||
|
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
|
||||||
|
subj.error()
|
||||||
|
|
||||||
|
if subj.result['failed']:
|
||||||
|
amodule.fail_json(**subj.result)
|
||||||
|
else:
|
||||||
|
if subj.bservice_should_exist:
|
||||||
|
_, subj.bservice_info = subj.bservice_get_by_id(subj.bservice_id)
|
||||||
|
subj.result['facts'] = subj.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
else:
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,237 +1,10 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
|
||||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
|
||||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
|
||||||
#
|
|
||||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
DOCUMENTATION = r'''
|
||||||
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
---
|
||||||
module: decort_disk
|
module: decort_disk
|
||||||
short_description: Manage Disks (virtualized storage resources) in DECORT cloud
|
|
||||||
description: >
|
|
||||||
This module can be used to create new disk in DECORT cloud platform, obtain or
|
|
||||||
modify its characteristics, and delete it.
|
|
||||||
version_added: "2.2"
|
|
||||||
author:
|
|
||||||
- Sergey Shubin <sergey.shubin@digitalenergy.online>
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- PyJWT Python module
|
|
||||||
- requests Python module
|
|
||||||
- netaddr Python module
|
|
||||||
- decort_utils utility library (module)
|
|
||||||
- DECORT cloud platform version 3.6.1 or higher
|
|
||||||
notes:
|
|
||||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
|
||||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
|
||||||
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
|
|
||||||
the DECORT cloud controller on which this JWT will be used.'
|
|
||||||
options:
|
|
||||||
account_id:
|
|
||||||
description:
|
|
||||||
- ID of the account, which owns this disk. This is the alternative to I(account_name) option.
|
|
||||||
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
|
|
||||||
default: 0
|
|
||||||
required: no
|
|
||||||
account_name:
|
|
||||||
description:
|
|
||||||
- 'Name of the account, which will own this disk.'
|
|
||||||
- 'This parameter is ignored if I(account_id) is specified.'
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
annotation:
|
|
||||||
description:
|
|
||||||
- Optional text description of this disk.
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
app_id:
|
|
||||||
description:
|
|
||||||
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- 'Required if I(authenticator=oauth2).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
app_secret:
|
|
||||||
description:
|
|
||||||
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
authenticator:
|
|
||||||
description:
|
|
||||||
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
|
|
||||||
default: jwt
|
|
||||||
choices: [ jwt, oauth2, legacy ]
|
|
||||||
required: yes
|
|
||||||
controller_url:
|
|
||||||
description:
|
|
||||||
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
|
|
||||||
- 'This parameter is always required regardless of the specified I(authenticator) type.'
|
|
||||||
required: yes
|
|
||||||
id:
|
|
||||||
description:
|
|
||||||
- `ID of the disk to manage. If I(id) is specified it is assumed, that this disk already
|
|
||||||
exists. In other words, you cannot create new disk by specifying its ID, use I(name)
|
|
||||||
when creating new disk.`
|
|
||||||
- `If non-zero I(id) is specified, then I(name), I(account_id) and I(account_name)
|
|
||||||
are ignored.`
|
|
||||||
default: 0
|
|
||||||
required: no
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- `Name of the disk to manage. To manage disk by name you also need to specify either
|
|
||||||
I(account_id) or I(account_name).`
|
|
||||||
- If non-zero I(id) is specified, I(name) is ignored.
|
|
||||||
- `Note that the platform does not enforce uniqueness of disk names, so if more than one
|
|
||||||
disk with this name exists under the specified account, module will return the first
|
|
||||||
occurence.`
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
force_detach:
|
|
||||||
description:
|
|
||||||
- `By default it is not allowed to delete or destroy disk that is currently attached to a compute
|
|
||||||
instance (e.g. virtual machine or bare metal server). Set this argument to true to change this
|
|
||||||
behavior.`
|
|
||||||
- This argument is meaningful for I(state=absent) operations only and ignored otherwise.
|
|
||||||
default: false
|
|
||||||
required: no
|
|
||||||
jwt:
|
|
||||||
description:
|
|
||||||
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
|
|
||||||
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
|
|
||||||
required: no
|
|
||||||
oauth2_url:
|
|
||||||
description:
|
|
||||||
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
|
|
||||||
- 'This parameter is required when when I(authenticator=oauth2).'
|
|
||||||
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
|
|
||||||
required: no
|
|
||||||
place_with:
|
|
||||||
description:
|
|
||||||
- `This argument can be used to simplify data disks creation along with a new compute, by placing
|
|
||||||
disks in the same storage, where corresponding OS image is deployed.`
|
|
||||||
- `Specify ID of an OS image, and the newly created disk will be provisioned from the same
|
|
||||||
storage, where this OS image is located. You may optionally specify I(pool) to control
|
|
||||||
actual disk placement within that storage, or leave I(pool=default) to let platform manage
|
|
||||||
it automatically.`
|
|
||||||
- This parameter is used when creating new disks and ignored for all other operations.
|
|
||||||
- This is an alternative to specifying I(sep_id).
|
|
||||||
default: 0
|
|
||||||
required: no
|
|
||||||
pool:
|
|
||||||
description:
|
|
||||||
- Name of the pool where to place new disk. Once disk is created, its pool cannot be changed.
|
|
||||||
- This parameter is used when creating new disk and igonred for all other operations.
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
sep_id:
|
|
||||||
description:
|
|
||||||
- `ID of the Storage Endpoint Provider (SEP) where to place new disk. Once disk is created,
|
|
||||||
its SEP cannot be changed.`
|
|
||||||
- `You may think of SEP as an identifier of a storage system connected to DECORT platform. There
|
|
||||||
may be several different storage systems and, consequently, several SEPs available to choose from.`
|
|
||||||
- This parameter is used when creating new disk and igonred for all other operations.
|
|
||||||
- See also I(place_with) for an alternative way to specify disk placement.
|
|
||||||
default: 0
|
|
||||||
required: no
|
|
||||||
size:
|
|
||||||
description:
|
|
||||||
- Size of the disk in GB. This parameter is mandatory when creating new disk.
|
|
||||||
- `If specified for an existing disk, and it is greater than current disk size, platform will try to resize
|
|
||||||
the disk on the fly. Downsizing disk is not allowed.`
|
|
||||||
required: no
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Specify the desired state of the disk at the exit of the module.
|
|
||||||
- 'If desired I(state=present):'
|
|
||||||
- ' - Disk does not exist or is in [DESTROYED, PURGED] states, create new disk according to the specifications.'
|
|
||||||
- ' - Disk is in DELETED state, restore it and change size if necessary.'
|
|
||||||
- ' - Disk is in one of [CREATED, ASSIGNED] states, do nothing.'
|
|
||||||
- ' - Disk in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=absent):'
|
|
||||||
- ' - Disk is in one of [CREATED, ASSIGNED, DELETED] states, destroy it.'
|
|
||||||
- ' - Disk not found or in [DESTROYED, PURGED] states, do nothing.'
|
|
||||||
- ' - Disk in any other state, abort with an error.'
|
|
||||||
default: present
|
|
||||||
choices: [ absent, present ]
|
|
||||||
user:
|
|
||||||
description:
|
|
||||||
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
|
|
||||||
required: no
|
|
||||||
verify_ssl:
|
|
||||||
description:
|
|
||||||
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
|
|
||||||
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
|
|
||||||
environment that uses self-signed certificates. Note that disabling SSL verification in any other
|
|
||||||
scenario can lead to security issues, so please know what you are doing.'
|
|
||||||
default: True
|
|
||||||
required: no
|
|
||||||
workflow_callback:
|
|
||||||
description:
|
|
||||||
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
|
|
||||||
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
|
|
||||||
- API call at this URL will be used to relay such information to the application.
|
|
||||||
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
|
|
||||||
required: no
|
|
||||||
workflow_context:
|
|
||||||
description:
|
|
||||||
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
|
|
||||||
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
|
|
||||||
that up-level orchestrator could match returned information to the its internal entities.'
|
|
||||||
required: no
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
- name: create new Disk named "MyDataDisk01" of size 50 GB, on SEP ID 1, in default pool, under the account "MyAccount".
|
|
||||||
decort_vins:
|
|
||||||
authenticator: oauth2
|
|
||||||
app_id: "{{ MY_APP_ID }}"
|
|
||||||
app_secret: "{{ MY_APP_SECRET }}"
|
|
||||||
controller_url: "https://cloud.digitalenergy.online"
|
|
||||||
name: "MyDataDisk01"
|
|
||||||
sep_id: 1
|
|
||||||
size: 50
|
|
||||||
account_name: "MyAccount"
|
|
||||||
state: present
|
|
||||||
delegate_to: localhost
|
|
||||||
register: my_disk
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
facts:
|
|
||||||
description: facts about the disk
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
facts:
|
|
||||||
id: 50
|
|
||||||
name: data01
|
|
||||||
size: 10
|
|
||||||
sep_id: 1
|
|
||||||
pool: datastore
|
|
||||||
state: ASSIGNED
|
|
||||||
account_id: 7
|
|
||||||
attached_to: 18
|
|
||||||
gid: 1001
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
@@ -240,302 +13,338 @@ from ansible.module_utils.basic import env_fallback
|
|||||||
from ansible.module_utils.decort_utils import *
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
def decort_disk_package_facts(disk_facts, check_mode=False):
|
class decort_disk(DecortController):
|
||||||
"""Package a dictionary of disk facts according to the decort_disk module specification.
|
def __init__(self):
|
||||||
This dictionary will be returned to the upstream Ansible engine at the completion of
|
super(decort_disk, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
the module run.
|
arg_amodule = self.amodule
|
||||||
|
|
||||||
@param (dict) disk_facts: dictionary with Disk facts as returned by API call to .../disks/get
|
validated_acc_id = 0
|
||||||
@param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
|
validated_acc_info = None
|
||||||
"""
|
self.disk_id = 0
|
||||||
|
self.account_id = 0
|
||||||
|
# limitIO check for exclusive parameters
|
||||||
|
|
||||||
|
if arg_amodule.params['limitIO']:
|
||||||
|
self.disk_check_iotune_arg(arg_amodule.params['limitIO'])
|
||||||
|
|
||||||
ret_dict = dict(id=0,
|
if not arg_amodule.params['id']:
|
||||||
name="none",
|
if (
|
||||||
state="CHECK_MODE",
|
not arg_amodule.params['account_id']
|
||||||
size=0,
|
and not arg_amodule.params['account_name']
|
||||||
account_id=0,
|
):
|
||||||
sep_id=0,
|
self.result['changed'] = False
|
||||||
pool="none",
|
self.result['msg'] = (
|
||||||
attached_to=0,
|
'Cannot manage Disk by name without specifying account ID '
|
||||||
gid=0
|
'or name.'
|
||||||
)
|
)
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
validated_acc_id, validated_acc_info = self.account_find(
|
||||||
|
arg_amodule.params['account_name'],
|
||||||
|
arg_amodule.params['account_id'])
|
||||||
|
if not validated_acc_id:
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = (
|
||||||
|
f"Current user does not have access to the account "
|
||||||
|
f"ID {arg_amodule.params['account_id']} / "
|
||||||
|
f"name '{arg_amodule.params['account_name']}' "
|
||||||
|
f"or non-existent account specified."
|
||||||
|
)
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
|
||||||
|
self.acc_id = validated_acc_id
|
||||||
|
self._acc_info = validated_acc_info
|
||||||
|
validated_disk_id, validated_disk_facts = self.disk_find(
|
||||||
|
disk_id=arg_amodule.params['id'],
|
||||||
|
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
|
||||||
|
account_id=self.acc_id,
|
||||||
|
check_state=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if arg_amodule.params['place_with']:
|
||||||
|
image_id, image_facts = self.image_find(arg_amodule.params['place_with'], "", 0)
|
||||||
|
arg_amodule.params['sep_id'] = image_facts['sepId']
|
||||||
|
|
||||||
|
self.disk_id = validated_disk_id
|
||||||
|
self.disk_info = validated_disk_facts
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
|
||||||
|
self.disk_id = self.disk_create(accountId=self.acc_id,
|
||||||
|
name = self.amodule.params['name'],
|
||||||
|
description=self.amodule.params['description'],
|
||||||
|
size=self.amodule.params['size'],
|
||||||
|
iops=self.amodule.params['iops'],
|
||||||
|
sep_id=self.amodule.params['sep_id'],
|
||||||
|
pool=self.amodule.params['pool'],
|
||||||
|
)
|
||||||
|
#IO tune
|
||||||
|
if self.amodule.params['limitIO']:
|
||||||
|
self.disk_limitIO(disk_id=self.disk_id,
|
||||||
|
limits=self.amodule.params['limitIO'])
|
||||||
|
#set share status
|
||||||
|
if self.amodule.params['shareable']:
|
||||||
|
self.disk_share(self.disk_id,self.amodule.params['shareable'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def action(self,restore=False):
|
||||||
|
|
||||||
|
#restore never be done
|
||||||
|
if restore:
|
||||||
|
self.disk_restore(self.disk_id)
|
||||||
|
#rename if id present
|
||||||
|
if (
|
||||||
|
self.amodule.params['name'] is not None
|
||||||
|
and self.amodule.params['name'] != self.disk_info['name']
|
||||||
|
):
|
||||||
|
self.disk_rename(disk_id=self.disk_id,
|
||||||
|
name=self.amodule.params['name'])
|
||||||
|
#resize
|
||||||
|
if (
|
||||||
|
self.amodule.params['size'] is not None
|
||||||
|
and self.amodule.params['size'] != self.disk_info['sizeMax']
|
||||||
|
):
|
||||||
|
self.disk_resize(self.disk_info,self.amodule.params['size'])
|
||||||
|
#IO TUNE
|
||||||
|
if self.amodule.params['limitIO']:
|
||||||
|
clean_io = [param for param in self.amodule.params['limitIO'] \
|
||||||
|
if self.amodule.params['limitIO'][param] == None]
|
||||||
|
for key in clean_io: del self.amodule.params['limitIO'][key]
|
||||||
|
if self.amodule.params['limitIO'] != self.disk_info['iotune']:
|
||||||
|
self.disk_limitIO(self.disk_id,self.amodule.params['limitIO'])
|
||||||
|
#share check/update
|
||||||
|
#raise Exception(self.amodule.params['shareable'])
|
||||||
|
if self.amodule.params['shareable'] != self.disk_info['shareable']:
|
||||||
|
self.disk_share(self.disk_id,self.amodule.params['shareable'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.disk_id = self.disk_delete(disk_id=self.disk_id,
|
||||||
|
detach=self.amodule.params['force_detach'],
|
||||||
|
permanently=self.amodule.params['permanently'],
|
||||||
|
reason=self.amodule.params['reason'])
|
||||||
|
self.disk_info['status'] = "DELETED"
|
||||||
|
return
|
||||||
|
|
||||||
|
def rename(self):
|
||||||
|
|
||||||
|
|
||||||
|
self.disk_rename(diskId = self.disk_id,
|
||||||
|
name = self.amodule.params['name'])
|
||||||
|
self.disk_info['name'] = self.amodule.params['name']
|
||||||
|
return
|
||||||
|
|
||||||
|
def nop(self):
|
||||||
|
|
||||||
|
self.result['failed'] = False
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.disk_id:
|
||||||
|
self.result['msg'] = ("No state change required for Disk ID {} because of its "
|
||||||
|
"current status '{}'.").format(self.disk_id, self.disk_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("No state change to '{}' can be done for "
|
||||||
|
"non-existent Disk.").format(self.amodule.params['state'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def package_facts(self, check_mode=False):
|
||||||
|
ret_dict = dict(id=0,
|
||||||
|
name="none",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
size=0,
|
||||||
|
account_id=0,
|
||||||
|
sep_id=0,
|
||||||
|
pool="none",
|
||||||
|
gid=0
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_mode or self.disk_info is None:
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
# remove io param with zero value
|
||||||
|
clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0]
|
||||||
|
for key in clean_io: del self.disk_info['iotune'][key]
|
||||||
|
|
||||||
|
ret_dict['id'] = self.disk_info['id']
|
||||||
|
ret_dict['name'] = self.disk_info['name']
|
||||||
|
ret_dict['size'] = self.disk_info['sizeMax']
|
||||||
|
ret_dict['state'] = self.disk_info['status']
|
||||||
|
ret_dict['account_id'] = self.disk_info['accountId']
|
||||||
|
ret_dict['sep_id'] = self.disk_info['sepId']
|
||||||
|
ret_dict['pool'] = self.disk_info['pool']
|
||||||
|
ret_dict['computes'] = self.disk_info['computes']
|
||||||
|
ret_dict['gid'] = self.disk_info['gid']
|
||||||
|
ret_dict['iotune'] = self.disk_info['iotune']
|
||||||
|
ret_dict['size_available'] = self.disk_info['sizeAvailable']
|
||||||
|
ret_dict['size_used'] = self.disk_info['sizeUsed']
|
||||||
|
|
||||||
if check_mode:
|
|
||||||
# in check mode return immediately with the default values
|
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
if disk_facts is None:
|
@property
|
||||||
# if void facts provided - change state value to ABSENT and return
|
def amodule_init_args(self) -> dict:
|
||||||
ret_dict['state'] = "ABSENT"
|
return self.pack_amodule_init_args(
|
||||||
return ret_dict
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
ret_dict['id'] = disk_facts['id']
|
type='int',
|
||||||
ret_dict['name'] = disk_facts['name']
|
default=0,
|
||||||
ret_dict['size'] = disk_facts['sizeMax']
|
),
|
||||||
ret_dict['state'] = disk_facts['status']
|
account_name=dict(
|
||||||
ret_dict['account_id'] = disk_facts['accountId']
|
type='str',
|
||||||
ret_dict['sep_id'] = disk_facts['sepId']
|
default='',
|
||||||
ret_dict['pool'] = disk_facts['pool']
|
),
|
||||||
ret_dict['attached_to'] = disk_facts['vmid']
|
description=dict(
|
||||||
ret_dict['gid'] = disk_facts['gid']
|
type='str',
|
||||||
|
),
|
||||||
return ret_dict
|
id=dict(
|
||||||
|
type='int',
|
||||||
def decort_disk_parameters():
|
default=0,
|
||||||
"""Build and return a dictionary of parameters expected by decort_disk module in a form accepted
|
),
|
||||||
by AnsibleModule utility class."""
|
name=dict(
|
||||||
|
type='str',
|
||||||
return dict(
|
),
|
||||||
account_id=dict(type='int', required=False, default=0),
|
force_detach=dict(
|
||||||
account_name=dict(type='str', required=False, default=''),
|
type='bool',
|
||||||
annotation=dict(type='str', required=False, default='Disk by decort_disk'),
|
default=False,
|
||||||
app_id=dict(type='str',
|
),
|
||||||
required=False,
|
place_with=dict(
|
||||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
type='int',
|
||||||
app_secret=dict(type='str',
|
default=0,
|
||||||
required=False,
|
),
|
||||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
pool=dict(
|
||||||
no_log=True),
|
type='str',
|
||||||
authenticator=dict(type='str',
|
default='',
|
||||||
required=True,
|
),
|
||||||
choices=['legacy', 'oauth2', 'jwt']),
|
sep_id=dict(
|
||||||
controller_url=dict(type='str', required=True),
|
type='int',
|
||||||
id=dict(type='int', required=False, default=0),
|
default=0,
|
||||||
name=dict(type='str', required=False),
|
),
|
||||||
force_detach=dict(type='bool', required=False, default=False),
|
size=dict(
|
||||||
jwt=dict(type='str',
|
type='int',
|
||||||
required=False,
|
),
|
||||||
fallback=(env_fallback, ['DECORT_JWT']),
|
iops=dict(
|
||||||
no_log=True),
|
type='int',
|
||||||
oauth2_url=dict(type='str',
|
default=2000,
|
||||||
required=False,
|
),
|
||||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
limitIO=dict(
|
||||||
password=dict(type='str',
|
type='dict',
|
||||||
required=False,
|
options=dict(
|
||||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
total_bytes_sec=dict(
|
||||||
no_log=True),
|
type='int',
|
||||||
place_with=dict(type='int', required=False, default=0),
|
),
|
||||||
pool=dict(type='str', required=False, default=''),
|
read_bytes_sec=dict(
|
||||||
sep_id=dict(type='int', required=False, default=0),
|
type='int',
|
||||||
size=dict(type='int', required=False),
|
),
|
||||||
state=dict(type='str',
|
write_bytes_sec=dict(
|
||||||
default='present',
|
type='int',
|
||||||
choices=['absent', 'present']),
|
),
|
||||||
user=dict(type='str',
|
total_iops_sec=dict(
|
||||||
required=False,
|
type='int',
|
||||||
fallback=(env_fallback, ['DECORT_USER'])),
|
),
|
||||||
verify_ssl=dict(type='bool', required=False, default=True),
|
read_iops_sec=dict(
|
||||||
workflow_callback=dict(type='str', required=False),
|
type='int',
|
||||||
workflow_context=dict(type='str', required=False),
|
),
|
||||||
)
|
write_iops_sec=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
total_bytes_sec_max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
read_bytes_sec_max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
write_bytes_sec_max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
total_iops_sec_max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
read_iops_sec_max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
write_iops_sec_max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
size_iops_sec=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
permanently=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
shareable=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
reason=dict(
|
||||||
|
type='str',
|
||||||
|
default='Managed by Ansible decort_disk',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'present',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=[
|
||||||
|
('id', 'name'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module_parameters = decort_disk_parameters()
|
decon = decort_disk()
|
||||||
|
amodule = decon.amodule
|
||||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
|
||||||
supports_check_mode=True,
|
|
||||||
mutually_exclusive=[
|
|
||||||
['oauth2', 'password'],
|
|
||||||
['password', 'jwt'],
|
|
||||||
['jwt', 'oauth2'],
|
|
||||||
],
|
|
||||||
required_together=[
|
|
||||||
['app_id', 'app_secret'],
|
|
||||||
['user', 'password'],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
decon = DecortController(amodule)
|
|
||||||
|
|
||||||
disk_id = 0
|
|
||||||
disk_facts = None # will hold Disk facts
|
|
||||||
validated_acc_id = 0
|
|
||||||
acc_facts = None # will hold Account facts
|
|
||||||
|
|
||||||
if amodule.params['id']:
|
|
||||||
# expect existing Disk with the specified ID
|
|
||||||
# This call to disk_find will abort the module if no Disk with such ID is present
|
|
||||||
disk_id, disk_facts = decon.disk_find(amodule.params['id'])
|
|
||||||
if not disk_id:
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = "Specified Disk ID {} not found.".format(amodule.params['id'])
|
|
||||||
amodule.fail_json(**decon.result)
|
|
||||||
validated_acc_id =disk_facts['accountId']
|
|
||||||
elif amodule.params['account_id'] > 0 or amodule.params['account_name'] != "":
|
|
||||||
# Make sure disk name is specified, if not - fail the module
|
|
||||||
if amodule.params['name'] == "":
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = ("Cannot manage disk if both ID is 0 and disk name is empty.")
|
|
||||||
amodule.fail_json(**decon.result)
|
|
||||||
# Specified account must be present and accessible by the user, otherwise abort the module
|
|
||||||
validated_acc_id, acc_facts = decon.account_find(amodule.params['account_name'], amodule.params['account_id'])
|
|
||||||
if not validated_acc_id:
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = ("Current user does not have access to the requested account "
|
|
||||||
"or non-existent account specified.")
|
|
||||||
amodule.fail_json(**decon.result)
|
|
||||||
# This call to disk_find may return disk_id=0 if no Disk with this name found in
|
|
||||||
disk_id, disk_facts = decon.disk_find(disk_id=0, disk_name=amodule.params['name'],
|
|
||||||
account_id=validated_acc_id,
|
|
||||||
check_state=False)
|
|
||||||
else:
|
|
||||||
# this is "invalid arguments combination" sink
|
|
||||||
# if we end up here, it means that module was invoked with disk_id=0 and undefined account
|
|
||||||
decon.result['failed'] = True
|
|
||||||
if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "":
|
|
||||||
decon.result['msg'] = "Cannot find Disk by name when account name is empty and account ID is 0."
|
|
||||||
if amodule.params['name'] == "":
|
|
||||||
decon.result['msg'] = "Cannot find Disk by empty name."
|
|
||||||
amodule.fail_json(**decon.result)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Initial validation of module arguments is complete
|
#Full range of Disk status is as follows:
|
||||||
#
|
#
|
||||||
# At this point non-zero disk_id means that we will be managing pre-existing Disk
|
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
|
||||||
# Otherwise we are about to create a new disk
|
|
||||||
#
|
#
|
||||||
# Valid Disk model statii are as follows:
|
if decon.disk_id:
|
||||||
#
|
#disk exist
|
||||||
# "CREATED", "ASSIGNED", DELETED", "DESTROYED", "PURGED"
|
if decon.disk_info['status'] in ["MODELED", "CREATING"]:
|
||||||
#
|
|
||||||
|
|
||||||
disk_should_exist = False
|
|
||||||
target_sep_id = 0
|
|
||||||
# target_pool = ""
|
|
||||||
|
|
||||||
if disk_id:
|
|
||||||
disk_should_exist = True
|
|
||||||
if disk_facts['status'] in ["MODELED", "CREATING" ]:
|
|
||||||
# error: nothing can be done to existing Disk in the listed statii regardless of
|
|
||||||
# the requested state
|
|
||||||
decon.result['failed'] = True
|
decon.result['failed'] = True
|
||||||
decon.result['changed'] = False
|
decon.result['changed'] = False
|
||||||
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
|
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
|
||||||
"status '{}'").format(disk_id, disk_facts['status'])
|
"status '{}'").format(decon.disk_id, decon.disk_info['status'])
|
||||||
elif disk_facts['status'] in ["CREATED", "ASSIGNED"]:
|
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
|
||||||
if amodule.params['state'] == 'absent':
|
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]:
|
||||||
decon.disk_delete(disk_id, True, amodule.params['force_detach']) # delete permanently
|
if amodule.params['state'] == 'absent':
|
||||||
disk_facts['status'] = 'DESTROYED'
|
decon.delete()
|
||||||
disk_should_exist = False
|
|
||||||
elif amodule.params['state'] == 'present':
|
elif amodule.params['state'] == 'present':
|
||||||
# resize Disk as necessary & if possible
|
decon.action()
|
||||||
if decon.check_amodule_argument('size', False):
|
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]:
|
||||||
decon.disk_resize(disk_facts, amodule.params['size'])
|
#re-provision disk
|
||||||
elif disk_facts['status'] == "DELETED":
|
if amodule.params['state'] in ('present'):
|
||||||
if amodule.params['state'] == 'present':
|
decon.create()
|
||||||
# restore
|
|
||||||
decon.disk_restore(disk_id)
|
|
||||||
_, disk_facts = decon.disk_find(disk_id)
|
|
||||||
decon.disk_resize(disk_facts, amodule.params['size'])
|
|
||||||
disk_should_exist = True
|
|
||||||
elif amodule.params['state'] == 'absent':
|
|
||||||
# destroy permanently
|
|
||||||
decon.disk_delete(disk_id, permanently=True)
|
|
||||||
disk_facts['status'] = 'DESTROYED'
|
|
||||||
disk_should_exist = False
|
|
||||||
elif disk_facts['status'] in ["DESTROYED", "PURGED"]:
|
|
||||||
if amodule.params['state'] == 'present':
|
|
||||||
# Need to re-provision this Disk.
|
|
||||||
# Some attributes may change, some must stay the same:
|
|
||||||
# - disk name - stays, take from disk_facts
|
|
||||||
# - account ID - stays, take from validated account ID
|
|
||||||
# - size - may change, take from module arguments
|
|
||||||
# - SEP ID - may change, build based on module arguments
|
|
||||||
# - pool - may change, take from module arguments
|
|
||||||
# - annotation - may change, take from module arguments
|
|
||||||
#
|
|
||||||
# First validate required parameters:
|
|
||||||
decon.check_amodule_argument('size') # this will fail the module if size is not specified
|
|
||||||
target_sep_id = 0
|
|
||||||
if decon.check_amodule_argument('sep_id', False) and amodule.params['sep_id'] > 0:
|
|
||||||
# non-zero sep_id is explicitly passed in module arguments
|
|
||||||
target_sep_id = amodule.params['sep_id']
|
|
||||||
elif decon.check_amodule_argument('place_with', False) and amodule.params['place_with'] > 0:
|
|
||||||
# request to place this disk on the same SEP as the specified OS image
|
|
||||||
# validate specified OS image and assign SEP ID accordingly
|
|
||||||
image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0)
|
|
||||||
target_sep_id = image_facts['sepId']
|
|
||||||
else:
|
|
||||||
# no new SEP ID is explicitly specified, and no place_with option - use sepId from the disk_facts
|
|
||||||
target_sep_id = disk_facts['sepId']
|
|
||||||
disk_id = decon.disk_provision(disk_name=disk_facts['name'], # as this disk was found, its name is in the facts
|
|
||||||
size=amodule.params['size'],
|
|
||||||
account_id=validated_acc_id,
|
|
||||||
sep_id=target_sep_id,
|
|
||||||
pool=amodule.params['pool'],
|
|
||||||
desc=amodule.params['annotation'],
|
|
||||||
location="")
|
|
||||||
disk_should_exist = True
|
|
||||||
elif amodule.params['state'] == 'absent':
|
|
||||||
# nop
|
|
||||||
decon.result['failed'] = False
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("No state change required for Disk ID {} because of its "
|
|
||||||
"current status '{}'").format(disk_id,
|
|
||||||
disk_facts['status'])
|
|
||||||
disk_should_exist = False
|
|
||||||
else:
|
|
||||||
# disk_id =0 -> pre-existing Disk was not found.
|
|
||||||
disk_should_exist = False # we will change it back to True if Disk is created successfully
|
|
||||||
# If requested state is 'absent' - nothing to do
|
|
||||||
if amodule.params['state'] == 'absent':
|
|
||||||
decon.result['failed'] = False
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
|
|
||||||
"non-existent Disk name '{}'").format(amodule.params['name'])
|
|
||||||
elif amodule.params['state'] == 'present':
|
|
||||||
decon.check_amodule_argument('name') # if disk name not specified, fail the module
|
|
||||||
decon.check_amodule_argument('size') # if disk size not specified, fail the module
|
|
||||||
|
|
||||||
# as we already have account ID, we can create Disk and get disk id on success
|
|
||||||
if decon.check_amodule_argument('sep_id', False) and amodule.params['sep_id'] > 0:
|
|
||||||
# non-zero sep_id is explicitly passed in module arguments
|
|
||||||
target_sep_id = amodule.params['sep_id']
|
|
||||||
elif decon.check_amodule_argument('place_with', False) and amodule.params['place_with'] > 0:
|
|
||||||
# request to place this disk on the same SEP as the specified OS image
|
|
||||||
# validate specified OS image and assign SEP ID accordingly
|
|
||||||
image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0)
|
|
||||||
target_sep_id = image_facts['sepId']
|
|
||||||
else:
|
else:
|
||||||
# no SEP ID is explicitly specified, and no place_with option - we do not know where
|
decon.nop()
|
||||||
# to place the new disk - fail the module
|
elif decon.disk_info['status'] == "DELETED":
|
||||||
decon.result['failed'] = True
|
if amodule.params['state'] in ('present'):
|
||||||
decon.result['msg'] = ("Cannot create new Disk name '{}': no SEP ID specified and "
|
decon.action(restore=True)
|
||||||
"no 'place_with' option used.").format(amodule.params['name'])
|
elif (amodule.params['state'] == 'absent' and
|
||||||
amodule.fail_json(**decon.result)
|
amodule.params['permanently']):
|
||||||
|
decon.delete()
|
||||||
disk_id = decon.disk_provision(disk_name=amodule.params['name'],
|
else:
|
||||||
size=amodule.params['size'],
|
decon.nop()
|
||||||
account_id=validated_acc_id,
|
else:
|
||||||
sep_id=target_sep_id,
|
# preexisting Disk was not found
|
||||||
pool_name=amodule.params['pool'],
|
if amodule.params['state'] == 'absent':
|
||||||
desc=amodule.params['annotation'],
|
decon.nop()
|
||||||
location="")
|
else:
|
||||||
disk_should_exist = True
|
decon.create()
|
||||||
elif amodule.params['state'] == 'disabled':
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
|
||||||
"Disk name '{}'").format(amodule.params['state'],
|
|
||||||
amodule.params['name'])
|
|
||||||
|
|
||||||
#
|
|
||||||
# conditional switch end - complete module run
|
|
||||||
#
|
|
||||||
if decon.result['failed']:
|
if decon.result['failed']:
|
||||||
amodule.fail_json(**decon.result)
|
amodule.fail_json(**decon.result)
|
||||||
else:
|
else:
|
||||||
# prepare Disk facts to be returned as part of decon.result and then call exit_json(...)
|
if decon.result['changed'] and amodule.params['state'] in ('present'):
|
||||||
if disk_should_exist:
|
_, decon.disk_info = decon.disk_find(decon.disk_id)
|
||||||
if decon.result['changed']:
|
decon.result['facts'] = decon.package_facts(amodule.check_mode)
|
||||||
# If we arrive here, there is a good chance that the Disk is present - get fresh Disk
|
|
||||||
# facts by Disk ID.
|
|
||||||
# Otherwise, Disk facts from previous call (when the Disk was still in existence) will
|
|
||||||
# be returned.
|
|
||||||
_, disk_facts = decon.disk_find(disk_id)
|
|
||||||
decon.result['facts'] = decort_disk_package_facts(disk_facts, amodule.check_mode)
|
|
||||||
amodule.exit_json(**decon.result)
|
amodule.exit_json(**decon.result)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
#SHARE
|
||||||
|
|||||||
392
library/decort_group.py
Normal file
392
library/decort_group.py
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_group
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class decort_group(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super(decort_group, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
arg_amodule = self.amodule
|
||||||
|
|
||||||
|
self.group_should_exist = False
|
||||||
|
validated_bservice_id = None
|
||||||
|
#find and validate B-Service
|
||||||
|
|
||||||
|
validated_bservice_id, bservice_info = self.bservice_get_by_id(arg_amodule.params['bservice_id'])
|
||||||
|
if not validated_bservice_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Cannot find B-service ID {}.").format(arg_amodule.params['bservice_id'])
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
#find group
|
||||||
|
self.bservice_id = validated_bservice_id
|
||||||
|
self.bservice_info = bservice_info
|
||||||
|
self.group_id,self.group_info = self.group_find(
|
||||||
|
bs_id=validated_bservice_id,
|
||||||
|
bs_info=bservice_info,
|
||||||
|
group_id=arg_amodule.params['id'],
|
||||||
|
group_name=arg_amodule.params['name'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.group_id:
|
||||||
|
self.group_should_exist = True
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
|
||||||
|
return
|
||||||
|
def nop(self):
|
||||||
|
"""No operation (NOP) handler for B-service.
|
||||||
|
This function is intended to be called from the main switch construct of the module
|
||||||
|
when current state -> desired state change logic does not require any changes to
|
||||||
|
the actual Compute state.
|
||||||
|
"""
|
||||||
|
self.result['failed'] = False
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.group_id:
|
||||||
|
self.result['msg'] = ("No state change required for B-service ID {} because of its "
|
||||||
|
"current status '{}'.").format(self.group_id, self.group_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("No state change to '{}' can be done for "
|
||||||
|
"non-existent B-service instance.").format(self.amodule.params['state'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.group_id:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for Group ID {} in the "
|
||||||
|
"current status '{}'.").format(self.group_id,
|
||||||
|
self.amodule.params['state'],
|
||||||
|
self.group_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for non-existent Group name '{}' "
|
||||||
|
"in B-service {}").format(self.amodule.params['state'],
|
||||||
|
self.amodule.params['name'],
|
||||||
|
self.amodule.params['bservice_id'],
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
chipset = self.aparams['chipset']
|
||||||
|
if chipset is None:
|
||||||
|
chipset = 'i440fx'
|
||||||
|
self.message(
|
||||||
|
msg=f'Chipset not specified, '
|
||||||
|
f'default value "{chipset}" will be used.',
|
||||||
|
warning=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.group_id=self.group_provision(
|
||||||
|
bs_id=self.bservice_id,
|
||||||
|
arg_name=self.amodule.params['name'],
|
||||||
|
arg_count=self.amodule.params['count'],
|
||||||
|
arg_cpu=self.amodule.params['cpu'],
|
||||||
|
arg_ram=self.amodule.params['ram'],
|
||||||
|
arg_boot_disk=self.amodule.params['boot_disk'],
|
||||||
|
arg_image_id=self.amodule.params['image_id'],
|
||||||
|
arg_driver=self.amodule.params['driver'],
|
||||||
|
arg_role=self.amodule.params['role'],
|
||||||
|
arg_network=self.amodule.params['networks'],
|
||||||
|
arg_timeout=self.amodule.params['timeoutStart'],
|
||||||
|
chipset=chipset,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.amodule.params['state'] in ('started','present'):
|
||||||
|
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
|
||||||
|
|
||||||
|
self.group_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def action(self):
|
||||||
|
#change desired state
|
||||||
|
if (
|
||||||
|
self.group_info['techStatus'] == 'STARTED' and self.amodule.params['state'] == 'stopped') or (
|
||||||
|
self.group_info['techStatus'] == 'STOPPED' and self.amodule.params['state'] in ('started','present')
|
||||||
|
):
|
||||||
|
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
|
||||||
|
|
||||||
|
aparam_chipset = self.aparams['chipset']
|
||||||
|
if (
|
||||||
|
self.aparams['count'] is not None
|
||||||
|
and self.aparams['count'] != len(self.group_info['computes'])
|
||||||
|
):
|
||||||
|
self.group_resize_count(
|
||||||
|
bs_id=self.bservice_id,
|
||||||
|
gr_dict=self.group_info,
|
||||||
|
desired_count=self.aparams['count'],
|
||||||
|
chipset=aparam_chipset,
|
||||||
|
)
|
||||||
|
|
||||||
|
if aparam_chipset is not None:
|
||||||
|
for vm in self.group_info['computes']:
|
||||||
|
if vm['chipset'] != aparam_chipset:
|
||||||
|
self.compute_update(
|
||||||
|
compute_id=vm['id'],
|
||||||
|
chipset=aparam_chipset,
|
||||||
|
)
|
||||||
|
|
||||||
|
for aparam_name, info_key in {'cpu': 'cpu',
|
||||||
|
'boot_disk': 'disk',
|
||||||
|
'role': 'role',
|
||||||
|
'ram': 'ram',
|
||||||
|
'name': 'name',
|
||||||
|
}.items():
|
||||||
|
aparam_value = self.aparams[aparam_name]
|
||||||
|
group_info_value = self.group_info[info_key]
|
||||||
|
if aparam_value != None and aparam_value != group_info_value:
|
||||||
|
self.group_update(
|
||||||
|
bs_id=self.bservice_id,
|
||||||
|
gr_dict=self.group_info,
|
||||||
|
arg_cpu=self.aparams['cpu'],
|
||||||
|
arg_disk=self.aparams['boot_disk'],
|
||||||
|
arg_name=self.aparams['name'],
|
||||||
|
arg_role=self.aparams['role'],
|
||||||
|
arg_ram=self.aparams['ram'],
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.aparams['networks'] != None:
|
||||||
|
self.group_update_net(
|
||||||
|
bs_id=self.bservice_id,
|
||||||
|
gr_dict=self.group_info,
|
||||||
|
arg_net=self.aparams['networks'],
|
||||||
|
)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
|
||||||
|
self.group_delete(
|
||||||
|
self.bservice_id,
|
||||||
|
self.group_id
|
||||||
|
)
|
||||||
|
self.group_should_exist = False
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def package_facts(self,check_mode=False):
|
||||||
|
|
||||||
|
ret_dict = dict(
|
||||||
|
name="",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
account_id=0,
|
||||||
|
rg_id=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
if self.result['changed'] == True:
|
||||||
|
self.group_id,self.group_info = self.group_find(
|
||||||
|
self.bservice_id,
|
||||||
|
self.bservice_info,
|
||||||
|
self.group_id
|
||||||
|
)
|
||||||
|
|
||||||
|
ret_dict['account_id'] = self.group_info['accountId']
|
||||||
|
ret_dict['rg_id'] = self.group_info['rgId']
|
||||||
|
ret_dict['id'] = self.group_info['id']
|
||||||
|
ret_dict['name'] = self.group_info['name']
|
||||||
|
ret_dict['techStatus'] = self.group_info['techStatus']
|
||||||
|
ret_dict['state'] = self.group_info['status']
|
||||||
|
ret_dict['Computes'] = self.group_info['computes']
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'started',
|
||||||
|
'stopped',
|
||||||
|
'present',
|
||||||
|
'check',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
image_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
image_name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
driver=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'KVM_X86',
|
||||||
|
'SVA_KVM_X86',
|
||||||
|
],
|
||||||
|
default='KVM_X86',
|
||||||
|
),
|
||||||
|
boot_disk=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
bservice_id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
count=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
timeoutStart=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
role=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
cpu=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ram=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
networks=dict(
|
||||||
|
type='list',
|
||||||
|
elements='dict',
|
||||||
|
options=dict(
|
||||||
|
type=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
choices=[
|
||||||
|
'VINS',
|
||||||
|
'EXTNET',
|
||||||
|
]
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
chipset=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'Q35',
|
||||||
|
'i440fx',
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=[
|
||||||
|
('id', 'name'),
|
||||||
|
('id', 'networks'),
|
||||||
|
('id', 'count'),
|
||||||
|
('id', 'cpu'),
|
||||||
|
('id', 'ram'),
|
||||||
|
('id', 'boot_disk'),
|
||||||
|
('id', 'image_id'),
|
||||||
|
('id', 'driver'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_errors = False
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.aparams['chipset'] is None
|
||||||
|
and self.aparams['count'] > len(self.group_info['computes'])
|
||||||
|
):
|
||||||
|
check_errors = True
|
||||||
|
self.message(
|
||||||
|
msg='Check for parameter "chipset" failed: '
|
||||||
|
'Chipset must be specified when increasing '
|
||||||
|
'VM count in group'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.aparams['count'] is None
|
||||||
|
or self.aparams['count'] == len(self.group_info['computes'])
|
||||||
|
):
|
||||||
|
aparam_chipset = self.aparams['chipset']
|
||||||
|
if aparam_chipset is not None:
|
||||||
|
for vm in self.group_info['computes']:
|
||||||
|
if (
|
||||||
|
vm['chipset'] != aparam_chipset
|
||||||
|
and self.group_info['techStatus'] != 'STOPPED'
|
||||||
|
and self.amodule.params['state'] != 'stopped'
|
||||||
|
):
|
||||||
|
check_errors = True
|
||||||
|
self.message(
|
||||||
|
msg=f'Check for parameter "chipset" failed: '
|
||||||
|
f'group ID {self.group_id} must be stopped '
|
||||||
|
f'to change chipset',
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
subj = decort_group()
|
||||||
|
amodule = subj.amodule
|
||||||
|
|
||||||
|
if amodule.params['state'] == 'check':
|
||||||
|
subj.result['changed'] = False
|
||||||
|
if subj.group_id:
|
||||||
|
# cluster is found - package facts and report success to Ansible
|
||||||
|
subj.result['failed'] = False
|
||||||
|
subj.result['facts'] = subj.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
# we exit the module at this point
|
||||||
|
else:
|
||||||
|
subj.result['failed'] = True
|
||||||
|
subj.result['msg'] = ("Cannot locate Group name '{}'. "
|
||||||
|
"B-service ID {}").format(amodule.params['name'],
|
||||||
|
amodule.params['bservice_id'],)
|
||||||
|
amodule.fail_json(**subj.result)
|
||||||
|
|
||||||
|
if subj.group_id:
|
||||||
|
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
|
||||||
|
"ENABLING","DISABLING","RESTORING","MODELED",
|
||||||
|
"DISABLED","DESTROYED"):
|
||||||
|
subj.error()
|
||||||
|
elif subj.group_info['status'] in ("DELETED","DESTROYED"):
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
if amodule.params['state'] in ('present','started','stopped'):
|
||||||
|
subj.create()
|
||||||
|
elif subj.group_info['techStatus'] in ("STARTED","STOPPED"):
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.destroy()
|
||||||
|
else:
|
||||||
|
subj.action()
|
||||||
|
|
||||||
|
else:
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
if amodule.params['state'] in ('present','started','stopped'):
|
||||||
|
subj.create()
|
||||||
|
|
||||||
|
if subj.result['failed']:
|
||||||
|
amodule.fail_json(**subj.result)
|
||||||
|
else:
|
||||||
|
if subj.group_should_exist:
|
||||||
|
subj.result['facts'] = subj.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
else:
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,157 +1,38 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
|
||||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
|
||||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
|
||||||
#
|
|
||||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
DOCUMENTATION = r'''
|
||||||
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
---
|
||||||
module: decort_jwt
|
module: decort_jwt
|
||||||
short_description: Obtain access token to be used for authentication to DECORT cloud controller
|
|
||||||
description:
|
|
||||||
- Obtain JWT (JSON Web Token) from the specified Oauth2 provider. This JWT can be used in subsequent DECS modules'
|
|
||||||
invocations to authenticate them to the DECS cloud controller.
|
|
||||||
version_added: "2.4"
|
|
||||||
author: "Sergey Shubin (sergey.shubin@digitalenergy.online)"
|
|
||||||
notes:
|
|
||||||
- Environment variables can be used to pass parameters to the module (see options below for details).
|
|
||||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
|
||||||
- 'If you register module output as I(my_jwt), the JWT value is accessed as I(my_jwt.jwt)'
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- PyJWT module
|
|
||||||
- requests module
|
|
||||||
- decort_utils utility library (module)
|
|
||||||
- DECORT cloud platform version 3.6.1 or higher
|
|
||||||
options:
|
|
||||||
app_id:
|
|
||||||
description:
|
|
||||||
- 'Application ID for authenticating to the Oauth2 provider specified in I(oauth2_url).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
app_secret:
|
|
||||||
description:
|
|
||||||
- 'Application API secret used for authenticating to the Oauth2 provider specified in I(oauth2_url).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
oauth2_url:
|
|
||||||
description:
|
|
||||||
- 'URL of the oauth2 authentication provider to obtain JWT from.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
|
|
||||||
validity:
|
|
||||||
description:
|
|
||||||
- Validity of the JWT in seconds. Default value is 3600 (one hour).
|
|
||||||
required: no
|
|
||||||
verify_ssl:
|
|
||||||
description:
|
|
||||||
- 'Controls SSL verification mode when making API calls to DECS controller. Set it to False if you
|
|
||||||
want to disable SSL certificate verification.'
|
|
||||||
- `Intended use case is when you run module in a trusted environment that uses self-signed certificates.
|
|
||||||
Note that disabling SSL verification in any other scenario can lead to security issues, so please use
|
|
||||||
with caution.'
|
|
||||||
default: True
|
|
||||||
required: no
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
- name: Obtain JWT and store it as my_jwt for authenticating subsequent task to DECORT cloud controller
|
|
||||||
decort_jwt:
|
|
||||||
app_id: "{{ my_app_id }}"
|
|
||||||
app_secret: "{{ my_app_secret }}"
|
|
||||||
oauth2_url: https://sso.decs.online
|
|
||||||
delegate_to: localhost
|
|
||||||
register: my_jwt
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
jwt:
|
|
||||||
description: JSON Web Token that can be used to access DECS cloud controller
|
|
||||||
returned: always
|
|
||||||
type: string
|
|
||||||
sample: None
|
|
||||||
'''
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.basic import env_fallback
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
def decort_jwt_parameters():
|
|
||||||
"""Build and return a dictionary of parameters expected by decort_jwt module in a form accepted
|
|
||||||
by AnsibleModule utility class"""
|
|
||||||
|
|
||||||
return dict(
|
class DecortJWT(DecortController):
|
||||||
app_id=dict(type='str',
|
def __init__(self):
|
||||||
required=True,
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
|
||||||
app_secret=dict(type='str',
|
@property
|
||||||
required=True,
|
def amodule_init_args(self) -> dict:
|
||||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
amodule_init_args = self.common_amodule_init_args
|
||||||
no_log=True),
|
amodule_argument_spec = amodule_init_args['argument_spec']
|
||||||
oauth2_url=dict(type='str',
|
del amodule_argument_spec['controller_url']
|
||||||
required=True,
|
del amodule_argument_spec['jwt']
|
||||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
amodule_argument_spec['authenticator']['choices'].remove('jwt')
|
||||||
validity=dict(type='int',
|
|
||||||
required=False,
|
return amodule_init_args
|
||||||
default=3600),
|
|
||||||
verify_ssl=dict(type='bool', required=False, default=True),
|
def run(self):
|
||||||
workflow_callback=dict(type='str', required=False),
|
self.result['jwt'] = self.jwt
|
||||||
workflow_context=dict(type='str', required=False),
|
self.amodule.exit_json(**self.result)
|
||||||
)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module_parameters = decort_jwt_parameters()
|
DecortJWT().run()
|
||||||
|
|
||||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
|
||||||
supports_check_mode=True,)
|
|
||||||
|
|
||||||
result = {'failed': False, 'changed': False}
|
|
||||||
|
|
||||||
token_get_url = amodule.params['oauth2_url'] + "/v1/oauth/access_token"
|
|
||||||
req_data = dict(grant_type="client_credentials",
|
|
||||||
client_id=amodule.params['app_id'],
|
|
||||||
client_secret=amodule.params['app_secret'],
|
|
||||||
response_type="id_token",
|
|
||||||
validity=amodule.params['validity'],)
|
|
||||||
# TODO: Need standard code snippet to handle server timeouts gracefully
|
|
||||||
# Consider a few retries before giving up or use requests.Session & requests.HTTPAdapter
|
|
||||||
# see https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request
|
|
||||||
|
|
||||||
# catch requests.exceptions.ConnectionError to handle incorrect oauth2_url case
|
|
||||||
try:
|
|
||||||
token_get_resp = requests.post(token_get_url, data=req_data, verify=amodule.params['verify_ssl'])
|
|
||||||
except requests.exceptions.ConnectionError as errco:
|
|
||||||
result.update(failed=True)
|
|
||||||
result['msg'] = "Failed to connect to {}: {}".format(token_get_url, errco)
|
|
||||||
amodule.fail_json(**result)
|
|
||||||
except requests.exceptions.Timeout as errti:
|
|
||||||
result.update(failed=True)
|
|
||||||
result['msg'] = "Timeout when trying to connect to {}: {}".format(token_get_url, errti)
|
|
||||||
amodule.fail_json(**result)
|
|
||||||
|
|
||||||
# alternative -- if resp == requests.codes.ok
|
|
||||||
if token_get_resp.status_code != 200:
|
|
||||||
result.update(failed=True)
|
|
||||||
result['msg'] = "Failed to obtain JWT access token from oauth2_url {} for app_id {}: {} {}".format(
|
|
||||||
token_get_url, amodule.params['app_id'],
|
|
||||||
token_get_resp.status_code, token_get_resp.reason)
|
|
||||||
amodule.fail_json(**result)
|
|
||||||
|
|
||||||
# Common return values: https://docs.ansible.com/ansible/2.3/common_return_values.html
|
|
||||||
result['jwt'] = token_get_resp.content.decode('utf8')
|
|
||||||
amodule.exit_json(**result)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
610
library/decort_k8s.py
Normal file
610
library/decort_k8s.py
Normal file
@@ -0,0 +1,610 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_k8s
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible.module_utils.decort_utils import *
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
|
class decort_k8s(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super(decort_k8s, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
arg_amodule = self.amodule
|
||||||
|
|
||||||
|
validated_acc_id = 0
|
||||||
|
validated_rg_id = 0
|
||||||
|
validated_rg_facts = None
|
||||||
|
validated_k8ci_id = 0
|
||||||
|
self.k8s_should_exist = False
|
||||||
|
self.is_k8s_stopped_or_will_be_stopped: None | bool = None
|
||||||
|
|
||||||
|
self.wg_default_params = {
|
||||||
|
'num': 1,
|
||||||
|
'cpu': 1,
|
||||||
|
'ram': 1024,
|
||||||
|
'labels': [],
|
||||||
|
'taints': [],
|
||||||
|
'annotations': [],
|
||||||
|
'ci_user_data': {},
|
||||||
|
'chipset': 'i440fx',
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] is None:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = "Cannot manage k8s cluster when its ID is 0 and name is empty."
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
|
||||||
|
if arg_amodule.params['id'] is None:
|
||||||
|
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
|
||||||
|
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
|
||||||
|
arg_amodule.params['account_id'])
|
||||||
|
if not validated_acc_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Current user does not have access to the account ID {} / "
|
||||||
|
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
|
||||||
|
arg_amodule.params['account_name'])
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
# fail the module -> exit
|
||||||
|
# now validate RG
|
||||||
|
validated_rg_id, validated_rg_facts = self.rg_find(
|
||||||
|
arg_account_id=validated_acc_id,
|
||||||
|
arg_rg_id=arg_amodule.params['rg_id'],
|
||||||
|
arg_rg_name=arg_amodule.params['rg_name']
|
||||||
|
)
|
||||||
|
if not validated_rg_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
|
||||||
|
arg_amodule.params['rg_name'])
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
# fail the module - exit
|
||||||
|
|
||||||
|
self.rg_id = validated_rg_id
|
||||||
|
arg_amodule.params['rg_id'] = validated_rg_id
|
||||||
|
arg_amodule.params['rg_name'] = validated_rg_facts['name']
|
||||||
|
self.acc_id = validated_rg_facts['accountId']
|
||||||
|
|
||||||
|
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
|
||||||
|
k8s_name=arg_amodule.params['name'],
|
||||||
|
rg_id=validated_rg_id,
|
||||||
|
check_state=False)
|
||||||
|
|
||||||
|
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
|
||||||
|
self.k8s_should_exist = True
|
||||||
|
self.acc_id = self.k8s_info['accountId']
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
else:
|
||||||
|
self.check_amodule_args_for_create()
|
||||||
|
return
|
||||||
|
|
||||||
|
def package_facts(self,check_mode=False):
|
||||||
|
|
||||||
|
ret_dict = dict(
|
||||||
|
name="",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
account_id=0,
|
||||||
|
rg_id=0,
|
||||||
|
config=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
|
||||||
|
ret_dict['config'] = self.k8s_getConfig()
|
||||||
|
|
||||||
|
if check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
#if self.k8s_facts is None:
|
||||||
|
# #if void facts provided - change state value to ABSENT and return
|
||||||
|
# ret_dict['state'] = "ABSENT"
|
||||||
|
# return ret_dict
|
||||||
|
|
||||||
|
ret_dict['id'] = self.k8s_info['id']
|
||||||
|
ret_dict['name'] = self.k8s_info['name']
|
||||||
|
ret_dict['techStatus'] = self.k8s_info['techStatus']
|
||||||
|
ret_dict['state'] = self.k8s_info['status']
|
||||||
|
ret_dict['rg_id'] = self.k8s_info['rgId']
|
||||||
|
ret_dict['vins_id'] = self.k8s_vins_id
|
||||||
|
ret_dict['account_id'] = self.acc_id
|
||||||
|
ret_dict['k8s_Masters'] = self.k8s_info['k8sGroups']['masters']
|
||||||
|
ret_dict['k8s_Workers'] = self.k8s_info['k8sGroups']['workers']
|
||||||
|
ret_dict['lb_id'] = self.k8s_info['lbId']
|
||||||
|
ret_dict['description'] = self.k8s_info['desc']
|
||||||
|
ret_dict['zone_id'] = self.k8s_info['zoneId']
|
||||||
|
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
def nop(self):
|
||||||
|
"""No operation (NOP) handler for k8s cluster management by decort_k8s module.
|
||||||
|
This function is intended to be called from the main switch construct of the module
|
||||||
|
when current state -> desired state change logic does not require any changes to
|
||||||
|
the actual k8s cluster state.
|
||||||
|
"""
|
||||||
|
self.result['failed'] = False
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.k8s_id:
|
||||||
|
self.result['msg'] = ("No state change required for K8s ID {} because of its "
|
||||||
|
"current status '{}'.").format(self.k8s_id, self.k8s_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("No state change to '{}' can be done for "
|
||||||
|
"non-existent K8s instance.").format(self.amodule.params['state'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.k8s_id:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for K8s cluster ID {} in the "
|
||||||
|
"current status '{}'.").format(self.k8s_id,
|
||||||
|
self.amodule.params['state'],
|
||||||
|
self.k8s_info['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for non-existent K8s Cluster name '{}' "
|
||||||
|
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
|
||||||
|
self.amodule.params['name'],
|
||||||
|
self.amodule.params['rg_id'],
|
||||||
|
self.amodule.params['rg_name'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
master_chipset = self.amodule.params['master_chipset']
|
||||||
|
if master_chipset is None:
|
||||||
|
master_chipset = 'i440fx'
|
||||||
|
|
||||||
|
target_wgs = deepcopy(self.amodule.params['workers'])
|
||||||
|
for wg in target_wgs:
|
||||||
|
for param, default_value in self.wg_default_params.items():
|
||||||
|
if wg[param] is None:
|
||||||
|
wg[param] = default_value
|
||||||
|
|
||||||
|
k8s_id = self.k8s_provision(self.amodule.params['name'],
|
||||||
|
self.amodule.params['k8ci_id'],
|
||||||
|
self.amodule.params['rg_id'],
|
||||||
|
self.amodule.params['vins_id'],
|
||||||
|
self.amodule.params['network_plugin'],
|
||||||
|
self.amodule.params['master_count'],
|
||||||
|
self.amodule.params['master_cpu'],
|
||||||
|
self.amodule.params['master_ram'],
|
||||||
|
self.amodule.params['master_disk'],
|
||||||
|
self.amodule.params['master_sepid'],
|
||||||
|
self.amodule.params['master_pool'],
|
||||||
|
target_wgs[0],
|
||||||
|
self.amodule.params['extnet_id'],
|
||||||
|
self.amodule.params['with_lb'],
|
||||||
|
self.amodule.params['ha_lb'],
|
||||||
|
self.amodule.params['additionalSANs'],
|
||||||
|
self.amodule.params['init_conf'],
|
||||||
|
self.amodule.params['cluster_conf'],
|
||||||
|
self.amodule.params['kublet_conf'],
|
||||||
|
self.amodule.params['kubeproxy_conf'],
|
||||||
|
self.amodule.params['join_conf'],
|
||||||
|
self.amodule.params['oidc_cert'],
|
||||||
|
self.amodule.params['description'],
|
||||||
|
self.amodule.params['extnet_only'],
|
||||||
|
master_chipset,
|
||||||
|
lb_sysctl=self.amodule.params['lb_sysctl'],
|
||||||
|
zone_id=self.aparams['zone_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if not k8s_id:
|
||||||
|
if k8s_id == 0:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
|
||||||
|
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id,
|
||||||
|
k8s_name=self.amodule.params['name'],
|
||||||
|
rg_id=self.rg_id,
|
||||||
|
check_state=False)
|
||||||
|
|
||||||
|
if self.k8s_id:
|
||||||
|
self.k8s_should_exist = True
|
||||||
|
if len(target_wgs) > 1:
|
||||||
|
self.k8s_workers_modify(
|
||||||
|
arg_k8swg=self.k8s_info,
|
||||||
|
arg_modwg=target_wgs,
|
||||||
|
)
|
||||||
|
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self.k8s_delete(self.k8s_id,self.amodule.params['permanent'])
|
||||||
|
self.k8s_info['status'] = 'DELETED'
|
||||||
|
self.k8s_should_exist = False
|
||||||
|
return
|
||||||
|
|
||||||
|
def action(self, disared_state, preupdate: bool = False):
|
||||||
|
if self.amodule.params['master_chipset'] is not None:
|
||||||
|
for master_node in self.k8s_info['k8sGroups']['masters'][
|
||||||
|
'detailedInfo'
|
||||||
|
]:
|
||||||
|
_, master_node_info, _ = self._compute_get_by_id(
|
||||||
|
comp_id=master_node['id']
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
master_node_info['chipset']
|
||||||
|
!= self.amodule.params['master_chipset']
|
||||||
|
):
|
||||||
|
self.result['msg'] = (
|
||||||
|
'"master_chipset" cannot be changed '
|
||||||
|
'for existing K8s cluster.'
|
||||||
|
)
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
if preupdate:
|
||||||
|
# K8s info updating
|
||||||
|
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
|
||||||
|
#k8s state
|
||||||
|
self.k8s_state(self.k8s_info, disared_state)
|
||||||
|
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
|
||||||
|
#check groups and modify if needed
|
||||||
|
if self.aparams['workers'] is not None:
|
||||||
|
self.k8s_workers_modify(self.k8s_info, self.amodule.params['workers'])
|
||||||
|
|
||||||
|
aparam_zone_id = self.aparams['zone_id']
|
||||||
|
if aparam_zone_id is not None and aparam_zone_id != self.k8s_info['zoneId']:
|
||||||
|
self.k8s_migrate_to_zone(
|
||||||
|
k8s_id=self.k8s_id,
|
||||||
|
zone_id=aparam_zone_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.result['changed'] == True:
|
||||||
|
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
|
||||||
|
#TODO check workers metadata and modify if needed
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
quotas=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'disabled',
|
||||||
|
'enabled',
|
||||||
|
'present',
|
||||||
|
'started',
|
||||||
|
'stopped',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
permanent=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
getConfig=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
rg_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
vins_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
k8ci_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
network_plugin=dict(
|
||||||
|
type='str',
|
||||||
|
default='flannel',
|
||||||
|
),
|
||||||
|
master_count=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
choices=[1, 3, 5, 7],
|
||||||
|
),
|
||||||
|
master_cpu=dict(
|
||||||
|
type='int',
|
||||||
|
default=2,
|
||||||
|
),
|
||||||
|
master_ram=dict(
|
||||||
|
type='int',
|
||||||
|
default=2048,
|
||||||
|
),
|
||||||
|
master_disk=dict(
|
||||||
|
type='int',
|
||||||
|
default=10,
|
||||||
|
),
|
||||||
|
master_sepid=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
master_pool=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
workers=dict(
|
||||||
|
type='list',
|
||||||
|
elements='dict',
|
||||||
|
options=dict(
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
num=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
cpu=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
ram=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
disk=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
sep_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
pool=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
annotations=dict(
|
||||||
|
type='list',
|
||||||
|
elements='str',
|
||||||
|
),
|
||||||
|
ci_user_data=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
labels=dict(
|
||||||
|
type='list',
|
||||||
|
elements='str',
|
||||||
|
),
|
||||||
|
taints=dict(
|
||||||
|
type='list',
|
||||||
|
elements='str',
|
||||||
|
),
|
||||||
|
chipset=dict(
|
||||||
|
type='str',
|
||||||
|
choices=['Q35', 'i440fx'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
workers_metadata=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
extnet_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
description=dict(
|
||||||
|
type='str',
|
||||||
|
default='Created by decort ansible module',
|
||||||
|
),
|
||||||
|
with_lb=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
ha_lb=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
extnet_only=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
additionalSANs=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
init_conf=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
cluster_conf=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
kublet_conf=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
kubeproxy_conf=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
join_conf=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
oidc_cert=dict(
|
||||||
|
type='raw',
|
||||||
|
),
|
||||||
|
master_chipset=dict(
|
||||||
|
type='str',
|
||||||
|
choices=['Q35', 'i440fx'],
|
||||||
|
),
|
||||||
|
lb_sysctl=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
zone_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=[
|
||||||
|
('id', 'name'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_errors = False
|
||||||
|
|
||||||
|
self.is_k8s_stopped_or_will_be_stopped = (
|
||||||
|
(
|
||||||
|
self.k8s_info['techStatus'] == 'STOPPED'
|
||||||
|
and (
|
||||||
|
self.aparams['state'] is None
|
||||||
|
or self.aparams['state'] in ('present', 'stopped')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
self.k8s_info['techStatus'] != 'STOPPED'
|
||||||
|
and self.aparams['state'] == 'stopped'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
aparam_sysctl = self.aparams['lb_sysctl']
|
||||||
|
if aparam_sysctl is not None:
|
||||||
|
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId'])
|
||||||
|
sysctl_with_str_values = {
|
||||||
|
k: str(v) for k, v in aparam_sysctl.items()
|
||||||
|
}
|
||||||
|
if sysctl_with_str_values != lb_info['sysctlParams']:
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "lb_sysctl" failed: '
|
||||||
|
'cannot change lb_sysctl for an existing cluster '
|
||||||
|
'load balancer.'
|
||||||
|
)
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
if (
|
||||||
|
self.aparams['zone_id'] is not None
|
||||||
|
and self.aparams['zone_id'] != self.k8s_info['zoneId']
|
||||||
|
and not self.is_k8s_stopped_or_will_be_stopped
|
||||||
|
):
|
||||||
|
check_errors = True
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "zone_id" failed: '
|
||||||
|
'K8s cluster must be stopped to migrate to a zone.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def check_amodule_args_for_create(self):
|
||||||
|
check_errors = False
|
||||||
|
|
||||||
|
validated_k8ci_id = self.k8s_k8ci_find(self.aparams['k8ci_id'])
|
||||||
|
if not validated_k8ci_id:
|
||||||
|
self.message(f'Cannot find K8CI ID {"k8ci_id"}.')
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if not self.aparams['workers']:
|
||||||
|
self.message('At least one worker group must be present.')
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.aparams['lb_sysctl'] is not None
|
||||||
|
and not self.aparams['with_lb']
|
||||||
|
):
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "lb_sysctl" failed: '
|
||||||
|
'"lb_sysctl" can only be set if the parameter "with_lb" '
|
||||||
|
'is set to True.'
|
||||||
|
)
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.aparams['master_count'] is not None
|
||||||
|
and self.aparams['master_count'] > 1
|
||||||
|
and not self.aparams['with_lb']
|
||||||
|
):
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "master_count" failed: '
|
||||||
|
'master_count can be more than 1 only if the parameter '
|
||||||
|
'"with_lb" is set to True.'
|
||||||
|
)
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
subj = decort_k8s()
|
||||||
|
amodule = subj.amodule
|
||||||
|
|
||||||
|
if subj.amodule.check_mode:
|
||||||
|
subj.result['changed'] = False
|
||||||
|
if subj.k8s_id:
|
||||||
|
# cluster is found - package facts and report success to Ansible
|
||||||
|
subj.result['failed'] = False
|
||||||
|
subj.result['facts'] = subj.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
# we exit the module at this point
|
||||||
|
else:
|
||||||
|
subj.result['failed'] = True
|
||||||
|
subj.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
|
||||||
|
"RG ID {}").format(amodule.params['name'],
|
||||||
|
amodule.params['rg_id'],)
|
||||||
|
amodule.fail_json(**subj.result)
|
||||||
|
|
||||||
|
if subj.k8s_id:
|
||||||
|
if subj.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
|
||||||
|
"ENABLING","DISABLING","RESTORING","MODELED"):
|
||||||
|
subj.error()
|
||||||
|
elif subj.k8s_info['status'] == "DELETED":
|
||||||
|
if amodule.params['state'] in (
|
||||||
|
'disabled', 'enabled', 'present', 'started', 'stopped'
|
||||||
|
):
|
||||||
|
subj.k8s_restore(subj.k8s_id)
|
||||||
|
subj.action(disared_state=amodule.params['state'],
|
||||||
|
preupdate=True)
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
if amodule.params['permanent']:
|
||||||
|
subj.destroy()
|
||||||
|
else:
|
||||||
|
subj.nop()
|
||||||
|
elif subj.k8s_info['status'] in ('ENABLED', 'DISABLED'):
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.destroy()
|
||||||
|
else:
|
||||||
|
subj.action(disared_state=amodule.params['state'])
|
||||||
|
elif subj.k8s_info['status'] == "DESTROYED":
|
||||||
|
if amodule.params['state'] in ('present','enabled'):
|
||||||
|
subj.create()
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
else:
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
subj.nop()
|
||||||
|
if amodule.params['state'] in ('present','started'):
|
||||||
|
subj.create()
|
||||||
|
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
|
||||||
|
subj.error()
|
||||||
|
|
||||||
|
if subj.result['failed']:
|
||||||
|
amodule.fail_json(**subj.result)
|
||||||
|
else:
|
||||||
|
if subj.k8s_should_exist:
|
||||||
|
subj.result['facts'] = subj.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
else:
|
||||||
|
amodule.exit_json(**subj.result)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
File diff suppressed because it is too large
Load Diff
420
library/decort_lb.py
Normal file
420
library/decort_lb.py
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_lb
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class decort_lb(DecortController):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super(decort_lb,self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
arg_amodule = self.amodule
|
||||||
|
|
||||||
|
self.lb_id = 0
|
||||||
|
self.lb_facts = None
|
||||||
|
self.vins_id = 0
|
||||||
|
self.vins_facts = None
|
||||||
|
self.rg_id = 0
|
||||||
|
self.rg_facts = None
|
||||||
|
self.default_server_check = "enabled"
|
||||||
|
self.default_alg = "roundrobin"
|
||||||
|
self.default_settings = {
|
||||||
|
"downinter": 10000,
|
||||||
|
"fall": 2,
|
||||||
|
"inter": 5000,
|
||||||
|
"maxconn": 250,
|
||||||
|
"maxqueue": 256,
|
||||||
|
"rise": 2,
|
||||||
|
"slowstart": 60000,
|
||||||
|
"weight": 100,
|
||||||
|
}
|
||||||
|
self.is_lb_stopped_or_will_be_stopped: None | bool = None
|
||||||
|
|
||||||
|
if arg_amodule.params['lb_id']:
|
||||||
|
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id'])
|
||||||
|
if not self.lb_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = "Specified LB ID {} not found."\
|
||||||
|
.format(arg_amodule.params['lb _id'])
|
||||||
|
self.fail_json(**self.result)
|
||||||
|
self.rg_id = self.lb_facts['rgId']
|
||||||
|
self.vins_id = self.lb_facts['vinsId']
|
||||||
|
|
||||||
|
elif arg_amodule.params['rg_id']:
|
||||||
|
self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="")
|
||||||
|
if not self.rg_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id'])
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
self.acc_id = self.rg_facts['accountId']
|
||||||
|
|
||||||
|
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
|
||||||
|
|
||||||
|
if not arg_amodule.params['rg_name']:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = ("RG name must be specified with account present")
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
self.acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'],
|
||||||
|
arg_amodule.params['account_id'])
|
||||||
|
if not self.acc_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = ("Current user does not have access to the requested account "
|
||||||
|
"or non-existent account specified.")
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
self.rg_id, self.rg_facts = self.rg_find(self.acc_id,0, arg_rg_name=arg_amodule.params['rg_name'])
|
||||||
|
|
||||||
|
if arg_amodule.params['vins_id']:
|
||||||
|
self.vins_id, self.vins_facts = self.vins_find(
|
||||||
|
vins_id=arg_amodule.params['vins_id']
|
||||||
|
)
|
||||||
|
if not self.vins_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = (
|
||||||
|
f'Specified ViNS ID {arg_amodule.params["vins_id"]}'
|
||||||
|
f' not found'
|
||||||
|
)
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
elif arg_amodule.params['vins_name']:
|
||||||
|
self.vins_id, self.vins_facts = self.vins_find(
|
||||||
|
vins_id=arg_amodule.params['vins_id'],
|
||||||
|
vins_name=arg_amodule.params['vins_name'],
|
||||||
|
rg_id=self.rg_id)
|
||||||
|
if not self.vins_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = (
|
||||||
|
f'Specified ViNS name {arg_amodule.params["vins_name"]}'
|
||||||
|
f' not found in RG ID {self.rg_id}'
|
||||||
|
)
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
|
||||||
|
if self.rg_id and arg_amodule.params['lb_name']:
|
||||||
|
self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id)
|
||||||
|
|
||||||
|
if self.lb_id and self.lb_facts['status'] != 'DESTROYED':
|
||||||
|
self.acc_id = self.lb_facts['accountId']
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
else:
|
||||||
|
self.check_amodule_args_for_create()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
start_after_create = self.aparams['state'] != 'stopped'
|
||||||
|
self.lb_id = self.lb_provision(self.amodule.params['lb_name'],
|
||||||
|
self.rg_id,self.vins_id,
|
||||||
|
self.amodule.params['ext_net_id'],
|
||||||
|
self.amodule.params['ha_lb'],
|
||||||
|
self.amodule.params['description'],
|
||||||
|
sysctl=self.amodule.params['sysctl'],
|
||||||
|
zone_id=self.aparams['zone_id'],
|
||||||
|
start=start_after_create,
|
||||||
|
)
|
||||||
|
if self.lb_id and (self.amodule.params['backends'] or
|
||||||
|
self.amodule.params['frontends']):
|
||||||
|
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id)
|
||||||
|
self.lb_update(
|
||||||
|
lb_facts=self.lb_facts,
|
||||||
|
aparam_backends=self.amodule.params['backends'],
|
||||||
|
aparam_frontends=self.amodule.params['frontends'],
|
||||||
|
aparam_servers=self.amodule.params['servers'],
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
def action(self,d_state='',restore=False):
|
||||||
|
if restore == True:
|
||||||
|
self.lb_restore(lb_id=self.lb_id)
|
||||||
|
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
|
||||||
|
self.lb_state(self.lb_facts, 'enabled')
|
||||||
|
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
|
||||||
|
|
||||||
|
self.lb_update(
|
||||||
|
lb_facts=self.lb_facts,
|
||||||
|
aparam_backends=self.amodule.params['backends'],
|
||||||
|
aparam_frontends=self.amodule.params['frontends'],
|
||||||
|
aparam_servers=self.amodule.params['servers'],
|
||||||
|
aparam_sysctl=self.aparams['sysctl'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if d_state != '':
|
||||||
|
self.lb_state(self.lb_facts, d_state)
|
||||||
|
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
|
||||||
|
|
||||||
|
if (d_state == 'enabled' and
|
||||||
|
self.lb_facts.get('status') == 'ENABLED' and
|
||||||
|
self.lb_facts.get('techStatus') == 'STOPPED'):
|
||||||
|
self.lb_state(self.lb_facts, 'started')
|
||||||
|
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
|
||||||
|
|
||||||
|
aparam_zone_id = self.aparams['zone_id']
|
||||||
|
if aparam_zone_id is not None and aparam_zone_id != self.lb_facts['zoneId']:
|
||||||
|
self.lb_migrate_to_zone(
|
||||||
|
lb_id=self.lb_id,
|
||||||
|
zone_id=aparam_zone_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.lb_delete(self.lb_id, self.amodule.params['permanently'])
|
||||||
|
self.lb_facts['status'] = 'DESTROYED'
|
||||||
|
return
|
||||||
|
def nop(self):
|
||||||
|
"""No operation (NOP) handler for LB management by decort_lb module.
|
||||||
|
This function is intended to be called from the main switch construct of the module
|
||||||
|
when current state -> desired state change logic does not require any changes to
|
||||||
|
the actual LB state.
|
||||||
|
"""
|
||||||
|
self.result['failed'] = False
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.lb_id:
|
||||||
|
self.result['msg'] = ("No state change required for LB ID {} because of its "
|
||||||
|
"current status '{}'.").format(self.lb_id, self.lb_facts['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("No state change to '{}' can be done for "
|
||||||
|
"non-existent LB instance.").format(self.amodule.params['state'])
|
||||||
|
return
|
||||||
|
def error(self):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.vins_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the "
|
||||||
|
"current status '{}'").format(self.lb_id,
|
||||||
|
self.amodule.params['state'],
|
||||||
|
self.lb_facts['status'])
|
||||||
|
else:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
||||||
|
"LB name '{}'").format(self.amodule.params['state'],
|
||||||
|
self.amodule.params['lb_name'])
|
||||||
|
return
|
||||||
|
def package_facts(self, arg_check_mode=False):
|
||||||
|
"""Package a dictionary of LB facts according to the decort_lb module specification.
|
||||||
|
This dictionary will be returned to the upstream Ansible engine at the completion of
|
||||||
|
the module run.
|
||||||
|
|
||||||
|
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret_dict = dict(id=0,
|
||||||
|
name="none",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
sysctl={},
|
||||||
|
)
|
||||||
|
|
||||||
|
if arg_check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
if self.lb_facts is None:
|
||||||
|
# if void facts provided - change state value to ABSENT and return
|
||||||
|
ret_dict['state'] = "ABSENT"
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
ret_dict['id'] = self.lb_facts['id']
|
||||||
|
ret_dict['name'] = self.lb_facts['name']
|
||||||
|
ret_dict['state'] = self.lb_facts['status']
|
||||||
|
ret_dict['account_id'] = self.lb_facts['accountId']
|
||||||
|
ret_dict['rg_id'] = self.lb_facts['rgId']
|
||||||
|
ret_dict['gid'] = self.lb_facts['gid']
|
||||||
|
if self.amodule.params['state']!="absent":
|
||||||
|
ret_dict['backends'] = self.lb_facts['backends']
|
||||||
|
ret_dict['frontends'] = self.lb_facts['frontends']
|
||||||
|
ret_dict['sysctl'] = self.lb_facts['sysctlParams']
|
||||||
|
ret_dict['zone_id'] = self.lb_facts['zoneId']
|
||||||
|
ret_dict['tech_status'] = self.lb_facts['techStatus']
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
description=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
ext_net_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
ext_ip_addr=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'disabled',
|
||||||
|
'enabled',
|
||||||
|
'present',
|
||||||
|
'restart',
|
||||||
|
'started',
|
||||||
|
'stopped',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
rg_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
vins_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
vins_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
lb_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
lb_name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
ha_lb=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
backends=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
frontends=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
servers=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
permanently=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
sysctl=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
zone_id=dict(
|
||||||
|
type=int,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=[
|
||||||
|
('rg_id', 'rg_name'),
|
||||||
|
('lb_id', 'lb_name'),
|
||||||
|
('vins_id', 'vins_name'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_errors = False
|
||||||
|
|
||||||
|
lb_info: dict = self.lb_facts
|
||||||
|
self.is_lb_stopped_or_will_be_stopped = (
|
||||||
|
(
|
||||||
|
lb_info['techStatus'] == 'STOPPED'
|
||||||
|
and (
|
||||||
|
self.aparams['state'] is None
|
||||||
|
or self.aparams['state'] in ('present', 'stopped')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
lb_info['techStatus'] != 'STOPPED'
|
||||||
|
and self.aparams['state'] == 'stopped'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
if (
|
||||||
|
self.aparams['zone_id'] is not None
|
||||||
|
and self.aparams['zone_id'] != lb_info['zoneId']
|
||||||
|
and not self.is_lb_stopped_or_will_be_stopped
|
||||||
|
):
|
||||||
|
check_errors = True
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "zone_id" failed: '
|
||||||
|
'Load balancer must be stopped to migrate to a zone.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def check_amodule_args_for_create(self):
|
||||||
|
check_errors = False
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
decon = decort_lb()
|
||||||
|
amodule = decon.amodule
|
||||||
|
if decon.lb_id:
|
||||||
|
if decon.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]:
|
||||||
|
decon.result['failed'] = True
|
||||||
|
decon.result['changed'] = False
|
||||||
|
decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current "
|
||||||
|
"status '{}'").format(decon.lb_id, decon.lb_facts['status'])
|
||||||
|
elif decon.lb_facts['status'] in ('DISABLED', 'ENABLED', 'CREATED'):
|
||||||
|
if amodule.params['state'] == 'absent':
|
||||||
|
decon.delete()
|
||||||
|
else:
|
||||||
|
decon.action(d_state=amodule.params['state'])
|
||||||
|
elif decon.lb_facts['status'] == "DELETED":
|
||||||
|
if amodule.params['state'] == 'present':
|
||||||
|
decon.action(restore=True)
|
||||||
|
elif amodule.params['state'] == 'enabled':
|
||||||
|
decon.action(d_state='enabled', restore=True)
|
||||||
|
elif (amodule.params['state'] == 'absent' and
|
||||||
|
amodule.params['permanently']):
|
||||||
|
decon.delete()
|
||||||
|
elif amodule.params['state'] == 'disabled':
|
||||||
|
decon.error()
|
||||||
|
elif decon.lb_facts['status'] == "DESTROYED":
|
||||||
|
if amodule.params['state'] in ('present', 'enabled'):
|
||||||
|
decon.create()
|
||||||
|
elif amodule.params['state'] == 'absent':
|
||||||
|
decon.nop()
|
||||||
|
elif amodule.params['state'] == 'disabled':
|
||||||
|
decon.error()
|
||||||
|
else:
|
||||||
|
state = amodule.params['state']
|
||||||
|
if state is None:
|
||||||
|
state = 'present'
|
||||||
|
if state == 'absent':
|
||||||
|
decon.nop()
|
||||||
|
elif state in ('present', 'enabled', 'stopped', 'started'):
|
||||||
|
decon.create()
|
||||||
|
elif state == 'disabled':
|
||||||
|
decon.error()
|
||||||
|
|
||||||
|
if decon.result['failed']:
|
||||||
|
amodule.fail_json(**decon.result)
|
||||||
|
else:
|
||||||
|
if decon.result['changed'] and amodule.params['state'] != 'absent':
|
||||||
|
_, decon.lb_facts = decon.lb_find(decon.lb_id)
|
||||||
|
if decon.lb_id:
|
||||||
|
decon.result['facts'] = decon.package_facts(amodule.check_mode)
|
||||||
|
amodule.exit_json(**decon.result)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,169 +1,10 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
|
||||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
|
||||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
|
||||||
#
|
|
||||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
DOCUMENTATION = r'''
|
||||||
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
---
|
||||||
module: decort_osimage
|
module: decort_osimage
|
||||||
short_description: Locate OS image in DCORT cloud by its name and return image ID
|
|
||||||
description: >
|
|
||||||
This module can be used to obtain image ID of an OS image in DECORT cloud to use with subsequent calls to
|
|
||||||
decort_vm module for batch VM provisioning. It will speed up VM creation and save a bunch of extra calls to
|
|
||||||
DECORT cloud controller on each VM creation act.
|
|
||||||
Note that this module is effectively an information provisioner. It is not designed to and does not manage
|
|
||||||
nor change state of OS image (or any other) objects in DECORT cloud.
|
|
||||||
version_added: "2.2"
|
|
||||||
author:
|
|
||||||
- Sergey Shubin <sergey.shubin@digitalenergy.online>
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- PyJWT Python module
|
|
||||||
- requests Python module
|
|
||||||
- netaddr Python module
|
|
||||||
- decort_utils utility library (module)
|
|
||||||
- DECORT cloud platform version 3.6.1 or higher.
|
|
||||||
notes:
|
|
||||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
|
||||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
|
||||||
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
|
|
||||||
the DECORT cloud controller on which this JWT will be used.'
|
|
||||||
options:
|
|
||||||
app_id:
|
|
||||||
description:
|
|
||||||
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- 'Required if I(authenticator=oauth2).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
app_secret:
|
|
||||||
description:
|
|
||||||
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
authenticator:
|
|
||||||
description:
|
|
||||||
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
|
|
||||||
default: jwt
|
|
||||||
choices: [ jwt, oauth2, legacy ]
|
|
||||||
required: yes
|
|
||||||
controller_url:
|
|
||||||
description:
|
|
||||||
- URL of the DECORT controller that will be contacted to obtain OS image details.
|
|
||||||
- 'This parameter is always required regardless of the specified I(authenticator) type.'
|
|
||||||
required: yes
|
|
||||||
image_name:
|
|
||||||
description:
|
|
||||||
- Name of the OS image to use. Module will return the ID of this image.
|
|
||||||
- 'The specified image name will be looked up in the target DECORT controller and error will be generated if
|
|
||||||
no matching image is found.'
|
|
||||||
required: yes
|
|
||||||
jwt:
|
|
||||||
description:
|
|
||||||
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
|
|
||||||
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
|
|
||||||
required: no
|
|
||||||
oauth2_url:
|
|
||||||
description:
|
|
||||||
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
|
|
||||||
- 'This parameter is required when when I(authenticator=oauth2).'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
|
|
||||||
required: no
|
|
||||||
pool:
|
|
||||||
description:
|
|
||||||
- 'Name of the storage pool, where the image should be found.'
|
|
||||||
- 'Omit this option if no matching by pool name is required. The first matching image will be returned."
|
|
||||||
required: no
|
|
||||||
sep_id:
|
|
||||||
description:
|
|
||||||
- 'ID of the SEP (Storage End-point Provider), where the image should be found.'
|
|
||||||
- 'Omit this option if no matching by SEP ID is required. The first matching image will be returned."
|
|
||||||
required: no
|
|
||||||
account_name:
|
|
||||||
description:
|
|
||||||
- 'Name of the account for which the specified OS image will be looked up.'
|
|
||||||
- 'This parameter is required for listing OS images.'
|
|
||||||
required: yes
|
|
||||||
user:
|
|
||||||
description:
|
|
||||||
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
|
|
||||||
required: no
|
|
||||||
vdc_id:
|
|
||||||
description:
|
|
||||||
- ID of the VDC to limit the search of the OS image to.
|
|
||||||
required: no
|
|
||||||
verify_ssl:
|
|
||||||
description:
|
|
||||||
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
|
|
||||||
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
|
|
||||||
environment that uses self-signed certificates. Note that disabling SSL verification in any other
|
|
||||||
scenario can lead to security issues, so please know what you are doing.'
|
|
||||||
default: True
|
|
||||||
required: no
|
|
||||||
workflow_callback:
|
|
||||||
description:
|
|
||||||
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
|
|
||||||
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
|
|
||||||
- API call at this URL will be used to relay such information to the application.
|
|
||||||
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
|
|
||||||
required: no
|
|
||||||
workflow_context:
|
|
||||||
description:
|
|
||||||
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
|
|
||||||
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
|
|
||||||
that up-level orchestrator could match returned information to the its internal entities.'
|
|
||||||
required: no
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
- name: locate OS image specified by its name, store result in image_to_use variable.
|
|
||||||
decort_osimage:
|
|
||||||
authenticator: oauth2
|
|
||||||
app_id: "{{ MY_APP_ID }}"
|
|
||||||
app_secret: "{{ MY_APP_SECRET }}"
|
|
||||||
controller_url: "https://ds1.digitalenergy.online"
|
|
||||||
image_name: "Ubuntu 18.04 v1.2.5"
|
|
||||||
account_name: "GreyseDevelopment"
|
|
||||||
delegate_to: localhost
|
|
||||||
register: image_to_use
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
facts:
|
|
||||||
description: facts about the specified OS image
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
facts:
|
|
||||||
id: 100
|
|
||||||
name: "Ubuntu 16.04 v1.0"
|
|
||||||
size: 3
|
|
||||||
sep_id: 1
|
|
||||||
pool: "vmstore"
|
|
||||||
type: Linux
|
|
||||||
arch: x86_64
|
|
||||||
state: CREATED
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
@@ -172,131 +13,388 @@ from ansible.module_utils.basic import env_fallback
|
|||||||
from ansible.module_utils.decort_utils import *
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False):
|
class decort_osimage(DecortController):
|
||||||
"""Package a dictionary of OS image according to the decort_osimage module specification. This
|
def __init__(self):
|
||||||
dictionary will be returned to the upstream Ansible engine at the completion of the module run.
|
super(decort_osimage, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
amodule = self.amodule
|
||||||
|
|
||||||
@param arg_osimage_facts: dictionary with OS image facts as returned by API call to .../images/list
|
self.validated_image_id = 0
|
||||||
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode.
|
self.validated_virt_image_id = 0
|
||||||
|
self.validated_image_name = amodule.params['image_name']
|
||||||
|
self.validated_virt_image_name = None
|
||||||
|
self.validated_virt_image_id = amodule.params['virt_id']
|
||||||
|
if amodule.params['account_name']:
|
||||||
|
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
|
||||||
|
else:
|
||||||
|
self.validated_account_id = amodule.params['account_id']
|
||||||
|
|
||||||
@return: dictionary with OS image specs populated from arg_osimage_facts.
|
if self.validated_account_id == 0:
|
||||||
"""
|
# we failed either to find or access the specified account - fail the module
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
|
||||||
|
amodule.fail_json(**self.result)
|
||||||
|
|
||||||
ret_dict = dict(id=0,
|
|
||||||
name="none",
|
|
||||||
size=0,
|
|
||||||
type="none",
|
|
||||||
state="CHECK_MODE",
|
|
||||||
)
|
|
||||||
|
|
||||||
if arg_check_mode:
|
if amodule.params['virt_id'] != 0 and amodule.params['virt_name']:
|
||||||
# in check mode return immediately with the default values
|
self.validated_virt_image_id, image_facts =\
|
||||||
|
self.decort_virt_image_find(amodule)
|
||||||
|
if (self.validated_virt_image_id and
|
||||||
|
amodule.params['virt_name'] != image_facts['name']):
|
||||||
|
self.decort_virt_image_rename(amodule)
|
||||||
|
self.result['msg'] = 'Virtual image renamed successfully'
|
||||||
|
elif amodule.params['image_id'] != 0 and amodule.params['image_name']:
|
||||||
|
self.validated_image_id, image_facts = self.decort_image_find(amodule)
|
||||||
|
if (self.validated_image_id and
|
||||||
|
amodule.params['image_name'] != image_facts['name']):
|
||||||
|
decort_osimage.decort_image_rename(self,amodule)
|
||||||
|
self.result['msg'] = ("Image renamed successfully")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def decort_image_find(self, amodule):
|
||||||
|
# function that finds the OS image
|
||||||
|
image_id, image_facts = self.image_find(image_id=amodule.params['image_id'], image_name=self.validated_image_name,
|
||||||
|
account_id=self.validated_account_id, rg_id=0,
|
||||||
|
sepid=amodule.params['sep_id'],
|
||||||
|
pool=amodule.params['pool'])
|
||||||
|
return image_id, image_facts
|
||||||
|
|
||||||
|
def decort_virt_image_find(self, amodule):
|
||||||
|
# function that finds a virtual image
|
||||||
|
image_id, image_facts = self.virt_image_find(image_id=amodule.params['virt_id'],
|
||||||
|
account_id=self.validated_account_id, rg_id=0,
|
||||||
|
sepid=amodule.params['sep_id'],
|
||||||
|
virt_name=amodule.params['virt_name'],
|
||||||
|
pool=amodule.params['pool'])
|
||||||
|
return image_id, image_facts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def decort_image_create(self,amodule):
|
||||||
|
aparam_boot = self.aparams['boot']
|
||||||
|
boot_mode = 'bios'
|
||||||
|
loader_type = 'unknown'
|
||||||
|
if aparam_boot is not None:
|
||||||
|
if aparam_boot['mode'] is None:
|
||||||
|
self.message(
|
||||||
|
msg=self.MESSAGES.default_value_used(
|
||||||
|
param_name='boot.mode',
|
||||||
|
default_value=boot_mode
|
||||||
|
),
|
||||||
|
warning=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
boot_mode = aparam_boot['mode']
|
||||||
|
|
||||||
|
if aparam_boot['loader_type'] is None:
|
||||||
|
self.message(
|
||||||
|
msg=self.MESSAGES.default_value_used(
|
||||||
|
param_name='boot.loader_type',
|
||||||
|
default_value=loader_type
|
||||||
|
),
|
||||||
|
warning=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
loader_type = aparam_boot['loader_type']
|
||||||
|
|
||||||
|
network_interface_naming = self.aparams['network_interface_naming']
|
||||||
|
if network_interface_naming is None:
|
||||||
|
network_interface_naming = 'ens'
|
||||||
|
self.message(
|
||||||
|
msg=self.MESSAGES.default_value_used(
|
||||||
|
param_name='network_interface_naming',
|
||||||
|
default_value=network_interface_naming
|
||||||
|
),
|
||||||
|
warning=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
hot_resize = self.aparams['hot_resize']
|
||||||
|
if hot_resize is None:
|
||||||
|
hot_resize = False
|
||||||
|
self.message(
|
||||||
|
msg=self.MESSAGES.default_value_used(
|
||||||
|
param_name='hot_resize',
|
||||||
|
default_value=hot_resize
|
||||||
|
),
|
||||||
|
warning=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# function that creates OS image
|
||||||
|
image_facts = self.image_create(img_name=self.validated_image_name,
|
||||||
|
url=amodule.params['url'],
|
||||||
|
gid=amodule.params['gid'],
|
||||||
|
boot_mode=boot_mode,
|
||||||
|
boot_loader_type=loader_type,
|
||||||
|
hot_resize=hot_resize,
|
||||||
|
username=amodule.params['image_username'],
|
||||||
|
password=amodule.params['image_password'],
|
||||||
|
account_id=self.validated_account_id,
|
||||||
|
usernameDL=amodule.params['usernameDL'],
|
||||||
|
passwordDL=amodule.params['passwordDL'],
|
||||||
|
sepId=amodule.params['sepId'],
|
||||||
|
poolName=amodule.params['poolName'],
|
||||||
|
drivers=amodule.params['drivers'],
|
||||||
|
network_interface_naming=network_interface_naming)
|
||||||
|
self.result['changed'] = True
|
||||||
|
return image_facts
|
||||||
|
|
||||||
|
def decort_virt_image_link(self,amodule):
|
||||||
|
# function that links an OS image to a virtual one
|
||||||
|
self.virt_image_link(imageId=self.validated_virt_image_id, targetId=self.target_image_id)
|
||||||
|
image_id, image_facts = decort_osimage.decort_virt_image_find(self, amodule)
|
||||||
|
self.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
|
||||||
|
self.result['msg'] = ("Image '{}' linked to virtual image '{}'").format(self.target_image_id,
|
||||||
|
decort_osimage.decort_osimage_package_facts(image_facts)['id'],)
|
||||||
|
return image_id, image_facts
|
||||||
|
|
||||||
|
def decort_image_delete(self,amodule):
|
||||||
|
# function that removes an image
|
||||||
|
self.image_delete(imageId=amodule.image_id_delete)
|
||||||
|
self.result['changed'] = True
|
||||||
|
self.result['msg'] = ("Image '{}' deleted").format(amodule.image_id_delete)
|
||||||
|
|
||||||
|
def decort_virt_image_create(self,amodule):
|
||||||
|
# function that creates a virtual image
|
||||||
|
image_facts = self.virt_image_create(
|
||||||
|
name=amodule.params['virt_name'],
|
||||||
|
target_id=self.target_image_id,
|
||||||
|
account_id=self.aparams['account_id'],
|
||||||
|
)
|
||||||
|
image_id, image_facts = decort_osimage.decort_virt_image_find(self, amodule)
|
||||||
|
self.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
|
||||||
|
return image_id, image_facts
|
||||||
|
|
||||||
|
def decort_image_rename(self,amodule):
|
||||||
|
# image renaming function
|
||||||
|
image_facts = self.image_rename(imageId=self.validated_image_id, name=amodule.params['image_name'])
|
||||||
|
self.result['msg'] = ("Image renamed successfully")
|
||||||
|
image_id, image_facts = decort_osimage.decort_image_find(self, amodule)
|
||||||
|
return image_id, image_facts
|
||||||
|
|
||||||
|
def decort_virt_image_rename(self, amodule):
|
||||||
|
image_facts = self.image_rename(imageId=self.validated_virt_image_id,
|
||||||
|
name=amodule.params['virt_name'])
|
||||||
|
self.result['msg'] = ("Virtual image renamed successfully")
|
||||||
|
image_id, image_facts = self.decort_virt_image_find(amodule)
|
||||||
|
return image_id, image_facts
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decort_osimage_package_facts(
|
||||||
|
arg_osimage_facts: dict | None,
|
||||||
|
arg_check_mode=False,
|
||||||
|
):
|
||||||
|
"""Package a dictionary of OS image according to the decort_osimage module specification. This
|
||||||
|
dictionary will be returned to the upstream Ansible engine at the completion of the module run.
|
||||||
|
|
||||||
|
@param arg_osimage_facts: dictionary with OS image facts as returned by API call to .../images/list
|
||||||
|
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode.
|
||||||
|
|
||||||
|
@return: dictionary with OS image specs populated from arg_osimage_facts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret_dict = dict(id=0,
|
||||||
|
name="none",
|
||||||
|
size=0,
|
||||||
|
type="none",
|
||||||
|
state="CHECK_MODE", )
|
||||||
|
|
||||||
|
if arg_check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
if arg_osimage_facts is None:
|
||||||
|
# if void facts provided - change state value to ABSENT and return
|
||||||
|
ret_dict['state'] = "ABSENT"
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
ret_dict['id'] = arg_osimage_facts['id']
|
||||||
|
ret_dict['name'] = arg_osimage_facts['name']
|
||||||
|
ret_dict['size'] = arg_osimage_facts['size']
|
||||||
|
# ret_dict['arch'] = arg_osimage_facts['architecture']
|
||||||
|
ret_dict['sep_id'] = arg_osimage_facts['sepId']
|
||||||
|
ret_dict['pool'] = arg_osimage_facts['pool']
|
||||||
|
ret_dict['state'] = arg_osimage_facts['status']
|
||||||
|
ret_dict['linkto'] = arg_osimage_facts['linkTo']
|
||||||
|
ret_dict['accountId'] = arg_osimage_facts['accountId']
|
||||||
|
ret_dict['boot_mode'] = arg_osimage_facts['bootType']
|
||||||
|
|
||||||
|
ret_dict['boot_loader_type'] = ''
|
||||||
|
match arg_osimage_facts['type']:
|
||||||
|
case 'cdrom' | 'virtual' as type:
|
||||||
|
ret_dict['type'] = type
|
||||||
|
case _ as boot_loader_type:
|
||||||
|
ret_dict['type'] = 'template'
|
||||||
|
ret_dict['boot_loader_type'] = boot_loader_type
|
||||||
|
|
||||||
|
ret_dict['network_interface_naming'] = arg_osimage_facts[
|
||||||
|
'networkInterfaceNaming'
|
||||||
|
]
|
||||||
|
ret_dict['hot_resize'] = arg_osimage_facts['hotResize']
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
if arg_osimage_facts is None:
|
@property
|
||||||
# if void facts provided - change state value to ABSENT and return
|
def amodule_init_args(self) -> dict:
|
||||||
ret_dict['state'] = "ABSENT"
|
return self.pack_amodule_init_args(
|
||||||
return ret_dict
|
argument_spec=dict(
|
||||||
|
pool=dict(
|
||||||
ret_dict['id'] = arg_osimage_facts['id']
|
type='str',
|
||||||
ret_dict['name'] = arg_osimage_facts['name']
|
default='',
|
||||||
ret_dict['size'] = arg_osimage_facts['size']
|
),
|
||||||
ret_dict['type'] = arg_osimage_facts['type']
|
sep_id=dict(
|
||||||
# ret_dict['arch'] = arg_osimage_facts['architecture']
|
type='int',
|
||||||
ret_dict['sep_id'] = arg_osimage_facts['sepId']
|
default=0,
|
||||||
ret_dict['pool'] = arg_osimage_facts['pool']
|
),
|
||||||
ret_dict['state'] = arg_osimage_facts['status']
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
return ret_dict
|
),
|
||||||
|
account_id=dict(
|
||||||
def decort_osimage_parameters():
|
type='int',
|
||||||
"""Build and return a dictionary of parameters expected by decort_osimage module in a form accepted
|
),
|
||||||
by AnsibleModule utility class."""
|
image_name=dict(
|
||||||
|
type='str',
|
||||||
return dict(
|
),
|
||||||
app_id=dict(type='str',
|
image_id=dict(
|
||||||
required=False,
|
type='int',
|
||||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
default=0,
|
||||||
app_secret=dict(type='str',
|
),
|
||||||
required=False,
|
virt_id=dict(
|
||||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
type='int',
|
||||||
no_log=True),
|
default=0,
|
||||||
authenticator=dict(type='str',
|
),
|
||||||
required=True,
|
virt_name=dict(
|
||||||
choices=['legacy', 'oauth2', 'jwt']),
|
type='str',
|
||||||
controller_url=dict(type='str', required=True),
|
),
|
||||||
image_name=dict(type='str', required=True),
|
state=dict(
|
||||||
jwt=dict(type='str',
|
type='str',
|
||||||
required=False,
|
default='present',
|
||||||
fallback=(env_fallback, ['DECORT_JWT']),
|
choices=[
|
||||||
no_log=True),
|
'absent',
|
||||||
oauth2_url=dict(type='str',
|
'present',
|
||||||
required=False,
|
],
|
||||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
),
|
||||||
password=dict(type='str',
|
drivers=dict(
|
||||||
required=False,
|
type='str',
|
||||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
default='KVM_X86',
|
||||||
no_log=True),
|
),
|
||||||
pool=dict(type='str', required=False, default=""),
|
url=dict(
|
||||||
sep_id=dict(type='int', required=False, default=0),
|
type='str',
|
||||||
account_name=dict(type='str', required=True),
|
),
|
||||||
user=dict(type='str',
|
gid=dict(
|
||||||
required=False,
|
type='int',
|
||||||
fallback=(env_fallback, ['DECORT_USER'])),
|
default=0,
|
||||||
vdc_id=dict(type='int', required=False, default=0),
|
),
|
||||||
verify_ssl=dict(type='bool', required=False, default=True),
|
sepId=dict(
|
||||||
workflow_callback=dict(type='str', required=False),
|
type='int',
|
||||||
workflow_context=dict(type='str', required=False),
|
default=0,
|
||||||
)
|
),
|
||||||
|
poolName=dict(
|
||||||
# Workflow digest:
|
type='str',
|
||||||
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when
|
),
|
||||||
# creating DecortController
|
hot_resize=dict(
|
||||||
# 2) obtain a list of OS images accessible to the specified account (and optionally - within
|
type='bool',
|
||||||
# the specified VDC)
|
),
|
||||||
# 3) match specified OS image by its name - if image is not found abort the module
|
image_username=dict(
|
||||||
# 5) report result to Ansible
|
type='str',
|
||||||
|
),
|
||||||
|
image_password=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
usernameDL=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
passwordDL=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
boot=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
mode=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'bios',
|
||||||
|
'uefi',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
loader_type=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'windows',
|
||||||
|
'linux',
|
||||||
|
'unknown',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
network_interface_naming=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'ens',
|
||||||
|
'eth',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module_parameters = decort_osimage_parameters()
|
decon = decort_osimage()
|
||||||
|
amodule = decon.amodule
|
||||||
|
if amodule.params['virt_name'] or amodule.params['virt_id']:
|
||||||
|
|
||||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule)
|
||||||
supports_check_mode=True,
|
if amodule.params['image_name'] or amodule.params['image_id']:
|
||||||
mutually_exclusive=[
|
decon.target_image_id, _ = decort_osimage.decort_image_find(decon, amodule)
|
||||||
['oauth2', 'password'],
|
else:
|
||||||
['password', 'jwt'],
|
decon.target_image_id = 0
|
||||||
['jwt', 'oauth2'],
|
if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0:
|
||||||
],
|
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
|
||||||
required_together=[
|
decon.validated_virt_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
|
||||||
['app_id', 'app_secret'],
|
decon.validated_virt_image_name = decort_osimage.decort_osimage_package_facts(image_facts)['name']
|
||||||
['user', 'password'],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
decon = DecortController(amodule)
|
if decort_osimage.decort_osimage_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and decon.target_image_id > 0:
|
||||||
|
image_id, image_facts = decort_osimage.decort_virt_image_create(decon,amodule)
|
||||||
|
decon.result['msg'] = ("Virtual image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id'])
|
||||||
|
decon.result['changed'] = True
|
||||||
|
elif decort_osimage.decort_osimage_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and decon.target_image_id == 0:
|
||||||
|
decon.result['msg'] = ("Cannot find OS image")
|
||||||
|
amodule.fail_json(**decon.result)
|
||||||
|
|
||||||
# we need account ID to locate OS images - find the account by the specified name and get its ID
|
if decon.validated_virt_image_id and decon.target_image_id:
|
||||||
validated_account_id, _ = decon.account_find(amodule.params['account_name'])
|
if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.target_image_id:
|
||||||
if validated_account_id == 0:
|
decort_osimage.decort_virt_image_link(decon,amodule)
|
||||||
# we failed either to find or access the specified account - fail the module
|
decon.result['changed'] = True
|
||||||
decon.result['failed'] = True
|
amodule.exit_json(**decon.result)
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
|
if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent":
|
||||||
amodule.fail_json(**decon.result)
|
amodule.image_id_delete = decon.validated_virt_image_id
|
||||||
|
decort_osimage.decort_image_delete(decon, amodule)
|
||||||
|
|
||||||
|
elif amodule.params['image_name'] or amodule.params['image_id']:
|
||||||
|
image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
|
||||||
|
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
|
||||||
|
if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0:
|
||||||
|
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
|
||||||
|
|
||||||
|
if amodule.params['state'] == "present" and decon.validated_image_id == 0 and amodule.params['image_name'] and amodule.params['url']:
|
||||||
|
decort_osimage.decort_image_create(decon,amodule)
|
||||||
|
decon.result['changed'] = True
|
||||||
|
image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
|
||||||
|
decon.result['msg'] = ("OS image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id'])
|
||||||
|
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
|
||||||
|
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
|
||||||
|
|
||||||
|
elif amodule.params['state'] == "absent" and decon.validated_image_id:
|
||||||
|
amodule.image_id_delete = decon.validated_image_id
|
||||||
|
decort_osimage.decort_image_delete(decon,amodule)
|
||||||
|
|
||||||
image_id, image_facts = decon.image_find(image_id=0, image_name=amodule.params['image_name'],
|
|
||||||
account_id=validated_account_id, rg_id=0,
|
|
||||||
sepid=amodule.params['sep_id'],
|
|
||||||
pool=amodule.params['pool'])
|
|
||||||
if decon.result['failed'] == True:
|
if decon.result['failed'] == True:
|
||||||
# we failed to find the specified image - fail the module
|
# we failed to find the specified image - fail the module
|
||||||
decon.result['changed'] = False
|
decon.result['changed'] = False
|
||||||
amodule.fail_json(**decon.result)
|
amodule.fail_json(**decon.result)
|
||||||
|
|
||||||
decon.result['facts'] = decort_osimage_package_facts(image_facts, amodule.check_mode)
|
|
||||||
decon.result['changed'] = False # decort_osimage is a read-only module - make sure the 'changed' flag is set to False
|
|
||||||
amodule.exit_json(**decon.result)
|
amodule.exit_json(**decon.result)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,283 +1,94 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
|
||||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
|
||||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
|
||||||
#
|
|
||||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
DOCUMENTATION = r'''
|
||||||
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
---
|
||||||
module: decort_pfw
|
module: decort_pfw
|
||||||
short_description: Manage network Port Forward rules for Compute instances in DECORT cloud
|
|
||||||
description: >
|
|
||||||
This module can be used to create new port forwarding rules in DECORT cloud platform,
|
|
||||||
modify and delete them.
|
|
||||||
version_added: "2.2"
|
|
||||||
author:
|
|
||||||
- Sergey Shubin <sergey.shubin@digitalenergy.online>
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- PyJWT Python module
|
|
||||||
- requests Python module
|
|
||||||
- netaddr Python module
|
|
||||||
- decort_utils utility library (module)
|
|
||||||
- DECORT cloud platform version 3.6.1 or higher
|
|
||||||
notes:
|
|
||||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
|
||||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
|
||||||
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
|
|
||||||
the DECORT cloud controller on which this JWT will be used.'
|
|
||||||
options:
|
|
||||||
account_id:
|
|
||||||
description:
|
|
||||||
- ID of the account, which owns this disk. This is the alternative to I(account_name) option.
|
|
||||||
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
|
|
||||||
default: 0
|
|
||||||
required: no
|
|
||||||
account_name:
|
|
||||||
description:
|
|
||||||
- 'Name of the account, which will own this disk.'
|
|
||||||
- 'This parameter is ignored if I(account_id) is specified.'
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
app_id:
|
|
||||||
description:
|
|
||||||
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- 'Required if I(authenticator=oauth2).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
app_secret:
|
|
||||||
description:
|
|
||||||
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
authenticator:
|
|
||||||
description:
|
|
||||||
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
|
|
||||||
default: jwt
|
|
||||||
choices: [ jwt, oauth2, legacy ]
|
|
||||||
required: yes
|
|
||||||
controller_url:
|
|
||||||
description:
|
|
||||||
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
|
|
||||||
- 'This parameter is always required regardless of the specified I(authenticator) type.'
|
|
||||||
required: yes
|
|
||||||
compute_id:
|
|
||||||
description:
|
|
||||||
- ID of the Compute instance to manage network port forwarding rules for.
|
|
||||||
required: yes
|
|
||||||
jwt:
|
|
||||||
description:
|
|
||||||
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
|
|
||||||
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
|
|
||||||
required: no
|
|
||||||
oauth2_url:
|
|
||||||
description:
|
|
||||||
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
|
|
||||||
- 'This parameter is required when when I(authenticator=oauth2).'
|
|
||||||
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
|
|
||||||
required: no
|
|
||||||
rules:
|
|
||||||
description:
|
|
||||||
- 'Set of rules to configure for the Compute instance identidied by I(compute_id) in the virtual
|
|
||||||
network segment identidied by I(vins_id).'
|
|
||||||
- The set is specified as a list of dictionaries with the following structure:
|
|
||||||
- ' - (int) public_port_start - starting port number on the ViNS external interface.'
|
|
||||||
- ' - (int) public_port_end - optional end port number of the ViNS external interface. If not specified
|
|
||||||
or set equal to I(public_port_start), a one-to-one rule is created. Otherwise a ranged rule will
|
|
||||||
be created, which maps specified external port range to local ports starting from I(local_port).'
|
|
||||||
- ' - (int) local_port - port number on the local interface of the Compute. For ranged rule it is
|
|
||||||
interpreted as a base port to translate public port range to internal port range.'
|
|
||||||
- ' - (string) proto - protocol, specify either I(tcp) or I(udp).'
|
|
||||||
- 'Note that rules are meaningful only if I(state=present). If I(state=absent) is specified, rules set
|
|
||||||
will be ignored, and all rules for the specified Compute will be deleted.'
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- 'Specify the desired state of the port forwarding rules set for the Compute instance identified by
|
|
||||||
I(compute_id).'
|
|
||||||
- 'If I(state=present), the rules will be applied according to the I(rules) parameter.'
|
|
||||||
- 'If I(state=absent), all rules for the specified Compute instance will be deleted regardless of
|
|
||||||
I(rules) parameter.'
|
|
||||||
default: present
|
|
||||||
choices: [ absent, present ]
|
|
||||||
verify_ssl:
|
|
||||||
description:
|
|
||||||
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
|
|
||||||
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
|
|
||||||
environment that uses self-signed certificates. Note that disabling SSL verification in any other
|
|
||||||
scenario can lead to security issues, so please know what you are doing.'
|
|
||||||
default: True
|
|
||||||
required: no
|
|
||||||
vins_id:
|
|
||||||
description:
|
|
||||||
- ID of the virtual network segment (ViNS), where port forwarding rules will be set up.
|
|
||||||
- This ViNS must have connection to external network.
|
|
||||||
- Compute instance specified by I(compute_id) must be connected to this ViNS.
|
|
||||||
workflow_callback:
|
|
||||||
description:
|
|
||||||
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
|
|
||||||
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
|
|
||||||
- API call at this URL will be used to relay such information to the application.
|
|
||||||
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
|
|
||||||
required: no
|
|
||||||
workflow_context:
|
|
||||||
description:
|
|
||||||
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
|
|
||||||
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
|
|
||||||
that up-level orchestrator could match returned information to the its internal entities.'
|
|
||||||
required: no
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
- name: configure one-toone rule for SSH protocol on Compute ID 100 connected to ViNS ID 5.
|
|
||||||
decort_pfw:
|
|
||||||
authenticator: oauth2
|
|
||||||
app_id: "{{ MY_APP_ID }}"
|
|
||||||
app_secret: "{{ MY_APP_SECRET }}"
|
|
||||||
controller_url: "https://cloud.digitalenergy.online"
|
|
||||||
compute_id: 100
|
|
||||||
vins_id: 5
|
|
||||||
rules:
|
|
||||||
- public_port_start: 10022
|
|
||||||
local_port: 22
|
|
||||||
proto: tcp
|
|
||||||
state: present
|
|
||||||
delegate_to: localhost
|
|
||||||
register: my_pfw
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
facts:
|
|
||||||
description: facts about created PFW rules
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
facts:
|
|
||||||
compute_id: 100
|
|
||||||
vins_id: 5
|
|
||||||
rules:
|
|
||||||
-
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.basic import env_fallback
|
|
||||||
|
|
||||||
from ansible.module_utils.decort_utils import *
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
def decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, check_mode=False):
|
class decort_pfw(DecortController):
|
||||||
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification.
|
def __init__(self):
|
||||||
This dictionary will be returned to the upstream Ansible engine at the completion of
|
super(decort_pfw, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
the module run.
|
|
||||||
|
|
||||||
@param (dict) pfw_facts: dictionary with PFW facts as returned by API call to .../???/get
|
@property
|
||||||
@param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
|
def amodule_init_args(self) -> dict:
|
||||||
"""
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
compute_id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
rules=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'present',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
vins_id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
ret_dict = dict(state="CHECK_MODE",
|
def decort_pfw_package_facts(self, comp_facts, vins_facts, pfw_facts, check_mode=False):
|
||||||
compute_id=0,
|
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification.
|
||||||
public_ip="",
|
This dictionary will be returned to the upstream Ansible engine at the completion of
|
||||||
rules=[],
|
the module run.
|
||||||
vins_id=0,
|
|
||||||
)
|
@param (dict) pfw_facts: dictionary with PFW facts as returned by API call to .../???/get
|
||||||
|
@param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret_dict = dict(state="CHECK_MODE",
|
||||||
|
compute_id=0,
|
||||||
|
public_ip="",
|
||||||
|
rules=[],
|
||||||
|
vins_id=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
if pfw_facts is None:
|
||||||
|
# if void facts provided - change state value to ABSENT and return
|
||||||
|
ret_dict['state'] = "ABSENT"
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
ret_dict['compute_id'] = comp_facts['id']
|
||||||
|
ret_dict['vins_id'] = vins_facts['id']
|
||||||
|
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip']
|
||||||
|
|
||||||
|
if len(pfw_facts) != 0:
|
||||||
|
ret_dict['state'] = 'PRESENT'
|
||||||
|
ret_dict['rules'] = pfw_facts
|
||||||
|
else:
|
||||||
|
ret_dict['state'] = 'ABSENT'
|
||||||
|
|
||||||
if check_mode:
|
|
||||||
# in check mode return immediately with the default values
|
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
if pfw_facts is None:
|
def decort_pfw_parameters(self):
|
||||||
# if void facts provided - change state value to ABSENT and return
|
"""Build and return a dictionary of parameters expected by decort_pfw module in a form accepted
|
||||||
ret_dict['state'] = "ABSENT"
|
by AnsibleModule utility class."""
|
||||||
return ret_dict
|
|
||||||
|
|
||||||
ret_dict['compute_id'] = comp_facts['id']
|
return
|
||||||
ret_dict['vins_id'] = vins_facts['id']
|
|
||||||
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip']
|
|
||||||
|
|
||||||
if len(pfw_facts) != 0:
|
|
||||||
ret_dict['state'] = 'PRESENT'
|
|
||||||
ret_dict['rules'] = pfw_facts
|
|
||||||
else:
|
|
||||||
ret_dict['state'] = 'ABSENT'
|
|
||||||
|
|
||||||
return ret_dict
|
|
||||||
|
|
||||||
def decort_pfw_parameters():
|
|
||||||
"""Build and return a dictionary of parameters expected by decort_pfw module in a form accepted
|
|
||||||
by AnsibleModule utility class."""
|
|
||||||
|
|
||||||
return dict(
|
|
||||||
app_id=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
|
||||||
app_secret=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
|
||||||
no_log=True),
|
|
||||||
authenticator=dict(type='str',
|
|
||||||
required=True,
|
|
||||||
choices=['legacy', 'oauth2', 'jwt']),
|
|
||||||
compute_id=dict(type='int', required=True),
|
|
||||||
controller_url=dict(type='str', required=True),
|
|
||||||
jwt=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_JWT']),
|
|
||||||
no_log=True),
|
|
||||||
oauth2_url=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
|
||||||
password=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
|
||||||
no_log=True),
|
|
||||||
rules=dict(type='list', required=False, default=[]),
|
|
||||||
state=dict(type='str',
|
|
||||||
default='present',
|
|
||||||
choices=['absent', 'present']),
|
|
||||||
user=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_USER'])),
|
|
||||||
verify_ssl=dict(type='bool', required=False, default=True),
|
|
||||||
vins_id=dict(type='int', required=True),
|
|
||||||
workflow_callback=dict(type='str', required=False),
|
|
||||||
workflow_context=dict(type='str', required=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module_parameters = decort_pfw_parameters()
|
decon = decort_pfw()
|
||||||
|
amodule = decon.amodule
|
||||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
|
||||||
supports_check_mode=True,
|
|
||||||
mutually_exclusive=[
|
|
||||||
['oauth2', 'password'],
|
|
||||||
['password', 'jwt'],
|
|
||||||
['jwt', 'oauth2'],
|
|
||||||
],
|
|
||||||
required_together=[
|
|
||||||
['app_id', 'app_secret'],
|
|
||||||
['user', 'password'],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
decon = DecortController(amodule)
|
|
||||||
|
|
||||||
pfw_facts = None # will hold PFW facts as returned by pfw_configure
|
pfw_facts = None # will hold PFW facts as returned by pfw_configure
|
||||||
|
|
||||||
@@ -314,9 +125,11 @@ def main():
|
|||||||
if amodule.params['state'] == 'absent':
|
if amodule.params['state'] == 'absent':
|
||||||
# ignore amodule.params['rules'] and remove all rules associated with this Compute
|
# ignore amodule.params['rules'] and remove all rules associated with this Compute
|
||||||
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, None)
|
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, None)
|
||||||
else:
|
elif amodule.params['rules'] is not None:
|
||||||
# manage PFW rules accodring to the module arguments
|
# manage PFW rules accodring to the module arguments
|
||||||
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, amodule.params['rules'])
|
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, amodule.params['rules'])
|
||||||
|
else:
|
||||||
|
pfw_facts = decon._pfw_get(comp_facts['id'], vins_facts['id'])
|
||||||
|
|
||||||
#
|
#
|
||||||
# complete module run
|
# complete module run
|
||||||
@@ -325,7 +138,7 @@ def main():
|
|||||||
amodule.fail_json(**decon.result)
|
amodule.fail_json(**decon.result)
|
||||||
else:
|
else:
|
||||||
# prepare PFW facts to be returned as part of decon.result and then call exit_json(...)
|
# prepare PFW facts to be returned as part of decon.result and then call exit_json(...)
|
||||||
decon.result['facts'] = decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode)
|
decon.result['facts'] = decon.decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode)
|
||||||
amodule.exit_json(**decon.result)
|
amodule.exit_json(**decon.result)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,207 +1,10 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
|
||||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
|
||||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
|
||||||
#
|
|
||||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
DOCUMENTATION = r'''
|
||||||
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
---
|
||||||
module: decort_rg
|
module: decort_rg
|
||||||
short_description: Manage resource groups (RGs) in DECORT cloud
|
|
||||||
description: >
|
|
||||||
This module can be used to create a new resource group in DECORT cloud platform, modify its
|
|
||||||
characteristics, and delete it.
|
|
||||||
version_added: "2.2"
|
|
||||||
author:
|
|
||||||
- Sergey Shubin <sergey.shubin@digitalenergy.online>
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- PyJWT Python module
|
|
||||||
- requests Python module
|
|
||||||
- netaddr Python module
|
|
||||||
- decort_utils utility library (module)
|
|
||||||
- DECORT cloud platform version 3.6.1 or higher
|
|
||||||
notes:
|
|
||||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
|
||||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
|
||||||
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
|
|
||||||
the DECORT cloud controller on which this JWT will be used.'
|
|
||||||
- New RGs provisioned with this module will be deployed to the first location under specified DECORT
|
|
||||||
controller (if there is more than one location).
|
|
||||||
options:
|
|
||||||
account_id:
|
|
||||||
description:
|
|
||||||
- ID of the account under which this RG will be created. This is the alternative to I(account_name)
|
|
||||||
option. If both I(account_id) and I(account_name) specified, the latter is ignored.
|
|
||||||
account_name:
|
|
||||||
description:
|
|
||||||
- 'Name of the account under which this RG will be created (for new RGs) or is located.'
|
|
||||||
- 'This parameter is ignored if I(account_id) is specified.'
|
|
||||||
required: no
|
|
||||||
annotation:
|
|
||||||
description:
|
|
||||||
- Optional text description of this resource group.
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
app_id:
|
|
||||||
description:
|
|
||||||
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- 'Required if I(authenticator=oauth2).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
app_secret:
|
|
||||||
description:
|
|
||||||
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
authenticator:
|
|
||||||
description:
|
|
||||||
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
|
|
||||||
default: jwt
|
|
||||||
choices: [ jwt, oauth2, legacy ]
|
|
||||||
required: yes
|
|
||||||
controller_url:
|
|
||||||
description:
|
|
||||||
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
|
|
||||||
- 'This parameter is always required regardless of the specified I(authenticator) type.'
|
|
||||||
required: yes
|
|
||||||
jwt:
|
|
||||||
description:
|
|
||||||
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
|
|
||||||
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
|
|
||||||
required: no
|
|
||||||
oauth2_url:
|
|
||||||
description:
|
|
||||||
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
|
|
||||||
- 'This parameter is required when when I(authenticator=oauth2).'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
|
|
||||||
required: no
|
|
||||||
quotas:
|
|
||||||
description:
|
|
||||||
- Dictionary that defines resource quotas to be set on a newly created RG.
|
|
||||||
- 'This parameter is optional and only used when creating new RG. It is ignored for any operations on an
|
|
||||||
existing RG.'
|
|
||||||
- 'The following keys are valid to set the resource quotas:'
|
|
||||||
- ' - I(cpu) (integer) - limit on the total number of CPUs that can be consumed by all compute instances
|
|
||||||
in this RG.'
|
|
||||||
- ' - I(ram) (integer) - limit on the total amount of RAM in GB that can be consumed by compute instances
|
|
||||||
in this RG.'
|
|
||||||
- ' - I(disk) (integer) - limit on the total volume of disk space in GB that can be consumed by all
|
|
||||||
compute instances in this RG.'
|
|
||||||
- ' - I(ext_ips) (integer) - maximum number of external IP addresses that can be allocated to the compute
|
|
||||||
instances and virtual network segments (ViNS) in this RG.'
|
|
||||||
- 'Each of the above keys is optional. For example, you may specify I(cpu) and I(ram) while omitting the
|
|
||||||
other two keys. Then the quotas will be set on RAM and CPU leaving disk volume and the number of external
|
|
||||||
IP addresses unlimited.'
|
|
||||||
required: no
|
|
||||||
rg_name:
|
|
||||||
description:
|
|
||||||
- Name of the RG to manage.
|
|
||||||
required: yes
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Specify the desired state of the resource group at the exit of the module.
|
|
||||||
- 'Regardless of I(state), if RG exists and is in one of [DEPLOYING, DESTROYING, MIGRATING, ] states,
|
|
||||||
do nothing.'
|
|
||||||
- 'If desired I(state=present):'
|
|
||||||
- ' - RG does not exist or is in DESTROYED state, create new RG according to the specifications.'
|
|
||||||
- ' - RG is in one of [CREATED, DISABLED] states, change quotas if necessary.'
|
|
||||||
- ' - RG is in DELETED state, restore it and change quotas if necessary. RG will be left in DISABLED state.'
|
|
||||||
- ' - RG in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=enabled):'
|
|
||||||
- ' - RG does not exist or is in DESTROYED state, create new RG according to the specifications.'
|
|
||||||
- ' - RG is in CREATED state, change quotas if necessary.'
|
|
||||||
- ' - RG is in DELETED state, restore it, change quotas if necessary and enable.'
|
|
||||||
- ' - RG is in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=absent):'
|
|
||||||
- ' - RG is in one of [CREATED, DISABLED, DELETED] states, destroy it.'
|
|
||||||
- ' - RG in DESTROYED state, do nothing.'
|
|
||||||
- ' - RG in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=disabled):'
|
|
||||||
- ' - RG does not exist or is in one of [ENABLING, DISABLING, DELETING, DELETED, DESTROYING, DESTROYED]
|
|
||||||
states, abort with an error.'
|
|
||||||
- ' - RG is DISABLED state, change quotas if necessary.'
|
|
||||||
- ' - RG is in CREATED state, change quotas if necessary and disable the RG.'
|
|
||||||
default: present
|
|
||||||
choices: [ absent, disabled, enabled, present ]
|
|
||||||
user:
|
|
||||||
description:
|
|
||||||
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
|
|
||||||
required: no
|
|
||||||
verify_ssl:
|
|
||||||
description:
|
|
||||||
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
|
|
||||||
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
|
|
||||||
environment that uses self-signed certificates. Note that disabling SSL verification in any other
|
|
||||||
scenario can lead to security issues, so please know what you are doing.'
|
|
||||||
default: True
|
|
||||||
required: no
|
|
||||||
workflow_callback:
|
|
||||||
description:
|
|
||||||
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
|
|
||||||
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
|
|
||||||
- API call at this URL will be used to relay such information to the application.
|
|
||||||
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
|
|
||||||
required: no
|
|
||||||
workflow_context:
|
|
||||||
description:
|
|
||||||
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
|
|
||||||
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
|
|
||||||
that up-level orchestrator could match returned information to the its internal entities.'
|
|
||||||
required: no
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
- name: create a new RG named "MyFirstRG" if it does not exist yet, set quotas on CPU and the number of exteranl IPs.
|
|
||||||
decort_rg:
|
|
||||||
authenticator: oauth2
|
|
||||||
app_id: "{{ MY_APP_ID }}"
|
|
||||||
app_secret: "{{ MY_APP_SECRET }}"
|
|
||||||
controller_url: "https://cloud.digitalenergy.online"
|
|
||||||
rg_name: "MyFirstRG"
|
|
||||||
account_name: "MyMainAccount"
|
|
||||||
quotas:
|
|
||||||
cpu: 16
|
|
||||||
ext_ips: 4
|
|
||||||
annotation: "My first RG created with Ansible module"
|
|
||||||
state: present
|
|
||||||
delegate_to: localhost
|
|
||||||
register: my_rg
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
facts:
|
|
||||||
description: facts about the resource group
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
facts:
|
|
||||||
id: 100
|
|
||||||
name: MyFirstRG
|
|
||||||
state: CREATED
|
|
||||||
account_id: 10
|
|
||||||
gid: 1001
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
@@ -210,254 +13,435 @@ from ansible.module_utils.basic import env_fallback
|
|||||||
from ansible.module_utils.decort_utils import *
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
def decort_rg_package_facts(arg_rg_facts, arg_check_mode=False):
|
class decort_rg(DecortController):
|
||||||
"""Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will
|
def __init__(self):
|
||||||
be returned to the upstream Ansible engine at the completion of the module run.
|
super(decort_rg, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
amodule = self.amodule
|
||||||
|
|
||||||
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
|
self.validated_acc_id = 0
|
||||||
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
|
self.validated_rg_id = 0
|
||||||
"""
|
self.validated_rg_facts = None
|
||||||
|
|
||||||
ret_dict = dict(id=0,
|
if amodule.params['rg_id'] is None:
|
||||||
name="none",
|
if self.amodule.params['account_id']:
|
||||||
state="CHECK_MODE",
|
self.validated_acc_id, _ = self.account_find("", amodule.params['account_id'])
|
||||||
)
|
elif amodule.params['account_name']:
|
||||||
|
self.validated_acc_id, _ = self.account_find(amodule.params['account_name'])
|
||||||
|
if not self.validated_acc_id:
|
||||||
|
# we failed to locate account by either name or ID - abort with an error
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = ("Current user does not have access to the requested account "
|
||||||
|
"or non-existent account specified.")
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
|
||||||
|
# Check if the RG with the specified parameters already exists
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
if amodule.params['state'] != "absent":
|
||||||
|
self.rg_should_exist = True
|
||||||
|
else:
|
||||||
|
self.rg_should_exist = False
|
||||||
|
|
||||||
|
if self.validated_rg_id and self.rg_facts['status'] != 'DESTROYED':
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
# If this is the first getting info
|
||||||
|
if not self.validated_rg_id:
|
||||||
|
self.validated_rg_id, self.rg_facts = self.rg_find(
|
||||||
|
arg_account_id=self.validated_acc_id,
|
||||||
|
arg_rg_id=self.aparams['rg_id'],
|
||||||
|
arg_rg_name=self.aparams['rg_name'],
|
||||||
|
arg_check_state=False,
|
||||||
|
)
|
||||||
|
# If this is a repeated getting info
|
||||||
|
else:
|
||||||
|
# If check mode is enabled, there is no needed to
|
||||||
|
# request info again
|
||||||
|
if self.amodule.check_mode:
|
||||||
|
return
|
||||||
|
|
||||||
|
_, self.rg_facts = self.rg_find(arg_rg_id=self.validated_rg_id)
|
||||||
|
|
||||||
|
def access(self):
|
||||||
|
should_change_access = False
|
||||||
|
acc_granted = False
|
||||||
|
for rg_item in self.rg_facts['acl']:
|
||||||
|
if rg_item['userGroupId'] == self.amodule.params['access']['user']:
|
||||||
|
acc_granted = True
|
||||||
|
if self.amodule.params['access']['action'] == 'grant':
|
||||||
|
if rg_item['right'] != self.amodule.params['access']['right']:
|
||||||
|
should_change_access = True
|
||||||
|
if self.amodule.params['access']['action'] == 'revoke':
|
||||||
|
should_change_access = True
|
||||||
|
if acc_granted == False and self.amodule.params['access']['action'] == 'grant':
|
||||||
|
should_change_access = True
|
||||||
|
|
||||||
|
if should_change_access == True:
|
||||||
|
self.rg_access(self.validated_rg_id, self.amodule.params['access'])
|
||||||
|
self.rg_facts['access'] = self.amodule.params['access']
|
||||||
|
self.rg_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.validated_rg_id > 0:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the "
|
||||||
|
"current status '{}'.").format(self.validated_rg_id,
|
||||||
|
self.amodule.params['state'],
|
||||||
|
self.rg_facts['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
|
||||||
|
"in account ID {} ").format(self.amodule.params['state'],
|
||||||
|
self.amodule.params['rg_name'],
|
||||||
|
self.validated_acc_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
resources = self.rg_facts['Resources']['Reserved']
|
||||||
|
incorrect_quota = dict(Requested=dict(),
|
||||||
|
Reserved=dict(),)
|
||||||
|
query_key_map = dict(cpu='cpu',
|
||||||
|
ram='ram',
|
||||||
|
disk='disksize',
|
||||||
|
ext_ips='extips',
|
||||||
|
net_transfer='exttraffic',)
|
||||||
|
if self.amodule.params['quotas']:
|
||||||
|
for quota_item in self.amodule.params['quotas']:
|
||||||
|
if self.amodule.params['quotas'][quota_item] < resources[query_key_map[quota_item]]:
|
||||||
|
incorrect_quota['Requested'][quota_item]=self.amodule.params['quotas'][quota_item]
|
||||||
|
incorrect_quota['Reserved'][quota_item]=resources[query_key_map[quota_item]]
|
||||||
|
|
||||||
|
if incorrect_quota['Requested']:
|
||||||
|
self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
|
||||||
|
self.result['failed'] = True
|
||||||
|
|
||||||
|
if not self.result['failed']:
|
||||||
|
self.rg_update(
|
||||||
|
arg_rg_dict=self.rg_facts,
|
||||||
|
arg_quotas=self.amodule.params['quotas'],
|
||||||
|
arg_res_types=self.amodule.params['resType'],
|
||||||
|
arg_newname=self.amodule.params['rename'],
|
||||||
|
arg_sep_pools=self.amodule.params['sep_pools'],
|
||||||
|
arg_desc=self.amodule.params['description'],
|
||||||
|
)
|
||||||
|
self.rg_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def setDefNet(self):
|
||||||
|
rg_def_net_type = self.rg_facts['def_net_type']
|
||||||
|
rg_def_net_id = self.rg_facts['def_net_id']
|
||||||
|
aparam_def_net_type = self.aparams['def_netType']
|
||||||
|
aparam_def_net_id = self.aparams['def_netId']
|
||||||
|
|
||||||
|
need_to_reset = (aparam_def_net_type == 'NONE'
|
||||||
|
and rg_def_net_type != aparam_def_net_type)
|
||||||
|
|
||||||
|
need_to_change = False
|
||||||
|
if aparam_def_net_id is not None:
|
||||||
|
need_to_change = (aparam_def_net_id != rg_def_net_id
|
||||||
|
or aparam_def_net_type != rg_def_net_type)
|
||||||
|
|
||||||
|
if need_to_reset or need_to_change:
|
||||||
|
self.rg_setDefNet(
|
||||||
|
arg_rg_id=self.validated_rg_id,
|
||||||
|
arg_net_type=aparam_def_net_type,
|
||||||
|
arg_net_id=aparam_def_net_id,
|
||||||
|
)
|
||||||
|
self.rg_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.validated_rg_id = self.rg_provision(
|
||||||
|
self.validated_acc_id,
|
||||||
|
self.amodule.params['rg_name'],
|
||||||
|
self.amodule.params['owner'],
|
||||||
|
self.amodule.params['description'],
|
||||||
|
self.amodule.params['resType'],
|
||||||
|
self.amodule.params['def_netType'],
|
||||||
|
self.amodule.params['ipcidr'],
|
||||||
|
self.amodule.params['extNetId'],
|
||||||
|
self.amodule.params['extNetIp'],
|
||||||
|
self.amodule.params['quotas'],
|
||||||
|
"", # this is location code. TODO: add module argument
|
||||||
|
sdn_access_group_id=self.aparams['sdn_access_group_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.validated_rg_id:
|
||||||
|
self.validated_rg_id, self.rg_facts = self.rg_find(
|
||||||
|
arg_account_id=self.validated_acc_id,
|
||||||
|
arg_rg_id=self.validated_rg_id,
|
||||||
|
arg_rg_name="",
|
||||||
|
arg_check_state=False
|
||||||
|
)
|
||||||
|
self.rg_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
self.rg_enable(self.validated_rg_id,
|
||||||
|
self.amodule.params['state'])
|
||||||
|
if self.amodule.params['state'] == "enabled":
|
||||||
|
self.rg_facts['status'] = 'CREATED'
|
||||||
|
else:
|
||||||
|
self.rg_facts['status'] = 'DISABLED'
|
||||||
|
self.rg_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
self.rg_restore(self.validated_rg_id)
|
||||||
|
self.rg_facts['status'] = 'DISABLED'
|
||||||
|
self.rg_should_exist = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self.rg_delete(
|
||||||
|
rg_id=self.validated_rg_id,
|
||||||
|
permanently=self.amodule.params['permanently'],
|
||||||
|
recursively=self.aparams['recursive_deletion'],
|
||||||
|
)
|
||||||
|
if self.amodule.params['permanently'] == True:
|
||||||
|
self.rg_facts['status'] = 'DESTROYED'
|
||||||
|
else:
|
||||||
|
self.rg_facts['status'] = 'DELETED'
|
||||||
|
self.rg_should_exist = False
|
||||||
|
return
|
||||||
|
|
||||||
|
def package_facts(self, check_mode=False):
|
||||||
|
"""Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will
|
||||||
|
be returned to the upstream Ansible engine at the completion of the module run.
|
||||||
|
|
||||||
|
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
|
||||||
|
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret_dict = dict(id=0,
|
||||||
|
name="none",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
#if arg_rg_facts is None:
|
||||||
|
# # if void facts provided - change state value to ABSENT and return
|
||||||
|
# ret_dict['state'] = "ABSENT"
|
||||||
|
# return ret_dict
|
||||||
|
|
||||||
|
ret_dict['id'] = self.rg_facts['id']
|
||||||
|
ret_dict['name'] = self.rg_facts['name']
|
||||||
|
ret_dict['state'] = self.rg_facts['status']
|
||||||
|
ret_dict['account_id'] = self.rg_facts['accountId']
|
||||||
|
ret_dict['gid'] = self.rg_facts['gid']
|
||||||
|
ret_dict['quota'] = self.rg_facts['resourceLimits']
|
||||||
|
ret_dict['resTypes'] = self.rg_facts['resourceTypes']
|
||||||
|
ret_dict['defNetId'] = self.rg_facts['def_net_id']
|
||||||
|
ret_dict['defNetType'] = self.rg_facts['def_net_type']
|
||||||
|
ret_dict['ViNS'] = self.rg_facts['vins']
|
||||||
|
ret_dict['computes'] = self.rg_facts['vms']
|
||||||
|
ret_dict['uniqPools'] = self.rg_facts['uniqPools']
|
||||||
|
ret_dict['description'] = self.rg_facts['desc']
|
||||||
|
ret_dict['sdn_access_group_id'] = self.rg_facts['sdn_access_group_id']
|
||||||
|
|
||||||
if arg_check_mode:
|
|
||||||
# in check mode return immediately with the default values
|
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
if arg_rg_facts is None:
|
@property
|
||||||
# if void facts provided - change state value to ABSENT and return
|
def amodule_init_args(self) -> dict:
|
||||||
ret_dict['state'] = "ABSENT"
|
return self.pack_amodule_init_args(
|
||||||
return ret_dict
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
access=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
description=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
def_netType=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
'PRIVATE',
|
||||||
|
'PUBLIC',
|
||||||
|
'NONE',
|
||||||
|
],
|
||||||
|
default='PRIVATE',
|
||||||
|
),
|
||||||
|
def_netId=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
extNetId=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
extNetIp=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
owner=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
ipcidr=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
rename=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
quotas=dict(
|
||||||
|
type='dict',
|
||||||
|
),
|
||||||
|
resType=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'disabled',
|
||||||
|
'enabled',
|
||||||
|
'present',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
permanently=dict(
|
||||||
|
type='bool',
|
||||||
|
default='False',
|
||||||
|
),
|
||||||
|
rg_name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
sep_pools=dict(
|
||||||
|
type='list',
|
||||||
|
elements='dict',
|
||||||
|
options=dict(
|
||||||
|
sep_id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
pool_names=dict(
|
||||||
|
type='list',
|
||||||
|
required=True,
|
||||||
|
elements='str',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
recursive_deletion=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
sdn_access_group_id=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
ret_dict['id'] = arg_rg_facts['id']
|
def check_amodule_args_for_change(self):
|
||||||
ret_dict['name'] = arg_rg_facts['name']
|
check_errors = False
|
||||||
ret_dict['state'] = arg_rg_facts['status']
|
|
||||||
ret_dict['account_id'] = arg_rg_facts['accountId']
|
|
||||||
ret_dict['gid'] = arg_rg_facts['gid']
|
|
||||||
|
|
||||||
return ret_dict
|
if (
|
||||||
|
self.aparams['sdn_access_group_id'] is not None
|
||||||
|
and (
|
||||||
|
self.aparams['sdn_access_group_id']
|
||||||
|
!= self.rg_facts['sdn_access_group_id']
|
||||||
|
)
|
||||||
|
):
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "sdn_access_group_id" failed: '
|
||||||
|
'cannot change sdn_access_group_id for an existing resource '
|
||||||
|
f'group ID {self.validated_rg_id}.'
|
||||||
|
)
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
def decort_rg_parameters():
|
if check_errors:
|
||||||
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted
|
self.exit(fail=True)
|
||||||
by AnsibleModule utility class."""
|
|
||||||
|
|
||||||
return dict(
|
# Workflow digest:
|
||||||
account_id=dict(type='int', required=False),
|
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
|
||||||
account_name=dict(type='str', required=False, default=''),
|
# 2) check if the RG with the specified id or rg_name:name exists
|
||||||
annotation=dict(type='str', required=False, default=''),
|
# 3) if RG does not exist -> deploy
|
||||||
app_id=dict(type='str',
|
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
|
||||||
required=False,
|
# 5) report result to Ansible
|
||||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
|
||||||
app_secret=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
|
||||||
no_log=True),
|
|
||||||
authenticator=dict(type='str',
|
|
||||||
required=True,
|
|
||||||
choices=['legacy', 'oauth2', 'jwt']),
|
|
||||||
controller_url=dict(type='str', required=True),
|
|
||||||
# datacenter=dict(type='str', required=False, default=''),
|
|
||||||
jwt=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_JWT']),
|
|
||||||
no_log=True),
|
|
||||||
oauth2_url=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
|
||||||
password=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
|
||||||
no_log=True),
|
|
||||||
quotas=dict(type='dict', required=False),
|
|
||||||
state=dict(type='str',
|
|
||||||
default='present',
|
|
||||||
choices=['absent', 'disabled', 'enabled', 'present']),
|
|
||||||
user=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_USER'])),
|
|
||||||
rg_name=dict(type='str', required=True,),
|
|
||||||
verify_ssl=dict(type='bool', required=False, default=True),
|
|
||||||
workflow_callback=dict(type='str', required=False),
|
|
||||||
workflow_context=dict(type='str', required=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Workflow digest:
|
|
||||||
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
|
|
||||||
# 2) check if the RG with the specified id or rg_name:name exists
|
|
||||||
# 3) if RG does not exist -> deploy
|
|
||||||
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
|
|
||||||
# 5) report result to Ansible
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module_parameters = decort_rg_parameters()
|
decon = decort_rg()
|
||||||
|
amodule = decon.amodule
|
||||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
#amodule.check_mode=True
|
||||||
supports_check_mode=True,
|
if decon.validated_rg_id > 0:
|
||||||
mutually_exclusive=[
|
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
|
||||||
['oauth2', 'password'],
|
decon.error()
|
||||||
['password', 'jwt'],
|
elif decon.rg_facts['status'] in ("CREATED"):
|
||||||
['jwt', 'oauth2'],
|
|
||||||
],
|
|
||||||
required_together=[
|
|
||||||
['app_id', 'app_secret'],
|
|
||||||
['user', 'password'],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
decon = DecortController(amodule)
|
|
||||||
|
|
||||||
# We need valid Account ID to manage RG.
|
|
||||||
# Account may be specified either by account_id or account_name. In both cases we
|
|
||||||
# have to validate account presence and accesibility by the current user.
|
|
||||||
validated_acc_id = 0
|
|
||||||
if decon.check_amodule_argument('account_id', False):
|
|
||||||
validated_acc_id, _ = decon.account_find("", amodule.params['account_id'])
|
|
||||||
else:
|
|
||||||
decon.check_amodule_argument('account_name') # if no account_name, this function will abort module
|
|
||||||
validated_acc_id, _ = decon.account_find(amodule.params['account_name'])
|
|
||||||
|
|
||||||
if not validated_acc_id:
|
|
||||||
# we failed to locate account by either name or ID - abort with an error
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = ("Current user does not have access to the requested account "
|
|
||||||
"or non-existent account specified.")
|
|
||||||
decon.fail_json(**decon.result)
|
|
||||||
|
|
||||||
# Check if the RG with the specified parameters already exists
|
|
||||||
rg_id, rg_facts = decon.rg_find(validated_acc_id,
|
|
||||||
0, arg_rg_name=amodule.params['rg_name'],
|
|
||||||
arg_check_state=False)
|
|
||||||
rg_should_exist = True
|
|
||||||
|
|
||||||
if rg_id:
|
|
||||||
if rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
|
|
||||||
# error: nothing can be done to existing RG in the listed statii regardless of
|
|
||||||
# the requested state
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("No change can be done for existing RG ID {} because of its current "
|
|
||||||
"status '{}'").format(rg_id, rg_facts['status'])
|
|
||||||
elif rg_facts['status'] == "DISABLED":
|
|
||||||
if amodule.params['state'] == 'absent':
|
if amodule.params['state'] == 'absent':
|
||||||
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
|
decon.destroy()
|
||||||
rg_facts['status'] = 'DESTROYED'
|
elif amodule.params['state'] == "disabled":
|
||||||
rg_should_exist = False
|
decon.enable()
|
||||||
elif amodule.params['state'] in ('present', 'disabled'):
|
|
||||||
# update quotas
|
|
||||||
decon.rg_quotas(rg_facts, amodule.params['quotas'])
|
|
||||||
elif amodule.params['state'] == 'enabled':
|
|
||||||
# update quotas and enable
|
|
||||||
decon.rg_quotas(rg_facts, amodule.params['quotas'])
|
|
||||||
decon.rg_state(rg_facts, 'enabled')
|
|
||||||
elif rg_facts['status'] == "CREATED":
|
|
||||||
if amodule.params['state'] == 'absent':
|
|
||||||
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
|
|
||||||
rg_facts['status'] = 'DESTROYED'
|
|
||||||
rg_should_exist = False
|
|
||||||
elif amodule.params['state'] in ('present', 'enabled'):
|
|
||||||
# update quotas
|
|
||||||
decon.rg_quotas(rg_facts, amodule.params['quotas'])
|
|
||||||
elif amodule.params['state'] == 'disabled':
|
|
||||||
# disable and update quotas
|
|
||||||
decon.rg_state(rg_facts, 'disabled')
|
|
||||||
decon.rg_quotas(rg_facts, amodule.params['quotas'])
|
|
||||||
elif rg_facts['status'] == "DELETED":
|
|
||||||
if amodule.params['state'] in ['present', 'enabled']:
|
if amodule.params['state'] in ['present', 'enabled']:
|
||||||
# restore and enable
|
if (
|
||||||
# TODO: check if restore RG API returns the new RG ID of the restored RG instance.
|
amodule.params['quotas']
|
||||||
decon.rg_restore(arg_rg_id=rg_id)
|
or amodule.params['resType']
|
||||||
decon.rg_state(rg_facts, 'enabled')
|
or amodule.params['rename'] != ""
|
||||||
# TODO: Not sure what to do with the quotas after RG is restored. May need to update rg_facts.
|
or amodule.params['sep_pools'] is not None
|
||||||
rg_should_exist = True
|
or amodule.params['description'] is not None
|
||||||
elif amodule.params['state'] == 'absent':
|
):
|
||||||
# destroy permanently
|
decon.update()
|
||||||
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
|
if amodule.params['access']:
|
||||||
rg_facts['status'] = 'DESTROYED'
|
decon.access()
|
||||||
rg_should_exist = False
|
if amodule.params['def_netType'] is not None:
|
||||||
elif amodule.params['state'] == 'disabled':
|
decon.setDefNet()
|
||||||
# error
|
|
||||||
decon.result['failed'] = True
|
elif decon.rg_facts['status'] == "DELETED":
|
||||||
decon.result['changed'] = False
|
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for RG ID {} in the "
|
decon.destroy()
|
||||||
"current status '{}'").format(rg_id,
|
elif (amodule.params['state'] == 'present'
|
||||||
amodule.params['state'],
|
or amodule.params['state'] == 'disabled'):
|
||||||
rg_facts['status'])
|
decon.restore()
|
||||||
rg_should_exist = False
|
elif amodule.params['state'] == 'enabled':
|
||||||
elif rg_facts['status'] == "DESTROYED":
|
decon.restore()
|
||||||
if amodule.params['state'] in ('present', 'enabled'):
|
decon.enable()
|
||||||
# need to re-provision RG
|
elif decon.rg_facts['status'] in ("DISABLED"):
|
||||||
decon.check_amodule_argument('rg_name')
|
if amodule.params['state'] == 'absent':
|
||||||
# As we already have validated account ID we can create RG and get rg_id on success
|
decon.destroy()
|
||||||
# pass empty string for location code, rg_provision will select the 1st location
|
elif amodule.params['state'] == ("enabled"):
|
||||||
rg_id = decon.rg_provision(validated_acc_id,
|
decon.enable()
|
||||||
amodule.params['rg_name'], decon.decort_username,
|
|
||||||
amodule.params['quotas'],
|
|
||||||
"", # this is location code. TODO: add module argument
|
|
||||||
amodule.params['annotation'])
|
|
||||||
rg_should_exist = True
|
|
||||||
elif amodule.params['state'] == 'absent':
|
|
||||||
# nop
|
|
||||||
decon.result['failed'] = False
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("No state change required for RG ID {} because of its "
|
|
||||||
"current status '{}'").format(rg_id,
|
|
||||||
rg_facts['status'])
|
|
||||||
rg_should_exist = False
|
|
||||||
elif amodule.params['state'] == 'disabled':
|
|
||||||
# error
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for RG ID {} in the "
|
|
||||||
"current status '{}'").format(rg_id,
|
|
||||||
amodule.params['state'],
|
|
||||||
rg_facts['status'])
|
|
||||||
else:
|
else:
|
||||||
# Preexisting RG was not found.
|
if amodule.params['state'] in ('present', 'enabled'):
|
||||||
rg_should_exist = False # we will change it back to True if RG is explicitly created or restored
|
if not amodule.params['rg_name']:
|
||||||
# If requested state is 'absent' - nothing to do
|
decon.result['failed'] = True
|
||||||
if amodule.params['state'] == 'absent':
|
decon.result['msg'] = (
|
||||||
decon.result['failed'] = False
|
'Resource group could not be created because'
|
||||||
decon.result['changed'] = False
|
' the "rg_name" parameter was not specified.'
|
||||||
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
|
)
|
||||||
"non-existent RG name '{}'").format(amodule.params['rg_name'])
|
else:
|
||||||
elif amodule.params['state'] in ('present', 'enabled'):
|
decon.create()
|
||||||
# Target RG does not exist yet - create it and store the returned ID in rg_id variable for later use
|
if amodule.params['access'] and not amodule.check_mode:
|
||||||
# To create RG we need account name (or account ID) and RG name - check
|
decon.access()
|
||||||
# that these parameters are present and proceed.
|
elif amodule.params['state'] in ('disabled'):
|
||||||
decon.check_amodule_argument('rg_name')
|
decon.error()
|
||||||
# as we already have account ID we can create RG and get rg_id on success
|
|
||||||
# pass empty string for location code, rg_provision will select the 1st location
|
|
||||||
rg_id = decon.rg_provision(validated_acc_id,
|
|
||||||
amodule.params['rg_name'], decon.decort_username,
|
|
||||||
amodule.params['quotas'],
|
|
||||||
"", # this is location code. TODO: add module argument
|
|
||||||
amodule.params['annotation'])
|
|
||||||
rg_should_exist = True
|
|
||||||
elif amodule.params['state'] == 'disabled':
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
|
||||||
"RG name '{}' ").format(amodule.params['state'],
|
|
||||||
amodule.params['rg_name'])
|
|
||||||
#
|
|
||||||
# conditional switch end - complete module run
|
|
||||||
if decon.result['failed']:
|
if decon.result['failed']:
|
||||||
amodule.fail_json(**decon.result)
|
amodule.fail_json(**decon.result)
|
||||||
else:
|
else:
|
||||||
# prepare RG facts to be returned as part of decon.result and then call exit_json(...)
|
if decon.rg_should_exist:
|
||||||
# rg_facts = None
|
|
||||||
if rg_should_exist:
|
|
||||||
if decon.result['changed']:
|
if decon.result['changed']:
|
||||||
# If we arrive here, there is a good chance that the RG is present - get fresh RG facts from
|
decon.get_info()
|
||||||
# the cloud by RG ID.
|
decon.result['facts'] = decon.package_facts(amodule.check_mode)
|
||||||
# Otherwise, RG facts from previous call (when the RG was still in existence) will be returned.
|
amodule.exit_json(**decon.result)
|
||||||
_, rg_facts = decon.rg_find(arg_account_id=0, arg_rg_id=rg_id)
|
else:
|
||||||
decon.result['facts'] = decort_rg_package_facts(rg_facts, amodule.check_mode)
|
amodule.exit_json(**decon.result)
|
||||||
amodule.exit_json(**decon.result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
188
library/decort_snapshot.py
Normal file
188
library/decort_snapshot.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_snapshot
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
|
'''
|
||||||
|
|
||||||
|
import time
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
|
|
||||||
|
class DecortSnapshot(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
self.check_amodule_args()
|
||||||
|
|
||||||
|
self.vm_id: int
|
||||||
|
self.vm_facts: dict
|
||||||
|
self.aparams_label = self.aparams['label']
|
||||||
|
self.aparams_vm_id = self.aparams['vm_id']
|
||||||
|
|
||||||
|
self.vm_id, self.vm_facts, _ = self._compute_get_by_id(
|
||||||
|
comp_id=self.aparams_vm_id,
|
||||||
|
)
|
||||||
|
if not self.vm_id:
|
||||||
|
self.message(f'VM {self.aparams_vm_id} not found')
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
self.vm_name = self.vm_facts['name']
|
||||||
|
self.vm_snapshots = self.vm_facts['snapSets']
|
||||||
|
self.vm_snapshot_labels = [
|
||||||
|
snapshot['label'] for snapshot in self.vm_snapshots
|
||||||
|
]
|
||||||
|
|
||||||
|
self.new_snapshot_label = None
|
||||||
|
if self.aparams['state'] == 'present':
|
||||||
|
if self.aparams_label is None:
|
||||||
|
self.new_snapshot_label = (
|
||||||
|
f'{self.vm_name}_{self.sec_to_dt_str(time.time())}'
|
||||||
|
)
|
||||||
|
elif self.aparams_label not in self.vm_snapshot_labels:
|
||||||
|
self.new_snapshot_label = self.aparams_label
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.new_snapshot_label is None
|
||||||
|
and self.aparams_label is not None
|
||||||
|
and self.aparams_label not in self.vm_snapshot_labels
|
||||||
|
):
|
||||||
|
self.message(
|
||||||
|
f'Snapshot {self.aparams_label} '
|
||||||
|
f'not found for VM {self.aparams_vm_id}'
|
||||||
|
)
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
label=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
choices=(
|
||||||
|
'absent',
|
||||||
|
'present',
|
||||||
|
'merge_aborted',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
usage=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
vm_id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_if=[
|
||||||
|
('state', 'absent', ('label',)),
|
||||||
|
],
|
||||||
|
required_one_of=[
|
||||||
|
('label', 'state'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args(self):
|
||||||
|
check_error = False
|
||||||
|
if (
|
||||||
|
self.aparams['state'] == 'absent'
|
||||||
|
and self.aparams['usage']
|
||||||
|
):
|
||||||
|
self.message(
|
||||||
|
'Parameter "usage" is not supported when deleting snapshot'
|
||||||
|
)
|
||||||
|
check_error = True
|
||||||
|
|
||||||
|
if check_error:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_info(first_run=True)
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
self.change()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_info(self, first_run: bool = False):
|
||||||
|
if not first_run:
|
||||||
|
self.vm_snapshots = self.snapshot_list(
|
||||||
|
compute_id=self.aparams_vm_id,
|
||||||
|
)
|
||||||
|
label = self.new_snapshot_label or self.aparams_label
|
||||||
|
for snapshot in self.vm_snapshots:
|
||||||
|
if snapshot['label'] == label:
|
||||||
|
self.facts = snapshot
|
||||||
|
if self.aparams['usage']:
|
||||||
|
self.facts['stored'] = self.get_snapshot_usage()
|
||||||
|
self.facts['vm_id'] = self.aparams_vm_id
|
||||||
|
break
|
||||||
|
|
||||||
|
def change(self):
|
||||||
|
match self.aparams['state']:
|
||||||
|
case 'present':
|
||||||
|
if self.new_snapshot_label:
|
||||||
|
self.create()
|
||||||
|
case 'absent':
|
||||||
|
if self.aparams_label in self.vm_snapshot_labels:
|
||||||
|
self.delete()
|
||||||
|
case 'merge_aborted':
|
||||||
|
self.abort_merge()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.snapshot_create(
|
||||||
|
compute_id=self.aparams_vm_id,
|
||||||
|
label=self.new_snapshot_label,
|
||||||
|
)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.snapshot_delete(
|
||||||
|
compute_id=self.aparams_vm_id,
|
||||||
|
label=self.aparams_label,
|
||||||
|
)
|
||||||
|
self.facts = {}
|
||||||
|
|
||||||
|
def abort_merge(self):
|
||||||
|
self.snapshot_abort_merge(
|
||||||
|
vm_id=self.aparams_vm_id,
|
||||||
|
label=self.aparams_label,
|
||||||
|
)
|
||||||
|
self.get_info()
|
||||||
|
|
||||||
|
def get_snapshot_usage(self) -> int:
|
||||||
|
label = self.new_snapshot_label or self.aparams_label
|
||||||
|
common_snapshots_usage_info, _ = self.snapshot_usage(
|
||||||
|
compute_id=self.aparams_vm_id,
|
||||||
|
label=label,
|
||||||
|
)
|
||||||
|
return common_snapshots_usage_info['stored']
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_errors = False
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.aparams['state'] == 'merge_aborted'
|
||||||
|
and self.vm_facts['techStatus'] != 'MERGE'
|
||||||
|
):
|
||||||
|
check_errors = True
|
||||||
|
self.message(
|
||||||
|
f'Check for parameter "state" failed: '
|
||||||
|
'Merge can be aborted only for VM in "MERGE" tech status.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_errors:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DecortSnapshot().run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
51
library/decort_trunk.py
Normal file
51
library/decort_trunk.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_trunk
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
|
|
||||||
|
class DecortTrunk(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
self.id: int = self.aparams['id']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_info()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
self.facts = self.trunk_get(id=self.id)
|
||||||
|
self.facts['account_ids'] = self.facts.pop('accountIds')
|
||||||
|
self.facts['created_timestamp'] = self.facts.pop('created_at')
|
||||||
|
self.facts['deleted_timestamp'] = self.facts.pop('deleted_at')
|
||||||
|
self.facts['updated_timestamp'] = self.facts.pop('updated_at')
|
||||||
|
self.facts['native_vlan_id'] = self.facts.pop('nativeVlanId')
|
||||||
|
self.facts['ovs_bridge'] = self.facts.pop('ovsBridge')
|
||||||
|
self.facts['vlan_ids'] = self.facts.pop('trunkTags')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DecortTrunk().run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
615
library/decort_user_info.py
Normal file
615
library/decort_user_info.py
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_user_info
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
|
|
||||||
|
class DecortUserInfo(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
self.check_amodule_args()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
accounts=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
deleted=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
rights=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value for e in self.AccountUserRights
|
||||||
|
],
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
status=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value for e in self.AccountStatus
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
resource_consumption=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value
|
||||||
|
for e in self.AccountSortableField
|
||||||
|
],
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
api_methods=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
audits=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
api_method=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
status_code=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
min=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
max=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
time=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
start=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
timestamp=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
datetime=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mutually_exclusive=[
|
||||||
|
('timestamp', 'datetime'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
end=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
timestamp=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
datetime=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
mutually_exclusive=[
|
||||||
|
('timestamp', 'datetime'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
apply_defaults=True,
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
default=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value
|
||||||
|
for e in self.AuditsSortableField
|
||||||
|
],
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
objects_search=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
resource_consumption=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
zones=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
deletable=dict(
|
||||||
|
type='bool',
|
||||||
|
),
|
||||||
|
description=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
grid_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
name=dict(
|
||||||
|
type='str',
|
||||||
|
),
|
||||||
|
node_id=dict(
|
||||||
|
type='int',
|
||||||
|
),
|
||||||
|
status=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value for e in self.ZoneStatus
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
apply_defaults=True,
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
default=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=self.ZoneField._member_names_,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trunks=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
filter=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
ids=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
account_ids=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
status=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value for e in self.TrunkStatus
|
||||||
|
],
|
||||||
|
),
|
||||||
|
vlan_ids=dict(
|
||||||
|
type='list',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pagination=dict(
|
||||||
|
type='dict',
|
||||||
|
apply_defaults=True,
|
||||||
|
options=dict(
|
||||||
|
number=dict(
|
||||||
|
type='int',
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
size=dict(
|
||||||
|
type='int',
|
||||||
|
default=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
sorting=dict(
|
||||||
|
type='dict',
|
||||||
|
options=dict(
|
||||||
|
asc=dict(
|
||||||
|
type='bool',
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
field=dict(
|
||||||
|
type='str',
|
||||||
|
choices=[
|
||||||
|
e.value
|
||||||
|
for e in self.TrunksSortableField
|
||||||
|
],
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args(self):
|
||||||
|
"""
|
||||||
|
Additional validation of Ansible Module arguments.
|
||||||
|
This validation cannot be implemented using
|
||||||
|
Ansible Argument spec.
|
||||||
|
"""
|
||||||
|
|
||||||
|
check_error = False
|
||||||
|
|
||||||
|
match self.aparams['audits']:
|
||||||
|
case {
|
||||||
|
'filter': {'time': {'start': {'datetime': str() as dt_str}}}
|
||||||
|
}:
|
||||||
|
if self.dt_str_to_sec(dt_str=dt_str) is None:
|
||||||
|
self.message(self.MESSAGES.str_not_parsed(string=dt_str))
|
||||||
|
check_error = True
|
||||||
|
match self.aparams['audits']:
|
||||||
|
case {
|
||||||
|
'filter': {'time': {'end': {'datetime': str() as dt_str}}}
|
||||||
|
}:
|
||||||
|
if self.dt_str_to_sec(dt_str=dt_str) is None:
|
||||||
|
self.message(self.MESSAGES.str_not_parsed(string=dt_str))
|
||||||
|
check_error = True
|
||||||
|
|
||||||
|
aparam_trunks = self.aparams['trunks']
|
||||||
|
if (
|
||||||
|
aparam_trunks is not None
|
||||||
|
and aparam_trunks['filter'] is not None
|
||||||
|
and aparam_trunks['filter']['vlan_ids'] is not None
|
||||||
|
):
|
||||||
|
for vlan_id in aparam_trunks['filter']['vlan_ids']:
|
||||||
|
if not (
|
||||||
|
self.TRUNK_VLAN_ID_MIN_VALUE
|
||||||
|
<= vlan_id
|
||||||
|
<= self.TRUNK_VLAN_ID_MAX_VALUE
|
||||||
|
):
|
||||||
|
check_error = True
|
||||||
|
self.message(
|
||||||
|
'Check for parameter "trunks.filter.vlan_ids" failed: '
|
||||||
|
f'VLAN ID {vlan_id} must be in range 1-4095.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_error:
|
||||||
|
self.exit(fail=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_accounts_args(self) -> None | dict:
|
||||||
|
"""
|
||||||
|
Map the module argument `accounts` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.user_accounts`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['accounts']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
|
||||||
|
mapped_args['deleted'] = input_args['deleted']
|
||||||
|
|
||||||
|
mapped_args['resource_consumption'] = (
|
||||||
|
input_args['resource_consumption']
|
||||||
|
)
|
||||||
|
|
||||||
|
input_args_filter = input_args['filter']
|
||||||
|
if input_args_filter:
|
||||||
|
input_args_filter_rights = input_args_filter['rights']
|
||||||
|
if input_args_filter_rights:
|
||||||
|
mapped_args['account_user_rights'] = (
|
||||||
|
self.AccountUserRights(input_args_filter_rights)
|
||||||
|
)
|
||||||
|
|
||||||
|
mapped_args['account_id'] = input_args_filter['id']
|
||||||
|
|
||||||
|
mapped_args['account_name'] = input_args_filter['name']
|
||||||
|
|
||||||
|
input_args_filter_status = input_args_filter['status']
|
||||||
|
if input_args_filter_status:
|
||||||
|
mapped_args['account_status'] = (
|
||||||
|
self.AccountStatus(input_args_filter_status)
|
||||||
|
)
|
||||||
|
|
||||||
|
input_args_pagination = input_args['pagination']
|
||||||
|
if input_args_pagination:
|
||||||
|
mapped_args['page_number'] = input_args_pagination['number']
|
||||||
|
mapped_args['page_size'] = input_args_pagination['size']
|
||||||
|
|
||||||
|
input_args_sorting = input_args['sorting']
|
||||||
|
if input_args_sorting:
|
||||||
|
mapped_args['sort_by_asc'] = input_args_sorting['asc']
|
||||||
|
|
||||||
|
input_args_sorting_field = input_args_sorting['field']
|
||||||
|
if input_args_sorting_field:
|
||||||
|
mapped_args['sort_by_field'] = (
|
||||||
|
self.AccountSortableField(input_args_sorting_field)
|
||||||
|
)
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_audits_args(self):
|
||||||
|
"""
|
||||||
|
Map the module argument `audits` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.user_audits`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['audits']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
|
||||||
|
input_args_filter = input_args['filter']
|
||||||
|
if input_args_filter:
|
||||||
|
mapped_args['api_method'] = input_args_filter['api_method']
|
||||||
|
|
||||||
|
match input_args_filter['status_code']:
|
||||||
|
case {'min': int() as min_status_code}:
|
||||||
|
mapped_args['min_status_code'] = min_status_code
|
||||||
|
match input_args_filter['status_code']:
|
||||||
|
case {'max': int() as max_status_code}:
|
||||||
|
mapped_args['max_status_code'] = max_status_code
|
||||||
|
|
||||||
|
match input_args_filter['time']:
|
||||||
|
case {'start': {'timestamp': int() as start_unix_time}}:
|
||||||
|
mapped_args['start_unix_time'] = start_unix_time
|
||||||
|
case {'start': {'datetime': str() as start_dt_str}}:
|
||||||
|
mapped_args['start_unix_time'] = self.dt_str_to_sec(
|
||||||
|
dt_str=start_dt_str
|
||||||
|
)
|
||||||
|
match input_args_filter['time']:
|
||||||
|
case {'end': {'timestamp': int() as end_unix_time}}:
|
||||||
|
mapped_args['end_unix_time'] = end_unix_time
|
||||||
|
case {'end': {'datetime': str() as end_dt_str}}:
|
||||||
|
mapped_args['end_unix_time'] = self.dt_str_to_sec(
|
||||||
|
dt_str=end_dt_str
|
||||||
|
)
|
||||||
|
|
||||||
|
input_args_pagination = input_args['pagination']
|
||||||
|
if input_args_pagination:
|
||||||
|
mapped_args['page_number'] = input_args_pagination['number']
|
||||||
|
mapped_args['page_size'] = input_args_pagination['size']
|
||||||
|
|
||||||
|
input_args_sorting = input_args['sorting']
|
||||||
|
if input_args_sorting:
|
||||||
|
mapped_args['sort_by_asc'] = input_args_sorting['asc']
|
||||||
|
|
||||||
|
input_args_sorting_field = input_args_sorting['field']
|
||||||
|
if input_args_sorting_field:
|
||||||
|
mapped_args['sort_by_field'] = (
|
||||||
|
self.AuditsSortableField(input_args_sorting_field)
|
||||||
|
)
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_zones_args(self):
|
||||||
|
"""
|
||||||
|
Map the module argument `zones` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.user_zones`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['zones']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
|
||||||
|
input_args_filter = input_args['filter']
|
||||||
|
if input_args_filter:
|
||||||
|
mapped_args.update(input_args_filter)
|
||||||
|
|
||||||
|
input_args_filter_status = input_args_filter['status']
|
||||||
|
if input_args_filter_status:
|
||||||
|
mapped_args['status'] = (
|
||||||
|
self.ZoneStatus(input_args_filter_status)
|
||||||
|
)
|
||||||
|
|
||||||
|
input_args_pagination = input_args['pagination']
|
||||||
|
if input_args_pagination:
|
||||||
|
mapped_args['page_number'] = input_args_pagination['number']
|
||||||
|
mapped_args['page_size'] = input_args_pagination['size']
|
||||||
|
|
||||||
|
input_args_sorting = input_args['sorting']
|
||||||
|
if input_args_sorting:
|
||||||
|
mapped_args['sort_by_asc'] = input_args_sorting['asc']
|
||||||
|
|
||||||
|
input_args_sorting_field = input_args_sorting['field']
|
||||||
|
if input_args_sorting_field:
|
||||||
|
mapped_args['sort_by_field'] = (
|
||||||
|
self.ZoneField._member_map_[input_args_sorting_field]
|
||||||
|
)
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mapped_trunks_args(self):
|
||||||
|
"""
|
||||||
|
Map the module argument `trunks` to
|
||||||
|
arguments dictionary for the method
|
||||||
|
`DecortController.user_trunks`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
input_args = self.aparams['trunks']
|
||||||
|
if not input_args:
|
||||||
|
return input_args
|
||||||
|
|
||||||
|
mapped_args = {}
|
||||||
|
|
||||||
|
input_args_filter = input_args['filter']
|
||||||
|
if input_args_filter:
|
||||||
|
mapped_args.update(input_args_filter)
|
||||||
|
|
||||||
|
input_args_filter_status = input_args_filter['status']
|
||||||
|
if input_args_filter_status:
|
||||||
|
mapped_args['status'] = (
|
||||||
|
self.TrunkStatus(input_args_filter_status)
|
||||||
|
)
|
||||||
|
|
||||||
|
input_args_filter_vlan_ids = input_args_filter['vlan_ids']
|
||||||
|
if input_args_filter_vlan_ids is not None:
|
||||||
|
mapped_args['vlan_ids'] = ', '.join(
|
||||||
|
map(str, input_args_filter_vlan_ids)
|
||||||
|
)
|
||||||
|
|
||||||
|
input_args_pagination = input_args['pagination']
|
||||||
|
if input_args_pagination:
|
||||||
|
mapped_args['page_number'] = input_args_pagination['number']
|
||||||
|
mapped_args['page_size'] = input_args_pagination['size']
|
||||||
|
|
||||||
|
input_args_sorting = input_args['sorting']
|
||||||
|
if input_args_sorting:
|
||||||
|
mapped_args['sort_by_asc'] = input_args_sorting['asc']
|
||||||
|
|
||||||
|
input_args_sorting_field = input_args_sorting['field']
|
||||||
|
if input_args_sorting_field:
|
||||||
|
mapped_args['sort_by_field'] = (
|
||||||
|
self.TrunksSortableField(input_args_sorting_field)
|
||||||
|
)
|
||||||
|
|
||||||
|
return mapped_args
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_info()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
self.facts = self.user_whoami()
|
||||||
|
self.id = self.facts['name']
|
||||||
|
|
||||||
|
user_get = self.user_get(id=self.id)
|
||||||
|
for key in ['emailaddresses', 'data']:
|
||||||
|
self.facts[key] = user_get[key]
|
||||||
|
|
||||||
|
if self.aparams['accounts']:
|
||||||
|
self.facts['accounts'] = self.user_accounts(
|
||||||
|
**self.mapped_accounts_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.aparams['resource_consumption']:
|
||||||
|
self.facts.update(self.user_resource_consumption())
|
||||||
|
|
||||||
|
if self.aparams['audits']:
|
||||||
|
self.facts['audits'] = self.user_audits(**self.mapped_audits_args)
|
||||||
|
|
||||||
|
if self.aparams['api_methods']:
|
||||||
|
self.facts['api_methods'] = self.user_api_methods(id=self.id)
|
||||||
|
|
||||||
|
search_string = self.aparams['objects_search']
|
||||||
|
if search_string:
|
||||||
|
self.facts['objects_search'] = self.user_objects_search(
|
||||||
|
search_string=search_string,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.aparams['zones']:
|
||||||
|
self.facts['zones'] = self.user_zones(**self.mapped_zones_args)
|
||||||
|
|
||||||
|
if self.aparams['trunks']:
|
||||||
|
self.facts['trunks'] = self.user_trunks(**self.mapped_trunks_args)
|
||||||
|
for trunk_facts in self.facts['trunks']:
|
||||||
|
trunk_facts['account_ids'] = trunk_facts.pop('accountIds')
|
||||||
|
trunk_facts['created_timestamp'] = trunk_facts.pop(
|
||||||
|
'created_at'
|
||||||
|
)
|
||||||
|
trunk_facts['deleted_timestamp'] = trunk_facts.pop(
|
||||||
|
'deleted_at'
|
||||||
|
)
|
||||||
|
trunk_facts['updated_timestamp'] = trunk_facts.pop(
|
||||||
|
'updated_at'
|
||||||
|
)
|
||||||
|
trunk_facts['native_vlan_id'] = trunk_facts.pop(
|
||||||
|
'nativeVlanId'
|
||||||
|
)
|
||||||
|
trunk_facts['ovs_bridge'] = trunk_facts.pop('ovsBridge')
|
||||||
|
trunk_facts['vlan_ids'] = trunk_facts.pop('trunkTags')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DecortUserInfo().run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,243 +1,10 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
|
||||||
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
|
|
||||||
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
|
|
||||||
#
|
|
||||||
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
DOCUMENTATION = r'''
|
||||||
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
|
|
||||||
#
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
---
|
||||||
module: decort_vins
|
module: decort_vins
|
||||||
short_description: Manage Virtual Network Segments (ViNS) in DECORT cloud
|
|
||||||
description: >
|
|
||||||
This module can be used to create new ViNS in DECORT cloud platform, obtain or
|
|
||||||
modify its characteristics, and delete it.
|
|
||||||
version_added: "2.2"
|
|
||||||
author:
|
|
||||||
- Sergey Shubin <sergey.shubin@digitalenergy.online>
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- PyJWT Python module
|
|
||||||
- requests Python module
|
|
||||||
- netaddr Python module
|
|
||||||
- decort_utils utility library (module)
|
|
||||||
- DECORT cloud platform version 3.6.1 or higher
|
|
||||||
notes:
|
|
||||||
- Environment variables can be used to pass selected parameters to the module, see details below.
|
|
||||||
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
|
|
||||||
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
|
|
||||||
the DECORT cloud controller on which this JWT will be used.'
|
|
||||||
options:
|
|
||||||
account_id:
|
|
||||||
description:
|
|
||||||
- 'ID of the account under which this ViNS will be created (for new ViNS) or is located (for already
|
|
||||||
existing ViNS). This is the alternative to I(account_name) option.'
|
|
||||||
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
|
|
||||||
required: no
|
|
||||||
account_name:
|
|
||||||
description:
|
|
||||||
- 'Name of the account under which this ViNS will be created (for new RGs) or is located (for already
|
|
||||||
existing ViNS).'
|
|
||||||
- 'This parameter is ignored if I(account_id) is specified.'
|
|
||||||
required: no
|
|
||||||
annotation:
|
|
||||||
description:
|
|
||||||
- Optional text description of this virtual network segment.
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
app_id:
|
|
||||||
description:
|
|
||||||
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- 'Required if I(authenticator=oauth2).'
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
app_secret:
|
|
||||||
description:
|
|
||||||
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
|
|
||||||
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
|
|
||||||
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
|
|
||||||
environment variable.'
|
|
||||||
required: no
|
|
||||||
authenticator:
|
|
||||||
description:
|
|
||||||
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
|
|
||||||
default: jwt
|
|
||||||
choices: [ jwt, oauth2, legacy ]
|
|
||||||
required: yes
|
|
||||||
controller_url:
|
|
||||||
description:
|
|
||||||
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
|
|
||||||
- 'This parameter is always required regardless of the specified I(authenticator) type.'
|
|
||||||
required: yes
|
|
||||||
ext_net_id:
|
|
||||||
description:
|
|
||||||
- 'Controls ViNS connection to an external network. This argument is optional with default value of -1,
|
|
||||||
which means no external connection.'
|
|
||||||
- Specify 0 to connect ViNS to external network and let platform select external network Id automatically.
|
|
||||||
- Specify positive value to request ViNS connection to the external network with corresponding ID.
|
|
||||||
- You may also control external IP address selection with I(ext_ip_addr) argument.
|
|
||||||
default: -1
|
|
||||||
required: no
|
|
||||||
ext_ip_addr:
|
|
||||||
description:
|
|
||||||
- IP address to assign to the external interface of this ViNS when connecting to the external net.
|
|
||||||
- If empty string is passed, the platform will assign free IP address automatically.
|
|
||||||
- 'Note that if invalid IP address or an address already occupied by another client is specified,
|
|
||||||
the module will abort with an error.'
|
|
||||||
- 'This argument is used only for new connection to the specified network. You cannot select another
|
|
||||||
external IP address without changing external network ID.'
|
|
||||||
- ViNS connection to the external network is controlled by I(ext_net_id) argument.
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
ipcidr:
|
|
||||||
description:
|
|
||||||
- Internal ViNS network address in a format XXX.XXX.XXX.XXX/XX (includes address and netmask).
|
|
||||||
- If empty string is passed, the platform will assign network address automatically.
|
|
||||||
- 'When selecting this address manually, note that this address must be unique amomng all ViNSes in
|
|
||||||
the target account.'
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
jwt:
|
|
||||||
description:
|
|
||||||
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
|
|
||||||
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
|
|
||||||
required: no
|
|
||||||
oauth2_url:
|
|
||||||
description:
|
|
||||||
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
|
|
||||||
- 'This parameter is required when when I(authenticator=oauth2).'
|
|
||||||
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
|
|
||||||
required: no
|
|
||||||
rg_id:
|
|
||||||
description:
|
|
||||||
- 'ID of the resource group (RG), where this ViNS will be created (for a new ViNS) or located
|
|
||||||
(for already existing ViNS).'
|
|
||||||
- If ViNS is created at the account level, I(rg_id) should be omitted or set to 0.
|
|
||||||
- If both I(rg_id) and I(rg_name) are specified, then I(rg_name) is ignored.
|
|
||||||
default: 0
|
|
||||||
required: no
|
|
||||||
rg_name:
|
|
||||||
description:
|
|
||||||
- 'Name of the resource group (RG), where this ViNS will be created (for new ViNS) or
|
|
||||||
located (for already existing ViNS).'
|
|
||||||
- If ViNS is created at the account level, I(rg_name) should be omitted or set to emtpy string.
|
|
||||||
- If both I(rg_name) and I(rg_id) are specified, then I(rg_name) is ignored.
|
|
||||||
default: empty string
|
|
||||||
required: no
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Specify the desired state of the ViNS at the exit of the module.
|
|
||||||
- 'Regardless of I(state), if ViNS exists and is in one of [DEPLOYING, DESTROYING, MIGRATING] states,
|
|
||||||
do nothing.'
|
|
||||||
- 'If desired I(state=present):'
|
|
||||||
- ' - ViNS does not exist or is in DESTROYED state, create new ViNS according to the specifications.'
|
|
||||||
- ' - ViNS is in DELETED state, restore it and change quotas if necessary. Note that on successful
|
|
||||||
restore ViNS will be left in DISABLED state.'
|
|
||||||
- ' - ViNS is in one of [CREATED, ENABLED, DISABLED] states, do nothing.'
|
|
||||||
- ' - ViNS in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=enabled):'
|
|
||||||
- ' - ViNS does not exist or is in DESTROYED state, create new ViNS according to the specifications.'
|
|
||||||
- ' - ViNS is in DELETED state, restore and enable it.'
|
|
||||||
- ' - ViNS is in one of [CREATED, ENABLED] states, do nothing.'
|
|
||||||
- ' - viNS is in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=absent):'
|
|
||||||
- ' - ViNS is in one of [CREATED, ENABLED, DISABLED, DELETED] states, destroy it.'
|
|
||||||
- ' - ViNS in DESTROYED state, do nothing.'
|
|
||||||
- ' - ViNS in any other state, abort with an error.'
|
|
||||||
- 'If desired I(state=disabled):'
|
|
||||||
- ' - ViNS is in one of [CREATED, ENABLED] states, disable it.'
|
|
||||||
- ' - ViNS is DISABLED state, do nothing.'
|
|
||||||
- ' - ViNS does not exist or is in one of [ENABLING, DISABLING, DELETING, DELETED, DESTROYING, DESTROYED]
|
|
||||||
states, abort with an error.'
|
|
||||||
default: present
|
|
||||||
choices: [ absent, disabled, enabled, present ]
|
|
||||||
user:
|
|
||||||
description:
|
|
||||||
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
|
|
||||||
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
|
|
||||||
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
|
|
||||||
required: no
|
|
||||||
verify_ssl:
|
|
||||||
description:
|
|
||||||
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
|
|
||||||
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
|
|
||||||
environment that uses self-signed certificates. Note that disabling SSL verification in any other
|
|
||||||
scenario can lead to security issues, so please know what you are doing.'
|
|
||||||
default: True
|
|
||||||
required: no
|
|
||||||
vins_id:
|
|
||||||
description:
|
|
||||||
- ID of the ViNs to manage. If ViNS is identified by ID it must be present.
|
|
||||||
- If ViNS ID is specified, I(account_id), I(account_name), I(rg_id) and I(rg_name) are ignored.
|
|
||||||
vins_name:
|
|
||||||
description:
|
|
||||||
- Name of the ViNS.
|
|
||||||
- ViNS can exist at either account or resource group level.
|
|
||||||
- ViNS name is unique only within its parent (i.e. account or resource group).
|
|
||||||
- 'To create ViNS at account level omit both I(rg_id) and I(rg_name), or set them to 0 and empty
|
|
||||||
string respectively.'
|
|
||||||
required: yes
|
|
||||||
workflow_callback:
|
|
||||||
description:
|
|
||||||
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
|
|
||||||
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
|
|
||||||
- API call at this URL will be used to relay such information to the application.
|
|
||||||
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
|
|
||||||
required: no
|
|
||||||
workflow_context:
|
|
||||||
description:
|
|
||||||
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
|
|
||||||
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
|
|
||||||
that up-level orchestrator could match returned information to the its internal entities.'
|
|
||||||
required: no
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
|
||||||
- name: create a new ViNS named "MyViNS" if it does not exist yet under RG "MyRG" in the account "MyAccount".
|
|
||||||
decort_vins:
|
|
||||||
authenticator: oauth2
|
|
||||||
app_id: "{{ MY_APP_ID }}"
|
|
||||||
app_secret: "{{ MY_APP_SECRET }}"
|
|
||||||
controller_url: "https://cloud.digitalenergy.online"
|
|
||||||
vins_name: "MyViNS"
|
|
||||||
rg_name: "MyRG"
|
|
||||||
account_name: "MyAccount"
|
|
||||||
state: present
|
|
||||||
delegate_to: localhost
|
|
||||||
register: my_vins
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
facts:
|
|
||||||
description: facts about the virtual network segment
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
facts:
|
|
||||||
id: 5
|
|
||||||
name: MyViNS
|
|
||||||
int_net_addr: 192.168.1.0
|
|
||||||
ext_net_addr: 10.50.11.118
|
|
||||||
state: CREATED
|
|
||||||
account_id: 7
|
|
||||||
rg_id: 19
|
|
||||||
gid: 1001
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
@@ -246,101 +13,346 @@ from ansible.module_utils.basic import env_fallback
|
|||||||
from ansible.module_utils.decort_utils import *
|
from ansible.module_utils.decort_utils import *
|
||||||
|
|
||||||
|
|
||||||
def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False):
|
class decort_vins(DecortController):
|
||||||
"""Package a dictionary of ViNS facts according to the decort_vins module specification.
|
def __init__(self):
|
||||||
This dictionary will be returned to the upstream Ansible engine at the completion of
|
super(decort_vins, self).__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
the module run.
|
arg_amodule = self.amodule
|
||||||
|
|
||||||
@param arg_vins_facts: dictionary with viNS facts as returned by API call to .../vins/get
|
self.vins_id = 0
|
||||||
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
|
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
|
||||||
"""
|
vins_facts = None # will hold ViNS facts
|
||||||
|
validated_rg_id = 0
|
||||||
|
rg_facts = None # will hold RG facts
|
||||||
|
validated_acc_id = 0
|
||||||
|
|
||||||
ret_dict = dict(id=0,
|
if arg_amodule.params['vins_id']:
|
||||||
name="none",
|
# expect existing ViNS with the specified ID
|
||||||
state="CHECK_MODE",
|
# This call to vins_find will abort the module if no ViNS with such ID is present
|
||||||
)
|
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False)
|
||||||
|
if self.vins_id == 0:
|
||||||
if arg_check_mode:
|
self.result['failed'] = True
|
||||||
# in check mode return immediately with the default values
|
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
|
||||||
return ret_dict
|
self.amodule.fail_json(**self.result)
|
||||||
|
self.vins_level = "ID"
|
||||||
if arg_vins_facts is None:
|
#raise Exception(self.vins_facts)
|
||||||
# if void facts provided - change state value to ABSENT and return
|
validated_acc_id = self.vins_facts['accountId']
|
||||||
ret_dict['state'] = "ABSENT"
|
validated_rg_id = self.vins_facts['rgId']
|
||||||
return ret_dict
|
|
||||||
|
|
||||||
ret_dict['id'] = arg_vins_facts['id']
|
|
||||||
ret_dict['name'] = arg_vins_facts['name']
|
|
||||||
ret_dict['state'] = arg_vins_facts['status']
|
|
||||||
ret_dict['account_id'] = arg_vins_facts['accountId']
|
|
||||||
ret_dict['rg_id'] = arg_vins_facts['rgId']
|
|
||||||
ret_dict['int_net_addr'] = arg_vins_facts['network']
|
|
||||||
ret_dict['gid'] = arg_vins_facts['gid']
|
|
||||||
|
|
||||||
if arg_vins_facts['vnfs'].get('GW'):
|
|
||||||
gw_config = arg_vins_facts['vnfs']['GW']['config']
|
|
||||||
ret_dict['ext_ip_addr'] = gw_config['ext_net_ip']
|
|
||||||
ret_dict['ext_net_id'] = gw_config['ext_net_id']
|
|
||||||
else:
|
|
||||||
ret_dict['ext_ip_addr'] = ""
|
|
||||||
ret_dict['ext_net_id'] = -1
|
|
||||||
|
|
||||||
# arg_vins_facts['vnfs']['GW']['config']
|
elif arg_amodule.params['rg_id']:
|
||||||
# ext_ip_addr -> ext_net_ip
|
# expect ViNS @ RG level in the RG with specified ID
|
||||||
# ??? -> ext_net_id
|
self.vins_level = "RG"
|
||||||
# tech_status -> techStatus
|
# This call to rg_find will abort the module if no RG with such ID is present
|
||||||
|
validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID
|
||||||
|
arg_amodule.params['rg_id'], arg_rg_name="")
|
||||||
|
validated_acc_id = rg_facts['accountId']
|
||||||
|
|
||||||
|
# This call to vins_find may return vins_id=0 if no ViNS found
|
||||||
|
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
|
||||||
|
account_id=0,
|
||||||
|
rg_id=arg_amodule.params['rg_id'],
|
||||||
|
rg_facts=rg_facts,
|
||||||
|
check_state=False)
|
||||||
|
# TODO: add checks and setup ViNS presence flags accordingly
|
||||||
|
pass
|
||||||
|
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
|
||||||
|
# Specified account must be present and accessible by the user, otherwise abort the module
|
||||||
|
validated_acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id'])
|
||||||
|
if not validated_acc_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = ("Current user does not have access to the requested account "
|
||||||
|
"or non-existent account specified.")
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
if arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0
|
||||||
|
# expect ViNS @ RG level in the RG with specified name under specified account
|
||||||
|
# RG with the specified name must be present under the account, otherwise abort the module
|
||||||
|
validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name'])
|
||||||
|
if (not validated_rg_id or
|
||||||
|
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
|
||||||
|
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
|
||||||
|
account_id=0, # set to 0, as we are looking for ViNS under RG
|
||||||
|
rg_id=validated_rg_id,
|
||||||
|
rg_facts=rg_facts,
|
||||||
|
check_state=False)
|
||||||
|
self.vins_level = "RG"
|
||||||
|
# TODO: add checks and setup ViNS presence flags accordingly
|
||||||
|
else: # At this point we know for sure that rg_name="" and rg_id=0
|
||||||
|
# So we expect ViNS @ account level
|
||||||
|
# This call to vins_find may return vins_id=0 if no ViNS found
|
||||||
|
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
|
||||||
|
account_id=validated_acc_id,
|
||||||
|
rg_id=0,
|
||||||
|
rg_facts=rg_facts,
|
||||||
|
check_state=False)
|
||||||
|
self.vins_level = "ACC"
|
||||||
|
# TODO: add checks and setup ViNS presence flags accordingly
|
||||||
|
else:
|
||||||
|
# this is "invalid arguments combination" sink
|
||||||
|
# if we end up here, it means that module was invoked with vins_id=0 and rg_id=0
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['msg'] = "Cannot find ViNS by name"
|
||||||
|
if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == '':
|
||||||
|
self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0."
|
||||||
|
if arg_amodule.params['rg_name'] == "":
|
||||||
|
# rg_name without account specified
|
||||||
|
self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
|
||||||
|
self.amodule.fail_json(**self.result)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
self.rg_id = validated_rg_id
|
||||||
|
self.acc_id = validated_acc_id
|
||||||
|
|
||||||
|
if self.vins_id and self.vins_facts['status'] != 'DESTROYED':
|
||||||
|
self.check_amodule_args_for_change()
|
||||||
|
else:
|
||||||
|
self.check_amodule_args_for_create()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
self.vins_id = self.vins_provision(self.amodule.params['vins_name'],
|
||||||
|
self.acc_id, self.rg_id,
|
||||||
|
self.amodule.params['ipcidr'],
|
||||||
|
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'],
|
||||||
|
self.amodule.params['description'],
|
||||||
|
zone_id=self.amodule.params['zone_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']:
|
||||||
|
_, self.vins_facts = self.vins_find(self.vins_id)
|
||||||
|
if self.amodule.params['connect_to']:
|
||||||
|
self.vins_update_ifaces(self.vins_facts,self.amodule.params['connect_to'],)
|
||||||
|
if self.amodule.params['mgmtaddr']:
|
||||||
|
self.vins_update_mgmt(self.vins_facts,self.amodule.params['mgmtaddr'])
|
||||||
|
|
||||||
|
return
|
||||||
|
def action(self,d_state='',restore=False):
|
||||||
|
if restore == True:
|
||||||
|
self.vins_restore(arg_vins_id=self.vins_id)
|
||||||
|
self.vins_state(self.vins_facts, 'enabled')
|
||||||
|
self.vins_facts['status'] = "ENABLED"
|
||||||
|
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
|
||||||
|
|
||||||
|
self.vins_update_extnet(self.vins_facts,
|
||||||
|
self.amodule.params['ext_net_id'],
|
||||||
|
self.amodule.params['ext_ip_addr'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED":
|
||||||
|
self.vins_state(self.vins_facts, d_state)
|
||||||
|
self.vins_facts['status'] = "ENABLED"
|
||||||
|
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
|
||||||
|
d_state = ''
|
||||||
|
|
||||||
|
if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED":
|
||||||
|
self.vins_update_ifaces(self.vins_facts,
|
||||||
|
self.amodule.params['connect_to'],
|
||||||
|
)
|
||||||
|
if self.result['changed']:
|
||||||
|
_, self.vins_facts = self.vins_find(self.vins_id)
|
||||||
|
self.vins_update_mgmt(self.vins_facts,
|
||||||
|
self.amodule.params['mgmtaddr'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if d_state != '':
|
||||||
|
self.vins_state(self.vins_facts, d_state)
|
||||||
|
|
||||||
|
aparam_zone_id = self.aparams['zone_id']
|
||||||
|
if aparam_zone_id is not None and aparam_zone_id != self.vins_facts['zoneId']:
|
||||||
|
self.vins_migrate_to_zone(
|
||||||
|
net_id=self.vins_id,
|
||||||
|
zone_id=aparam_zone_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
def delete(self):
|
||||||
|
self.vins_delete(self.vins_id, permanently=True)
|
||||||
|
self.vins_facts['status'] = 'DESTROYED'
|
||||||
|
return
|
||||||
|
def nop(self):
|
||||||
|
"""No operation (NOP) handler for ViNS management by decort_vins module.
|
||||||
|
This function is intended to be called from the main switch construct of the module
|
||||||
|
when current state -> desired state change logic does not require any changes to
|
||||||
|
the actual ViNS state.
|
||||||
|
"""
|
||||||
|
self.result['failed'] = False
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.vins_id:
|
||||||
|
self.result['msg'] = ("No state change required for ViNS ID {} because of its "
|
||||||
|
"current status '{}'.").format(self.vins_id, self.vins_facts['status'])
|
||||||
|
else:
|
||||||
|
self.result['msg'] = ("No state change to '{}' can be done for "
|
||||||
|
"non-existent ViNS instance.").format(self.amodule.params['state'])
|
||||||
|
return
|
||||||
|
def error(self):
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
if self.vins_id:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
|
||||||
|
"current status '{}'").format(self.vins_id,
|
||||||
|
self.amodule.params['state'],
|
||||||
|
self.vins_facts['status'])
|
||||||
|
else:
|
||||||
|
self.result['failed'] = True
|
||||||
|
self.result['changed'] = False
|
||||||
|
self.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
||||||
|
"ViNS name '{}'").format(self.amodule.params['state'],
|
||||||
|
self.amodule.params['vins_name'])
|
||||||
|
return
|
||||||
|
def package_facts(self, arg_check_mode=False):
|
||||||
|
"""Package a dictionary of ViNS facts according to the decort_vins module specification.
|
||||||
|
This dictionary will be returned to the upstream Ansible engine at the completion of
|
||||||
|
the module run.
|
||||||
|
|
||||||
|
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
ret_dict = dict(id=0,
|
||||||
|
name="none",
|
||||||
|
state="CHECK_MODE",
|
||||||
|
)
|
||||||
|
|
||||||
|
if arg_check_mode:
|
||||||
|
# in check mode return immediately with the default values
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
if self.vins_facts is None:
|
||||||
|
# if void facts provided - change state value to ABSENT and return
|
||||||
|
ret_dict['state'] = "ABSENT"
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
ret_dict['id'] = self.vins_facts['id']
|
||||||
|
ret_dict['name'] = self.vins_facts['name']
|
||||||
|
ret_dict['state'] = self.vins_facts['status']
|
||||||
|
ret_dict['account_id'] = self.vins_facts['accountId']
|
||||||
|
ret_dict['rg_id'] = self.vins_facts['rgId']
|
||||||
|
ret_dict['int_net_addr'] = self.vins_facts['network']
|
||||||
|
ret_dict['gid'] = self.vins_facts['gid']
|
||||||
|
custom_interfaces = list(filter(lambda i: i['type']=="CUSTOM",self.vins_facts['VNFDev']['interfaces']))
|
||||||
|
if custom_interfaces:
|
||||||
|
ret_dict['custom_net_addr'] = []
|
||||||
|
for runner in custom_interfaces:
|
||||||
|
ret_dict['custom_net_addr'].append(runner['ipAddress'])
|
||||||
|
mgmt_interfaces = list(filter(lambda i: i['listenSsh'] and i['name']!="ens9",self.vins_facts['VNFDev']['interfaces']))
|
||||||
|
if mgmt_interfaces:
|
||||||
|
ret_dict['ssh_ipaddr'] = []
|
||||||
|
for runner in mgmt_interfaces:
|
||||||
|
ret_dict['ssh_ipaddr'].append(runner['ipAddress'])
|
||||||
|
ret_dict['ssh_password'] = self.vins_facts['VNFDev']['config']['mgmt']['password']
|
||||||
|
ret_dict['ssh_port'] = 9022
|
||||||
|
if self.vins_facts['vnfs'].get('GW'):
|
||||||
|
gw_config = self.vins_facts['vnfs']['GW']['config']
|
||||||
|
ret_dict['ext_ip_addr'] = gw_config['ext_net_ip']
|
||||||
|
ret_dict['ext_net_id'] = gw_config['ext_net_id']
|
||||||
|
else:
|
||||||
|
ret_dict['ext_ip_addr'] = ""
|
||||||
|
ret_dict['ext_net_id'] = -1
|
||||||
|
ret_dict['zone_id'] = self.vins_facts['zoneId']
|
||||||
|
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
|
||||||
return ret_dict
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
account_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
account_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
description=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
ext_net_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=-1,
|
||||||
|
),
|
||||||
|
ext_ip_addr=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
ipcidr=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
mgmtaddr=dict(
|
||||||
|
type='list',
|
||||||
|
default=[],
|
||||||
|
),
|
||||||
|
custom_config=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
config_save=dict(
|
||||||
|
type='bool',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
connect_to=dict(
|
||||||
|
type='list',
|
||||||
|
default=[],
|
||||||
|
),
|
||||||
|
state=dict(
|
||||||
|
type='str',
|
||||||
|
default='present',
|
||||||
|
choices=[
|
||||||
|
'absent',
|
||||||
|
'disabled',
|
||||||
|
'enabled',
|
||||||
|
'present',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
rg_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
rg_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
vins_id=dict(
|
||||||
|
type='int',
|
||||||
|
default=0,
|
||||||
|
),
|
||||||
|
vins_name=dict(
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
),
|
||||||
|
zone_id=dict(
|
||||||
|
type=int,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=[
|
||||||
|
('vins_id', 'vins_name'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_amodule_args_for_change(self):
|
||||||
|
check_errors = False
|
||||||
|
if self.check_aparam_zone_id() is False:
|
||||||
|
check_errors = True
|
||||||
|
|
||||||
def decort_vins_parameters():
|
if check_errors:
|
||||||
"""Build and return a dictionary of parameters expected by decort_vins module in a form accepted
|
self.exit(fail=True)
|
||||||
by AnsibleModule utility class."""
|
|
||||||
|
|
||||||
return dict(
|
def check_amodule_args_for_create(self):
|
||||||
account_id=dict(type='int', required=False),
|
check_errors = False
|
||||||
account_name=dict(type='str', required=False, default=''),
|
if self.check_aparam_zone_id() is False:
|
||||||
annotation=dict(type='str', required=False, default=''),
|
check_errors = True
|
||||||
app_id=dict(type='str',
|
|
||||||
required=False,
|
if check_errors:
|
||||||
fallback=(env_fallback, ['DECORT_APP_ID'])),
|
self.exit(fail=True)
|
||||||
app_secret=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_APP_SECRET']),
|
|
||||||
no_log=True),
|
|
||||||
authenticator=dict(type='str',
|
|
||||||
required=True,
|
|
||||||
choices=['legacy', 'oauth2', 'jwt']),
|
|
||||||
controller_url=dict(type='str', required=True),
|
|
||||||
# datacenter=dict(type='str', required=False, default=''),
|
|
||||||
ext_net_id=dict(type='int', required=False, default=-1),
|
|
||||||
ext_ip_addr=dict(type='str', required=False, default=''),
|
|
||||||
ipcidr=dict(type='str', required=False, default=''),
|
|
||||||
jwt=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_JWT']),
|
|
||||||
no_log=True),
|
|
||||||
oauth2_url=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
|
|
||||||
password=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_PASSWORD']),
|
|
||||||
no_log=True),
|
|
||||||
state=dict(type='str',
|
|
||||||
default='present',
|
|
||||||
choices=['absent', 'disabled', 'enabled', 'present']),
|
|
||||||
user=dict(type='str',
|
|
||||||
required=False,
|
|
||||||
fallback=(env_fallback, ['DECORT_USER'])),
|
|
||||||
rg_id=dict(type='int', required=False, default=0),
|
|
||||||
rg_name=dict(type='str', required=False, default=''),
|
|
||||||
verify_ssl=dict(type='bool', required=False, default=True),
|
|
||||||
vins_id=dict(type='int', required=False, default=0),
|
|
||||||
vins_name=dict(type='str', required=True),
|
|
||||||
workflow_callback=dict(type='str', required=False),
|
|
||||||
workflow_context=dict(type='str', required=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Workflow digest:
|
# Workflow digest:
|
||||||
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
|
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
|
||||||
@@ -350,99 +362,8 @@ def decort_vins_parameters():
|
|||||||
# 5) report result to Ansible
|
# 5) report result to Ansible
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module_parameters = decort_vins_parameters()
|
decon = decort_vins()
|
||||||
|
amodule = decon.amodule
|
||||||
amodule = AnsibleModule(argument_spec=module_parameters,
|
|
||||||
supports_check_mode=True,
|
|
||||||
mutually_exclusive=[
|
|
||||||
['oauth2', 'password'],
|
|
||||||
['password', 'jwt'],
|
|
||||||
['jwt', 'oauth2'],
|
|
||||||
],
|
|
||||||
required_together=[
|
|
||||||
['app_id', 'app_secret'],
|
|
||||||
['user', 'password'],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
decon = DecortController(amodule)
|
|
||||||
|
|
||||||
vins_id = 0
|
|
||||||
vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
|
|
||||||
vins_facts = None # will hold ViNS facts
|
|
||||||
validated_rg_id = 0
|
|
||||||
rg_facts = None # will hold RG facts
|
|
||||||
validated_acc_id = 0
|
|
||||||
acc_facts = None # will hold Account facts
|
|
||||||
|
|
||||||
if amodule.params['vins_id']:
|
|
||||||
# expect existing ViNS with the specified ID
|
|
||||||
# This call to vins_find will abort the module if no ViNS with such ID is present
|
|
||||||
vins_id, vins_facts = decon.vins_find(amodule.params['vins_id'])
|
|
||||||
if not vins_id:
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = "Specified ViNS ID {} not found.".format(amodule.params['vins_id'])
|
|
||||||
decon.fail_json(**decon.result)
|
|
||||||
vins_level="ID"
|
|
||||||
validated_acc_id = vins_facts['accountId']
|
|
||||||
validated_rg_id = vins_facts['rgId']
|
|
||||||
elif amodule.params['rg_id']:
|
|
||||||
# expect ViNS @ RG level in the RG with specified ID
|
|
||||||
vins_level="RG"
|
|
||||||
# This call to rg_find will abort the module if no RG with such ID is present
|
|
||||||
validated_rg_id, rg_facts = decon.rg_find(0, # account ID set to 0 as we search for RG by RG ID
|
|
||||||
amodule.params['rg_id'], arg_rg_name="")
|
|
||||||
# This call to vins_find may return vins_id=0 if no ViNS found
|
|
||||||
vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'],
|
|
||||||
account_id=0,
|
|
||||||
rg_id=amodule.params['rg_id'],
|
|
||||||
check_state=False)
|
|
||||||
# TODO: add checks and setup ViNS presence flags accordingly
|
|
||||||
pass
|
|
||||||
elif amodule.params['account_id'] or amodule.params['account_name'] != "":
|
|
||||||
# Specified account must be present and accessible by the user, otherwise abort the module
|
|
||||||
validated_acc_id, acc_facts = decon.account_find(amodule.params['account_name'], amodule.params['account_id'])
|
|
||||||
if not validated_acc_id:
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = ("Current user does not have access to the requested account "
|
|
||||||
"or non-existent account specified.")
|
|
||||||
decon.fail_json(**decon.result)
|
|
||||||
if amodule.params['rg_name'] != "": # at this point we know that rg_id=0
|
|
||||||
# expect ViNS @ RG level in the RG with specified name under specified account
|
|
||||||
# RG with the specified name must be present under the account, otherwise abort the module
|
|
||||||
validated_rg_id, rg_facts = decon.rg_find(validated_acc_id, 0, amodule.params['rg_name'])
|
|
||||||
if (not validated_rg_id or
|
|
||||||
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
|
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['msg'] = "RG name '{}' not found or has invalid state.".format(amodule.params['rg_name'])
|
|
||||||
decon.fail_json(**decon.result)
|
|
||||||
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
|
|
||||||
vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'],
|
|
||||||
account_id=0, # set to 0, as we are looking for ViNS under RG
|
|
||||||
rg_id=validated_rg_id,
|
|
||||||
check_state=False)
|
|
||||||
vins_level = "RG"
|
|
||||||
# TODO: add checks and setup ViNS presence flags accordingly
|
|
||||||
else: # At this point we know for sure that rg_name="" and rg_id=0
|
|
||||||
# So we expect ViNS @ account level
|
|
||||||
# This call to vins_find may return vins_id=0 if no ViNS found
|
|
||||||
vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'],
|
|
||||||
account_id=validated_acc_id,
|
|
||||||
rg_id=0,
|
|
||||||
check_state=False)
|
|
||||||
vins_level = "ACC"
|
|
||||||
# TODO: add checks and setup ViNS presence flags accordingly
|
|
||||||
else:
|
|
||||||
# this is "invalid arguments combination" sink
|
|
||||||
# if we end up here, it means that module was invoked with vins_id=0 and rg_id=0
|
|
||||||
decon.result['failed'] = True
|
|
||||||
if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "":
|
|
||||||
decon.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0."
|
|
||||||
if amodule.params['rg_name'] == "":
|
|
||||||
# rg_name without account specified
|
|
||||||
decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
|
|
||||||
decon.fail_json(**decon.result)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Initial validation of module arguments is complete
|
# Initial validation of module arguments is complete
|
||||||
#
|
#
|
||||||
@@ -453,124 +374,76 @@ def main():
|
|||||||
#
|
#
|
||||||
# When managing existing ViNS we need to account for both "static" and "transient"
|
# When managing existing ViNS we need to account for both "static" and "transient"
|
||||||
# status. Full range of ViNS statii is as follows:
|
# status. Full range of ViNS statii is as follows:
|
||||||
#
|
#
|
||||||
# "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING"
|
# "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING"
|
||||||
#
|
#
|
||||||
|
# if cconfig_save is true, only config save without other updates
|
||||||
vins_should_exist = False
|
vins_should_exist = False
|
||||||
|
|
||||||
if vins_id:
|
if decon.vins_id:
|
||||||
vins_should_exist = True
|
vins_should_exist = True
|
||||||
if vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
|
if decon.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
|
||||||
# error: nothing can be done to existing ViNS in the listed statii regardless of
|
# error: nothing can be done to existing ViNS in the listed statii regardless of
|
||||||
# the requested state
|
# the requested state
|
||||||
decon.result['failed'] = True
|
decon.result['failed'] = True
|
||||||
decon.result['changed'] = False
|
decon.result['changed'] = False
|
||||||
decon.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
|
decon.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
|
||||||
"status '{}'").format(vins_id, vins_facts['status'])
|
"status '{}'").format(decon.vins_id, decon.vins_facts['status'])
|
||||||
elif vins_facts['status'] == "DISABLED":
|
elif decon.vins_facts['status'] == "DISABLED":
|
||||||
if amodule.params['state'] == 'absent':
|
if amodule.params['state'] == 'absent':
|
||||||
decon.vins_delete(vins_id, permanently=True)
|
decon.delete()
|
||||||
vins_facts['status'] = 'DESTROYED'
|
|
||||||
vins_should_exist = False
|
vins_should_exist = False
|
||||||
elif amodule.params['state'] in ('present', 'disabled'):
|
elif amodule.params['state'] in ('present', 'disabled'):
|
||||||
# update ViNS, leave in disabled state
|
# update ViNS, leave in disabled state
|
||||||
decon.vins_update(vins_facts,
|
decon.action()
|
||||||
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
|
|
||||||
elif amodule.params['state'] == 'enabled':
|
elif amodule.params['state'] == 'enabled':
|
||||||
# update ViNS and enable
|
# update ViNS and enable
|
||||||
decon.vins_update(vins_facts,
|
decon.action('enabled')
|
||||||
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
|
elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]:
|
||||||
decon.vins_state(vins_facts, 'enabled')
|
|
||||||
elif vins_facts['status'] in ["CREATED", "ENABLED"]:
|
|
||||||
if amodule.params['state'] == 'absent':
|
if amodule.params['state'] == 'absent':
|
||||||
decon.vins_delete(vins_id, permanently=True)
|
decon.delete()
|
||||||
vins_facts['status'] = 'DESTROYED'
|
|
||||||
vins_should_exist = False
|
vins_should_exist = False
|
||||||
elif amodule.params['state'] in ('present', 'enabled'):
|
elif amodule.params['state'] in ('present', 'enabled'):
|
||||||
# update ViNS
|
# update ViNS
|
||||||
decon.vins_update(vins_facts,
|
decon.action()
|
||||||
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
|
|
||||||
elif amodule.params['state'] == 'disabled':
|
elif amodule.params['state'] == 'disabled':
|
||||||
# disable and update ViNS
|
# disable and update ViNS
|
||||||
decon.vins_state(vins_facts, 'disabled')
|
decon.action('disabled')
|
||||||
decon.vins_update(vins_facts,
|
elif decon.vins_facts['status'] == "DELETED":
|
||||||
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
|
|
||||||
elif vins_facts['status'] == "DELETED":
|
|
||||||
if amodule.params['state'] in ['present', 'enabled']:
|
if amodule.params['state'] in ['present', 'enabled']:
|
||||||
# restore and enable
|
# restore and enable
|
||||||
decon.vins_restore(arg_vins_id=vins_id)
|
decon.action(restore=True)
|
||||||
decon.vins_state(vins_facts, 'enabled')
|
|
||||||
vins_should_exist = True
|
vins_should_exist = True
|
||||||
elif amodule.params['state'] == 'absent':
|
elif amodule.params['state'] == 'absent':
|
||||||
# destroy permanently
|
# destroy permanently
|
||||||
decon.vins_delete(vins_id, permanently=True)
|
decon.delete()
|
||||||
vins_facts['status'] = 'DESTROYED'
|
|
||||||
vins_should_exist = False
|
vins_should_exist = False
|
||||||
elif amodule.params['state'] == 'disabled':
|
elif amodule.params['state'] == 'disabled':
|
||||||
# error
|
decon.error()
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
|
|
||||||
"current status '{}'").format(vins_id,
|
|
||||||
amodule.params['state'],
|
|
||||||
vins_facts['status'])
|
|
||||||
vins_should_exist = False
|
vins_should_exist = False
|
||||||
elif vins_facts['status'] == "DESTROYED":
|
elif decon.vins_facts['status'] == "DESTROYED":
|
||||||
if amodule.params['state'] in ('present', 'enabled'):
|
if amodule.params['state'] in ('present', 'enabled'):
|
||||||
# need to re-provision ViNS; some attributes may be changed, some stay the same.
|
# need to re-provision ViNS;
|
||||||
# account and RG - stays the same
|
decon.create()
|
||||||
# vins_name - stays the same
|
|
||||||
# IPcidr - take from module arguments
|
|
||||||
# ext IP address - take from module arguments
|
|
||||||
# annotation - take from module arguments
|
|
||||||
vins_id = decon.vins_provision(vins_facts['name'],
|
|
||||||
validated_acc_id, validated_rg_id,
|
|
||||||
amodule.params['ipcidr'],
|
|
||||||
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
|
|
||||||
amodule.params['annotation'])
|
|
||||||
vins_should_exist = True
|
vins_should_exist = True
|
||||||
elif amodule.params['state'] == 'absent':
|
elif amodule.params['state'] == 'absent':
|
||||||
# nop
|
decon.nop()
|
||||||
decon.result['failed'] = False
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("No state change required for ViNS ID {} because of its "
|
|
||||||
"current status '{}'").format(vins_id,
|
|
||||||
vins_facts['status'])
|
|
||||||
vins_should_exist = False
|
vins_should_exist = False
|
||||||
elif amodule.params['state'] == 'disabled':
|
elif amodule.params['state'] == 'disabled':
|
||||||
# error
|
decon.error()
|
||||||
decon.result['failed'] = True
|
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
|
|
||||||
"current status '{}'").format(vins_id,
|
|
||||||
amodule.params['state'],
|
|
||||||
vins_facts['status'])
|
|
||||||
else:
|
else:
|
||||||
# Preexisting ViNS was not found.
|
# Preexisting ViNS was not found.
|
||||||
vins_should_exist = False # we will change it back to True if ViNS is created or restored
|
vins_should_exist = False # we will change it back to True if ViNS is created or restored
|
||||||
# If requested state is 'absent' - nothing to do
|
# If requested state is 'absent' - nothing to do
|
||||||
if amodule.params['state'] == 'absent':
|
if amodule.params['state'] == 'absent':
|
||||||
decon.result['failed'] = False
|
decon.nop()
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
|
|
||||||
"non-existent ViNS name '{}'").format(amodule.params['vins_name'])
|
|
||||||
elif amodule.params['state'] in ('present', 'enabled'):
|
elif amodule.params['state'] in ('present', 'enabled'):
|
||||||
decon.check_amodule_argument('vins_name')
|
decon.check_amodule_argument('vins_name')
|
||||||
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
|
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
|
||||||
vins_id = decon.vins_provision(amodule.params['vins_name'],
|
decon.create()
|
||||||
validated_acc_id, validated_rg_id,
|
vins_should_exist = True
|
||||||
amodule.params['ipcidr'],
|
|
||||||
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
|
|
||||||
amodule.params['annotation'])
|
|
||||||
vins_should_exist = True
|
|
||||||
elif amodule.params['state'] == 'disabled':
|
elif amodule.params['state'] == 'disabled':
|
||||||
decon.result['failed'] = True
|
decon.error()
|
||||||
decon.result['changed'] = False
|
|
||||||
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
|
|
||||||
"ViNS name '{}'").format(amodule.params['state'],
|
|
||||||
amodule.params['vins_name'])
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# conditional switch end - complete module run
|
# conditional switch end - complete module run
|
||||||
#
|
#
|
||||||
@@ -578,14 +451,9 @@ def main():
|
|||||||
amodule.fail_json(**decon.result)
|
amodule.fail_json(**decon.result)
|
||||||
else:
|
else:
|
||||||
# prepare ViNS facts to be returned as part of decon.result and then call exit_json(...)
|
# prepare ViNS facts to be returned as part of decon.result and then call exit_json(...)
|
||||||
if vins_should_exist:
|
if decon.result['changed']:
|
||||||
if decon.result['changed']:
|
_, decon.vins_facts = decon.vins_find(decon.vins_id)
|
||||||
# If we arrive here, there is a good chance that the ViNS is present - get fresh ViNS
|
decon.result['facts'] = decon.package_facts(amodule.check_mode)
|
||||||
# facts from # the cloud by ViNS ID.
|
|
||||||
# Otherwise, ViNS facts from previous call (when the ViNS was still in existence) will
|
|
||||||
# be returned.
|
|
||||||
_, vins_facts = decon.vins_find(vins_id)
|
|
||||||
decon.result['facts'] = decort_vins_package_facts(vins_facts, amodule.check_mode)
|
|
||||||
amodule.exit_json(**decon.result)
|
amodule.exit_json(**decon.result)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
48
library/decort_zone.py
Normal file
48
library/decort_zone.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: decort_zone
|
||||||
|
|
||||||
|
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.decort_utils import DecortController
|
||||||
|
|
||||||
|
|
||||||
|
class DecortZone(DecortController):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(AnsibleModule(**self.amodule_init_args))
|
||||||
|
self.id: int = self.aparams['id']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amodule_init_args(self) -> dict:
|
||||||
|
return self.pack_amodule_init_args(
|
||||||
|
argument_spec=dict(
|
||||||
|
id=dict(
|
||||||
|
type='int',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.get_info()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
self.facts = self.zone_get(id=self.id)
|
||||||
|
self.facts['grid_id'] = self.facts.pop('gid')
|
||||||
|
self.facts['created_timestamp'] = self.facts.pop('createdTime')
|
||||||
|
self.facts['updated_timestamp'] = self.facts.pop('updatedTime')
|
||||||
|
self.facts['node_ids'] = self.facts.pop('nodeIds')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DecortZone().run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
File diff suppressed because it is too large
Load Diff
2
requirements-dev.txt
Normal file
2
requirements-dev.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-r requirements.txt
|
||||||
|
pre-commit==4.1.0
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ansible==11.6.0
|
||||||
|
requests==2.32.3
|
||||||
Reference in New Issue
Block a user