3 Commits

Author SHA1 Message Date
e54a9591e4 11.0.0 2026-02-11 13:50:28 +03:00
8c554c8edd 10.0.1 2025-12-02 15:31:36 +03:00
becbe65993 10.0.0 2025-11-14 12:44:02 +03:00
45 changed files with 7716 additions and 5826 deletions

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,122 +1,170 @@
# Список изменений в версии 9.0.0
# Список изменений в версии 11.0.0
## Добавлено
### Глобально
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-798 | Обновлены системные требования: версия интерпретатора Python обновлена до 3.12, версия Python-библиотеки ansible обновлена до 11.6.0 |
| BANS-909 | В системные требования добавлена библиотека Python `dynamix_sdk`. |
| BANS-918 | Добавлен общий для всех модулей параметр `ignore_api_compatibility`. |
| BANS-913 | Добавлен общий для всех модулей параметр `ignore_sdk_version_check`. |
| BANS-954 | Добавлен модуль `decort_vm` в связи с переименованием из `decort_kvmvm`. |
| BANS-953 | Добавлен модуль `decort_image` в связи с переименованием из `decort_osimage`. |
| BANS-997 | Добавлен модуль `decort_security_group_list`, позволяющий получить список доступных групп безопасности. |
| BANS-884 | Добавлен модуль `decort_disk_list`, позволяющий получить список доступных дисков. |
| BANS-936 | Добавлен модуль `decort_rg_list`, позволяющий получить список доступных ресурсных групп. |
| BANS-949 | Добавлен модуль `decort_vins_list`, позволяющий получить список доступных внутренних сетей. |
| BANS-940 | Добавлен модуль `decort_vm_list`, позволяющий получить список доступных виртуальных машин. |
| BANS-959 | Добавлен модуль `decort_flip_group_list`, позволяющий получить список доступных групп с плавающим IP-адресом. |
| BANS-952 | Добавлен модуль `decort_image_list`, позволяющий получить список доступных образов. |
| BANS-983 | Добавлен модуль `decort_account_list`, позволяющий получить список доступных аккаунтов. |
| BANS-985 | Добавлен модуль `decort_audit_list`, позволяющий получить список аудитов. |
| BANS-988 | Добавлен модуль `decort_trunk_list`, позволяющий получить список доступных транковых портов. |
| BANS-987 | Добавлен модуль `decort_zone_list`, позволяющий получить список доступных зон. |
| BANS-989 | Добавлен модуль `decort_storage_policy_list`, позволяющий получить список политик хранения. |
| BANS-945 | Добавлен модуль `decort_user` в связи с переименованием из `decort_user_info`. |
### Модуль decort_kvmvm
### Модуль decort_vm
| Идентификатор<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` для внешней сети. |
| BANS-926 | Для параметра `chipset` добавлено значение по умолчанию `Q35` при создании ВМ. |
| BANS-933 | Добавлено возвращаемое значение `pinned_to_node` в связи с переименованием из `pinned_to_stack`. |
| BANS-934 | Добавлено возвращаемое значение `read_only`. |
| BANS-994 | Добавлена возможность задать параметр `mtu` при создании сетевого интерфейса для TRUNK-сети и изменить `mtu` у существующего интерфейса, подключённого к TRUNK-сети. |
| BANS-991 | Добавлена возможность указать параметр `ip_addr` при присоединении и изменении `DPDK` сети. |
| BANS-1017 | Добавлено возвращаемое значение `disks.cache`. |
| BANS-1034 | Добавлена возможность указать параметр `ip_addr` при присоединении и изменении `VFNIC` сети. |
| BANS-992 | Добавлен параметр `networks.net_prefix`. |
### Модуль decort_lb
### Модуль decort_group
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-793 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
| BANS-819 | Добавлено возвращаемое значение `account_id`. |
| BANS-800 | Добавлены значения `stopped` и `started` для параметра `state` и возвращаемое значение `tech_status`. |
| BANS-927 | Для параметра `chipset` добавлено значение по умолчанию `Q35` при создании группы. |
### Модуль 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`. |
| BANS-928 | Для параметра `chipset` добавлено значение по умолчанию `Q35` при создании кластера. |
### Модуль 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` для получения информации о зонах. |
| BANS-966 | Добавлен параметр `get_resource_consumption` и возвращаемое значение `resource_consumption`. |
### Модуль decort_trunk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-825 | Добавлен модуль `decort_trunk` для получения информации о транковых портах. |
| BANS-993 | Добавлено возвращаемое значение `mtu`. |
| BANS-976 | Добавлены возвращаемые значения `created_datetime`, `deleted_datetime`, `updated_datetime`. |
### Модуль decort_snapshot
### Модуль decort_zone
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-808 | Добавлено значение `merge_aborted` для параметра `state`. |
| BANS-970 | Добавлены возвращаемые значения `created_datetime`, `updated_datetime` и возвращаемые значения `account_ids`, `bservice_ids`, `vm_ids`, `extnet_ids`, `k8s_ids`, `lb_ids`, `vins_ids` в связи с переименованием из `accountIds`, `bserviceIds`, `computeIds`, `extnetIds`, `k8sIds`, `lbIds`, `vinsIds`. |
| BANS-1024 | Добавлено возвращаемое значение `node_auto_start`. |
### Модуль decort_osimage
### Модуль decort_vm_snapshot
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-849 | Добавлен параметр `account_id`, используемый при создании шаблонных и виртуальных образов. |
| BANS-978 | Добавлено возвращаемое значение `datetime`. |
### Модуль decort_storage_policy
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-977 | Добавлены возвращаемые значения `sep_name`, `sep_tech_status`. |
## Удалено
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-815 | Удалено значение по умолчанию для параметра `description`. |
| BANS-1019 | Добавлено возвращаемое значение `cache_mode`. |
| BANS-1050 | Добавлено возвращаемое значение `blkdiscard`. |
### Модуль 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` теперь только при создании балансировщика нагрузки. |
| BANS-954 | Удалён модуль `decort_kvmvm` в связи с переименованием в `decort_vm`. |
| BANS-969 | Модуль `decort_account_info` расформирован, его функционал перенесён в модули: `decort_disk_list`, `decort_rg_list`, `decort_vins_list`, `decort_vm_list`, `decort_flip_group_list`, `decort_image_list`, `decort_account`. |
| BANS-953 | Удалён модуль `decort_osimage` в связи с переименованием в `decort_image`. |
| BANS-945 | Удалён модуль `decort_user_info` в связи с переименованием в `decort_user`. |
### Модуль decort_account
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-817 | Модуль некорректно отслеживал завершение удаления и восстановления аккаунта. |
| BANS-924 | Удалён параметр `quotas.ext_traffic`. |
| BANS-998 |Для параметра `state` удалено значение по умолчанию. |
### Модуль decort_rg
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-925 | Удалён параметр `quotas.net_transfer`. |
### Модуль decort_vm
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-926 | Для параметра `chipset` удалено значение по умолчанию `i440fx` при создании ВМ. |
| BANS-933 | Удалено возвращаемое значение `pinned_to_stack` в связи с переименованием в `pinned_to_node`. |
| BANS-961 | Параметр `storage_policy_id` удалён из обязательных при пересоздании загрузочного диска. |
### Модуль decort_group
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-927 | Для параметра `chipset` удалено значение по умолчанию `i440fx` при создании группы. |
| BANS-1027 | Удалён параметр `driver`. |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-928 | Для параметра `chipset` удалено значение по умолчанию `i440fx` при создании кластера. |
### Модуль decort_user
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-983 | Удалён параметр `accounts` и возвращаемое значение `accounts` в связи с переносом этой функциональности в модуль `decort_account_list`. |
| BANS-985 | Удалён параметр `audits` и возвращаемое значение `audits` в связи с переносом этой функциональности в модуль `decort_audit_list`. |
| BANS-988 | Удалён параметр `trunks` и возвращаемое значение `trunks` в связи с переносом этой функциональности в модуль `decort_trunk_list`. |
| BANS-987 | Удалён параметр `zones` и возвращаемое значение `zones` в связи с переносом этой функциональности в модуль `decort_zone_list`. |
| BANS-989 | Удалён параметр `storage_policies` и возвращаемое значение `storage_policies` в связи с переносом этой функциональности в модуль `decort_storage_policy_list`. |
| BANS-997 | Удалён параметр `security_groups` и возвращаемое значение `security_groups` в связи с переносом этой функциональности в модуль `decort_security_group_list`. |
### Модуль decort_zone
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-970 | Удалены возвращаемые значения `accountIds`, `bserviceIds`, `computeIds`, `extnetIds`, `k8sIds`, `lbIds`, `vinsIds` в связи с переименованием в `account_ids`, `bservice_ids`, `vm_ids`, `extnet_ids`, `k8s_ids`, `lb_ids`, `vins_ids`. |
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1004 | Удалён параметр `reason` |
### Модуль decort_security_group
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1000 | Удалено возвращаемое значение `rules.remote_ip_prefix` в связи с переименованием в `rules.remote_net_cidr`. |
| BANS-1013 | Удален параметр `rules.objects.remote_ip_prefix` в связи с переименованием в `rules.objects.remote_net_cidr`. |
### Модуль decort_vm_snapshot
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1012 | Удалено возвращаемое значение `disks` в связи с переименованием в `disk_ids`. |
## Исправлено
### Модуль decort_vm
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-996 | Параметры `mac`, `security_groups`, `enable_secgroups`, `enabled` сетевого интерфейса DPDK-сети могли меняться при изменении `mtu`. |
| BANS-1052 | Параметры `numa_affinity`, `cpu_pin`, `hp_backed` не применялись при создании ВМ без образа. |
### Модуль decort_bservice
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-389 | После создания базовой службы, модуль не возвращал информацию о созданном объекте. |
### Модуль decort_vm_snapshot
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1022 | После создания снимка не возвращалась информация о снимке. |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1033 | Модуль без необходимости выполнял запрос к API `/cloudapi/k8s/update`, передавая в него параметры, не вызывающие изменения. |

View File

@@ -1,3 +0,0 @@
dev:
pip install -r requirements-dev.txt
pre-commit install

View File

@@ -5,6 +5,8 @@
| Версия платформы | Версия модулей Ansible |
|:----------------:|:--------------------------:|
| 4.5.0 | 11.0.x |
| 4.4.0 | 10.0.x |
| 4.4.0 build 963 | 9.0.x |
| 4.3.0 | 8.0.x |
| 4.2.0 | 7.0.x, 7.1.x, 7.2.x |

View File

@@ -1,11 +1,11 @@
---
#
# DECORT osimage module example
# DECORT image module example
#
- hosts: localhost
tasks:
- name: create
decort_osimage:
decort_image:
authenticator: oauth2
verify_ssl: False
controller_url: "https://ds1.digitalenergy.online"

View File

@@ -1,14 +1,14 @@
---
#
# DECORT osimage module example
# DECORT image module example
#
- hosts: localhost
tasks:
- name: create_virtual_osimage
decort_osimage:
- name: create_virtual_image
decort_image:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
virt_name: "alpine_last"
delegate_to: localhost
register: osimage
register: image

View File

@@ -1,11 +1,11 @@
---
#
# DECORT osimage module example
# DECORT image module example
#
- hosts: localhost
tasks:
- name: get_osimage
decort_osimage:
- name: get_image
decort_image:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"

View File

@@ -1,14 +1,14 @@
---
#
# DECORT osimage module example
# DECORT image module example
#
- hosts: localhost
tasks:
- name: rename_osimage
decort_osimage:
- name: rename_image
decort_image:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0v2.0"
image_id: 54321
delegate_to: localhost
register: osimage
register: image

View File

@@ -62,6 +62,10 @@ class DecortAccount(DecortController):
name=dict(
type='str',
),
get_resource_consumption=dict(
type='bool',
default=False,
),
quotas=dict(
type='dict',
options=dict(
@@ -71,9 +75,6 @@ class DecortAccount(DecortController):
disks_size=dict(
type='int',
),
ext_traffic=dict(
type='int',
),
gpu=dict(
type='int',
),
@@ -94,7 +95,6 @@ class DecortAccount(DecortController):
'disabled',
'present',
],
default='present',
),
sep_pools=dict(
type='list',
@@ -131,7 +131,7 @@ class DecortAccount(DecortController):
"""
arg_state = self.aparams['state']
if 'absent' in arg_state:
if arg_state is not None and 'absent' in arg_state:
# Parameters or combinations of parameters that can
# cause changing the object.
changing_params = [
@@ -175,6 +175,7 @@ class DecortAccount(DecortController):
if check_error:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.check_amodule_args_for_change()
@@ -187,6 +188,7 @@ class DecortAccount(DecortController):
self.acc_id, self._acc_info = self.account_find(
account_name=self.aparams['name'],
account_id=self.aparams['id'],
resource_consumption=self.aparams['get_resource_consumption'],
)
# If this is a repeated getting info
else:
@@ -195,6 +197,9 @@ class DecortAccount(DecortController):
if not self.amodule.check_mode:
self.acc_id, self._acc_info = self.account_find(
account_id=self.acc_id,
resource_consumption=(
self.aparams['get_resource_consumption']
),
)
self.facts = self.acc_info
@@ -361,7 +366,6 @@ class DecortAccount(DecortController):
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'],

View File

@@ -8,565 +8,46 @@ description: See L(Module Documentation,https://repository.basistech.ru/BASIS/de
'''
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()
module = AnsibleModule(
argument_spec=dict(
app_id=dict(type='raw'),
app_secret=dict(type='raw'),
authenticator=dict(type='raw'),
controller_url=dict(type='raw'),
domain=dict(type='raw'),
jwt=dict(type='raw'),
oauth2_url=dict(type='raw'),
password=dict(type='raw'),
username=dict(type='raw'),
verify_ssl=dict(type='raw'),
ignore_api_compatibility=dict(type='raw'),
ignore_sdk_version_check=dict(type='raw'),
audits=dict(type='raw'),
computes=dict(type='raw'),
disks=dict(type='raw'),
flip_groups=dict(type='raw'),
id=dict(type='raw'),
images=dict(type='raw'),
name=dict(type='raw'),
resource_groups=dict(type='raw'),
resource_consumption=dict(type='raw'),
vinses=dict(type='raw'),
),
)
module.fail_json(
msg=(
'The functionality of the module has been moved to the modules '
'"decort_disk_list", "decort_rg_list", "decort_vm_list", '
'"decort_vins_list", "decort_image_list", '
'"decort_flip_group_list", "decort_account".'
'\nPlease use the new modules to get information about the objects'
' available to the account.'
),
)
if __name__ == '__main__':

View File

@@ -0,0 +1,130 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_account_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortAccountList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
access_type=dict(
type='str',
choices=sdk_types.AccessType._member_names_,
),
id=dict(
type='int',
),
name=dict(
type='str',
),
status=dict(
type='str',
choices=sdk_types.AccountStatus._member_names_,
),
zone_id=dict(
type='int',
),
),
),
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=(
sdk_types.AccountForCAAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_access_type: str | None = aparam_filter['access_type']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.AccountForCAAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.account.list(
access_type=(
sdk_types.AccessType[aparam_access_type]
if aparam_access_type else None
),
id=aparam_filter['id'],
name=aparam_filter['name'],
status=(
sdk_types.AccountStatus[aparam_status]
if aparam_status else None
),
zone_id=aparam_filter['zone_id'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortAccountList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,168 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_audit_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortAuditList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
api_url_path=dict(
type='str',
),
bservice_id=dict(
type='int',
),
exclude_audit_lines=dict(
type='bool',
),
flip_group_id=dict(
type='int',
),
request_id=dict(
type='str',
),
k8s_id=dict(
type='int',
),
lb_id=dict(
type='int',
),
max_status_code=dict(
type='int',
),
min_status_code=dict(
type='int',
),
request_timestamp_end=dict(
type='int',
),
request_timestamp_start=dict(
type='int',
),
rg_id=dict(
type='int',
),
sep_id=dict(
type='int',
),
user_name=dict(
type='str',
),
vins_id=dict(
type='int',
),
vm_id=dict(
type='int',
),
),
),
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=(
sdk_types.AuditAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.AuditAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.audit.list(
account_id=aparam_filter['account_id'],
api_url_path=aparam_filter['api_url_path'],
bservice_id=aparam_filter['bservice_id'],
exclude_audit_lines=aparam_filter['exclude_audit_lines'] or False,
flip_group_id=aparam_filter['flip_group_id'],
request_id=aparam_filter['request_id'],
k8s_id=aparam_filter['k8s_id'],
lb_id=aparam_filter['lb_id'],
max_status_code=aparam_filter['max_status_code'],
min_status_code=aparam_filter['min_status_code'],
request_timestamp_end=aparam_filter['request_timestamp_end'],
request_timestamp_start=aparam_filter['request_timestamp_start'],
rg_id=aparam_filter['rg_id'],
sep_id=aparam_filter['sep_id'],
user_name=aparam_filter['user_name'],
vins_id=aparam_filter['vins_id'],
vm_id=aparam_filter['vm_id'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortAuditList().run()
if __name__ == '__main__':
main()

View File

@@ -28,7 +28,7 @@ class decort_bservice(DecortController):
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'],
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
@@ -113,7 +113,9 @@ class decort_bservice(DecortController):
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.bservice_state(self.bservice_info,'enabled')
self.bservice_state(self.bservice_info, self.aparams['state'])
self.bservice_should_exist = True
return
def action(self,d_state):
@@ -176,7 +178,6 @@ class decort_bservice(DecortController):
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'disabled',
@@ -260,71 +261,80 @@ class decort_bservice(DecortController):
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if self.amodule.check_mode:
self.result['changed'] = False
if self.bservice_id:
self.result['failed'] = False
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
# we exit the module at this point
else:
self.result['failed'] = True
self.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(**self.result)
pass
#MAIN MANAGE PART
if self.bservice_id:
if self.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
self.error()
elif self.bservice_info['status'] == "DELETED":
if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped'
):
self.restore(self.bservice_id)
self.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
self.nop()
elif self.bservice_info['status'] in (
'ENABLED', 'DISABLED', 'CREATED',
):
if amodule.params['state'] == 'absent':
self.destroy()
else:
self.action(amodule.params['state'])
elif self.bservice_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
self.create()
self.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
self.nop()
else:
state = amodule.params['state']
if state is None:
state = 'present'
if state == 'absent':
self.nop()
if state in ('present','started'):
self.create()
elif state in ('stopped', 'disabled','enabled'):
self.error()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.bservice_should_exist:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
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
decort_bservice().run()
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__":
if __name__ == '__main__':
main()

View File

@@ -23,7 +23,7 @@ class decort_disk(DecortController):
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'])
@@ -41,6 +41,8 @@ class decort_disk(DecortController):
validated_acc_id, validated_acc_info = self.account_find(
arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
self.acc_id = validated_acc_id
self._acc_info = validated_acc_info
if not validated_acc_id:
self.result['changed'] = False
self.result['msg'] = (
@@ -51,8 +53,6 @@ class decort_disk(DecortController):
)
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 "",
@@ -67,70 +67,130 @@ class decort_disk(DecortController):
self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts
if self.disk_id:
self.acc_id = validated_disk_facts['accountId']
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
def compare_iotune_params(self, new_iotune: dict, current_iotune: dict):
io_fields = sdk_types.IOTuneAPIResultNM.model_fields.keys()
for field in io_fields:
new_value = new_iotune.get(field)
current_value = current_iotune.get(field)
if new_value != current_value:
return False
return True
def limit_io(self, aparam_limit_io: dict):
self.sdk_checkmode(self.api.cloudapi.disks.limit_io)(
disk_id=self.disk_id,
read_bytes_sec_max=(aparam_limit_io.get('read_bytes_sec_max')),
read_bytes_sec=(aparam_limit_io.get('read_bytes_sec')),
read_iops_sec_max=(aparam_limit_io.get('read_iops_sec_max')),
read_iops_sec=(aparam_limit_io.get('read_iops_sec')),
size_iops_sec=(aparam_limit_io.get('size_iops_sec')),
total_bytes_sec_max=(aparam_limit_io.get('total_bytes_sec_max')),
total_bytes_sec=(aparam_limit_io.get('total_bytes_sec')),
total_iops_sec_max=(aparam_limit_io.get('total_iops_sec_max')),
total_iops_sec=(aparam_limit_io.get('total_iops_sec')),
write_bytes_sec_max=(aparam_limit_io.get('write_bytes_sec_max')),
write_bytes_sec=(aparam_limit_io.get('write_bytes_sec')),
write_iops_sec_max=(aparam_limit_io.get('write_iops_sec_max')),
write_iops_sec=(aparam_limit_io.get('write_iops_sec')),
)
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'],
self.disk_id = self.sdk_checkmode(self.api.cloudapi.disks.create)(
account_id=self.acc_id,
name=self.amodule.params['name'],
size_gb=self.amodule.params['size'],
storage_policy_id=self.aparams['storage_policy_id'],
description=self.amodule.params['description'],
sep_id=self.amodule.params['sep_id'],
sep_pool_name=self.amodule.params['pool'],
)
#IO tune
if self.amodule.params['limitIO']:
self.disk_limitIO(disk_id=self.disk_id,
limits=self.amodule.params['limitIO'])
aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
self.limit_io(aparam_limit_io=aparam_limit_io)
#set share status
if self.amodule.params['shareable']:
self.disk_share(self.disk_id,self.amodule.params['shareable'])
self.sdk_checkmode(self.api.cloudapi.disks.share)(
disk_id=self.disk_id,
)
return
def action(self,restore=False):
#restore never be done
if restore:
self.disk_restore(self.disk_id)
self.sdk_checkmode(self.api.cloudapi.disks.restore)(
disk_id=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'])
self.rename()
#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'])
self.sdk_checkmode(self.api.cloudapi.disks.resize2)(
disk_id=self.disk_id,
disk_size_gb=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'])
aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
if aparam_limit_io:
if not self.compare_iotune_params(
new_iotune=aparam_limit_io,
current_iotune=self.disk_info['iotune'],
):
self.limit_io(aparam_limit_io=aparam_limit_io)
#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'])
if self.amodule.params['shareable'] != self.disk_info['shareable']:
if self.amodule.params['shareable']:
self.sdk_checkmode(self.api.cloudapi.disks.share)(
disk_id=self.disk_id,
)
else:
self.sdk_checkmode(self.api.cloudapi.disks.unshare)(
disk_id=self.disk_id,
)
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id != self.disk_info['storage_policy_id']
):
self.sdk_checkmode(self.api.ca.disks.change_disk_storage_policy)(
disk_id=self.disk_id,
storage_policy_id=aparam_storage_policy_id,
)
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"
self.sdk_checkmode(self.api.cloudapi.disks.delete)(
disk_id=self.disk_id,
detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'],
)
self.disk_id, self.disk_info = self._disk_get_by_id(self.disk_id)
return
def rename(self):
self.disk_rename(diskId = self.disk_id,
name = self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
self.sdk_checkmode(self.api.cloudapi.disks.rename)(
disk_id=self.disk_id,
name=self.amodule.params['name'],
)
return
def nop(self):
@@ -175,6 +235,10 @@ class decort_disk(DecortController):
ret_dict['iotune'] = self.disk_info['iotune']
ret_dict['size_available'] = self.disk_info['sizeAvailable']
ret_dict['size_used'] = self.disk_info['sizeUsed']
ret_dict['storage_policy_id'] = self.disk_info['storage_policy_id']
ret_dict['to_clean'] = self.disk_info['to_clean']
ret_dict['cache_mode'] = self.disk_info['cache']
ret_dict['blkdiscard'] = self.disk_info['blkdiscard']
return ret_dict
@@ -219,12 +283,9 @@ class decort_disk(DecortController):
size=dict(
type='int',
),
iops=dict(
type='int',
default=2000,
),
limitIO=dict(
type='dict',
apply_defaults=True,
options=dict(
total_bytes_sec=dict(
type='int',
@@ -275,10 +336,6 @@ class decort_disk(DecortController):
type='bool',
default=False,
),
reason=dict(
type='str',
default='Managed by Ansible decort_disk',
),
state=dict(
type='str',
default='present',
@@ -287,6 +344,9 @@ class decort_disk(DecortController):
'present',
],
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
@@ -294,57 +354,127 @@ class decort_disk(DecortController):
],
)
def main():
decon = decort_disk()
amodule = decon.amodule
#
#Full range of Disk status is as follows:
#
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
#
if decon.disk_id:
#disk exist
if decon.disk_info['status'] in ["MODELED", "CREATING"]:
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(decon.disk_id, decon.disk_info['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] == 'present':
decon.action()
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]:
#re-provision disk
if amodule.params['state'] in ('present'):
decon.create()
else:
decon.nop()
elif decon.disk_info['status'] == "DELETED":
if amodule.params['state'] in ('present'):
decon.action(restore=True)
elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']):
decon.delete()
else:
decon.nop()
else:
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
decon.nop()
def check_amodule_args_for_change(self):
check_errors = False
if self.check_aparam_storage_policy_id() is False:
check_errors = True
if self.check_aparam_size() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new disk'
)
if self.check_aparam_storage_policy_id() is False:
check_errors = True
if self.check_aparam_size() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_aparam_storage_policy_id(self) -> bool:
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
return not check_errors
def check_aparam_size(self) -> bool:
check_errors = False
aparam_size = self.aparams['size']
if (
aparam_size is not None
and aparam_size <= 0
):
check_errors = True
self.message(
msg=(
'Check for parameter "size" failed: '
f'Disk cannot be size {aparam_size}'
),
)
return not check_errors
@DecortController.handle_sdk_exceptions
def run(self):
#
#Full range of Disk status is as follows:
#
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
#
amodule = self.amodule
if self.disk_id:
#disk exist
if self.disk_info['status'] in ["MODELED", "CREATING"]:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(self.disk_id, self.disk_info['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif self.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent':
self.delete()
elif amodule.params['state'] == 'present':
self.action()
elif self.disk_info['status'] in ["PURGED", "DESTROYED"]:
#re-provision disk
if amodule.params['state'] in ('present'):
self.create()
else:
self.nop()
elif self.disk_info['status'] == "DELETED":
if amodule.params['state'] in ('present'):
self.action(restore=True)
elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']):
self.delete()
else:
self.nop()
else:
decon.create()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed'] and amodule.params['state'] in ('present'):
_, decon.disk_info = decon.disk_find(decon.disk_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
self.nop()
else:
self.create()
if __name__ == "__main__":
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.result['changed']:
_, self.disk_info = self.disk_find(self.disk_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
def main():
decort_disk().run()
if __name__ == '__main__':
main()
#SHARE

155
library/decort_disk_list.py Normal file
View File

@@ -0,0 +1,155 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_disk_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortDiskList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
),
id=dict(
type='int',
),
name=dict(
type='str',
),
sep_id=dict(
type='int',
),
sep_pool_name=dict(
type='str',
),
shared=dict(
type='bool',
),
disk_max_size_gb=dict(
type='int',
),
status=dict(
type='str',
choices=sdk_types.DiskStatus._member_names_,
),
storage_policy_id=dict(
type='int',
),
type=dict(
type='str',
choices=sdk_types.DiskType._member_names_,
),
),
),
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=(
sdk_types.DiskForListAndListDeletedAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_type: str | None = aparam_filter['type']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.DiskForListAndListDeletedAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.disks.list(
account_id=aparam_filter['account_id'],
account_name=aparam_filter['account_name'],
disk_max_size_gb=aparam_filter['disk_max_size_gb'],
id=aparam_filter['id'],
name=aparam_filter['name'],
sep_id=aparam_filter['sep_id'],
sep_pool_name=aparam_filter['sep_pool_name'],
shared=aparam_filter['shared'],
status=(
sdk_types.DiskStatus[aparam_status]
if aparam_status else None
),
storage_policy_id=aparam_filter['storage_policy_id'],
type=(
sdk_types.DiskType[aparam_type]
if aparam_type else None
),
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortDiskList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,150 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_flip_group_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortFlipGroupList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
client_ids=dict(
type='list',
elements='int',
),
conn_id=dict(
type='int',
),
ext_net_id=dict(
type='int',
),
id=dict(
type='int',
),
include_deleted=dict(
type='bool',
default=False,
),
ip_addr=dict(
type='str',
),
name=dict(
type='str',
),
status=dict(
type='str',
choices=sdk_types.FlipGroupStatus._member_names_,
),
vins_id=dict(
type='int',
),
vins_name=dict(
type='str',
),
),
),
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=(
sdk_types.FlipGroupForListAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.FlipGroupForListAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.flipgroup.list(
account_id=aparam_filter['account_id'],
client_ids=aparam_filter['client_ids'],
conn_id=aparam_filter['conn_id'],
ext_net_id=aparam_filter['ext_net_id'],
id=aparam_filter['id'],
ip_addr=aparam_filter['ip_addr'],
name=aparam_filter['name'],
status=(
sdk_types.FlipGroupStatus[aparam_status]
if aparam_status else None
),
vins_id=aparam_filter['vins_id'],
vins_name=aparam_filter['vins_name'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortFlipGroupList().run()
if __name__ == '__main__':
main()

View File

@@ -36,11 +36,15 @@ class decort_group(DecortController):
group_id=arg_amodule.params['id'],
group_name=arg_amodule.params['name'],
)
self.acc_id = self.bservice_info['accountId']
self.rg_id = self.bservice_info['rgId']
if self.group_id:
self.group_should_exist = True
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def nop(self):
"""No operation (NOP) handler for B-service.
@@ -77,7 +81,7 @@ class decort_group(DecortController):
def create(self):
chipset = self.aparams['chipset']
if chipset is None:
chipset = 'i440fx'
chipset = 'Q35'
self.message(
msg=f'Chipset not specified, '
f'default value "{chipset}" will be used.',
@@ -92,13 +96,13 @@ class decort_group(DecortController):
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'],
arg_timeout=self.amodule.params['timeoutStart'],
chipset=chipset,
storage_policy_id=self.aparams['storage_policy_id'],
)
if self.amodule.params['state'] in ('started','present'):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
@@ -196,6 +200,7 @@ class decort_group(DecortController):
ret_dict['techStatus'] = self.group_info['techStatus']
ret_dict['state'] = self.group_info['status']
ret_dict['Computes'] = self.group_info['computes']
ret_dict['driver'] = self.group_info['driver']
return ret_dict
@property
@@ -229,17 +234,6 @@ class decort_group(DecortController):
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',
),
@@ -287,17 +281,13 @@ class decort_group(DecortController):
'i440fx',
]
),
storage_policy_id=dict(
type='int',
),
),
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'),
],
)
@@ -306,7 +296,7 @@ class decort_group(DecortController):
if (
self.aparams['chipset'] is None
and self.aparams['count'] > len(self.group_info['computes'])
and (self.aparams['count'] if self.aparams['count'] is not None else 0) > len(self.group_info['computes'])
):
check_errors = True
self.message(
@@ -335,58 +325,113 @@ class decort_group(DecortController):
)
break
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
for compute in self.group_info['computes']:
_, compute_info, _ = self._compute_get_by_id(compute['id'])
for disk in compute_info['disks']:
if aparam_storage_policy_id != disk['storage_policy_id']:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" '
'failed: storage_policy_id can not be changed '
f'for compute ID {compute['id']} '
f'disk ID {disk['id']}'
)
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)
def check_amodule_args_for_create(self):
check_errors = False
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()
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new group'
)
else:
if (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'RG ID {self.rg_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if (
aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if amodule.params['state'] == 'check':
self.result['changed'] = False
if self.group_id:
# cluster is found - package facts and report success to Ansible
self.result['failed'] = False
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
# we exit the module at this point
else:
subj.action()
self.result['failed'] = True
self.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],)
amodule.fail_json(**self.result)
if self.group_id:
if self.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED","DESTROYED"):
self.error()
elif self.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent':
self.nop()
if amodule.params['state'] in ('present','started','stopped'):
self.create()
elif self.group_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'absent':
self.destroy()
else:
self.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 amodule.params['state'] == 'absent':
self.nop()
if amodule.params['state'] in ('present','started','stopped'):
self.create()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.group_should_exist:
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_group().run()
if __name__ == '__main__':
main()

558
library/decort_image.py Normal file
View File

@@ -0,0 +1,558 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_image
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_image(DecortController):
def __init__(self):
super(decort_image, self).__init__(AnsibleModule(**self.amodule_init_args))
amodule = self.amodule
self.validated_image_id = 0
self.validated_virt_image_id = 0
self.validated_image_name = amodule.params['image_name']
self.validated_virt_image_name = None
self.image_info: dict
self.virt_image_info: dict
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']
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)
self.acc_id = self.validated_account_id
if (
self.aparams['virt_id'] != 0
or self.aparams['virt_name'] is not None
):
self.validated_virt_image_id, self.virt_image_info = (
self.decort_virt_image_find(amodule)
)
if self.virt_image_info:
_, linked_image_info = self._image_get_by_id(
image_id=self.virt_image_info['linkTo']
)
self.acc_id = linked_image_info['accountId']
if (
self.aparams['virt_name'] is not None
and self.aparams['virt_name']
!= self.virt_image_info['name']
):
self.decort_virt_image_rename(amodule)
self.result['msg'] = 'Virtual image renamed successfully'
elif (
self.aparams['image_id'] != 0
or self.aparams['image_name'] is not None
):
self.validated_image_id, self.image_info = (
self.decort_image_find(amodule)
)
if self.image_info:
self.acc_id = self.image_info['accountId']
if (
amodule.params['image_name']
and amodule.params['image_name'] != self.image_info['name']
):
decort_image.decort_image_rename(self,amodule)
self.result['msg'] = ("Image renamed successfully")
if self.validated_image_id:
self.check_amodule_args_for_change()
elif self.validated_virt_image_id:
self.check_amodule_args_for_change_virt_image()
elif self.aparams['virt_name']:
self.check_amodule_args_for_create_virt_image()
else:
self.check_amodule_args_for_create_image()
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'],
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'],
network_interface_naming=network_interface_naming,
storage_policy_id=amodule.params['storage_policy_id'],
)
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_image.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
self.result['msg'] = ("Image '{}' linked to virtual image '{}'").format(self.target_image_id,
decort_image.decort_image_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)
_, image_facts = decort_image._image_get_by_id(self, amodule.image_id_delete)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
return
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_image.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_image.decort_image_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_image.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_image_package_facts(
arg_image_facts: dict | None,
arg_check_mode=False,
):
"""Package a dictionary of OS image according to the decort_image module specification. This
dictionary will be returned to the upstream Ansible engine at the completion of the module run.
@param arg_image_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_image_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_image_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_image_facts['id']
ret_dict['name'] = arg_image_facts['name']
ret_dict['size'] = arg_image_facts['size']
# ret_dict['arch'] = arg_image_facts['architecture']
ret_dict['sep_id'] = arg_image_facts['sepId']
ret_dict['pool'] = arg_image_facts['pool']
ret_dict['state'] = arg_image_facts['status']
ret_dict['linkto'] = arg_image_facts['linkTo']
ret_dict['accountId'] = arg_image_facts['accountId']
ret_dict['boot_mode'] = arg_image_facts['bootType']
ret_dict['boot_loader_type'] = ''
match arg_image_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_image_facts[
'networkInterfaceNaming'
]
ret_dict['hot_resize'] = arg_image_facts['hotResize']
ret_dict['storage_policy_id'] = arg_image_facts['storage_policy_id']
ret_dict['to_clean'] = arg_image_facts['to_clean']
return ret_dict
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
pool=dict(
type='str',
default='',
),
sep_id=dict(
type='int',
default=0,
),
account_name=dict(
type='str',
),
account_id=dict(
type='int',
),
image_name=dict(
type='str',
),
image_id=dict(
type='int',
default=0,
),
virt_id=dict(
type='int',
default=0,
),
virt_name=dict(
type='str',
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'present',
],
),
url=dict(
type='str',
),
sepId=dict(
type='int',
default=0,
),
poolName=dict(
type='str',
),
hot_resize=dict(
type='bool',
),
image_username=dict(
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',
],
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
)
def check_amodule_args_for_change(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_change_virt_image(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and (
aparam_storage_policy_id
!= self.virt_image_info['storage_policy_id']
)
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id can not be changed in virtual image'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create_image(self):
check_errors = False
aparam_account_id = self.aparams['account_id']
if aparam_account_id is None:
check_errors = True
self.message(
msg='Check for parameter "account_id" failed: '
'account_id must be specified when creating '
'a new image'
)
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new image'
)
elif (
aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create_virt_image(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id can not be specified when creating '
'virtual image'
)
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if amodule.params['virt_name'] or amodule.params['virt_id']:
image_id, image_facts = self.decort_virt_image_find(amodule)
if amodule.params['image_name'] or amodule.params['image_id']:
self.target_image_id, _ = self.decort_image_find(amodule)
else:
self.target_image_id = 0
if self.decort_image_package_facts(image_facts)['id'] > 0:
self.result['facts'] = self.decort_image_package_facts(image_facts, amodule.check_mode)
self.validated_virt_image_id = self.decort_image_package_facts(image_facts)['id']
self.validated_virt_image_name = self.decort_image_package_facts(image_facts)['name']
if self.decort_image_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and self.target_image_id > 0:
image_id, image_facts = self.decort_virt_image_create(amodule)
self.result['msg'] = ("Virtual image '{}' created").format(self.decort_image_package_facts(image_facts)['id'])
self.result['changed'] = True
elif self.decort_image_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and self.target_image_id == 0:
self.result['msg'] = ("Cannot find OS image")
amodule.fail_json(**self.result)
if self.validated_virt_image_id:
if (
self.target_image_id
and self.decort_image_package_facts(image_facts)[
'linkto'
] != self.target_image_id
):
self.decort_virt_image_link(amodule)
self.result['changed'] = True
amodule.exit_json(**self.result)
if (
amodule.params['storage_policy_id'] is not None
and amodule.params['storage_policy_id']
!= image_facts['storage_policy_id']
):
self.image_change_storage_policy(
image_id=self.validated_virt_image_id,
storage_policy_id=amodule.params['storage_policy_id'],
)
if amodule.params['state'] == "absent" and self.validated_virt_image_id:
amodule.image_id_delete = self.validated_virt_image_id
image_id, image_facts = self.decort_virt_image_find(amodule)
if image_facts['status'] != 'PURGED':
self.decort_image_delete(amodule)
elif amodule.params['image_name'] or amodule.params['image_id']:
image_id, image_facts = self.decort_image_find(amodule)
self.validated_image_id = self.decort_image_package_facts(image_facts)['id']
if self.decort_image_package_facts(image_facts)['id'] > 0:
self.result['facts'] = self.decort_image_package_facts(image_facts, amodule.check_mode)
if amodule.params['state'] == "present" and self.validated_image_id == 0 and amodule.params['image_name'] and amodule.params['url']:
self.decort_image_create(amodule)
self.result['changed'] = True
image_id, image_facts = self.decort_image_find(amodule)
self.result['msg'] = ("OS image '{}' created").format(self.decort_image_package_facts(image_facts)['id'])
self.result['facts'] = self.decort_image_package_facts(image_facts, amodule.check_mode)
self.validated_image_id = self.decort_image_package_facts(image_facts)['id']
elif amodule.params['state'] == "absent" and self.validated_image_id:
amodule.image_id_delete = self.validated_image_id
image_id, image_facts = self.decort_image_find(amodule)
if image_facts['status'] != 'DESTROYED':
self.decort_image_delete(amodule)
if self.validated_image_id:
if (
amodule.params['storage_policy_id'] is not None
and amodule.params['storage_policy_id']
!= image_facts['storage_policy_id']
):
self.image_change_storage_policy(
image_id=self.validated_image_id,
storage_policy_id=amodule.params['storage_policy_id'],
)
if self.result['failed'] == True:
# we failed to find the specified image - fail the module
self.result['changed'] = False
amodule.fail_json(**self.result)
else:
if self.validated_image_id:
_, image_facts = self.decort_image_find(amodule=amodule)
elif self.validated_virt_image_id:
_, image_facts = self.decort_virt_image_find(amodule=amodule)
self.result['facts'] = self.decort_image_package_facts(
arg_image_facts=image_facts,
arg_check_mode=amodule.check_mode,
)
amodule.exit_json(**self.result)
def main():
decort_image().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,162 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_image_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortImageList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
bootable=dict(
type='bool',
),
id=dict(
type='int',
),
enabled=dict(
type='bool',
),
hot_resize=dict(
type='bool',
),
size_gb=dict(
type='int',
),
name=dict(
type='str',
),
public=dict(
type='bool',
),
sep_id=dict(
type='int',
),
sep_name=dict(
type='str',
),
sep_pool_name=dict(
type='str',
),
status=dict(
type='str',
choices=sdk_types.ImageStatus._member_names_,
),
type=dict(
type='str',
choices=sdk_types.ImageType._member_names_,
),
storage_policy_id=dict(
type='int',
),
),
),
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=(
sdk_types.ImageForListAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_type: str | None = aparam_filter['type']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.ImageForListAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.ca.image.list(
bootable=aparam_filter['bootable'],
enabled=aparam_filter['enabled'],
hot_resize=aparam_filter['hot_resize'],
id=aparam_filter['id'],
name=aparam_filter['name'],
public=aparam_filter['public'],
sep_id=aparam_filter['sep_id'],
sep_name=aparam_filter['sep_name'],
sep_pool_name=aparam_filter['sep_pool_name'],
size_gb=aparam_filter['size_gb'],
status=(
sdk_types.ImageStatus[aparam_status]
if aparam_status else None
),
storage_policy_id=aparam_filter['storage_policy_id'],
type=(
sdk_types.ImageType[aparam_type]
if aparam_type else None
),
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortImageList().run()
if __name__ == '__main__':
main()

View File

@@ -25,6 +25,7 @@ class DecortJWT(DecortController):
return amodule_init_args
@DecortController.handle_sdk_exceptions
def run(self):
self.result['jwt'] = self.jwt
self.amodule.exit_json(**self.result)

View File

@@ -33,10 +33,10 @@ class decort_k8s(DecortController):
'taints': [],
'annotations': [],
'ci_user_data': {},
'chipset': 'i440fx',
'chipset': 'Q35',
}
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] is None:
if arg_amodule.params['name'] is None 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."
@@ -158,7 +158,7 @@ class decort_k8s(DecortController):
def create(self):
master_chipset = self.amodule.params['master_chipset']
if master_chipset is None:
master_chipset = 'i440fx'
master_chipset = 'Q35'
target_wgs = deepcopy(self.amodule.params['workers'])
for wg in target_wgs:
@@ -166,34 +166,36 @@ class decort_k8s(DecortController):
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'],
)
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=master_chipset,
lb_sysctl=self.amodule.params['lb_sysctl'],
zone_id=self.aparams['zone_id'],
storage_policy_id=self.aparams['storage_policy_id'],
)
if not k8s_id:
if k8s_id == 0:
@@ -241,6 +243,12 @@ class decort_k8s(DecortController):
)
self.exit(fail=True)
if (
self.aparams['name'] is not None
and self.aparams['name'] != self.k8s_info['name']
):
self.k8s_update(id=self.k8s_id, name=self.aparams['name'])
if preupdate:
# K8s info updating
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
@@ -275,9 +283,6 @@ class decort_k8s(DecortController):
type='str',
default='',
),
quotas=dict(
type='dict',
),
state=dict(
type='str',
default='present',
@@ -296,7 +301,6 @@ class decort_k8s(DecortController):
),
name=dict(
type='str',
default='',
),
id=dict(
type='int',
@@ -448,6 +452,9 @@ class decort_k8s(DecortController):
zone_id=dict(
type='int',
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
@@ -499,6 +506,32 @@ class decort_k8s(DecortController):
'K8s cluster must be stopped to migrate to a zone.'
)
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
computes_ids = []
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
]:
computes_ids.append(master_node['id'])
for wg in self.k8s_info['k8sGroups']['workers']:
workers_ids = [
worker['id'] for worker in wg['detailedInfo']
]
computes_ids.extend(workers_ids)
for compute_id in computes_ids:
_, compute_info, _ = self._compute_get_by_id(
comp_id=compute_id
)
for disk in compute_info['disks']:
if aparam_storage_policy_id != disk['storage_policy_id']:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" '
'failed: storage_policy_id can not be changed '
f'for k8s cluster ID {self.k8s_id} compute ID '
f'{compute_id} disk ID {disk['id']}'
)
if check_errors:
self.exit(fail=True)
@@ -540,71 +573,96 @@ class decort_k8s(DecortController):
if self.check_aparam_zone_id() is False:
check_errors = True
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new cluster'
)
elif (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'RG ID {self.rg_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
def main():
subj = decort_k8s()
amodule = subj.amodule
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.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()
if self.amodule.check_mode:
self.result['changed'] = False
if self.k8s_id:
# cluster is found - package facts and report success to Ansible
self.result['failed'] = False
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
# we exit the module at this point
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)
self.result['failed'] = True
self.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
"RG ID {}").format(amodule.params['name'],
amodule.params['rg_id'],)
amodule.fail_json(**self.result)
if __name__ == "__main__":
if self.k8s_id:
if self.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
self.error()
elif self.k8s_info['status'] == "DELETED":
if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped'
):
self.k8s_restore(self.k8s_id)
self.action(disared_state=amodule.params['state'],
preupdate=True)
if amodule.params['state'] == 'absent':
if amodule.params['permanent']:
self.destroy()
else:
self.nop()
elif self.k8s_info['status'] in ('ENABLED', 'DISABLED'):
if amodule.params['state'] == 'absent':
self.destroy()
else:
self.action(disared_state=amodule.params['state'])
elif self.k8s_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
self.create()
if amodule.params['state'] == 'absent':
self.nop()
else:
if amodule.params['state'] == 'absent':
self.nop()
if amodule.params['state'] in ('present','started'):
self.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
self.error()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.k8s_should_exist:
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
def main():
decort_k8s().run()
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -42,8 +42,8 @@ class decort_lb(DecortController):
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)
.format(arg_amodule.params['lb_id'])
self.amodule.fail_json(**self.result)
self.rg_id = self.lb_facts['rgId']
self.vins_id = self.lb_facts['vinsId']
@@ -51,12 +51,11 @@ class decort_lb(DecortController):
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.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_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")
@@ -163,8 +162,9 @@ class decort_lb(DecortController):
def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently'])
self.lb_facts['status'] = 'DESTROYED'
self.lb_id, self.lb_facts = self._lb_get_by_id(self.lb_id)
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
@@ -252,10 +252,6 @@ class decort_lb(DecortController):
type='int',
default=0,
),
ext_ip_addr=dict(
type='str',
default='',
),
state=dict(
type='str',
choices=[
@@ -365,56 +361,60 @@ class decort_lb(DecortController):
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if self.lb_id:
if self.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing LB ID {} because of its current "
"status '{}'").format(self.lb_id, self.lb_facts['status'])
elif self.lb_facts['status'] in ('DISABLED', 'ENABLED', 'CREATED'):
if amodule.params['state'] == 'absent':
self.delete()
else:
self.action(d_state=amodule.params['state'])
elif self.lb_facts['status'] == "DELETED":
if amodule.params['state'] == 'present':
self.action(restore=True)
elif amodule.params['state'] == 'enabled':
self.action(d_state='enabled', restore=True)
elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']):
self.delete()
elif amodule.params['state'] == 'disabled':
self.error()
elif self.lb_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
self.create()
elif amodule.params['state'] == 'absent':
self.nop()
elif amodule.params['state'] == 'disabled':
self.error()
else:
state = amodule.params['state']
if state is None:
state = 'present'
if state == 'absent':
self.nop()
elif state in ('present', 'enabled', 'stopped', 'started'):
self.create()
elif state == 'disabled':
self.error()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.result['changed']:
_, self.lb_facts = self.lb_find(lb_id=self.lb_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
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()
decort_lb().run()
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__":
if __name__ == '__main__':
main()

View File

@@ -4,397 +4,59 @@ DOCUMENTATION = r'''
---
module: decort_osimage
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
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.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_osimage(DecortController):
def __init__(self):
super(decort_osimage, self).__init__(AnsibleModule(**self.amodule_init_args))
amodule = self.amodule
self.validated_image_id = 0
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']
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)
if amodule.params['virt_id'] != 0 and amodule.params['virt_name']:
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
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
pool=dict(
type='str',
default='',
),
sep_id=dict(
type='int',
default=0,
),
account_name=dict(
type='str',
),
account_id=dict(
type='int',
),
image_name=dict(
type='str',
),
image_id=dict(
type='int',
default=0,
),
virt_id=dict(
type='int',
default=0,
),
virt_name=dict(
type='str',
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'present',
],
),
drivers=dict(
type='str',
default='KVM_X86',
),
url=dict(
type='str',
),
gid=dict(
type='int',
default=0,
),
sepId=dict(
type='int',
default=0,
),
poolName=dict(
type='str',
),
hot_resize=dict(
type='bool',
),
image_username=dict(
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():
decon = decort_osimage()
amodule = decon.amodule
if amodule.params['virt_name'] or amodule.params['virt_id']:
module = AnsibleModule(
argument_spec=dict(
app_id=dict(type='raw'),
app_secret=dict(type='raw'),
authenticator=dict(type='raw'),
controller_url=dict(type='raw'),
domain=dict(type='raw'),
jwt=dict(type='raw'),
oauth2_url=dict(type='raw'),
password=dict(type='raw'),
username=dict(type='raw'),
verify_ssl=dict(type='raw'),
ignore_api_compatibility=dict(type='raw'),
ignore_sdk_version_check=dict(type='raw'),
pool=dict(type='raw'),
sep_id=dict(type='raw'),
account_name=dict(type='raw'),
account_id=dict(type='raw'),
image_name=dict(type='raw'),
image_id=dict(type='raw'),
virt_id=dict(type='raw'),
virt_name=dict(type='raw'),
state=dict(type='raw'),
url=dict(type='raw'),
sepId=dict(type='raw'),
poolName=dict(type='raw'),
hot_resize=dict(type='raw'),
image_username=dict(type='raw'),
image_password=dict(type='raw'),
usernameDL=dict(type='raw'),
passwordDL=dict(type='raw'),
boot=dict(type='raw'),
network_interface_naming=dict(type='raw'),
storage_policy_id=dict(type='raw'),
),
supports_check_mode=True,
)
image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule)
if amodule.params['image_name'] or amodule.params['image_id']:
decon.target_image_id, _ = decort_osimage.decort_image_find(decon, amodule)
else:
decon.target_image_id = 0
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)
decon.validated_virt_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
decon.validated_virt_image_name = decort_osimage.decort_osimage_package_facts(image_facts)['name']
module.fail_json(
msg=(
'The module "decort_osimage" has been renamed to "decort_image". '
'Please update your playbook to use "decort_image" '
'instead of "decort_osimage".'
),
)
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)
if decon.validated_virt_image_id and decon.target_image_id:
if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.target_image_id:
decort_osimage.decort_virt_image_link(decon,amodule)
decon.result['changed'] = True
amodule.exit_json(**decon.result)
if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent":
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)
if decon.result['failed'] == True:
# we failed to find the specified image - fail the module
decon.result['changed'] = False
amodule.fail_json(**decon.result)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -86,60 +86,65 @@ class decort_pfw(DecortController):
return
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
pfw_facts = None # will hold PFW facts as returned by pfw_configure
#
# Validate module arguments:
# 1) specified Compute instance exists in correct state
# 2) specified ViNS exists
# 3) ViNS has GW function
# 4) Compute is connected to this ViNS
#
validated_comp_id, comp_facts, rg_id = self.compute_find(amodule.params['compute_id'])
if not validated_comp_id:
self.result['failed'] = True
self.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id'])
amodule.fail_json(**self.result)
validated_vins_id, vins_facts = self.vins_find(amodule.params['vins_id'])
if not validated_vins_id:
self.result['failed'] = True
self.result['msg'] = "Cannot find specified ViNS ID {}.".format(amodule.params['vins_id'])
amodule.fail_json(**self.result)
gw_vnf_facts = vins_facts['vnfs'].get('GW')
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED":
self.result['failed'] = True
self.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id)
amodule.fail_json(**self.result)
#
# Initial validation of module arguments is complete
#
if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = self.pfw_configure(comp_facts, vins_facts, None)
elif amodule.params['rules'] is not None:
# manage PFW rules accodring to the module arguments
pfw_facts = self.pfw_configure(comp_facts, vins_facts, amodule.params['rules'])
else:
pfw_facts = self._pfw_get(comp_facts['id'], vins_facts['id'])
#
# complete module run
#
if self.result['failed']:
amodule.fail_json(**self.result)
else:
# prepare PFW facts to be returned as part of self.result and then call exit_json(...)
self.result['facts'] = self.decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode)
amodule.exit_json(**self.result)
def main():
decon = decort_pfw()
amodule = decon.amodule
decort_pfw().run()
pfw_facts = None # will hold PFW facts as returned by pfw_configure
#
# Validate module arguments:
# 1) specified Compute instance exists in correct state
# 2) specified ViNS exists
# 3) ViNS has GW function
# 4) Compute is connected to this ViNS
#
validated_comp_id, comp_facts, rg_id = decon.compute_find(amodule.params['compute_id'])
if not validated_comp_id:
decon.result['failed'] = True
decon.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id'])
amodule.fail_json(**decon.result)
validated_vins_id, vins_facts = decon.vins_find(amodule.params['vins_id'])
if not validated_vins_id:
decon.result['failed'] = True
decon.result['msg'] = "Cannot find specified ViNS ID {}.".format(amodule.params['vins_id'])
amodule.fail_json(**decon.result)
gw_vnf_facts = vins_facts['vnfs'].get('GW')
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED":
decon.result['failed'] = True
decon.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id)
amodule.fail_json(**decon.result)
#
# Initial validation of module arguments is complete
#
if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, None)
elif amodule.params['rules'] is not None:
# manage PFW rules accodring to the module arguments
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
#
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
# prepare PFW facts to be returned as part of decon.result and then call exit_json(...)
decon.result['facts'] = decon.decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -97,26 +97,56 @@ class decort_rg(DecortController):
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',)
query_key_map = dict(
cpu='cpu',
ram='ram',
disk='disksize',
ext_ips='extips',
storage_policies='policies',
)
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 quota_item == 'storage_policies':
rg_storage_policies = resources[query_key_map[quota_item]]
aparam_storage_policies = self.amodule.params['quotas'][
quota_item
]
for aparam_storage_policy in aparam_storage_policies:
aparam_storage_policy_id = aparam_storage_policy['id']
rg_storage_policy = rg_storage_policies.get(
str(aparam_storage_policy_id)
)
if (
rg_storage_policy
and aparam_storage_policy['storage_size_gb']
< rg_storage_policy['disksize']
):
incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item]
)
incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]]
)
elif (
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,
@@ -243,6 +273,7 @@ class decort_rg(DecortController):
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']
ret_dict['storage_policy_ids'] = self.rg_facts['storage_policy_ids']
return ret_dict
@@ -375,73 +406,78 @@ class decort_rg(DecortController):
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
def main():
decon = decort_rg()
amodule = decon.amodule
#amodule.check_mode=True
if decon.validated_rg_id > 0:
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
decon.error()
elif decon.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == "disabled":
decon.enable()
if amodule.params['state'] in ['present', 'enabled']:
if (
amodule.params['quotas']
or amodule.params['resType']
or amodule.params['rename'] != ""
or amodule.params['sep_pools'] is not None
or amodule.params['description'] is not None
):
decon.update()
if amodule.params['access']:
decon.access()
if amodule.params['def_netType'] is not None:
decon.setDefNet()
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
#amodule.check_mode=True
if self.validated_rg_id > 0:
if self.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
self.error()
elif self.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
self.destroy()
elif amodule.params['state'] == "disabled":
self.enable()
if amodule.params['state'] in ['present', 'enabled']:
if (
amodule.params['quotas']
or amodule.params['resType']
or amodule.params['rename'] != ""
or amodule.params['sep_pools'] is not None
or amodule.params['description'] is not None
):
self.update()
if amodule.params['access']:
self.access()
if amodule.params['def_netType'] is not None:
self.setDefNet()
elif decon.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
decon.destroy()
elif (amodule.params['state'] == 'present'
or amodule.params['state'] == 'disabled'):
decon.restore()
elif amodule.params['state'] == 'enabled':
decon.restore()
decon.enable()
elif decon.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == ("enabled"):
decon.enable()
elif self.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
self.destroy()
elif (amodule.params['state'] == 'present'
or amodule.params['state'] == 'disabled'):
self.restore()
elif amodule.params['state'] == 'enabled':
self.restore()
self.enable()
elif self.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
self.destroy()
elif amodule.params['state'] == ("enabled"):
self.enable()
else:
if amodule.params['state'] in ('present', 'enabled'):
if not amodule.params['rg_name']:
decon.result['failed'] = True
decon.result['msg'] = (
'Resource group could not be created because'
' the "rg_name" parameter was not specified.'
)
else:
decon.create()
if amodule.params['access'] and not amodule.check_mode:
decon.access()
elif amodule.params['state'] in ('disabled'):
decon.error()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.rg_should_exist:
if decon.result['changed']:
decon.get_info()
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
else:
amodule.exit_json(**decon.result)
if amodule.params['state'] in ('present', 'enabled'):
if not amodule.params['rg_name']:
self.result['failed'] = True
self.result['msg'] = (
'Resource group could not be created because'
' the "rg_name" parameter was not specified.'
)
else:
self.create()
if amodule.params['access'] and not amodule.check_mode:
self.access()
elif amodule.params['state'] in ('disabled'):
self.error()
if __name__ == "__main__":
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.rg_should_exist:
if self.result['changed']:
self.get_info()
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
def main():
decort_rg().run()
if __name__ == '__main__':
main()

148
library/decort_rg_list.py Normal file
View File

@@ -0,0 +1,148 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_rg_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortRGList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
),
created_after_timestamp=dict(
type='int',
),
created_before_timestamp=dict(
type='int',
),
id=dict(
type='int',
),
include_deleted=dict(
type='bool',
),
lock_status=dict(
type='str',
choices=sdk_types.LockStatus._member_names_,
),
name=dict(
type='str',
),
status=dict(
type='str',
choices=(
sdk_types.ResourceGroupStatus._member_names_
),
),
),
),
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=(
sdk_types.ResourceGroupAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_lock_status: str | None = aparam_filter['lock_status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.ResourceGroupAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.rg.list(
account_id=aparam_filter['account_id'],
account_name=aparam_filter['account_name'],
created_after_timestamp=aparam_filter['created_after_timestamp'],
created_before_timestamp=aparam_filter['created_before_timestamp'],
id=aparam_filter['id'],
include_deleted=aparam_filter['include_deleted'] or False,
lock_status=(
sdk_types.LockStatus[aparam_lock_status]
if aparam_lock_status else None
),
name=aparam_filter['name'],
status=(
sdk_types.ResourceGroupStatus[aparam_status]
if aparam_status else None
),
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortRGList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,366 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_security_group
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
from dynamix_sdk import exceptions as sdk_exceptions
import dynamix_sdk.types as sdk_types
class DecortSecurityGroup(DecortController):
id: int = 0
facts: dict = dict()
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(
account_id=dict(
type='int',
),
description=dict(
type='str',
),
id=dict(
type='int',
),
name=dict(
type='str',
),
rules=dict(
type='dict',
options=dict(
mode=dict(
type='str',
choices=[
e.value
for e in self.SecurityGroupRuleMode
],
default=self.SecurityGroupRuleMode.update.value,
),
objects=dict(
type='list',
elements='dict',
required=True,
options=dict(
direction=dict(
type='str',
choices=(
sdk_types.TrafficDirection.
_member_names_
),
required=True,
),
ethertype=dict(
type='str',
choices=(
sdk_types.SGRuleEthertype.
_member_names_
),
),
id=dict(
type='int',
),
port_range=dict(
type='dict',
options=dict(
min=dict(
type='int',
),
max=dict(
type='int',
),
),
),
protocol=dict(
type='str',
choices=(
sdk_types.SGRuleProtocol._member_names_
),
),
remote_net_cidr=dict(
type='str',
),
),
),
),
),
state=dict(
type='str',
choices=[e.value for e in self.SecurityGroupState],
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
if self.aparams['id'] is not None:
self.id = self.aparams['id']
elif self.aparams['name'] is not None:
security_groups = self.api.cloudapi.security_group.list(
account_id=self.aparams['account_id'],
name=self.aparams['name'],
)
if security_groups.data:
self.id = security_groups.data[0].id
if self.id:
self.get_info()
self.check_amodule_args_for_change()
self.change()
else:
self.check_amodule_args_for_create()
self.create()
if not self.amodule.check_mode:
self.change_rules()
if self.result['changed']:
self.get_info()
self.exit()
def get_info(self):
try:
storage_policy_model = self.api.cloudapi.security_group.get(
security_group_id=self.id
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='security_group',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = storage_policy_model.model_dump()
def check_amodule_args_for_create(self):
check_errors = False
aparam_account_id = self.aparams['account_id']
if aparam_account_id is None:
check_errors = True
self.message(
msg='Check for parameter "account_id" failed: '
'account_id must be specified when creating '
'a new security group'
)
aparam_name = self.aparams['name']
if aparam_name is None:
check_errors = True
self.message(
msg='Check for parameter "name" failed: '
'name must be specified when creating '
'a new security group'
)
aparam_state = self.aparams['state']
if (
aparam_state is not None
and aparam_state == self.SecurityGroupState.absent.value
):
check_errors = True
self.message(
msg='Check for parameter "state" failed: '
'state can not be "absent" when creating '
'a new security group'
)
if self.check_aparam_rules(for_create=True) is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_change(self):
check_errors = False
if self.check_aparam_rules() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_aparam_rules(self, for_create: bool = False):
check_errors = False
aparam_rules = self.aparams['rules']
if aparam_rules is None:
return True
mode = aparam_rules['mode']
rules = aparam_rules['objects']
if for_create and mode == self.SecurityGroupRuleMode.delete.value:
check_errors = True
self.message(
msg='Check for parameter "rules.mode" failed: '
'mode can not be "delete" when creating '
'new security group'
)
sg_rules_ids = []
if self.facts.get('rules'):
sg_rules_ids = [rule['id'] for rule in self.facts['rules']]
for rule in rules:
rule_id = rule.get('id')
if rule_id is not None:
if for_create:
check_errors = True
self.message(
msg='Check for parameter "rules.objects.id" failed: '
'can not set rule id when creating new '
'security group'
)
elif rule_id not in sg_rules_ids:
check_errors = True
self.message(
msg='Check for parameter "rules.objects.id" failed: '
f'rule ID {rule_id} not found for '
f'security group ID {self.id}'
)
if mode == self.SecurityGroupRuleMode.delete.value:
for param, value in rule.items():
if param != 'id' and value is not None:
check_errors = True
self.message(
msg='Check for parameter "rules.objects" '
'failed: only rule id can be specified if'
'rules.mode is "delete"'
)
break
elif mode == self.SecurityGroupRuleMode.delete.value:
check_errors = True
self.message(
msg='Check for parameter "rules.objects" '
'failed: rule id must be specified if mode is delete'
)
return not check_errors
def create(self):
id = self.sdk_checkmode(self.api.cloudapi.security_group.create)(
account_id=self.aparams['account_id'],
name=self.aparams['name'],
description=self.aparams['description'],
)
if id:
self.id = id
def change(self):
self.change_state()
self.change_params()
self.change_rules()
def change_state(self):
if self.aparams['state'] == self.SecurityGroupState.absent.value:
self.delete()
def change_params(self):
aparam_name = self.aparams['name']
aparam_description = self.aparams['description']
new_name, new_description = None, None
if (
aparam_name is not None
and aparam_name != self.facts['name']
):
new_name = aparam_name
if (
aparam_description is not None
and aparam_description != self.facts['description']
):
new_description = aparam_description
if new_name or new_description:
self.sdk_checkmode(self.api.cloudapi.security_group.update)(
security_group_id=self.id,
name=new_name,
description=new_description,
)
def change_rules(self):
aparam_rules = self.aparams['rules']
if aparam_rules is not None:
rules = aparam_rules['objects']
match aparam_rules['mode']:
case self.SecurityGroupRuleMode.delete.value:
for rule in rules:
self.security_group_detele_rule(
security_group_id=self.id,
rule_id=rule['id'],
)
case self.SecurityGroupRuleMode.match.value:
for rule in rules:
if rule.get('id') is None:
self.create_rule(rule=rule)
sg_rules_ids = set(
[rule['id'] for rule in self.facts['rules']]
)
aparam_rules_ids = set(
[rule['id'] for rule in rules if rule.get('id')]
)
rules_ids_to_delete = sg_rules_ids - aparam_rules_ids
for rule_id in rules_ids_to_delete:
self.security_group_detele_rule(
security_group_id=self.id,
rule_id=rule_id,
)
case self.SecurityGroupRuleMode.update.value:
for rule in rules:
if rule.get('id') is None:
self.create_rule(rule=rule)
def delete(self):
self.sdk_checkmode(self.api.cloudapi.security_group.delete)(
security_group_id=self.id,
)
self.facts = {}
self.exit()
def create_rule(self, rule: dict):
port_range_min, port_range_max = None, None
if rule.get('port_range'):
port_range_min = rule['port_range'].get('min')
port_range_max = rule['port_range'].get('max')
self.sdk_checkmode(self.api.cloudapi.security_group.create_rule)(
security_group_id=self.id,
traffic_direction=(
sdk_types.TrafficDirection[rule['direction']]
),
ethertype=(
sdk_types.SGRuleEthertype[rule['ethertype']]
if rule.get('ethertype') else sdk_types.SGRuleEthertype.IPV4
),
protocol=(
sdk_types.SGRuleProtocol[rule['protocol']]
if rule.get('protocol') else None
),
port_range_min=port_range_min,
port_range_max=port_range_max,
remote_net_cidr=rule.get('remote_net_cidr'),
)
def main():
DecortSecurityGroup().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,132 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_security_group_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortSecurityGroupList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
created_after_timestamp=dict(
type='int',
),
created_before_timestamp=dict(
type='int',
),
description=dict(
type='str',
),
id=dict(
type='int',
),
name=dict(
type='str',
),
updated_after_timestamp=dict(
type='int',
),
updated_before_timestamp=dict(
type='int',
),
),
),
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=(
sdk_types.SecurityGroupAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.SecurityGroupAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.security_group.list(
account_id=aparam_filter['account_id'],
created_after_timestamp=aparam_filter['created_after_timestamp'],
created_before_timestamp=aparam_filter['created_before_timestamp'],
description=aparam_filter['description'],
id=aparam_filter['id'],
name=aparam_filter['name'],
updated_after_timestamp=aparam_filter['updated_after_timestamp'],
updated_before_timestamp=aparam_filter['updated_before_timestamp'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortSecurityGroupList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,65 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_storage_policy
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
from dynamix_sdk import exceptions as sdk_exceptions
class DecortStoragePolicy(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,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
try:
storage_policy_model = self.api.cloudapi.storage_policy.get(
id=self.id
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='storage_policy',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = storage_policy_model.model_dump()
def main():
DecortStoragePolicy().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,152 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_storage_policy_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortStoragePolicyList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
description=dict(
type='str',
),
id=dict(
type='int',
),
iops_limit=dict(
type='int',
),
name=dict(
type='str',
),
rg_id=dict(
type='int',
),
sep_id=dict(
type='int',
),
sep_pool_name=dict(
type='str',
),
status=dict(
type='str',
choices=(
sdk_types.StoragePolicyStatus._member_names_
),
),
sep_tech_status=dict(
type='str',
choices=sdk_types.SEPTechStatus._member_names_,
),
),
),
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=(
sdk_types.StoragePolicyAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_sep_tech_status: str | None = aparam_filter['sep_tech_status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.StoragePolicyAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.storage_policy.list(
account_id=aparam_filter['account_id'],
description=aparam_filter['description'],
id=aparam_filter['id'],
iops_limit=aparam_filter['iops_limit'],
name=aparam_filter['name'],
rg_id=aparam_filter['rg_id'],
sep_id=aparam_filter['sep_id'],
sep_pool_name=aparam_filter['sep_pool_name'],
status=(
sdk_types.StoragePolicyStatus[aparam_status]
if aparam_status else None
),
sep_tech_status=(
sdk_types.SEPTechStatus[aparam_sep_tech_status]
if aparam_sep_tech_status else None
),
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortStoragePolicyList().run()
if __name__ == '__main__':
main()

View File

@@ -10,6 +10,8 @@ description: See L(Module Documentation,https://repository.basistech.ru/BASIS/de
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortTrunk(DecortController):
def __init__(self):
@@ -28,19 +30,31 @@ class DecortTrunk(DecortController):
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
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')
try:
trunk_model = self.api.cloudapi.trunk.get(
id=self.id
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='trunk',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = trunk_model.model_dump()
def main():

View File

@@ -0,0 +1,123 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_trunk_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortTrunkList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_ids=dict(
type='list',
elements='int',
),
ids=dict(
type='list',
elements='int',
),
status=dict(
type='str',
choices=sdk_types.TrunkStatus._member_names_,
),
vlan_ids=dict(
type='str',
),
),
),
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=(
sdk_types.TrunkAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.TrunkAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.trunk.list(
account_ids=aparam_filter['account_ids'],
ids=aparam_filter['ids'],
status=(
sdk_types.TrunkStatus[aparam_status]
if aparam_status else None
),
vlan_ids=aparam_filter['vlan_ids'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortTrunkList().run()
if __name__ == '__main__':
main()

68
library/decort_user.py Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_user
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 DecortUser(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(
api_methods=dict(
type='bool',
default=False,
),
objects_search=dict(
type='str',
),
resource_consumption=dict(
type='bool',
default=False,
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
self.facts = self.usermanager_whoami_result
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['resource_consumption']:
self.facts.update(self.user_resource_consumption())
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,
)
def main():
DecortUser().run()
if __name__ == '__main__':
main()

View File

@@ -8,607 +8,37 @@ description: See L(Module Documentation,https://repository.basistech.ru/BASIS/de
'''
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()
module = AnsibleModule(
argument_spec=dict(
app_id=dict(type='raw'),
app_secret=dict(type='raw'),
authenticator=dict(type='raw'),
controller_url=dict(type='raw'),
domain=dict(type='raw'),
jwt=dict(type='raw'),
oauth2_url=dict(type='raw'),
password=dict(type='raw'),
username=dict(type='raw'),
verify_ssl=dict(type='raw'),
ignore_api_compatibility=dict(type='raw'),
ignore_sdk_version_check=dict(type='raw'),
api_methods=dict(type='raw'),
objects_search=dict(type='raw'),
resource_consumption=dict(type='raw'),
),
supports_check_mode=True,
)
module.fail_json(
msg=(
'The module "decort_user_info" has been renamed to "decort_user". '
'Please update your playbook to use "decort_user" '
'instead of "decort_user_info".'
),
)
if __name__ == '__main__':

View File

@@ -170,7 +170,7 @@ class decort_vins(DecortController):
return
def delete(self):
self.vins_delete(self.vins_id, permanently=True)
self.vins_delete(self.vins_id, self.amodule.params['permanently'])
self.vins_facts['status'] = 'DESTROYED'
return
def nop(self):
@@ -320,6 +320,10 @@ class decort_vins(DecortController):
type='str',
default='',
),
permanently=dict(
type='bool',
default=False,
),
vins_id=dict(
type='int',
default=0,
@@ -361,101 +365,106 @@ class decort_vins(DecortController):
# 4) if ViNS exists: check desired state, desired configuration -> initiate action(s) accordingly
# 5) report result to Ansible
def main():
decon = decort_vins()
amodule = decon.amodule
#
# Initial validation of module arguments is complete
#
# At this point non-zero vins_id means that we will be managing pre-existing ViNS
# Otherwise we are about to create a new one as follows:
# - if validated_rg_id is non-zero, create ViNS @ RG level
# - if validated_rg_id is zero, create ViNS @ account level
#
# When managing existing ViNS we need to account for both "static" and "transient"
# status. Full range of ViNS statii is as follows:
#
# "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
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
#
# Initial validation of module arguments is complete
#
# At this point non-zero vins_id means that we will be managing pre-existing ViNS
# Otherwise we are about to create a new one as follows:
# - if validated_rg_id is non-zero, create ViNS @ RG level
# - if validated_rg_id is zero, create ViNS @ account level
#
# When managing existing ViNS we need to account for both "static" and "transient"
# status. Full range of ViNS statii is as follows:
#
# "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
if decon.vins_id:
vins_should_exist = True
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
# the requested state
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
"status '{}'").format(decon.vins_id, decon.vins_facts['status'])
elif decon.vins_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update ViNS, leave in disabled state
decon.action()
elif amodule.params['state'] == 'enabled':
# update ViNS and enable
decon.action('enabled')
elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
decon.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS
decon.action()
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
decon.action('disabled')
elif decon.vins_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
decon.action(restore=True)
vins_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
decon.delete()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
vins_should_exist = False
elif decon.vins_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
# need to re-provision ViNS;
decon.create()
vins_should_exist = True
elif amodule.params['state'] == 'absent':
decon.nop()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
else:
# Preexisting ViNS was not found.
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 amodule.params['state'] == 'absent':
decon.nop()
elif amodule.params['state'] in ('present', 'enabled'):
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
decon.create()
if self.vins_id:
vins_should_exist = True
elif amodule.params['state'] == 'disabled':
decon.error()
#
# conditional switch end - complete module run
#
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
# prepare ViNS facts to be returned as part of decon.result and then call exit_json(...)
if decon.result['changed']:
_, decon.vins_facts = decon.vins_find(decon.vins_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
if self.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
# error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
"status '{}'").format(self.vins_id, self.vins_facts['status'])
elif self.vins_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update ViNS, leave in disabled state
self.action()
elif amodule.params['state'] == 'enabled':
# update ViNS and enable
self.action('enabled')
elif self.vins_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS
self.action()
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
self.action('disabled')
elif self.vins_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
self.action(restore=True)
vins_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
if self.amodule.params['permanently']:
self.delete()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
self.error()
vins_should_exist = False
elif self.vins_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
# need to re-provision ViNS;
self.create()
vins_should_exist = True
elif amodule.params['state'] == 'absent':
self.nop()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
self.error()
else:
# Preexisting ViNS was not found.
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 amodule.params['state'] == 'absent':
self.nop()
elif amodule.params['state'] in ('present', 'enabled'):
self.check_amodule_argument('vins_name')
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
self.create()
vins_should_exist = True
elif amodule.params['state'] == 'disabled':
self.error()
#
# conditional switch end - complete module run
#
if self.result['failed']:
amodule.fail_json(**self.result)
else:
# prepare ViNS facts to be returned as part of self.result and then call exit_json(...)
if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_vins().run()
if __name__ == '__main__':
main()

141
library/decort_vins_list.py Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_vins_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortVINSList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
ext_net_ip=dict(
type='str',
),
id=dict(
type='int',
),
include_deleted=dict(
type='bool',
),
name=dict(
type='str',
),
rg_id=dict(
type='int',
),
status=dict(
type='str',
choices=sdk_types.VINSStatus._member_names_,
),
vnfdev_id=dict(
type='int',
),
zone_id=dict(
type='int',
),
),
),
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=(
sdk_types.VINSForListAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.VINSForListAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.vins.list(
account_id=aparam_filter['account_id'],
ext_net_ip=aparam_filter['ext_net_ip'],
id=aparam_filter['id'],
include_deleted=aparam_filter['include_deleted'] or False,
name=aparam_filter['name'],
rg_id=aparam_filter['rg_id'],
status=(
sdk_types.VINSStatus[aparam_status]
if aparam_status else None
),
vnfdev_id=aparam_filter['vnfdev_id'],
zone_id=aparam_filter['zone_id'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortVINSList().run()
if __name__ == '__main__':
main()

2510
library/decort_vm.py Normal file

File diff suppressed because it is too large Load Diff

158
library/decort_vm_list.py Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_vm_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortVMList(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(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
account_id=dict(
type='int',
),
ext_net_id=dict(
type='int',
),
ext_net_name=dict(
type='str',
),
id=dict(
type='int',
),
include_deleted=dict(
type='bool',
),
ip_addr=dict(
type='str',
),
name=dict(
type='str',
),
rg_id=dict(
type='int',
),
rg_name=dict(
type='str',
),
status=dict(
type='str',
choices=sdk_types.VMStatus._member_names_,
),
tech_status=dict(
type='str',
choices=sdk_types.VMTechStatus._member_names_,
),
zone_id=dict(
type='int',
),
),
),
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=(
sdk_types.VMAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_tech_status: str | None = aparam_filter['tech_status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.VMAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.compute.list(
account_id=aparam_filter['account_id'],
ext_net_id=aparam_filter['ext_net_id'],
ext_net_name=aparam_filter['ext_net_name'],
id=aparam_filter['id'],
include_deleted=aparam_filter['include_deleted'] or False,
ip_addr=aparam_filter['ip_addr'],
name=aparam_filter['name'],
rg_id=aparam_filter['rg_id'],
rg_name=aparam_filter['rg_name'],
status=(
sdk_types.VMStatus[aparam_status]
if aparam_status else None
),
tech_status=(
sdk_types.VMTechStatus[aparam_tech_status]
if aparam_tech_status else None
),
zone_id=aparam_filter['zone_id'],
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortVMList().run()
if __name__ == '__main__':
main()

View File

@@ -2,7 +2,7 @@
DOCUMENTATION = r'''
---
module: decort_snapshot
module: decort_vm_snapshot
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
@@ -12,7 +12,7 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortSnapshot(DecortController):
class DecortVMSnapshot(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.check_amodule_args()
@@ -30,9 +30,10 @@ class DecortSnapshot(DecortController):
self.exit(fail=True)
self.vm_name = self.vm_facts['name']
self.vm_snapshots = self.vm_facts['snapSets']
self.vm_snapshots = self.api.ca.compute.snapshot_list(
vm_id=self.vm_id).data
self.vm_snapshot_labels = [
snapshot['label'] for snapshot in self.vm_snapshots
snapshot.label for snapshot in self.vm_snapshots
]
self.new_snapshot_label = None
@@ -102,21 +103,23 @@ class DecortSnapshot(DecortController):
if check_error:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info(first_run=True)
self.get_info()
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,
)
def get_info(self, update_vm_snapshots: bool = False):
if update_vm_snapshots:
self.vm_snapshots = self.api.cloudapi.compute.snapshot_list(
vm_id=self.aparams_vm_id,
).data
label = self.new_snapshot_label or self.aparams_label
for snapshot in self.vm_snapshots:
if snapshot['label'] == label:
self.facts = snapshot
if snapshot.label == label:
self.facts = snapshot.model_dump()
if self.aparams['usage']:
self.facts['stored'] = self.get_snapshot_usage()
self.facts['vm_id'] = self.aparams_vm_id
@@ -134,11 +137,11 @@ class DecortSnapshot(DecortController):
self.abort_merge()
def create(self):
self.snapshot_create(
compute_id=self.aparams_vm_id,
self.sdk_checkmode(self.api.cloudapi.compute.snapshot_create)(
vm_id=self.aparams_vm_id,
label=self.new_snapshot_label,
)
self.get_info()
self.get_info(update_vm_snapshots=True)
def delete(self):
self.snapshot_delete(
@@ -149,7 +152,7 @@ class DecortSnapshot(DecortController):
def abort_merge(self):
self.snapshot_abort_merge(
vm_id=self.aparams_vm_id,
vm_id=self.aparams_vm_id,
label=self.aparams_label,
)
self.get_info()
@@ -161,7 +164,7 @@ class DecortSnapshot(DecortController):
label=label,
)
return common_snapshots_usage_info['stored']
def check_amodule_args_for_change(self):
check_errors = False
@@ -171,7 +174,7 @@ class DecortSnapshot(DecortController):
):
check_errors = True
self.message(
f'Check for parameter "state" failed: '
'Check for parameter "state" failed: '
'Merge can be aborted only for VM in "MERGE" tech status.'
)
@@ -179,9 +182,8 @@ class DecortSnapshot(DecortController):
self.exit(fail=True)
def main():
DecortSnapshot().run()
DecortVMSnapshot().run()
if __name__ == '__main__':

View File

@@ -10,6 +10,8 @@ description: See L(Module Documentation,https://repository.basistech.ru/BASIS/de
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortZone(DecortController):
def __init__(self):
@@ -28,16 +30,29 @@ class DecortZone(DecortController):
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
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')
try:
zone_model = self.api.cloudapi.zone.get(id=self.id)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='zone',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = zone_model.model_dump()
def main():

133
library/decort_zone_list.py Normal file
View File

@@ -0,0 +1,133 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_zone_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk.base import get_alias, name_mapping_dict
import dynamix_sdk.types as sdk_types
class DecortZoneList(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(
filter=dict(
type='dict',
apply_defaults=True,
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=sdk_types.ZoneStatus._member_names_,
),
),
),
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=(
sdk_types.ZoneForListAPIResultNM
.model_fields.keys()
),
required=True,
),
),
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status']
aparam_pagination: dict[str, Any] = self.aparams['pagination']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
sort_by: str | None = None
if aparam_sorting:
sorting_field = get_alias(
field_name=aparam_sorting['field'],
model_cls=sdk_types.ZoneForListAPIResultNM,
name_mapping_dict=name_mapping_dict,
)
sort_by_prefix = '+' if aparam_sorting['asc'] else '-'
sort_by = f'{sort_by_prefix}{sorting_field}'
self.facts = self.api.cloudapi.zone.list(
deletable=aparam_filter['deletable'],
description=aparam_filter['description'],
grid_id=aparam_filter['grid_id'],
id=aparam_filter['id'],
name=aparam_filter['name'],
node_id=aparam_filter['node_id'],
status=(
sdk_types.ZoneStatus[aparam_status]
if aparam_status else None
),
page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'],
sort_by=sort_by,
).model_dump()['data']
def main():
DecortZoneList().run()
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
-r requirements.txt
pre-commit==4.1.0

View File

@@ -1,2 +1,3 @@
ansible==11.6.0
requests==2.32.3
git+https://repository.basistech.ru/BASIS/dynamix-python-sdk.git@1.4.latest