5 Commits

Author SHA1 Message Date
1ab446e05d 12.0.0 2026-06-01 18:27:15 +03:00
ae986fa9e6 11.0.3 2026-05-25 14:58:44 +07:00
3eb7fb2791 11.0.2 2026-04-27 18:14:28 +03:00
81e765fdb1 11.0.1 2026-04-27 15:37:15 +03:00
ae50b0cd54 11.0.0 2026-03-13 17:31:54 +03:00
57 changed files with 11248 additions and 8747 deletions

View File

@@ -1,11 +1,174 @@
# Список изменений в версии 10.0.1
# Список изменений в версии 12.0.0
## Добавлено
## Удалено
## Исправлено
### Модуль decort_group
### Глобально
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-941 | Исправлена ошибка, из-за которой не происходил запуск группы после создании с указанием параметра `timeoutStart`. |
| BANS-1164 | Добавлен модуль `decort_sdn_hypervisor_list`, позволяющий получить список гипервизоров SDN.|
| BANS-1162 | Добавлен модуль `decort_sdn_access_group_list`, позволяющий получить список групп доступа SDN.|
| BANS-1161 | Добавлен модуль `decort_sdn_segment_list`, позволяющий получить список доступных сегментов SDN. |
| BANS-1154 | Добавлен модуль `decort_sdn_segment`, позволяющий привести к целевому состоянию и получить информацию о сегментах SDN. |
| BANS-1158 | Добавлен модуль `decort_sdn_hypervisor`, позволяющий привести к целевому состоянию и получить информацию о гипервизоре SDN. |
| BANS-1156 | Добавлен модуль `decort_sdn_access_group`, позволяющий создать, привести к целевому состоянию и получить информацию о группе доступа SDN. |
| BANS-1163 | Добавлен модуль `decort_sdn_logical_port_list`, позволяющий получить список доступных логических портов SDN. |
| BANS-1159 | Добавлен модуль `decort_sdn_network_object_group`, позволяющий привести к целевому состоянию и получить информацию о группах сетевых объектов. |
| BANS-1157 | Добавлен модуль `decort_sdn_logical_port`, позволяющий привести к целевому состоянию и получить информацию о логическом порте. |
| BANS-1184 | Добавлен модуль `decort_sdn_logical_port_address`, позволяющий привести к целевому состоянию и получить информацию об адресе логического порта SDN. |
| BANS-1186 | Добавлен модуль `decort_sdn_network_object_group_logical_port`, позволяющий привести к целевому состоянию логические порты сетевых групп объектов SDN. |
| BANS-1185 | Добавлен модуль `decort_sdn_network_object_group_ip_range`, позволяющий привести к целевому состоянию и получить информацию о диапазоне IP-адресов в группе сетевых объектов SDN. |
| BANS-1193 | Добавлен модуль `decort_sdn_network_object_group_mac`, позволяющий привести к целевому состоянию и получить информацию о MAC-адресе в группе сетевых объектов SDN. |
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1011 | Добавлены возвращаемые значения `account_name`, `acl`, `created_by`, `created_datetime`, `created_timestamp`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `description`, `destruction_datetime`, `destruction_timestamp`, `device_name`, `image_id`, `image_ids`, `milestones`, `params`, `parent_id`, `present_to`, `purge_datetime`, `purge_timestamp`, `replication`, `res_id`, `res_name`, `role`, `sep_type`, `shared`, `snapshots`, `tech_status`, `updated_by`, `updated_datetime`, `updated_timestamp`. |
| BANS-1104 | Добавлено возвращаемое значение `provision`. |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-238 | Добавлена возможность изменить описание объекта. |
| BANS-1134 | Добавлены возвращаемые значения `account_name`, `acl`, `acl.account`, `acl.k8s`, `acl.rg`, `bservice_id`, `created_by`, `created_datetime`, `created_timestamp`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `extnet_only`, `k8ci_id`, `k8ci_name`, `lb_ha_ips`, `lb_ha_ips.backend`, `lb_ha_ips.frontend`, `lb_ha_mode`, `network_plugin`, `node_groups`, `node_groups.master`, `node_groups.master.id`, `node_groups.master.name`, `node_groups.master.annotations`, `node_groups.master.labels`, `node_groups.master.taints`, `node_groups.master.vms.ext_ip`, `node_groups.master.vms.name`, `node_groups.master.vms.status`, `node_groups.worker`, `node_groups.worker.id`, `node_groups.worker.name`, `node_groups.worker.annotations`, `node_groups.worker.labels`, `node_groups.worker.taints`, `node_groups.worker.vms.ext_ip`, `node_groups.worker.vms.name`, `node_groups.worker.vms.status`, `rg_name`, `updated_by`, `updated_datetime`, `updated_timestamp`, `with_lb`. |
### Модуль decort_rg
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1063 | Добавлены возвращаемые значения `account_name`, `acl`, `cpu_allocation_parameter`, `cpu_allocation_ratio`, `created_by`, `created_timestamp`, `created_datetime`, `deleted_by`, `deleted_timestamp`, `deleted_datetime`, `dirty`, `guid`, `lock_status`, `milestones`, `secret`, `updated_by`, `updated_timestamp`, `updated_datetime`, `vm_features`. |
### Модуль decort_vins
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1061 | Добавлены возвращаемые значения `account_name`, `created_by`, `created_datetime`, `created_timestamp`, `default_gw`, `default_qos`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `description`, `guid`, `lock_status`, `manager_id`, `manager_type`, `milestones`, `net_prefix`, `pre_reservation_count`, `redundant`, `rg_name`, `secondary_vnfdev_id`, `security_group_mode`, `updated_by`, `updated_datetime`, `updated_timestamp`, `user_managed`, `vms`, `vnfdev`, `vnfs`, `vxlan_id`. |
| BANS-1132 | Добавлен параметр `security_group_mode`. |
### Модуль decort_vm
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1088 | Добавлено возвращаемое значение `weight`. |
### Модуль decort_disk_list
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1083 | Добавлен параметр `filter.vm_id`. |
| BANS-1084 | Добавлен параметр `filter.rg_id`. |
| BANS-1096 | Добавлено возвращаемое значение `independent`. |
| BANS-1103 | Добавлено возвращаемое значение `provision`. |
### Модуль decort_image
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1094 | Добавлено возвращаемое значение `independent`. |
| BANS-1108 | Добавлено возвращаемое значение `links_to`. |
### Модуль decort_zone_list
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1116 | Добавлено возвращаемое значение `drs_dx_app_id`. |
| BANS-1117 | Добавлено возвращаемое значение `drs_dx_url`. |
| BANS-1118 | Добавлено возвращаемое значение `drs`. |
| BANS-1119 | Добавлено возвращаемое значение `drs_name`. |
| BANS-1120 | Добавлено возвращаемое значение `drs_uid`. |
| BANS-1121 | Добавлено возвращаемое значение `drs_dx_sso_url`. |
| BANS-1147 | Добавлено возвращаемое значение `drs_dx_ssl_skip_verify`. |
| BANS-1148 | Добавлено возвращаемое значение `drs_bvs_domain`. |
| BANS-1149 | Добавлено возвращаемое значение `drs_broadcast_ip_addr`. |
| BANS-1166 | Добавлено возвращаемое значение `drs_dx_sso_type`. |
| BANS-1150 | Добавлено возвращаемое значение `drs_ping_ip_addr`. |
### Модуль decort_zone
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1124 | Добавлено возвращаемое значение `drs_dx_app_id`. |
| BANS-1122 | Добавлено возвращаемое значение `drs`. |
| BANS-1125 | Добавлено возвращаемое значение `drs_dx_url`. |
| BANS-1123 | Добавлено возвращаемое значение `drs_uid`. |
| BANS-1126 | Добавлено возвращаемое значение `drs_name`. |
| BANS-1127 | Добавлено возвращаемое значение `drs_dx_sso_url`. |
| BANS-1143 | Добавлено возвращаемое значение `drs_dx_ssl_skip_verify`. |
| BANS-1144 | Добавлено возвращаемое значение `drs_bvs_domain`. |
| BANS-1145 | Добавлено возвращаемое значение `drs_broadcast_ip_addr`. |
| BANS-1146 | Добавлено возвращаемое значение `drs_ping_ip_addr`. |
| BANS-1165 | Добавлено возвращаемое значение `drs_dx_sso_type`. |
### Модуль decort_lb
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1112 | Добавлены возвращаемые значения `acl`, `backend_ha_ip_addr`, `created_by`, `created_datetime`, `created_timestamp`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `description`, `dp_api_user`, `ext_net_id`, `frontend_ha_ip_addr`, `guid`, `ha_mode`, `manager_id`, `manager_type`, `milestones`, `part_of_k8s`, `primary_node.backend_ip_addr`, `primary_node.frontend_ip_addr`, `primary_node.guid`, `primary_node.mgmt_ip`, `primary_node.net_id`, `primary_node.vm_id`, `rg_name`, `secondary_node.backend_ip_addr`, `secondary_node.frontend_ip_addr`, `secondary_node.guid`, `secondary_node.mgmt_ip`, `secondary_node.net_id`, `secondary_node.vm_id`, `updated_by`, `updated_datetime`, `updated_timestamp`, `user_managed`, `vins_id`. |
### Модуль decort_user
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1176 | Добавлены возвращаемые значения `consumed.storage_policies.storage_size_quota_gb`, `reserved.storage_policies.storage_size_quota_gb`. |
## Удалено
### Глобально
| Идентификатор<br>задачи | Описание |
| --- | --- |
### Модуль decort_account
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1040 | Удалены возвращаемые значения `computes_amount` , `createdTime`, `createdTime_readable`, `deactivationTime`, `deactivationTime_readable`, `deletedTime`, `deletedTime_readable`, `resourceLimits`, `resourceLimits.CU_C`, `resourceLimits.CU_D`, `resourceLimits.CU_DM`, `resourceLimits.CU_I`, `resourceLimits.CU_M`, `resourceLimits.gpu_units`, `updatedTime`, `updatedTime_readable`, `vinses_amount` в связи с переименованием в `vm_counts` , `created_timestamp`, `created_datetime`, `deactivation_timestamp`, `deactivation_datetime`, `deleted_timestamp`, `deleted_datetime`, `quotas`, `quotas.cpu_count`, `quotas.disk_size_gb`, `quotas.storage_size_gb`, `quotas.ext_ip_count`, `quotas.ram_size_mb`, `quotas.gpu_count`, `updated_timestamp`, `updated_datetime`, `vins_count`. |
| BANS-1067 | Удалены параметры `access_emails`, `quotas.cpu`, `quotas.disk_size`, `quotas.gpu`, `quotas.public_ip`, `quotas.ram` в связи с переименованием в `send_access_emails`, `quotas.cpu_count`, `quotas.storage_size_gb`, `quotas.gpu_count`, `quotas.ext_ip_count`, `quotas.ram_size_mb`. |
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1011 | Удалены возвращаемые значения `computes`, `gid`, `iotune`, `pool`, `size`, `size_available`, `size_used`, `state` в связи с переименованием в `vms`, `grid_id`, `io_tune`, `sep_pool_name`, `size_max_gb`, `size_available_gb`, `size_used_gb`, `status` |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1068 | Для параметра `description` удалено значение по умолчанию. |
| BANS-1134 | Удалены возвращаемые значения `techStatus`, `state`, `k8s_Masters`, `k8s_Masters.cpu`, `k8s_Masters.ram`, `k8s_Masters.disk`, `k8s_Masters.num`, `k8s_Masters.detailedInfo`, `k8s_Masters.detailedInfo.techStatus`, `k8s_Workers`, `k8s_Workers.cpu`, `k8s_Workers.ram`, `k8s_Workers.disk`, `k8s_Workers.num`, `k8s_Workers.detailedInfo`, `k8s_Workers.detailedInfo.techStatus` в связи с переименованием в `tech_status`, `status`, `node_groups.master`, `node_groups.master.node_cpu_count`, `node_groups.master.node_ram_size_mb`, `node_groups.master.node_boot_disk_size_gb`, `node_groups.master.node_count`, `node_groups.master.vms`, `node_groups.master.vms.tech_status`, `node_groups.worker`, `node_groups.worker.node_cpu_count`, `node_groups.worker.node_ram_size_mb`, `node_groups.worker.node_boot_disk_size_gb`, `node_groups.worker.node_count`, `node_groups.worker.vms`, `node_groups.worker.vms.tech_status`. |
### Модуль decort_rg
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1063 | Удалены возвращаемые значения `computes`, `defNetId`, `defNetType`, `gid`, `quota`, `resTypes`, `uniqPools`, `ViNS` в связи с переименованием в `vm_ids`, `default_net_id`, `default_net_type`, `grid_id`, `quotas`, `resource_types`, `sep_pools`, `vins_ids` |
### Модуль decort_vins
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1061 | Удалены возвращаемые значения `int_net_addr`, `gid`, `state` в связи с переименованием в `net_ip`, `grid_id`, `status`. |
| BANS-1061 | Удалены возвращаемые значения `custom_net_addr`, `ext_ip_addr`, `ssh_ipaddr`, `ssh_password`, `ssh_port`. |
| BANS-1054 | Для параметра `state` значение по умолчанию изменено на значение по умолчанию если объект не существует или безвозвратно удалён. |
### Модуль decort_disk_list
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1085 | Удалён параметр `filter.type`. |
| BANS-1086 | Удалено возвращаемое значение `type`. |
### Модуль decort_user
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1080 | Удалено возвращаемое значение `emailaddresses` в связи с переименованием в `email_addresses`. |
| BANS-1176 | Удалены возвращаемые значения `resource_consumed`, `resource_consumed.cpu`, `resource_consumed.disksize`, `resource_consumed.disksizemax`, `resource_consumed.extips`, `resource_consumed.gpu`, `resource_consumed.ram`, `resource_consumed.policies`, `resource_consumed.policies.disksize`, `resource_consumed.policies.disksizemax`, `resource_consumed.policies.seps.disksize`, `resource_consumed.policies.seps.disksizemax`, `resource_consumed.seps`, `resource_consumed.seps.disksize`, `resource_consumed.seps.disksizemax`, `resource_reserved`, `resource_reserved.cpu`, `resource_reserved.disksize`, `resource_reserved.disksizemax`, `resource_reserved.extips`, `resource_reserved.gpu`, `resource_reserved.ram`, `resource_reserved.policies`, `resource_reserved.policies.disksize`, `resource_reserved.policies.disksizemax`, `resource_reserved.policies.seps.disksize`, `resource_reserved.policies.seps.disksizemax`, `resource_reserved.seps`, `resource_reserved.seps.disksize`, `resource_reserved.seps.disksizemax` в связи с переименованием в `consumed`, `consumed.cpu_count`, `consumed.storage_size_gb_by_real_usage`, `consumed.storage_size_gb_by_disk_max`, `consumed.ext_ip_count`, `consumed.gpu_count`, `consumed.ram_size_mb`, `consumed.storage_policies`, `consumed.storage_policies.storage_size_gb_by_real_usage`, `consumed.storage_policies.storage_size_gb_by_disk_max`, `consumed.storage_policies.sep_pools.storage_size_gb_by_real_usage`, `consumed.storage_policies.sep_pools.storage_size_gb_by_disk_max`, `consumed.sep_pools`, `consumed.sep_pools.storage_size_gb_by_real_usage`, `consumed.sep_pools.storage_size_gb_by_disk_max`, `reserved`, `reserved.cpu_count`, `reserved.storage_size_gb_by_real_usage`, `reserved.storage_size_gb_by_disk_max`, `reserved.ext_ip_count`, `reserved.gpu_count`, `reserved.ram_size_mb`, `reserved.storage_policies`, `reserved.storage_policies.storage_size_gb_by_real_usage`, `reserved.storage_policies.storage_size_gb_by_disk_max`, `reserved.storage_policies.sep_pools.storage_size_gb_by_real_usage`, `reserved.storage_policies.sep_pools.storage_size_gb_by_disk_max`, `reserved.sep_pools`, `reserved.sep_pools.storage_size_gb_by_real_usage`, `reserved.sep_pools.storage_size_gb_by_disk_max`. |
### Модуль decort_lb
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1112 | Удалены возвращаемые значения `backends.serverDefaultSettings`, `backends.servers.address`, `backends.servers.serverSettings`, `frontends.backend`, `frontends.bindings.address`, `gid`, `state`, `sysctl` в связи с переименованием в `backends.server_default_settings`, `backends.servers.ip_addr`, `backends.servers.server_settings`, `frontends.backend_name`, `frontends.bindings.ip_addr`, `grid_id`, `status`, `sysctl_params`. |
## Исправлено
### Модуль decort_account
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1055 | При передаче несуществующего ID аккаунта модуль завершал свою работу с некорректным текстом ошибки. Также модуль повторно выполнял запрос к API `cloudapi/account/get` без необходимости. |
### Модуль decort_vins
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1053 | Модуль завершал свою работу ошибкой Python при выполнении восстановления внутренней сети из корзины. |
| BANS-1079 | Модуль отключал внешнюю сеть при незаданном параметре `ext_net_id`. |
| BANS-937 | Модуль завершал работу ошибкой запроса к API при `state: absent` для несуществующего объекта. |
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1062 | Модуль завершал работу ошибкой при `state: absent` для несуществующего объекта. |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1188 | Модуль завершал работу ошибкой при `state: absent` для объекта, который не найден или безвозвратно удален. |

View File

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

View File

@@ -5,7 +5,9 @@
| Версия платформы | Версия модулей Ansible |
|:----------------:|:--------------------------:|
| 4.4.0 | 10.0.x |
| 4.6.0 | 12.0.x |
| 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

@@ -7,9 +7,9 @@ module: decort_account
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Iterable
from typing import Any, Iterable
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from ansible.module_utils.decort_utils import DecortController, sdk_types
class DecortAccount(DecortController):
@@ -23,7 +23,7 @@ class DecortAccount(DecortController):
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_emails=dict(
send_access_emails=dict(
type='bool',
),
acl=dict(
@@ -45,8 +45,13 @@ class DecortAccount(DecortController):
options=dict(
rights=dict(
type='str',
choices=['R', 'RCX', 'ARCXDU'],
default='R',
choices=(
sdk_types.AccessTypeForSet
._member_names_
),
default=(
sdk_types.AccessTypeForSet.R.name
),
),
id=dict(
type='str',
@@ -62,25 +67,26 @@ class DecortAccount(DecortController):
name=dict(
type='str',
),
get_resource_consumption=dict(
type='bool',
default=False,
),
quotas=dict(
type='dict',
options=dict(
cpu=dict(
cpu_count=dict(
type='int',
),
disks_size=dict(
storage_size_gb=dict(
type='int',
),
ext_traffic=dict(
gpu_count=dict(
type='int',
),
gpu=dict(
ext_ip_count=dict(
type='int',
),
public_ip=dict(
type='int',
),
ram=dict(
ram_size_mb=dict(
type='int',
),
),
@@ -94,7 +100,6 @@ class DecortAccount(DecortController):
'disabled',
'present',
],
default='present',
),
sep_pools=dict(
type='list',
@@ -131,11 +136,11 @@ 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 = [
'access_emails',
'send_access_emails',
'acl',
['id', 'name'],
'quotas',
@@ -175,6 +180,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,106 +193,308 @@ class DecortAccount(DecortController):
self.acc_id, self._acc_info = self.account_find(
account_name=self.aparams['name'],
account_id=self.aparams['id'],
)
)
# If this is a repeated getting info
else:
# If check mode is enabled, there is no needed to
# request info again
if not self.amodule.check_mode:
self.acc_id, self._acc_info = self.account_find(
account_id=self.acc_id,
account_id=self._acc_info.id,
)
self.facts = self.acc_info
if self._acc_info is not None:
acc_info_dict = self.acc_info.model_dump()
if self.aparams['get_resource_consumption']:
resource_consumption_dict = (
self.api.cloudapi.account.get_resource_consumption(
account_id=self.acc_info.id,
).model_dump()
)
resource_consumption_dict.pop('id')
resource_consumption_dict.pop('quotas')
acc_info_dict['resource_consumption'] = (
resource_consumption_dict
)
self.facts = acc_info_dict
def change(self):
self.change_state()
self.change_acl()
if self.account_update_args:
self.account_update(account_id=self.acc_id,
**self.account_update_args)
need_account_update_api_call: bool = False
sdk_param_send_access_emails = None
aparam_send_access_emails: bool | None = self.aparams[
'send_access_emails'
]
if (
aparam_send_access_emails is not None
and self.acc_info.send_access_emails != aparam_send_access_emails
):
sdk_param_send_access_emails = aparam_send_access_emails
need_account_update_api_call = True
sdk_param_name = None
aparam_name: str | None = self.aparams['name']
if (
self.aparams['id'] and aparam_name
and self.acc_info.name != aparam_name
):
sdk_param_name = aparam_name
need_account_update_api_call = True
sdk_param_cpu_count_quota = None
sdk_param_storage_size_quota_gb = None
sdk_param_gpu_count_quota = None
sdk_param_ext_ip_count_quota = None
sdk_param_ram_size_quota_mb = None
aparam_quotas: dict[str, Any] = self.aparams['quotas']
if aparam_quotas:
aparam_quotas_cpu_count: int | None = aparam_quotas['cpu_count']
if (
aparam_quotas_cpu_count is not None
and self.acc_info.quotas.cpu_count != aparam_quotas_cpu_count
):
sdk_param_cpu_count_quota = aparam_quotas_cpu_count
need_account_update_api_call = True
aparam_quotas_storage_size_gb: int | None = aparam_quotas[
'storage_size_gb'
]
if (
aparam_quotas_storage_size_gb is not None
and (
self.acc_info.quotas.storage_size_gb
!= aparam_quotas_storage_size_gb
)
):
sdk_param_storage_size_quota_gb = aparam_quotas_storage_size_gb
need_account_update_api_call = True
aparam_quotas_gpu_count: int | None = aparam_quotas['gpu_count']
if (
aparam_quotas_gpu_count is not None
and self.acc_info.quotas.gpu_count != aparam_quotas_gpu_count
):
sdk_param_gpu_count_quota = aparam_quotas_gpu_count
need_account_update_api_call = True
aparam_quotas_ext_ip_count: int | None = aparam_quotas[
'ext_ip_count'
]
if (
aparam_quotas_ext_ip_count is not None
and (
self.acc_info.quotas.ext_ip_count
!= aparam_quotas_ext_ip_count
)
):
sdk_param_ext_ip_count_quota = aparam_quotas_ext_ip_count
need_account_update_api_call = True
aparam_quotas_ram_size_mb: int | None = aparam_quotas[
'ram_size_mb'
]
if (
aparam_quotas_ram_size_mb is not None
and (
self.acc_info.quotas.ram_size_mb
!= aparam_quotas_ram_size_mb
)
):
sdk_param_ram_size_quota_mb = aparam_quotas_ram_size_mb
need_account_update_api_call = True
sdk_param_sep_pools = None
aparam_sep_pools: list[dict[str, Any]] = self.aparams['sep_pools']
if aparam_sep_pools is not None:
sep_pools: set[str] = set()
for sep in aparam_sep_pools:
sep_pool_names: list[str] = sep['pool_names']
for pool_name in sep_pool_names:
sep_pools.add(f'{sep["sep_id"]}_{pool_name}')
if set(self.acc_info.sep_pools) != sep_pools:
sdk_param_sep_pools = list(sep_pools)
need_account_update_api_call = True
sdk_param_description = None
aparam_desc: str | None = self.aparams['description']
if (
aparam_desc is not None
and self.acc_info.description != aparam_desc
):
sdk_param_description = aparam_desc
need_account_update_api_call = True
sdk_param_default_zone_id = None
aparam_default_zone_id: int | None = self.aparams['default_zone_id']
if (
aparam_default_zone_id is not None
and self.acc_info.default_zone_id != aparam_default_zone_id
):
sdk_param_default_zone_id = aparam_default_zone_id
need_account_update_api_call = True
if need_account_update_api_call:
OBJ = 'account'
self.sdk_checkmode(self.api.ca.account.update)(
account_id=self.acc_info.id,
cpu_count_quota=sdk_param_cpu_count_quota,
gpu_count_quota=sdk_param_gpu_count_quota,
name=sdk_param_name,
ext_ip_count_quota=sdk_param_ext_ip_count_quota,
ram_size_quota_mb=sdk_param_ram_size_quota_mb,
send_access_emails=sdk_param_send_access_emails,
storage_size_quota_gb=sdk_param_storage_size_quota_gb,
sep_pools=sdk_param_sep_pools,
description=sdk_param_description,
default_zone_id=sdk_param_default_zone_id,
)
if sdk_param_send_access_emails is not None:
smth = 'sending access emails'
if sdk_param_send_access_emails:
self.message(
self.MESSAGES.obj_smth_enabled(
obj=OBJ,
id=self.acc_info.id,
smth=smth,
)
)
else:
self.message(
self.MESSAGES.obj_smth_disabled(
obj=OBJ,
id=self.acc_info.id,
smth=smth,
)
)
if sdk_param_name is not None:
self.message(
self.MESSAGES.obj_renamed(
obj=OBJ,
id=self.acc_info.id,
new_name=sdk_param_name,
)
)
quotas = {
'CPU count quota': sdk_param_cpu_count_quota,
'storage size quota GB': sdk_param_storage_size_quota_gb,
'GPU count quota': sdk_param_gpu_count_quota,
'ext IP count quota': sdk_param_ext_ip_count_quota,
'RAM size quota MB': sdk_param_ram_size_quota_mb,
}
for q_name, q_value in quotas.items():
if q_value is not None:
self.message(
self.MESSAGES.obj_smth_changed(
obj=OBJ,
id=self.acc_info.id,
smth=q_name,
new_value=q_value
)
)
if sdk_param_default_zone_id is not None:
self.message(
self.MESSAGES.obj_smth_changed(
obj=OBJ,
id=self.acc_info.id,
smth='default_zone_id',
new_value=sdk_param_default_zone_id,
)
)
self.get_info()
def change_state(self):
match self._acc_info:
case None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
pass
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.exit(fail=True)
case {'status': 'DESTROYED'}:
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_id,
permanently=True,
already=True,
)
if self._acc_info is None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
pass
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.exit(fail=True)
elif self._acc_info.status == sdk_types.AccountStatus.DESTROYED:
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_info.id,
permanently=True,
already=True,
)
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.acc_id)
)
self.exit(fail=True)
case {'status': 'DELETED'}:
match self.aparams:
case {'state': 'absent'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_id,
permanently=False,
already=True,
)
)
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.acc_info.id)
)
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
self.restore()
case {'state': 'disabled'}:
self.restore()
self.disable()
case {'status': 'CONFIRMED'}:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
pass
case {'state': 'disabled'}:
self.disable()
case {'status': 'DISABLED'}:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed'}:
self.enable()
case {'state': 'present' | 'disabled'}:
pass
self.exit(fail=True)
elif self._acc_info.status == sdk_types.AccountStatus.DELETED:
match self.aparams:
case {'state': 'absent'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_info.id,
permanently=False,
already=True,
)
)
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
self.restore()
case {'state': 'disabled'}:
self.restore()
self.disable()
elif self._acc_info.status == sdk_types.AccountStatus.CONFIRMED:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
pass
case {'state': 'disabled'}:
self.disable()
elif self._acc_info.status == sdk_types.AccountStatus.DISABLED:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed'}:
self.enable()
case {'state': 'present' | 'disabled'}:
pass
def delete(self, permanently=False):
self.account_delete(account_id=self.acc_id, permanently=permanently)
self.account_delete(
account_id=self.acc_info.id,
permanently=permanently
)
self.get_info()
def disable(self):
self.account_disable(account_id=self.acc_id)
self.sdk_checkmode(self.api.ca.account.disable)(
account_id=self.acc_info.id
)
self.get_info()
def enable(self):
self.account_enable(account_id=self.acc_id)
self.sdk_checkmode(self.api.ca.account.enable)(
account_id=self.acc_info.id
)
self.get_info()
def restore(self):
self.account_restore(account_id=self.acc_id)
self.account_restore(account_id=self.acc_info.id)
self.get_info()
def change_acl(self):
@@ -294,7 +502,7 @@ class DecortAccount(DecortController):
return
actual_users = {
u['userGroupId']: u['right'] for u in self.acc_info['acl']
u.user_name: u.access_type for u in self.acc_info.acl
}
actual_users_ids = set(actual_users.keys())
@@ -323,8 +531,8 @@ class DecortAccount(DecortController):
aparams_users_ids.intersection(actual_users_ids)
upd_users = dict()
for id in upd_users_ids:
if actual_users[id] == 'CXDRAU':
actual_user_rights = 'ARCXDU'
if actual_users[id] == sdk_types.AccessType.CXDRAU:
actual_user_rights = sdk_types.AccessTypeForSet.ARCXDU
else:
actual_user_rights = actual_users[id]
@@ -336,69 +544,12 @@ class DecortAccount(DecortController):
actual_users_ids.difference(aparams_users_ids)
if del_users_ids or new_users or upd_users:
self.account_change_acl(account_id=self.acc_id,
self.account_change_acl(account_id=self.acc_info.id,
del_users=del_users_ids,
add_users=new_users,
upd_users=upd_users)
self.get_info()
@property
def account_update_args(self) -> dict:
result_args = dict()
aparam_access_emails = self.aparams['access_emails']
if (aparam_access_emails is not None
and self.acc_info['sendAccessEmails'] != aparam_access_emails):
result_args['access_emails'] = aparam_access_emails
aparam_name = self.aparams['name']
if (self.aparams['id'] and aparam_name
and self.acc_info['name'] != aparam_name):
result_args['name'] = aparam_name
aparam_quotas = self.aparams['quotas']
if aparam_quotas:
quotas_naming = [
['cpu', 'CU_C', 'cpu_quota'],
['disks_size', 'CU_DM', 'disks_size_quota'],
['ext_traffic', 'CU_NP', 'ext_traffic_quota'],
['gpu', 'gpu_units', 'gpu_quota'],
['public_ip', 'CU_I', 'public_ip_quota'],
['ram', 'CU_M', 'ram_quota'],
]
for aparam, info_key, result_arg in quotas_naming:
current_value = int(self.acc_info['resourceLimits'][info_key])
if (aparam_quotas[aparam] is not None
and current_value != aparam_quotas[aparam]):
result_args[result_arg] = aparam_quotas[aparam]
aparam_sep_pools = self.aparams['sep_pools']
if aparam_sep_pools is not None:
sep_pools = set()
for sep in aparam_sep_pools:
for pool_name in sep['pool_names']:
sep_pools.add(
f'{sep["sep_id"]}_{pool_name}'
)
if set(self.acc_info['uniqPools']) != sep_pools:
result_args['sep_pools'] = sep_pools
aparam_desc = self.aparams['description']
if (
aparam_desc is not None
and self.acc_info['description'] != aparam_desc
):
result_args['description'] = aparam_desc
aparam_default_zone_id = self.aparams['default_zone_id']
if (
aparam_default_zone_id is not None
and self.acc_info['defaultZoneId'] != aparam_default_zone_id
):
result_args['default_zone_id'] = aparam_default_zone_id
return result_args
def check_aparam_default_zone_id(self) -> bool | None:
aparam_default_zone_id = self.aparams['default_zone_id']
if aparam_default_zone_id is not None:

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

@@ -39,21 +39,21 @@ class decort_bservice(DecortController):
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(
validated_rg_id, validated_rg_model = self.rg_find(
arg_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name']
)
if not validated_rg_id:
if not validated_rg_id or not validated_rg_model:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
self.amodule.fail_json(**self.result)
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
validated_acc_id = validated_rg_facts['accountId']
arg_amodule.params['rg_name'] = validated_rg_model.name
validated_acc_id = validated_rg_model.account_id
self.bservice_id, self.bservice_info = self.bservice_find(
validated_acc_id,
@@ -104,16 +104,18 @@ class decort_bservice(DecortController):
return
def create(self):
self.bservice_id = self.bservice_id = self.bservice_provision(
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['sshuser'],
self.amodule.params['sshkey'],
self.bservice_id = self.sdk_checkmode(self.api.ca.bservice.create)(
name=self.amodule.params['name'],
rg_id=self.amodule.params['rg_id'],
ssh_user_name=self.amodule.params['sshuser'],
ssh_public_key=self.amodule.params['sshkey'],
zone_id=self.aparams['zone_id'],
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.bservice_state(self.bservice_info, self.aparams['state'])
self.bservice_should_exist = True
return
def action(self,d_state):
@@ -134,7 +136,10 @@ class decort_bservice(DecortController):
pass
def destroy(self):
self.bservice_delete(self.bservice_id)
self.sdk_checkmode(self.api.ca.bservice.delete)(
bservice_id=self.bservice_id,
permanently=True,
)
self.bservice_info['status'] = 'DELETED'
self.bservice_should_exist = False
return
@@ -259,76 +264,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', 'CREATED',
):
if amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'])
elif subj.bservice_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
else:
state = amodule.params['state']
if state is None:
state = 'present'
if state == 'absent':
subj.nop()
if state in ('present','started'):
subj.create()
elif 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

@@ -58,6 +58,7 @@ class decort_disk(DecortController):
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id,
check_state=False,
fail_if_not_found=False,
)
if arg_amodule.params['place_with']:
@@ -67,87 +68,149 @@ 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']
if self.disk_id and self.disk_info.status not in [
sdk_types.DiskStatus.DESTROYED,
sdk_types.DiskStatus.PURGED,
]:
self.acc_id = validated_disk_facts.account_id
self.check_amodule_args_for_change()
else:
elif (
self.amodule.params['state'] == 'present'
and not arg_amodule.params['id']
):
self.check_amodule_args_for_create()
def compare_iotune_params(
self,
new_iotune: dict,
current_iotune: sdk_types.IOTuneAPIResultNM,
):
io_fields = sdk_types.IOTuneAPIResultNM.model_fields.keys()
for field in io_fields:
new_value = new_iotune.get(field)
if new_value is None:
continue
current_value = getattr(current_iotune, field, None)
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'],
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']
if (
aparam_limit_io
and any(value is not None for value in aparam_limit_io.values())
):
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']
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']
and self.amodule.params['size'] != self.disk_info.size_max_gb
):
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
and any(value is not None for value in aparam_limit_io.values())
):
if not self.compare_iotune_params(
new_iotune=aparam_limit_io,
current_iotune=self.disk_info.io_tune,
):
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.shared:
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']
and aparam_storage_policy_id != self.disk_info.storage_policy_id
):
self.disk_change_storage_policy(
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_delete(disk_id=self.disk_id,
detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'],
reason=self.amodule.params['reason'])
self.disk_id, self.disk_info = self._disk_get_by_id(self.disk_id)
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_info = self._disk_get_by_id(disk_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):
@@ -156,7 +219,7 @@ class decort_disk(DecortController):
self.result['changed'] = False
if self.disk_id:
self.result['msg'] = ("No state change required for Disk ID {} because of its "
"current status '{}'.").format(self.disk_id, self.disk_info['status'])
"current status '{}'.").format(self.disk_id, self.disk_info.status)
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Disk.").format(self.amodule.params['state'])
@@ -176,26 +239,7 @@ class decort_disk(DecortController):
if check_mode or self.disk_info is None:
return ret_dict
# remove io param with zero value
clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0]
for key in clean_io: del self.disk_info['iotune'][key]
ret_dict['id'] = self.disk_info['id']
ret_dict['name'] = self.disk_info['name']
ret_dict['size'] = self.disk_info['sizeMax']
ret_dict['state'] = self.disk_info['status']
ret_dict['account_id'] = self.disk_info['accountId']
ret_dict['sep_id'] = self.disk_info['sepId']
ret_dict['pool'] = self.disk_info['pool']
ret_dict['computes'] = self.disk_info['computes']
ret_dict['gid'] = self.disk_info['gid']
ret_dict['iotune'] = self.disk_info['iotune']
ret_dict['size_available'] = self.disk_info['sizeAvailable']
ret_dict['size_used'] = self.disk_info['sizeUsed']
ret_dict['storage_policy_id'] = self.disk_info['storage_policy_id']
ret_dict['to_clean'] = self.disk_info['to_clean']
return ret_dict
return self.disk_info.model_dump()
@property
def amodule_init_args(self) -> dict:
@@ -240,6 +284,7 @@ class decort_disk(DecortController):
),
limitIO=dict(
type='dict',
apply_defaults=True,
options=dict(
total_bytes_sec=dict(
type='int',
@@ -290,10 +335,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',
@@ -349,8 +390,8 @@ class decort_disk(DecortController):
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']
and aparam_storage_policy_id
not in self.acc_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -379,57 +420,76 @@ class decort_disk(DecortController):
return not check_errors
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()
@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 [sdk_types.DiskStatus.MODELED, sdk_types.DiskStatus.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 [sdk_types.DiskStatus.CREATED, sdk_types.DiskStatus.ASSIGNED]:
if amodule.params['state'] == 'absent':
self.delete()
elif amodule.params['state'] == 'present':
self.action()
elif self.disk_info.status in [sdk_types.DiskStatus.PURGED, sdk_types.DiskStatus.DESTROYED]:
#re-provision disk
if amodule.params['state'] in ('present'):
self.create()
else:
self.nop()
elif self.disk_info.status == sdk_types.DiskStatus.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()
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
self.exit()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed']:
_, decon.disk_info = decon.disk_find(decon.disk_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
if (
(
amodule.params['state'] == 'present'
or amodule.params['state'] is None
)
and amodule.params['id']
):
self.message(
f'Disk with ID {amodule.params['id']} not found.'
)
self.exit(fail=True)
if (
amodule.params['state'] == 'present'
and not amodule.params['id']
):
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

154
library/decort_disk_list.py Normal file
View File

@@ -0,0 +1,154 @@
#!/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',
),
rg_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.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_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'],
rg_id=aparam_filter['rg_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():
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

@@ -81,24 +81,13 @@ 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.',
warning=True,
)
driver = self.aparams['driver']
if driver is None:
driver = 'KVM_X86'
self.message(
msg=self.MESSAGES.default_value_used(
param_name='driver',
default_value=driver,
),
warning=True,
)
self.group_id=self.group_provision(
bs_id=self.bservice_id,
arg_name=self.amodule.params['name'],
@@ -112,7 +101,6 @@ class decort_group(DecortController):
arg_timeout=self.amodule.params['timeoutStart'],
chipset=chipset,
storage_policy_id=self.aparams['storage_policy_id'],
driver=driver,
)
if self.amodule.params['state'] in ('started','present'):
@@ -296,9 +284,6 @@ class decort_group(DecortController):
storage_policy_id=dict(
type='int',
),
driver=dict(
type='str',
),
),
supports_check_mode=True,
required_one_of=[
@@ -354,17 +339,6 @@ class decort_group(DecortController):
f'disk ID {disk['id']}'
)
aparam_driver = self.aparams['driver']
if (
aparam_driver is not None
and aparam_driver != self.group_info['driver']
):
check_errors = True
self.message(
msg='Check for parameter "driver" failed: '
'driver can not be changed'
)
if check_errors:
self.exit(fail=True)
@@ -382,7 +356,7 @@ class decort_group(DecortController):
else:
if (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
not in self.rg_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -392,7 +366,7 @@ class decort_group(DecortController):
)
if (
aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
not in self.acc_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -405,54 +379,59 @@ class decort_group(DecortController):
self.exit(fail=True)
def main():
subj = decort_group()
amodule = subj.amodule
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.group_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],)
amodule.fail_json(**subj.result)
if subj.group_id:
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED","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()
@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()

569
library/decort_image.py Normal file
View File

@@ -0,0 +1,569 @@
#!/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: int = 0
self.validated_virt_image_id: int = 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.sdk_checkmode(self.api.ca.image.delete)(
image_id=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
@DecortController.handle_sdk_exceptions
def decort_image_rename(self,amodule):
# image renaming function
self.sdk_checkmode(self.api.ca.image.rename)(
image_id=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
@DecortController.handle_sdk_exceptions
def decort_virt_image_rename(self, amodule):
self.sdk_checkmode(self.api.ca.image.rename)(
image_id=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']
ret_dict['independent'] = arg_image_facts['independent']
ret_dict['links_to'] = arg_image_facts['linksTo']
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

@@ -20,7 +20,7 @@ class decort_k8s(DecortController):
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_rg_model = None
validated_k8ci_id = 0
self.k8s_should_exist = False
self.is_k8s_stopped_or_will_be_stopped: None | bool = None
@@ -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."
@@ -55,12 +55,12 @@ class decort_k8s(DecortController):
self.amodule.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(
validated_rg_id, validated_rg_model = self.rg_find(
arg_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name']
)
if not validated_rg_id:
if not validated_rg_id or not validated_rg_model:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
@@ -70,19 +70,21 @@ class decort_k8s(DecortController):
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
arg_amodule.params['rg_name'] = validated_rg_model.name
self.acc_id = validated_rg_model.account_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
self.k8s_id, self._k8s_info = self.k8s_find(
k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False,
)
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId']
self.acc_id = self.k8s_info['account_id']
self.check_amodule_args_for_change()
else:
elif arg_amodule.params['state'] != 'absent':
self.check_amodule_args_for_create()
return
@@ -96,32 +98,14 @@ class decort_k8s(DecortController):
config=None,
)
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
if check_mode:
# in check mode return immediately with the default values
return ret_dict
#if self.k8s_facts is None:
# #if void facts provided - change state value to ABSENT and return
# ret_dict['state'] = "ABSENT"
# return ret_dict
ret_dict['id'] = self.k8s_info['id']
ret_dict['name'] = self.k8s_info['name']
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.k8s_info['rgId']
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id
ret_dict['k8s_Masters'] = self.k8s_info['k8sGroups']['masters']
ret_dict['k8s_Workers'] = self.k8s_info['k8sGroups']['workers']
ret_dict['lb_id'] = self.k8s_info['lbId']
ret_dict['description'] = self.k8s_info['desc']
ret_dict['zone_id'] = self.k8s_info['zoneId']
return ret_dict
self.k8s_info['vins_id'] = self.k8s_vins_id
self.k8s_info['config'] = None
if self.amodule.params['getConfig'] and self.k8s_info['tech_status'] == "STARTED":
self.k8s_info['config'] = self.k8s_getConfig()
return self.k8s_info
def nop(self):
"""No operation (NOP) handler for k8s cluster management by decort_k8s module.
@@ -158,7 +142,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:
@@ -204,10 +188,12 @@ class decort_k8s(DecortController):
self.result['failed'] = True
self.amodule.fail_json(**self.result)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id,
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
self.k8s_id, self._k8s_info = self.k8s_find(
k8s_id=k8s_id,
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False,
)
if self.k8s_id:
self.k8s_should_exist = True
@@ -215,20 +201,26 @@ class decort_k8s(DecortController):
self.k8s_workers_modify(
arg_k8swg=self.k8s_info,
arg_modwg=target_wgs,
master_node_storage_policy_id=(
self.aparams['storage_policy_id']
),
)
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
return
def destroy(self):
self.k8s_delete(self.k8s_id,self.amodule.params['permanent'])
self.sdk_checkmode(self.api.ca.k8s.delete)(
k8s_id=self.k8s_id,
permanently=self.amodule.params['permanent'],
)
self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False
return
def action(self, disared_state, preupdate: bool = False):
if self.amodule.params['master_chipset'] is not None:
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
for master_node in self.k8s_info['node_groups']['master'][
'vms'
]:
_, master_node_info, _ = self._compute_get_by_id(
comp_id=master_node['id']
@@ -244,30 +236,46 @@ class decort_k8s(DecortController):
self.exit(fail=True)
if (
self.aparams['name'] is not None
and self.aparams['name'] != self.k8s_info['name']
(
self.aparams['name'] is not None
and self.aparams['name'] != self.k8s_info['name']
)
or (
self.aparams['description'] is not None
and self.aparams['description'] != self.k8s_info['description']
)
):
self.k8s_update(id=self.k8s_id, name=self.aparams['name'])
self.sdk_checkmode(self.api.ca.k8s.update)(
k8s_id=self.k8s_id,
description=self.aparams['description'],
name=self.aparams['name'],
)
if preupdate:
# K8s info updating
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#k8s state
self.k8s_state(self.k8s_info, disared_state)
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#check groups and modify if needed
if self.aparams['workers'] is not None:
self.k8s_workers_modify(self.k8s_info, self.amodule.params['workers'])
self.k8s_workers_modify(
arg_k8swg=self.k8s_info,
arg_modwg=self.amodule.params['workers'],
)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.k8s_info['zoneId']:
if (
aparam_zone_id is not None
and aparam_zone_id != self.k8s_info['zone_id']
):
self.k8s_migrate_to_zone(
k8s_id=self.k8s_id,
zone_id=aparam_zone_id,
)
if self.result['changed'] == True:
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#TODO check workers metadata and modify if needed
return
@@ -301,7 +309,6 @@ class decort_k8s(DecortController):
),
name=dict(
type='str',
default='',
),
id=dict(
type='int',
@@ -408,7 +415,6 @@ class decort_k8s(DecortController):
),
description=dict(
type='str',
default='Created by decort ansible module',
),
with_lb=dict(
type='bool',
@@ -468,25 +474,25 @@ class decort_k8s(DecortController):
self.is_k8s_stopped_or_will_be_stopped = (
(
self.k8s_info['techStatus'] == 'STOPPED'
self.k8s_info['tech_status'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
self.k8s_info['techStatus'] != 'STOPPED'
self.k8s_info['tech_status'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
aparam_sysctl = self.aparams['lb_sysctl']
if aparam_sysctl is not None:
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId'])
lb_model = self._lb_get_by_id(lb_id=self.k8s_info['lb_id'])
sysctl_with_str_values = {
k: str(v) for k, v in aparam_sysctl.items()
}
if sysctl_with_str_values != lb_info['sysctlParams']:
if sysctl_with_str_values != lb_model.sysctl_params:
self.message(
'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster '
@@ -498,7 +504,7 @@ class decort_k8s(DecortController):
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != self.k8s_info['zoneId']
and self.aparams['zone_id'] != self.k8s_info['zone_id']
and not self.is_k8s_stopped_or_will_be_stopped
):
check_errors = True
@@ -510,13 +516,11 @@ class decort_k8s(DecortController):
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'
]:
for master_node in self.k8s_info['node_groups']['master']['vms']:
computes_ids.append(master_node['id'])
for wg in self.k8s_info['k8sGroups']['workers']:
for wg in self.k8s_info['node_groups']['worker']:
workers_ids = [
worker['id'] for worker in wg['detailedInfo']
worker['id'] for worker in wg['vms']
]
computes_ids.extend(workers_ids)
for compute_id in computes_ids:
@@ -584,7 +588,7 @@ class decort_k8s(DecortController):
)
elif (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
not in self.rg_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -598,67 +602,74 @@ class decort_k8s(DecortController):
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.sdk_checkmode(self.api.ca.k8s.restore)(
k8s_id=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

@@ -18,11 +18,9 @@ class decort_lb(DecortController):
arg_amodule = self.amodule
self.lb_id = 0
self.lb_facts = None
self.vins_id = 0
self.vins_facts = None
self.rg_id = 0
self.rg_facts = None
self.rg_model = None
self.default_server_check = "enabled"
self.default_alg = "roundrobin"
self.default_settings = {
@@ -38,22 +36,34 @@ class decort_lb(DecortController):
self.is_lb_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['lb_id']:
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id'])
if not self.lb_id:
self.result['failed'] = True
self.result['msg'] = "Specified LB ID {} not found."\
.format(arg_amodule.params['lb_id'])
self.amodule.fail_json(**self.result)
self.rg_id = self.lb_facts['rgId']
self.vins_id = self.lb_facts['vinsId']
_, self._lb_info = self.lb_find(lb_id=arg_amodule.params['lb_id'])
if self._lb_info is None:
if arg_amodule.params['state'] == 'absent':
self.nop()
self.exit()
else:
self.message(
self.MESSAGES.obj_not_found(
obj='lb',
id=arg_amodule.params['lb_id'],
)
)
self.exit(fail=True)
self.lb_id = self._lb_info.id
self.rg_id = self._lb_info.rg_id
self.vins_id = self._lb_info.vins_id
elif arg_amodule.params['rg_id']:
self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="")
if not self.rg_id:
self.rg_id, self.rg_model = self.rg_find(
0,
arg_amodule.params['rg_id'],
arg_rg_name="",
)
if not self.rg_id or not self.rg_model:
self.result['failed'] = True
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']
self.acc_id = self.rg_model.account_id
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
if not arg_amodule.params['rg_name']:
@@ -67,24 +77,22 @@ class decort_lb(DecortController):
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.amodule.fail_json(**self.result)
self.rg_id, self.rg_facts = self.rg_find(self.acc_id,0, arg_rg_name=arg_amodule.params['rg_name'])
self.rg_id, self.rg_model = self.rg_find(
self.acc_id,
0,
arg_rg_name=arg_amodule.params['rg_name'],
)
if arg_amodule.params['vins_id']:
self.vins_id, self.vins_facts = self.vins_find(
self.vins_id = self._vins_get_by_id(
vins_id=arg_amodule.params['vins_id']
)
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = (
f'Specified ViNS ID {arg_amodule.params["vins_id"]}'
f' not found'
)
self.amodule.fail_json(**self.result)
).id
elif arg_amodule.params['vins_name']:
self.vins_id, self.vins_facts = self.vins_find(
self.vins_id, _ = self.vins_find(
vins_id=arg_amodule.params['vins_id'],
vins_name=arg_amodule.params['vins_name'],
rg_id=self.rg_id)
rg_id=self.rg_id,
)
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = (
@@ -94,10 +102,17 @@ class decort_lb(DecortController):
self.amodule.fail_json(**self.result)
if self.rg_id and arg_amodule.params['lb_name']:
self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id)
self.lb_id, self._lb_info = self.lb_find(
0,
arg_amodule.params['lb_name'],
self.rg_id,
)
if self.lb_id and self.lb_facts['status'] != 'DESTROYED':
self.acc_id = self.lb_facts['accountId']
if (
self._lb_info
and self._lb_info.status != sdk_types.LBStatus.DESTROYED
):
self.acc_id = self._lb_info.account_id
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
@@ -115,54 +130,73 @@ class decort_lb(DecortController):
zone_id=self.aparams['zone_id'],
start=start_after_create,
)
if self.lb_id and (self.amodule.params['backends'] or
self.amodule.params['frontends']):
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id)
if (
self.lb_id and (
self.amodule.params['backends']
or self.amodule.params['frontends']
)
):
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_update(
lb_facts=self.lb_facts,
lb_model=self.lb_info,
aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'],
)
return
def action(self,d_state='',restore=False):
if restore == True:
self.lb_restore(lb_id=self.lb_id)
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_state(self.lb_facts, 'enabled')
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
def action(
self,
d_state='',
restore=False,
):
if restore:
self.sdk_checkmode(self.api.ca.lb.restore)(
lb_id=self.lb_id,
)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_state(self.lb_info, 'enabled')
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_update(
lb_facts=self.lb_facts,
lb_model=self.lb_info,
aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'],
aparam_sysctl=self.aparams['sysctl'],
)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
if d_state != '':
self.lb_state(self.lb_facts, d_state)
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_state(self.lb_info, d_state)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
if (d_state == 'enabled' and
self.lb_facts.get('status') == 'ENABLED' and
self.lb_facts.get('techStatus') == 'STOPPED'):
self.lb_state(self.lb_facts, 'started')
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
if (
d_state == 'enabled'
and self.lb_info.status == sdk_types.LBStatus.ENABLED
and self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED
):
self.lb_state(self.lb_info, 'started')
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.lb_facts['zoneId']:
if (
aparam_zone_id is not None
and aparam_zone_id != self.lb_info.zone_id
):
self.lb_migrate_to_zone(
lb_id=self.lb_id,
lb_id=self.lb_info.id,
zone_id=aparam_zone_id,
)
return
def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently'])
self.lb_id, self.lb_facts = self._lb_get_by_id(self.lb_id)
self.sdk_checkmode(self.api.ca.lb.delete)(
lb_id=self.lb_id,
permanently=self.amodule.params['permanently'],
)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
return
def nop(self):
@@ -173,30 +207,39 @@ class decort_lb(DecortController):
"""
self.result['failed'] = False
self.result['changed'] = False
if self.lb_id:
self.result['msg'] = ("No state change required for LB ID {} because of its "
"current status '{}'.").format(self.lb_id, self.lb_facts['status'])
if self._lb_info:
self.result['msg'] = (
f'No state change required for LB ID {self._lb_info.id} '
f'because of its current status "{self._lb_info.status}".'
)
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent LB instance.").format(self.amodule.params['state'])
self.result['msg'] = (
f'No state change to "{self.amodule.params['state']}" '
f'can be done for non-existent LB instance.'
)
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.vins_id:
if self._lb_info:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the "
"current status '{}'").format(self.lb_id,
self.amodule.params['state'],
self.lb_facts['status'])
self.result['msg'] = (
f'Invalid target state "{self.amodule.params['state']}" '
f'requested for LB ID {self._lb_info.id} in the current status '
f'"{self._lb_info.status}".'
)
else:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"LB name '{}'").format(self.amodule.params['state'],
self.amodule.params['lb_name'])
self.result['msg'] = (
f'Invalid target state "{self.amodule.params['state']}" '
f'requested for non-existent LB name '
f'"{self.amodule.params['lb_name']}".'
)
return
def package_facts(self, arg_check_mode=False):
"""Package a dictionary of LB facts according to the decort_lb module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
@@ -205,34 +248,16 @@ class decort_lb(DecortController):
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
sysctl={},
)
ret_dict = {}
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
if self.lb_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
if self._lb_info is None:
return ret_dict
ret_dict['id'] = self.lb_facts['id']
ret_dict['name'] = self.lb_facts['name']
ret_dict['state'] = self.lb_facts['status']
ret_dict['account_id'] = self.lb_facts['accountId']
ret_dict['rg_id'] = self.lb_facts['rgId']
ret_dict['gid'] = self.lb_facts['gid']
if self.amodule.params['state']!="absent":
ret_dict['backends'] = self.lb_facts['backends']
ret_dict['frontends'] = self.lb_facts['frontends']
ret_dict['sysctl'] = self.lb_facts['sysctlParams']
ret_dict['zone_id'] = self.lb_facts['zoneId']
ret_dict['tech_status'] = self.lb_facts['techStatus']
return ret_dict
return self._lb_info.model_dump()
@property
def amodule_init_args(self) -> dict:
@@ -321,18 +346,16 @@ class decort_lb(DecortController):
def check_amodule_args_for_change(self):
check_errors = False
lb_info: dict = self.lb_facts
self.is_lb_stopped_or_will_be_stopped = (
self.is_lb_stopped_or_will_be_stopped = (
(
lb_info['techStatus'] == 'STOPPED'
self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
lb_info['techStatus'] != 'STOPPED'
self.lb_info.tech_status != sdk_types.LBTechStatus.STOPPED
and self.aparams['state'] == 'stopped'
)
)
@@ -341,7 +364,7 @@ class decort_lb(DecortController):
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != lb_info['zoneId']
and self.aparams['zone_id'] != self.lb_info.zone_id
and not self.is_lb_stopped_or_will_be_stopped
):
check_errors = True
@@ -361,56 +384,74 @@ class decort_lb(DecortController):
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if self._lb_info:
if self._lb_info.status in [
sdk_types.LBStatus.MODELED,
sdk_types.LBStatus.DISABLING,
sdk_types.LBStatus.ENABLING,
sdk_types.LBStatus.DELETING,
sdk_types.LBStatus.DESTROYING,
sdk_types.LBStatus.RESTORING,
]:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = (
f'No change can be done for existing LB ID '
f'{self._lb_info.id} because of its current status '
f'"{self._lb_info.status.name}".'
)
elif self._lb_info.status in [
sdk_types.LBStatus.DISABLED,
sdk_types.LBStatus.ENABLED,
sdk_types.LBStatus.CREATED,
]:
if amodule.params['state'] == 'absent':
self.delete()
else:
self.action(d_state=amodule.params['state'])
elif self._lb_info.status == sdk_types.LBStatus.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_info.status == sdk_types.LBStatus.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_info = self._lb_get_by_id(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']:
_, decon.lb_facts = decon.lb_find(lb_id=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,552 +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.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_osimage.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_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)
_, image_facts = decort_osimage._image_get_by_id(self, amodule.image_id_delete)
self.result['facts'] = decort_osimage.decort_osimage_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_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']
ret_dict['storage_policy_id'] = arg_osimage_facts['storage_policy_id']
ret_dict['to_clean'] = arg_osimage_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)
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']
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:
if (
decon.target_image_id
and 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 (
amodule.params['storage_policy_id'] is not None
and amodule.params['storage_policy_id']
!= image_facts['storage_policy_id']
):
decon.image_change_storage_policy(
image_id=decon.validated_virt_image_id,
storage_policy_id=amodule.params['storage_policy_id'],
)
if amodule.params['state'] == "absent" and decon.validated_virt_image_id:
amodule.image_id_delete = decon.validated_virt_image_id
image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule)
if image_facts['status'] != 'PURGED':
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
image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
if image_facts['status'] != 'DESTROYED':
decort_osimage.decort_image_delete(decon,amodule)
if decon.validated_image_id:
if (
amodule.params['storage_policy_id'] is not None
and amodule.params['storage_policy_id']
!= image_facts['storage_policy_id']
):
decon.image_change_storage_policy(
image_id=decon.validated_image_id,
storage_policy_id=amodule.params['storage_policy_id'],
)
if decon.result['failed'] == True:
# we failed to find the specified image - fail the module
decon.result['changed'] = False
amodule.fail_json(**decon.result)
else:
if decon.validated_image_id:
_, image_facts = decon.decort_image_find(amodule=amodule)
elif decon.validated_virt_image_id:
_, image_facts = decon.decort_virt_image_find(amodule=amodule)
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(
arg_osimage_facts=image_facts,
arg_check_mode=amodule.check_mode,
)
amodule.exit_json(**decon.result)
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 __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -43,7 +43,13 @@ class decort_pfw(DecortController):
supports_check_mode=True,
)
def decort_pfw_package_facts(self, comp_facts, vins_facts, pfw_facts, check_mode=False):
def decort_pfw_package_facts(
self,
comp_facts,
vins_model: sdk_types.CloudapiVinsGetResultModel,
pfw_facts,
check_mode=False,
):
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
@@ -68,9 +74,13 @@ class decort_pfw(DecortController):
ret_dict['state'] = "ABSENT"
return ret_dict
gw_vnf = vins_model.vnfs.gw
ret_dict['compute_id'] = comp_facts['id']
ret_dict['vins_id'] = vins_facts['id']
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip']
ret_dict['vins_id'] = vins_model.id
if gw_vnf:
ret_dict['public_ip'] = gw_vnf.config.ext_net_ip
else:
raise RuntimeError('VINS GW VNF must exist.')
if len(pfw_facts) != 0:
ret_dict['state'] = 'PRESENT'
@@ -86,60 +96,77 @@ 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)
vins_model = self._vins_get_by_id(vins_id=amodule.params['vins_id'])
gw_vnf = vins_model.vnfs.gw
if not gw_vnf or gw_vnf.status == sdk_types.VNFStatus.DESTROYED:
self.result['failed'] = True
self.result['msg'] = (
f'ViNS ID {vins_model.id} does not '
f'have a configured external connection.'
)
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_model,
None,
)
elif amodule.params['rules'] is not None:
# manage PFW rules accodring to the module arguments
pfw_facts = self.pfw_configure(
comp_facts,
vins_model,
amodule.params['rules'],
)
else:
pfw_facts = self._pfw_get(comp_facts['id'], vins_model.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_model,
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

@@ -19,8 +19,7 @@ class decort_rg(DecortController):
amodule = self.amodule
self.validated_acc_id = 0
self.validated_rg_id = 0
self.validated_rg_facts = None
self.rg_id: int = 0
if amodule.params['rg_id'] is None:
if self.amodule.params['account_id']:
@@ -42,13 +41,15 @@ class decort_rg(DecortController):
else:
self.rg_should_exist = False
if self.validated_rg_id and self.rg_facts['status'] != 'DESTROYED':
if self.rg_id and (
self.rg_info.status != sdk_types.ResourceGroupStatus.DESTROYED
):
self.check_amodule_args_for_change()
def get_info(self):
# If this is the first getting info
if not self.validated_rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find(
if self._rg_info is None:
self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id,
arg_rg_id=self.aparams['rg_id'],
arg_rg_name=self.aparams['rg_name'],
@@ -61,16 +62,16 @@ class decort_rg(DecortController):
if self.amodule.check_mode:
return
_, self.rg_facts = self.rg_find(arg_rg_id=self.validated_rg_id)
_, self._rg_info = self.rg_find(arg_rg_id=self.rg_id)
def access(self):
should_change_access = False
acc_granted = False
for rg_item in self.rg_facts['acl']:
if rg_item['userGroupId'] == self.amodule.params['access']['user']:
for rg_item in self.rg_info.acl:
if rg_item.user_name == self.amodule.params['access']['user']:
acc_granted = True
if self.amodule.params['access']['action'] == 'grant':
if rg_item['right'] != self.amodule.params['access']['right']:
if rg_item.access_type.value != self.amodule.params['access']['right']:
should_change_access = True
if self.amodule.params['access']['action'] == 'revoke':
should_change_access = True
@@ -78,19 +79,30 @@ class decort_rg(DecortController):
should_change_access = True
if should_change_access == True:
self.rg_access(self.validated_rg_id, self.amodule.params['access'])
self.rg_facts['access'] = self.amodule.params['access']
if self.amodule.params['access']['action'] == "grant":
self.sdk_checkmode(self.api.ca.rg.access_grant)(
access_type=sdk_types.AccessTypeForSet(
self.amodule.params['access']['right']
),
rg_id=self.rg_id,
user_name=self.amodule.params['access']['user'],
)
else:
self.sdk_checkmode(self.api.ca.rg.access_revoke)(
rg_id=self.rg_id,
user_name=self.amodule.params['access']['user'],
)
self.rg_should_exist = True
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.validated_rg_id > 0:
if self.rg_id:
self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the "
"current status '{}'.").format(self.validated_rg_id,
"current status '{}'.").format(self.rg_id,
self.amodule.params['state'],
self.rg_facts['status'])
self.rg_info.status.value)
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
"in account ID {} ").format(self.amodule.params['state'],
@@ -99,21 +111,31 @@ class decort_rg(DecortController):
return
def update(self):
resources = self.rg_facts['Resources']['Reserved']
try:
rg_res_model = (
self.api.cloudapi.rg.get_resource_consumption(rg_id=self.rg_id)
)
except sdk_exceptions.RequestException as e:
self.message(
msg=(
f'Failed to get RG Resources by ID {self.rg_id}: {e}'
)
)
self.exit(fail=True)
reserved_resources = rg_res_model.reserved
incorrect_quota = dict(Requested=dict(),
Reserved=dict(),)
query_key_map = dict(
cpu='cpu',
ram='ram',
disk='disksize',
ext_ips='extips',
net_transfer='exttraffic',
storage_policies='policies',
cpu='cpu_count',
ram='ram_size_mb',
disk='storage_size_gb_by_real_usage',
ext_ips='ext_ip_count',
storage_policies='storage_policies',
)
if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']:
if quota_item == 'storage_policies':
rg_storage_policies = resources[query_key_map[quota_item]]
rg_storage_policies = reserved_resources.storage_policies
aparam_storage_policies = self.amodule.params['quotas'][
quota_item
]
@@ -125,32 +147,35 @@ class decort_rg(DecortController):
if (
rg_storage_policy
and aparam_storage_policy['storage_size_gb']
< rg_storage_policy['disksize']
< rg_storage_policy.storage_size_gb_by_real_usage
):
incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item]
)
incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]]
getattr(
reserved_resources,
query_key_map[quota_item]
)
)
elif (
self.amodule.params['quotas'][quota_item]
< resources[query_key_map[quota_item]]
< getattr(reserved_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]]
getattr(reserved_resources, query_key_map[quota_item])
)
if incorrect_quota['Requested']:
if reserved_resources and 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,
rg_model=self.rg_info,
arg_quotas=self.amodule.params['quotas'],
arg_res_types=self.amodule.params['resType'],
arg_newname=self.amodule.params['rename'],
@@ -161,13 +186,15 @@ class decort_rg(DecortController):
return
def setDefNet(self):
rg_def_net_type = self.rg_facts['def_net_type']
rg_def_net_id = self.rg_facts['def_net_id']
rg_def_net_type = self.rg_info.default_net_type.value
rg_def_net_id = self.rg_info.default_net_id
aparam_def_net_type = self.aparams['def_netType']
aparam_def_net_id = self.aparams['def_netId']
need_to_reset = (aparam_def_net_type == 'NONE'
and rg_def_net_type != aparam_def_net_type)
need_to_reset = (
aparam_def_net_type == sdk_types.RGDefaultNetType.NONE
and rg_def_net_type != aparam_def_net_type
)
need_to_change = False
if aparam_def_net_id is not None:
@@ -175,16 +202,26 @@ class decort_rg(DecortController):
or aparam_def_net_type != rg_def_net_type)
if need_to_reset or need_to_change:
self.rg_setDefNet(
arg_rg_id=self.validated_rg_id,
arg_net_type=aparam_def_net_type,
arg_net_id=aparam_def_net_id,
)
if aparam_def_net_type == sdk_types.RGDefaultNetType.NONE:
self.sdk_checkmode(self.api.ca.rg.remove_def_net)(
rg_id=self.rg_id,
)
else:
if aparam_def_net_type == sdk_types.RGDefaultNetType.PRIVATE:
net_type = sdk_types.RGDefaultNetTypeForSet.PRIVATE
elif aparam_def_net_type == sdk_types.RGDefaultNetType.PUBLIC:
net_type = sdk_types.RGDefaultNetTypeForSet.PUBLIC
self.sdk_checkmode(self.api.ca.rg.set_def_net)(
net_type=net_type,
rg_id=self.rg_id,
net_id=aparam_def_net_id,
)
self.rg_should_exist = True
return
def create(self):
self.validated_rg_id = self.rg_provision(
self.rg_id = self.rg_provision(
self.validated_acc_id,
self.amodule.params['rg_name'],
self.amodule.params['owner'],
@@ -199,10 +236,10 @@ class decort_rg(DecortController):
sdn_access_group_id=self.aparams['sdn_access_group_id'],
)
if self.validated_rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find(
if self.rg_id:
self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id,
arg_rg_id=self.validated_rg_id,
arg_rg_id=self.rg_id,
arg_rg_name="",
arg_check_state=False
)
@@ -210,31 +247,30 @@ class decort_rg(DecortController):
return
def enable(self):
self.rg_enable(self.validated_rg_id,
self.amodule.params['state'])
if self.amodule.params['state'] == "enabled":
self.rg_facts['status'] = 'CREATED'
else:
self.rg_facts['status'] = 'DISABLED'
self.sdk_checkmode(self.api.ca.rg.enable)(
rg_id=self.rg_id,
)
elif self.amodule.params['state'] == "disabled":
self.sdk_checkmode(self.api.ca.rg.disable)(
rg_id=self.rg_id,
)
self.rg_should_exist = True
return
def restore(self):
self.rg_restore(self.validated_rg_id)
self.rg_facts['status'] = 'DISABLED'
self.sdk_checkmode(self.api.ca.rg.restore)(
rg_id=self.rg_id,
)
self.rg_should_exist = True
return
def destroy(self):
self.rg_delete(
rg_id=self.validated_rg_id,
self.sdk_checkmode(self.api.ca.rg.delete)(
rg_id=self.rg_id,
permanently=self.amodule.params['permanently'],
recursively=self.aparams['recursive_deletion'],
)
if self.amodule.params['permanently'] == True:
self.rg_facts['status'] = 'DESTROYED'
else:
self.rg_facts['status'] = 'DELETED'
self.rg_should_exist = False
return
@@ -260,23 +296,7 @@ class decort_rg(DecortController):
# ret_dict['state'] = "ABSENT"
# return ret_dict
ret_dict['id'] = self.rg_facts['id']
ret_dict['name'] = self.rg_facts['name']
ret_dict['state'] = self.rg_facts['status']
ret_dict['account_id'] = self.rg_facts['accountId']
ret_dict['gid'] = self.rg_facts['gid']
ret_dict['quota'] = self.rg_facts['resourceLimits']
ret_dict['resTypes'] = self.rg_facts['resourceTypes']
ret_dict['defNetId'] = self.rg_facts['def_net_id']
ret_dict['defNetType'] = self.rg_facts['def_net_type']
ret_dict['ViNS'] = self.rg_facts['vins']
ret_dict['computes'] = self.rg_facts['vms']
ret_dict['uniqPools'] = self.rg_facts['uniqPools']
ret_dict['description'] = self.rg_facts['desc']
ret_dict['sdn_access_group_id'] = self.rg_facts['sdn_access_group_id']
ret_dict['storage_policy_ids'] = self.rg_facts['storage_policy_ids']
return ret_dict
return self.rg_info.model_dump()
@property
def amodule_init_args(self) -> dict:
@@ -291,6 +311,24 @@ class decort_rg(DecortController):
),
access=dict(
type='dict',
options=dict(
action=dict(
type='str',
required=True,
choices=[
'grant',
'revoke',
],
),
user=dict(
type='str',
required=True,
),
right=dict(
type='str',
choices=sdk_types.AccessTypeForSet._member_names_,
),
),
),
description=dict(
type='str',
@@ -298,12 +336,8 @@ class decort_rg(DecortController):
),
def_netType=dict(
type='str',
choices=[
'PRIVATE',
'PUBLIC',
'NONE',
],
default='PRIVATE',
choices=sdk_types.RGDefaultNetType._member_names_,
default=sdk_types.RGDefaultNetType.PRIVATE.name,
),
def_netId=dict(
type='int',
@@ -387,13 +421,13 @@ class decort_rg(DecortController):
self.aparams['sdn_access_group_id'] is not None
and (
self.aparams['sdn_access_group_id']
!= self.rg_facts['sdn_access_group_id']
!= self.rg_info.sdn_access_group_id
)
):
self.message(
'Check for parameter "sdn_access_group_id" failed: '
'cannot change sdn_access_group_id for an existing resource '
f'group ID {self.validated_rg_id}.'
f'group ID {self.rg_id}.'
)
check_errors = True
@@ -407,73 +441,83 @@ 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.rg_id:
if self.rg_info.status in [
sdk_types.ResourceGroupStatus.MODELED,
sdk_types.ResourceGroupStatus.DISABLING,
sdk_types.ResourceGroupStatus.ENABLING,
sdk_types.ResourceGroupStatus.DESTROYING,
]:
self.error()
elif self.rg_info.status == sdk_types.ResourceGroupStatus.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_info.status == sdk_types.ResourceGroupStatus.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_info.status == sdk_types.ResourceGroupStatus.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,213 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_access_group
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
import requests
class DecortSDNAccessGroups(DecortController):
access_group_id: str | None = None
_access_group_info: dict[str, Any] | None = None
need_final_get: bool = True
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(
access_group_id=dict(
type='str',
),
comment=dict(
type='str',
),
display_name=dict(
type='str',
),
state=dict(
type='str',
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
@property
def access_group_info(self) -> dict[str, Any]:
if self._access_group_info is None:
if not isinstance(self.access_group_id, str):
raise TypeError
access_group_info = self.get()
if access_group_info is None:
raise TypeError
self._access_group_info = access_group_info
return self._access_group_info
def check_amodule_args(self):
check_errors = False
if (
self.aparams['state'] == 'absent'
and self.aparams['access_group_id'] is None
):
check_errors = True
self.message(
'Check for parameter "access_group_id" failed: '
'access_group_id must be specified when state is "absent".'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.aparams['comment'] is None:
check_errors = True
self.message(
'Check for parameter "comment" failed: parameter must '
'be specified for creating an access_group.'
)
if self.aparams['display_name'] is None:
check_errors = True
self.message(
'Check for parameter "display_name" failed: parameter must '
'be specified for creating an access_group.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
@DecortController.checkmode
def get(self) -> dict[str, Any] | None:
params = {'access_group_id': self.access_group_id}
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/access_group/get',
arg_params=params,
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='access_group',
id=self.access_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def access_group_find(self, display_name: str) -> dict[str, Any] | None:
params = {'display_name': display_name}
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/access_group/list',
arg_params=params,
accept_json_response=True,
)
for access_group in response.json() or []:
if access_group['display_name'] == display_name:
return access_group
return None
@DecortController.waypoint
@DecortController.checkmode
def create(self):
params = {
'comment': self.aparams['comment'],
'display_name': self.aparams['display_name'],
}
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/access_group/create',
arg_params=params,
accept_json_response=True,
)
self.access_group_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def delete(self):
params = {'access_group_id': self.access_group_id}
response = self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/access_group/delete',
arg_params=params,
not_fail_codes=[204, 404]
)
self.need_final_get = False
if response.status_code == 204:
self.set_changed()
self.message(
self.MESSAGES.obj_deleted(
obj='access_group',
id=self.access_group_id,
)
)
else:
self.message(
self.MESSAGES.obj_not_found(
obj='access_group',
id=self.access_group_id,
)
)
self.facts = {}
def run(self):
self.check_amodule_args()
if self.aparams['access_group_id']:
self.access_group_id = self.aparams['access_group_id']
elif self.aparams['state'] != 'absent':
self.check_amodule_args_for_create()
access_group_info = self.access_group_find(
display_name=self.aparams['display_name'],
)
if access_group_info:
self.access_group_id = access_group_info['id']
self._access_group_info = access_group_info
if self.access_group_id:
if self.aparams['state'] == 'absent':
self.delete()
else:
state = self.aparams['state']
if state is None:
state = 'present'
self.message(
msg=self.MESSAGES.default_value_used(
param_name='state',
default_value=state,
),
warning=True,
)
if state != 'absent':
self.create()
if self.need_final_get:
self.facts = self.get()
self.exit()
def main():
DecortSDNAccessGroups().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,120 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_access_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
import requests
class DecortSDNAccessGroupList(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(
enabled=dict(
type='bool',
),
deleted=dict(
type='bool',
),
display_name=dict(
type='str',
),
owner_display_name=dict(
type='str',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
'display_name',
'created_at',
'updated_at',
'deleted_at',
'owner_login',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
params: dict[str, Any] = dict()
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
params['page'] = aparam_pagination['number']
params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
params['sort_by'] = aparam_sorting['field']
params['sort_order'] = 'asc' if aparam_sorting['asc'] else 'desc'
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/access_group/list',
arg_params=params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNAccessGroupList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,160 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_hypervisor
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
import requests
class DecortSDNHypervisor(DecortController):
name: str | None = None
_hypervisor_info: dict[str, Any] | None = None
need_final_get: bool = True
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(
display_name=dict(
type='str',
),
name=dict(
type='str',
),
port_info=dict(
type='str',
choices=['detailed', 'general'],
),
state=dict(
type='str',
choices=['absent'],
),
),
supports_check_mode=True,
)
@property
def hypervisor_info(self) -> dict[str, Any]:
if self._hypervisor_info is None:
if not isinstance(self.name, str):
raise TypeError
hypervisor_info = self.get()
if hypervisor_info is None:
raise TypeError
self._hypervisor_info = hypervisor_info
return self._hypervisor_info
def check_amodule_args(self):
check_errors = False
if self.aparams['name'] is None:
check_errors = True
self.message(
'Check for parameter "name" failed: '
'name must be specified.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
def get(self) -> dict[str, Any] | None:
params = {'name': self.name}
if self.aparams['port_info'] is not None:
params['port_info'] = self.aparams['port_info']
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/hypervisor/get',
arg_params=params,
not_fail_codes=[400],
accept_json_response=True,
)
if response.status_code == 400:
self.message(
self.MESSAGES.obj_not_found(
obj='hypervisor',
id=self.name,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def update_display_name(self):
params = {
'name': self.aparams['name'],
'display_name': self.aparams['display_name'],
}
self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/hypervisor/update_display_name',
arg_params=params,
accept_json_response=True,
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def delete(self):
params = {'name': self.name}
response = self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/hypervisor/delete',
arg_params=params,
not_fail_codes=[400]
)
self.need_final_get = False
if response.status_code == 200:
self.set_changed()
self.message(
self.MESSAGES.obj_deleted(
obj='hypervisor',
id=self.name,
)
)
else:
self.message(
self.MESSAGES.obj_not_found(
obj='hypervisor',
id=self.name,
)
)
self.facts = {}
def run(self):
self.check_amodule_args()
self.name = self.aparams['name']
if self.aparams['state'] == 'absent':
self.delete()
else:
if (
self.aparams['display_name'] is not None
and self.aparams['display_name']
!= self.hypervisor_info.get('display_name')
):
self.update_display_name()
if self.need_final_get:
self.facts = self.get()
self.exit()
def main():
DecortSDNHypervisor().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,135 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_hypervisor_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
import requests
class DecortSDNHypervisorList(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(
port_info=dict(
type='str',
choices=['detailed', 'general']
),
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
hostname=dict(
type='str',
),
display_name=dict(
type='str',
),
ip=dict(
type='str',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
updated_from=dict(
type='str',
),
updated_to=dict(
type='str',
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
'name',
'hostname',
'last_sync',
'display_name',
'ip',
'created_at',
'updated_at',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
api_params: dict[str, Any] = dict()
aparam_port_info = self.aparams['port_info']
if aparam_port_info is not None:
api_params['port_info'] = aparam_port_info
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
api_params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
api_params['page'] = aparam_pagination['number']
api_params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
api_params['sort_by'] = aparam_sorting['field']
api_params['sort_order'] = (
'asc' if aparam_sorting['asc'] else 'desc'
)
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/hypervisor/list',
arg_params=api_params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNHypervisorList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,588 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_logical_port
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
import requests
import time
class DecortSDNLogicalPort(DecortController):
FIELDS_FOR_CREATE = (
'access_group_id',
'adapter_mac',
'description',
'display_name',
'hypervisor',
'labels',
'port_security',
'segment_id',
'unique_identifier',
)
FIELDS_FOR_UPDATE = (
'adapter_mac',
'description',
'display_name',
'hypervisor',
'labels',
'logical_port_id',
'port_security',
'segment_id',
'version_id',
)
UPDATE_FIELDS = (
'adapter_mac',
'description',
'display_name',
'hypervisor',
'labels',
'port_security',
'segment_id',
)
logical_port_id: str | None = None
_logical_port_info: dict[str, Any] | None = None
need_final_get: bool = True
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(
no_migration=dict(
type='bool',
),
hypervisor_change_via_migration=dict(
type='bool',
default=False,
),
logical_port_id=dict(
type='str',
),
state=dict(
type='str',
choices=[
'present',
'enabled',
'disabled',
'absent',
'absent_force',
],
),
access_group_id=dict(
type='str',
),
adapter_mac=dict(
type='str',
),
description=dict(
type='str',
),
display_name=dict(
type='str',
),
hypervisor=dict(
type='str',
),
labels=dict(
type='dict',
),
no_addresses=dict(
type='bool',
),
port_security=dict(
type='bool',
),
segment_id=dict(
type='str',
),
unique_identifier=dict(
type='str',
),
version_id=dict(
type='int',
),
),
supports_check_mode=True,
mutually_exclusive=[
('unique_identifier', 'logical_port_id'),
('no_migration', 'hypervisor_change_via_migration'),
],
)
def run(self):
self.check_amodule_args()
state = self.aparams['state']
if self.aparams['unique_identifier'] is not None:
self._logical_port_info = (
self.logical_port_get_by_unique_identifier(
fail_if_not_found=False,
)
)
self.logical_port_id = self._logical_port_info['id']
elif self.aparams['logical_port_id']:
self.logical_port_id = self.aparams['logical_port_id']
self._logical_port_info = self.logical_port_get(
fail_if_not_found=False,
)
elif self.aparams['display_name']:
self._logical_port_info = self.logical_port_find(
display_name=self.aparams['display_name']
)
if self._logical_port_info:
self.logical_port_id = self._logical_port_info['id']
if self._logical_port_info is not None:
if state in ('absent', 'absent_force'):
self.logical_port_delete(
force=state == 'absent_force',
)
elif self.aparams['no_migration']:
self.logical_port_migration_cancel()
elif self.aparams['hypervisor_change_via_migration']:
self.logical_port_migration_start()
else:
self.logical_port_update(
desired_enabled=(
True if state == 'enabled'
else False if state == 'disabled'
else None
)
)
else:
if state == 'present':
self.check_amodule_args_for_create()
self.logical_port_create()
elif state in ('enabled', 'disabled'):
self.message(
'Check for parameter "state" failed: state values '
'"enabled"/"disabled" can only be applied to an existing '
'logical port.'
)
self.exit(fail=True)
else:
self.need_final_get = False
self.facts = {}
if self.need_final_get and not self.amodule.check_mode:
if self._logical_port_info is not None:
self.facts = self.logical_port_info
else:
self.facts = self.logical_port_get()
self.exit()
def check_amodule_args(self):
check_errors = False
if (
self.aparams['no_migration']
and self.aparams['state'] == 'absent'
):
check_errors = True
self.message(
'Check for parameter "no_migration" failed: '
'no_migration cannot be used when state is "absent".'
)
if (
self.aparams['hypervisor_change_via_migration']
and self.aparams['hypervisor'] is None
):
check_errors = True
self.message(
'Check for parameters '
'"hypervisor_change_via_migration/hypervisor" failed: '
'"hypervisor" must be specified when '
'"hypervisor_change_via_migration" is true.'
)
if (
self.aparams['no_migration']
and self.aparams['hypervisor'] is not None
):
check_errors = True
self.message(
'Check for parameters "no_migration/hypervisor" failed: '
'"hypervisor" cannot be used when "no_migration" is true.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
for field in (
'access_group_id',
'description',
'display_name',
'hypervisor',
'port_security',
'segment_id',
):
if self.aparams[field] is None:
check_errors = True
self.message(
f'Check for parameter "{field}" failed: parameter '
f'"{field}" is required when creating a logical port.'
)
if check_errors:
self.exit(fail=True)
@property
def logical_port_info(self) -> dict[str, Any]:
if self._logical_port_info is None:
if (
self.aparams['unique_identifier'] is None
and self.aparams['logical_port_id'] is None
):
raise TypeError
logical_port_info = self.logical_port_get()
if logical_port_info is None:
raise TypeError
self._logical_port_info = logical_port_info
return self._logical_port_info
@DecortController.waypoint
def logical_port_get_by_unique_identifier(
self,
fail_if_not_found: bool = True,
) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name=(
'/restmachine/sdn/logical_port/get_by_unique_identifier'
),
arg_params={
'unique_identifier': self.aparams['unique_identifier']
},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
if fail_if_not_found:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=self.aparams['unique_identifier'],
)
)
self.exit(fail=True)
return None
return response.json()
@DecortController.waypoint
def logical_port_get(
self,
fail_if_not_found: bool = True,
) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/get',
arg_params={'logical_port_id': self.logical_port_id},
accept_json_response=True,
not_fail_codes=[404],
)
if response.status_code == 404:
if fail_if_not_found:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=self.logical_port_id,
)
)
self.exit(fail=True)
return None
return response.json()
@DecortController.waypoint
def logical_port_find(self, display_name: str) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/list',
arg_params={'display_name': display_name},
accept_json_response=True,
)
return response.json()[0] if response.json() else None
@DecortController.waypoint
@DecortController.checkmode
def logical_port_create(self):
payload = {}
for field in self.FIELDS_FOR_CREATE:
value = self.aparams[field]
if value is not None:
payload[field] = value
payload['enabled'] = True
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/logical_port/create',
arg_json_body=payload,
accept_json_response=True,
)
self.logical_port_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def logical_port_delete(
self,
force: bool = False,
):
version_id = self.aparams['version_id']
if version_id is None and self.logical_port_info is not None:
version_id = self.logical_port_info['version_id']
payload = {
'logical_port_id': self.logical_port_id,
'version_id': version_id,
'force': force,
}
self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/logical_port/delete',
arg_json_body=payload,
)
self.need_final_get = False
self.facts = {}
self.set_changed()
self.message(
msg=self.MESSAGES.obj_deleted(
obj='logical port',
id=self.logical_port_id,
permanently=True,
)
)
@DecortController.waypoint
@DecortController.checkmode
def logical_port_update(
self,
desired_enabled: bool | None = None,
):
need_update = False
addresses_to_remove = []
for field in self.UPDATE_FIELDS:
value = self.aparams[field]
if value is None:
continue
if self.aparams['no_addresses']:
bindings = self.logical_port_info['bindings']
if bindings and bindings.get('logical_port_addresses'):
for address in bindings['logical_port_addresses']:
addresses_to_remove.append(address['id'])
if addresses_to_remove:
need_update = True
if field == 'port_security':
current_value = self.logical_port_info['bindings'].get(
'port_security'
)
elif field == 'segment_id':
current_value = self.logical_port_info['bindings'].get(
'segment_id'
)
else:
current_value = self.logical_port_info.get(field)
if isinstance(value, dict) and isinstance(current_value, dict):
if any(
current_value.get(key) != expected_value
for key, expected_value in value.items()
):
need_update = True
break
continue
if value != current_value:
need_update = True
break
if (
not need_update
and desired_enabled is not None
and desired_enabled != self.logical_port_info['enabled']
):
need_update = True
if need_update:
payload = {
'logical_port_id': self.logical_port_id,
'version_id': (
self.aparams['version_id']
or self.logical_port_info['version_id']
),
'adapter_mac': (
self.aparams['adapter_mac']
or self.logical_port_info['adapter_mac']
),
'description': (
self.aparams['description']
or self.logical_port_info['description']
),
'display_name': (
self.aparams['display_name']
or self.logical_port_info['display_name']
),
'enabled': (
desired_enabled
if desired_enabled is not None
else self.logical_port_info['enabled']
),
'hypervisor': (
self.aparams['hypervisor']
or self.logical_port_info['hypervisor']
),
'labels': (
self.aparams['labels']
or self.logical_port_info.get('labels')
),
'port_security': (
self.aparams['port_security']
if self.aparams['port_security'] is not None
else self.logical_port_info['bindings']['port_security']
),
'segment_id': (
self.aparams['segment_id']
or self.logical_port_info['bindings']['segment_id']
),
}
if addresses_to_remove:
payload['remove_addresses'] = addresses_to_remove
self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/logical_port/update',
arg_json_body=payload,
accept_json_response=True,
)
self._logical_port_info = None
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def logical_port_migration_cancel(self):
version_id = self.aparams['version_id']
if version_id is None and self._logical_port_info is not None:
version_id = self._logical_port_info['version_id']
self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/logical_port/migration_cancel',
arg_json_body={
'logical_port_id': self.logical_port_id,
'version_id': version_id,
},
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def logical_port_migration_start(self):
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/logical_port/migration_start',
arg_json_body={
'logical_port_id': self.logical_port_id,
'target_hypervisor': self.aparams['hypervisor'],
'version_id': (
self.aparams['version_id']
or self.logical_port_info['version_id']
if self._logical_port_info is not None
else self.aparams['version_id']
),
},
accept_json_response=True,
)
waiting_statuses = {
'Idle',
'SynchronizingAtCore',
'SynchronizingAtOVN',
}
failed_statuses = {
'NoHypervisorAtOVN': (
'Logical port migration failed: no hypervisor at OVN.'
),
'FailedAtCore': 'Logical port migration failed at core.',
'TemporaryFailedAtCore': (
'Logical port migration temporary failed at core.'
),
}
target_hypervisor = self.aparams['hypervisor']
for _ in range(300):
logical_port_info = self.logical_port_get()
if logical_port_info is None:
self.message(
'Logical port migration failed: '
'can\'t get logical port info.'
)
self.exit(fail=True)
status_info = logical_port_info.get('status')
if not isinstance(status_info, dict):
status_info = {}
hypervisors = status_info.get('hypervisors', [])
target_hypervisor_status = None
for hypervisor in hypervisors:
if hypervisor.get('name') == target_hypervisor:
target_hypervisor_status = str(
hypervisor.get('operation_status')
)
break
if target_hypervisor_status == 'Synchronized':
self._logical_port_info = logical_port_info
self.set_changed()
return
if target_hypervisor_status == 'NoHypervisorAtOVN':
self.message(failed_statuses['NoHypervisorAtOVN'])
self.exit(fail=True)
if target_hypervisor_status == 'FailedAtCore':
self.message(failed_statuses['FailedAtCore'])
self.exit(fail=True)
if target_hypervisor_status == 'TemporaryFailedAtCore':
self.message(failed_statuses['TemporaryFailedAtCore'])
self.exit(fail=True)
if target_hypervisor_status in waiting_statuses:
time.sleep(5)
continue
if target_hypervisor_status is None:
self.message(
'Logical port migration failed: target hypervisor '
f'"{target_hypervisor}" not found in status.'
)
else:
self.message(
'Logical port migration failed with unexpected status '
f'for hypervisor "{target_hypervisor}": '
f'{target_hypervisor_status}.'
)
self.exit(fail=True)
self.message('Logical port migration timed out.')
self.exit(fail=True)
def main():
DecortSDNLogicalPort().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,206 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_logical_port_address
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
import requests
class DecortSDNLogicalPortAddress(DecortController):
logical_port_id: str | None = None
_logical_port_info: dict[str, Any] | None = None
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(
discovered=dict(
type='bool',
),
ip_addr=dict(
type='str',
required=True,
),
ip_type=dict(
type='str',
choices=['IPv4', 'IPv6'],
),
logical_port_id=dict(
type='str',
required=True,
),
mac=dict(
type='str',
),
primary=dict(
type='bool',
),
state=dict(
type='str',
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
def check_amodule_args_for_create(self):
check_errors = False
if self.aparams['ip_type'] is None:
check_errors = True
self.message(
'Check for parameter "ip_type" failed: '
'ip_type must be specified when creating a new address.'
)
if self.aparams['mac'] is None:
check_errors = True
self.message(
'Check for parameter "mac" failed: '
'mac must be specified when creating a new address.'
)
if check_errors:
self.exit(fail=True)
@property
def logical_port_info(self) -> dict[str, Any]:
if self._logical_port_info is None:
logical_port_info = self.logical_port_get()
if logical_port_info is None:
raise TypeError
self._logical_port_info = logical_port_info
return self._logical_port_info
def find_address(self) -> dict[str, Any] | None:
addresses = self.logical_port_info.get('bindings', {}).get(
'logical_port_addresses', []
)
for addr in addresses:
if addr.get('ip') == self.aparams['ip_addr']:
return addr
return None
def port_payload(self) -> dict:
info = self.logical_port_info
return {
'logical_port_id': self.logical_port_id,
'version_id': info['version_id'],
'adapter_mac': info['adapter_mac'],
'description': info['description'],
'display_name': info['display_name'],
'enabled': info['enabled'],
'hypervisor': info['hypervisor'],
'labels': info.get('labels'),
'port_security': info['bindings']['port_security'],
'segment_id': info['bindings']['segment_id'],
}
def logical_port_update(self, payload: dict):
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/logical_port/update',
arg_json_body=payload,
accept_json_response=True,
)
self._logical_port_info = response.json()
self.set_changed()
@DecortController.waypoint
def logical_port_get(self) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/get',
arg_params={'logical_port_id': self.logical_port_id},
accept_json_response=True,
not_fail_codes=[404],
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=self.logical_port_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def address_add(self):
payload = self.port_payload()
address_data: dict[str, Any] = {'ip': self.aparams['ip_addr']}
for param_name, api_param_name in (
('ip_type', 'ip_type'),
('discovered', 'is_discovered'),
('primary', 'is_primary'),
('mac', 'mac'),
):
if self.aparams[param_name] is not None:
address_data[api_param_name] = self.aparams[param_name]
payload['add_addresses'] = [address_data]
self.logical_port_update(payload)
@DecortController.waypoint
@DecortController.checkmode
def address_remove(self, address_id: str):
payload = self.port_payload()
payload['remove_addresses'] = [address_id]
self.logical_port_update(payload)
self.message(
self.MESSAGES.obj_deleted(
obj='logical port address',
id=self.aparams['ip_addr'],
)
)
def run(self):
self.logical_port_id = self.aparams['logical_port_id']
existing_addr = self.find_address()
if existing_addr:
if self.aparams['state'] == 'absent':
self.address_remove(existing_addr['id'])
else:
state = self.aparams['state']
if state is None:
state = 'present'
self.message(
msg=self.MESSAGES.default_value_used(
param_name='state',
default_value=state,
),
warning=True,
)
if state == 'absent':
self.message(
self.MESSAGES.obj_not_found(
obj='logical port address',
id=self.aparams['ip_addr'],
)
)
else:
self.check_amodule_args_for_create()
self.address_add()
self.facts = self.find_address() or {}
self.exit()
def main():
DecortSDNLogicalPortAddress().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,171 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_logical_port_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
import requests
class DecortSDNLogicalPortList(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_group_id=dict(
type='str',
),
adapter_mac=dict(
type='str',
),
address_detection=dict(
type='bool',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
display_name=dict(
type='str',
),
enabled=dict(
type='bool',
),
external_network_id=dict(
type='str',
),
hypervisor=dict(
type='str',
),
hypervisor_display_name=dict(
type='str',
),
hypervisor_status=dict(
type='str',
choices=['Up', 'Warning', 'Error'],
),
live_migration_target_hv=dict(
type='str',
),
operation_status=dict(
type='str',
choices=[
'Idle',
'SynchronizingAtCore',
'SynchronizingAtOVN',
'Synchronized',
'NoHypervisorAtOVN',
'FailedAtCore',
'TemporaryFailedAtCore',
],
),
port_security=dict(
type='bool',
),
segment_display_name=dict(
type='str',
),
segment_id=dict(
type='str',
),
unique_identifier=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=[
'created_at',
'deleted_at',
'display_name',
'hypervisor',
'hypervisor_display_name',
'port_security',
'primary_address',
'segment_display_name',
'segment_id',
'updated_at',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
api_params: dict[str, Any] = dict()
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
api_params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
api_params['page'] = aparam_pagination['number']
api_params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
api_params['sort_by'] = aparam_sorting['field']
api_params['sort_order'] = (
'asc' if aparam_sorting['asc'] else 'desc'
)
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/list',
arg_params=api_params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNLogicalPortList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,299 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group
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
import requests
class DecortSDNNetworkObjectGroup(DecortController):
REQUIRED_FIELDS = (
'access_group_id',
'description',
'name',
)
object_group_id: str | None = None
_object_group_info: dict[str, Any] | None = None
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(
access_group_id=dict(
type='str',
),
description=dict(
type='str',
),
no_addresses=dict(
type='bool',
),
no_logical_ports=dict(
type='bool',
),
name=dict(
type='str',
),
object_group_id=dict(
type='str',
),
version_id=dict(
type='int',
),
),
supports_check_mode=True,
)
@property
def object_group_info(self) -> dict[str, Any]:
if self._object_group_info is None:
if not isinstance(self.object_group_id, str):
raise TypeError
object_group_info = self.network_object_group_get()
if object_group_info is None:
raise TypeError
self._object_group_info = object_group_info
return self._object_group_info
def run(self):
if self.aparams['object_group_id']:
self.object_group_id = self.aparams['object_group_id']
elif self.aparams['name']:
object_group_info = self.network_object_group_find(
name=self.aparams['name']
)
if object_group_info:
self.object_group_id = object_group_info['id']
if self.object_group_id is None:
self.check_amodule_args_for_create()
self.network_object_group_create()
else:
self.check_amodule_args_for_update()
self.network_object_group_update()
if self.aparams['no_logical_ports']:
self.network_object_group_detach_logical_ports()
self.facts = self.network_object_group_get()
self.exit()
def check_amodule_args_for_create(self):
check_errors = False
for field in self.REQUIRED_FIELDS:
if self.aparams[field] is None:
check_errors = True
self.message(
f'Check for parameter "{field}" failed: '
f'"{field}" is required when creating an object group.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_update(self):
check_errors = False
version_id = self.aparams['version_id']
if (
version_id is not None
and version_id != self.object_group_info['version_id']
):
check_errors = True
self.message(
'Check for parameters "version_id" failed: '
'object group version mismatch: '
f'given version: {version_id}, '
f'current version: {self.object_group_info["version_id"]}.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
f'Network object group with id "{self.object_group_id}" '
'not found.'
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_find(self, name: str) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/list',
arg_params={'name': name},
accept_json_response=True,
)
return response.json()[0] if response.json() else None
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_create(self):
payload = dict()
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is not None:
payload[field] = value
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/create',
arg_json_body=payload,
accept_json_response=True,
)
self._object_group_info = response.json()
self.object_group_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_update(self):
need_update = False
remove_addresses = False
if self.aparams['no_addresses']:
current_addresses = self.object_group_info.get('addresses')
if isinstance(current_addresses, list) and current_addresses:
remove_addresses = True
need_update = True
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is None:
continue
current_value = self.object_group_info.get(field)
if isinstance(value, list) and isinstance(current_value, list):
if len(value) != len(current_value):
need_update = True
break
for index, expected_item in enumerate(value):
current_item = current_value[index]
if (
isinstance(expected_item, dict)
and isinstance(current_item, dict)
):
if any(
current_item.get(key) != expected_value
for key, expected_value in expected_item.items()
):
need_update = True
break
continue
if expected_item != current_item:
need_update = True
break
if need_update:
break
continue
if value != current_value:
need_update = True
break
if not need_update:
return
payload = {
'object_group_id': self.object_group_id,
'version_id': (
self.aparams['version_id']
or self.object_group_info['version_id']
),
'access_group_id': (
self.aparams['access_group_id']
or self.object_group_info['access_group_id']
),
'description': (
self.aparams['description']
or self.object_group_info['description']
),
'name': (
self.aparams['name']
or self.object_group_info['name']
),
}
if remove_addresses:
payload['addresses'] = []
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/network_object_group/update',
arg_json_body=payload,
accept_json_response=True,
)
self._object_group_info = response.json()
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_detach_logical_ports(self):
logical_ports = self.object_group_info.get('logical_ports')
if not isinstance(logical_ports, list) or not logical_ports:
return
port_bindings = []
for logical_port in logical_ports:
if (
isinstance(logical_port, dict)
and logical_port.get('id') is not None
and logical_port.get('version_id') is not None
):
port_bindings.append(
{
'port_id': logical_port['id'],
'port_version': logical_port['version_id'],
}
)
if not port_bindings:
return
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/detach_logical_ports', # noqa: E501
arg_json_body={
'access_group_id': (
self.aparams['access_group_id']
or self.object_group_info['access_group_id']
),
'object_group_id': self.object_group_id,
'version_id': (
self.aparams['version_id']
or self.object_group_info['version_id']
),
'port_bindings': port_bindings,
},
accept_json_response=True,
)
self._object_group_info = None
self.set_changed()
def main():
DecortSDNNetworkObjectGroup().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,209 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group_ip_range
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
import requests
class DecortSDNNetworkObjectGroupIPRange(DecortController):
object_group_id: str | None = None
_object_group_info: dict[str, Any] | None = None
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(
object_group_id=dict(
type='str',
required=True,
),
ip_addr_range=dict(
type='dict',
required=True,
options=dict(
start=dict(
type='str',
required=True,
),
end=dict(
type='str',
),
),
),
ip_proto=dict(
type='str',
choices=['IPv4', 'IPv6'],
),
net_prefix=dict(
type='str',
),
state=dict(
type='str',
required=True,
choices=['present', 'absent'],
),
),
supports_check_mode=True,
required_if=[('state', 'present', ['ip_proto'])],
)
@property
def object_group_info(self) -> dict[str, Any]:
if self._object_group_info is None:
object_group_info = self.network_object_group_get()
if object_group_info is None:
raise TypeError
self._object_group_info = object_group_info
return self._object_group_info
def find_ip_range(self) -> dict | None:
ip_addr = self.aparams['ip_addr_range']['start']
ip_proto = self.aparams['ip_proto']
for addr in self.object_group_info.get('addresses') or []:
if addr.get('ip_addr') != ip_addr:
continue
if (
ip_proto is not None
and addr.get('net_address_type') != ip_proto
):
continue
return addr
return None
def group_payload(self) -> dict:
return {
'object_group_id': self.object_group_id,
'version_id': self.object_group_info['version_id'],
'access_group_id': self.object_group_info['access_group_id'],
'description': self.object_group_info['description'],
'name': self.object_group_info['name'],
}
def ip_range_add(self):
current_addresses = self.object_group_info.get('addresses') or []
ip_addr_range = self.aparams['ip_addr_range']
ip_proto = self.aparams['ip_proto']
ip_range_data = {'ip_addr': ip_addr_range['start']}
if ip_proto is not None:
ip_range_data['net_address_type'] = ip_proto
if ip_addr_range['end'] is not None:
ip_range_data['ip_addr_range_end'] = ip_addr_range['end']
if self.aparams['net_prefix'] is not None:
ip_range_data['ip_prefix'] = self.aparams['net_prefix']
for index, addr in enumerate(current_addresses):
if addr.get('ip_addr') != ip_range_data['ip_addr']:
continue
if (
ip_proto is not None
and addr.get('net_address_type') != ip_proto
):
continue
if not any(
addr.get(field) != value
for field, value in ip_range_data.items()
if field in addr
):
return
updated_addresses = list(current_addresses)
updated_addresses[index] = {**addr, **ip_range_data}
self.network_object_group_update_addresses(updated_addresses)
return
self.network_object_group_update_addresses(
list(current_addresses) + [ip_range_data]
)
def ip_range_remove(self):
current_addresses = self.object_group_info.get('addresses') or []
ip_addr = self.aparams['ip_addr_range']['start']
ip_proto = self.aparams['ip_proto']
addresses_to_keep = [
addr for addr in current_addresses
if addr.get('ip_addr') != ip_addr
or (
ip_proto is not None
and addr.get('net_address_type') != ip_proto
)
]
if len(addresses_to_keep) == len(current_addresses):
self.message(
self.MESSAGES.obj_not_found(
obj='ip_range',
id=ip_addr,
)
)
return
self.network_object_group_update_addresses(addresses_to_keep)
self.message(
self.MESSAGES.obj_deleted(
obj='ip_range',
id=ip_addr,
)
)
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='network object group',
id=self.object_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_update_addresses(self, addresses: list):
payload = self.group_payload()
payload['addresses'] = addresses
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/network_object_group/update',
arg_json_body=payload,
accept_json_response=True,
)
self._object_group_info = response.json()
self.set_changed()
def run(self):
self.object_group_id = self.aparams['object_group_id']
if self.aparams['state'] == 'present':
self.ip_range_add()
else:
self.ip_range_remove()
self.facts = self.find_ip_range() or {}
self.exit()
def main():
DecortSDNNetworkObjectGroupIPRange().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,245 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group_logical_port
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
import requests
class DecortSDNNetworkObjectGroupLogicalPort(DecortController):
object_group_id: str
_object_group_info: dict[str, Any] | None = None
object_group_version: int | None = None
logical_port_id: str
logical_port_version: int | None = None
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(
access_group_id=dict(
type='str',
required=True,
),
object_group_id=dict(
type='str',
required=True,
),
logical_port_id=dict(
type='str',
required=True,
),
logical_port_version=dict(
type='int',
),
version_id=dict(
type='int',
),
state=dict(
type='str',
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
@property
def object_group_info(self) -> dict[str, Any]:
if self._object_group_info is None:
if not isinstance(self.object_group_id, str):
raise TypeError
info = self.network_object_group_get()
if info is None:
raise TypeError
self._object_group_info = info
return self._object_group_info
def run(self):
self.object_group_id = self.aparams['object_group_id']
self.logical_port_id = self.aparams['logical_port_id']
is_attached = self.is_logical_port_attached()
state = self.aparams['state']
self.object_group_version = self.get_object_group_version()
self.logical_port_version = self.get_port_version(
is_attached=is_attached
)
if state == 'present':
if not is_attached:
self.network_object_group_logical_port_attach()
elif state == 'absent':
if is_attached:
self.network_object_group_logical_port_detach()
self.facts = {}
self.exit()
def get_object_group_version(self) -> int:
provided_version: int | None = self.aparams['version_id']
current_version: int = self.object_group_info['version_id']
if (
provided_version is not None
and provided_version != current_version
):
self.message(
'Check for parameters "version_id" failed: '
'object group version mismatch: '
f'given version: {provided_version}, '
f'current version: {current_version}.'
)
self.exit(fail=True)
return provided_version or current_version
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='network object group',
id=self.object_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
def logical_port_get(self, logical_port_id: str) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/get',
arg_params={'logical_port_id': logical_port_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=logical_port_id,
)
)
self.exit(fail=True)
return response.json()
def is_logical_port_attached(self) -> bool:
logical_ports = self.object_group_info.get('logical_ports') or []
for lp in logical_ports:
if lp['id'] == self.logical_port_id:
return True
return False
def get_port_version_from_object_group(self) -> int | None:
logical_ports = self.object_group_info.get('logical_ports') or []
for lp in logical_ports:
if lp['id'] != self.logical_port_id:
continue
return lp['version_id']
return None
def get_port_version(self, is_attached: bool) -> int:
provided_version = self.aparams['logical_port_version']
current_version = None
if is_attached:
current_version = self.get_port_version_from_object_group()
if current_version is None:
info = self.logical_port_get(logical_port_id=self.logical_port_id)
current_version = info['version_id']
if (
provided_version is not None
and provided_version != current_version
):
self.message(
'Check for parameter "logical_port_version" failed: '
'logical port version mismatch: '
f'given version: {provided_version}, '
f'current version: {current_version}.'
)
self.exit(fail=True)
return provided_version or current_version
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_logical_port_attach(self):
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/attach_logical_ports', # noqa: E501
arg_json_body={
'object_group_id': self.object_group_id,
'access_group_id': self.aparams['access_group_id'],
'version_id': self.object_group_version,
'port_bindings': [
{
'port_id': self.logical_port_id,
'port_version': self.logical_port_version,
}
],
},
accept_json_response=True,
)
self.message(
msg=(
f'Logical port ID {self.logical_port_id} attached to object '
f'group ID {self.object_group_id}.'
)
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_logical_port_detach(self):
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/detach_logical_ports', # noqa: E501
arg_json_body={
'object_group_id': self.object_group_id,
'access_group_id': self.aparams['access_group_id'],
'version_id': self.object_group_version,
'port_bindings': [
{
'port_id': self.logical_port_id,
'port_version': self.logical_port_version,
}
],
},
accept_json_response=True,
)
self.message(
msg=(
f'Logical port ID {self.logical_port_id} detached from object '
f'group ID {self.object_group_id}.'
)
)
self.set_changed()
def main():
DecortSDNNetworkObjectGroupLogicalPort().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,166 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group_mac
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
import requests
class DecortSDNNetworkObjectGroupMAC(DecortController):
network_object_group_id: str | None = None
_network_object_group_info: dict[str, Any] | None = None
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(
network_object_group_id=dict(
type='str',
required=True,
),
mac=dict(
type='str',
required=True,
),
state=dict(
type='str',
required=True,
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
@property
def network_network_object_group_info(self) -> dict[str, Any]:
if self._network_object_group_info is None:
network_network_object_group_info = self.network_object_group_get()
if network_network_object_group_info is None:
raise TypeError
self._network_object_group_info = network_network_object_group_info
return self._network_object_group_info
def find_mac(self) -> dict | None:
mac = self.aparams['mac']
for addr in self.network_network_object_group_info.get('addresses') or []: # noqa: E501
if addr.get('mac_addr') == mac:
return addr
return None
def group_payload(self) -> dict:
return {
'object_group_id': self.network_object_group_id,
'version_id': self.network_network_object_group_info['version_id'],
'access_group_id': self.network_network_object_group_info['access_group_id'], # noqa: E501
'description': self.network_network_object_group_info['description'], # noqa: E501
'name': self.network_network_object_group_info['name'],
}
def mac_add(self):
current_addresses = self.network_network_object_group_info.get('addresses') or [] # noqa: E501
mac = self.aparams['mac']
for addr in current_addresses:
if addr.get('mac_addr') == mac:
return
mac_data = {
'mac_addr': mac,
'net_address_type': 'MAC',
}
self.network_object_group_update_addresses(
list(current_addresses) + [mac_data]
)
def mac_remove(self):
current_addresses = self.network_network_object_group_info.get('addresses') or [] # noqa: E501
mac = self.aparams['mac']
addresses_to_keep = [
addr for addr in current_addresses
if addr.get('mac_addr') != mac
]
if len(addresses_to_keep) == len(current_addresses):
self.message(
self.MESSAGES.obj_not_found(
obj='mac',
id=mac,
)
)
return
self.network_object_group_update_addresses(addresses_to_keep)
self.message(
self.MESSAGES.obj_deleted(
obj='mac',
id=mac,
)
)
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.network_object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='network object group',
id=self.network_object_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_update_addresses(self, addresses: list):
payload = self.group_payload()
payload['addresses'] = addresses
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/network_object_group/update',
arg_json_body=payload,
accept_json_response=True,
)
self._network_object_group_info = response.json()
self.set_changed()
def package_facts(self) -> dict:
facts = self.find_mac() or {}
if 'mac_addr' in facts:
facts['mac'] = facts.pop('mac_addr')
return facts
def run(self):
self.network_object_group_id = self.aparams['network_object_group_id']
if self.aparams['state'] == 'present':
self.mac_add()
else:
self.mac_remove()
self.facts = self.package_facts()
self.exit()
def main():
DecortSDNNetworkObjectGroupMAC().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,357 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_segment
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
import requests
class DecortSDNSegment(DecortController):
REQUIRED_FIELDS = (
'access_group_id',
'description',
'dhcp_v4',
'dhcp_v6',
'display_name',
'subnet_v4',
'subnet_v6',
'type',
)
segment_id: str | None = None
_segment_info: dict[str, Any] | None = None
need_final_get: bool = True
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(
access_group_id=dict(
type='str',
),
description=dict(
type='str',
),
dhcp_v4=dict(
type='dict',
),
dhcp_v6=dict(
type='dict',
),
display_name=dict(
type='str',
),
segment_id=dict(
type='str',
),
state=dict(
type='str',
choices=[
'present',
'enabled',
'disabled',
'absent',
'absent_force',
],
),
subnet_v4=dict(
type='str',
),
subnet_v6=dict(
type='str',
),
type=dict(
type='str',
choices=['User', 'ExtNet'],
),
version_id=dict(
type='int',
),
),
supports_check_mode=True,
)
@property
def segment_info(self) -> dict[str, Any]:
if self._segment_info is None:
if not isinstance(self.segment_id, str):
raise TypeError
segment_info = self.segment_get()
if segment_info is None:
raise TypeError
self._segment_info = segment_info
return self._segment_info
def run(self):
state = self.aparams['state']
if self.aparams['segment_id']:
self.segment_id = self.aparams['segment_id']
self._segment_info = self.segment_get(fail_if_not_found=False)
elif self.aparams['display_name']:
segment_info = self.segment_find(
display_name=self.aparams['display_name']
)
self._segment_info = segment_info
if segment_info:
self.segment_id = segment_info['id']
if state in ('absent', 'absent_force'):
if self._segment_info is None:
self.exit()
if self.segment_id:
self.segment_delete(
force=state == 'absent_force',
)
else:
self.need_final_get = False
elif state == 'present':
if self.segment_id:
self.check_amodule_args_for_update()
self.segment_update()
else:
self.check_amodule_args_for_create()
self.segment_create()
elif state in ('enabled', 'disabled'):
if not self.segment_id:
self.message(
'Check for parameter "state" failed: state values '
'"enabled"/"disabled" can only be applied to an existing '
'segment.'
)
self.check_amodule_args_for_update()
self.segment_update(
desired_enabled=(state == 'enabled'),
)
if self.need_final_get:
self.facts = self.segment_get()
self.exit()
def check_amodule_args_for_create(self):
check_errors = False
if (
self.aparams['subnet_v4'] is None
and self.aparams['subnet_v6'] is None
):
check_errors = True
self.message(
'Check for parameters "subnet_v4/subnet_v6" failed: at '
'least one of these parameters must be specified when '
'creating a segment.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_update(self):
check_errors = False
if (
self.aparams['version_id'] is not None
and self.aparams['version_id'] != self.segment_info['version_id']
):
check_errors = True
self.message(
'Check for parameters "version_id" failed: '
'segment version mismatch: '
f'given version: {self.aparams['version_id']}, '
f'current version: {self.segment_info['version_id']}.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
@DecortController.checkmode
def segment_get(
self,
access_group_id: str | None = None,
fail_if_not_found=True,
) -> dict[str, Any] | None:
params = {'segment_id': self.segment_id}
if access_group_id is not None:
params['access_group_id'] = access_group_id
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/segment/get',
arg_params=params,
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
if fail_if_not_found:
self.message(
self.MESSAGES.obj_not_found(
obj='segment',
id=self.segment_id,
)
)
self.exit(fail=True)
else:
return None
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def segment_find(self, display_name: str) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/segment/list',
arg_params={
'display_name': display_name,
},
accept_json_response=True,
)
return response.json()[0] if response.json() else None
@DecortController.waypoint
@DecortController.checkmode
def segment_create(self):
payload = dict()
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is not None:
payload[field] = value
payload['enabled'] = True
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/segment/create',
arg_json_body=payload,
accept_json_response=True,
)
self._segment_info = response.json()
self.segment_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def segment_update(
self,
desired_enabled: bool | None = None,
):
need_update = False
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is None:
continue
current_value = self.segment_info.get(field)
if isinstance(value, dict) and isinstance(current_value, dict):
if any(
current_value.get(key) != expected_value
for key, expected_value in value.items()
):
need_update = True
break
continue
if value != current_value:
need_update = True
break
if (
not need_update
and desired_enabled is not None
and desired_enabled != self.segment_info['enabled']
):
need_update = True
if need_update:
payload = {
'segment_id': self.segment_id,
'version_id': (
self.aparams['version_id']
or self.segment_info['version_id']
),
'access_group_id': (
self.aparams['access_group_id']
or self.segment_info['access_group_id']
),
'description': (
self.aparams['description']
or self.segment_info['description']
),
'dhcp_v4': (
self.aparams['dhcp_v4'] or self.segment_info.get('dhcp_v4')
),
'dhcp_v6': (
self.aparams['dhcp_v6'] or self.segment_info.get('dhcp_v6')
),
'display_name': (
self.aparams['display_name']
or self.segment_info['display_name']
),
'enabled': (
desired_enabled
if desired_enabled is not None
else self.segment_info['enabled']
),
'subnet_v4': (
self.aparams['subnet_v4']
or self.segment_info.get('subnet_v4')
),
'subnet_v6': (
self.aparams['subnet_v6']
or self.segment_info.get('subnet_v6')
),
'type': (
self.aparams['type'] or self.segment_info['type']
),
}
self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/segment/update',
arg_json_body=payload,
accept_json_response=True,
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def segment_delete(
self,
force: bool = False,
):
version_id = self.aparams['version_id']
if version_id is None:
version_id = self.segment_info['version_id']
payload = {
'segment_id': self.segment_id,
'version_id': version_id,
}
self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/segment/delete',
arg_params=payload,
)
self.need_final_get = False
self.set_changed()
self.message(
self.MESSAGES.obj_deleted(
obj='segment',
id=self.segment_id,
permanently=force,
)
)
self.facts = {}
def main():
DecortSDNSegment().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,142 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_segment_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
import requests
class DecortSDNSegmentList(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(
enabled=dict(
type='bool',
),
is_synced=dict(
type='bool',
),
display_name=dict(
type='str',
),
subnet=dict(
type='str',
),
access_group_id=dict(
type='str',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
updated_from=dict(
type='str',
),
updated_to=dict(
type='str',
),
operation_status=dict(
type='str',
choices=[
'Idle',
'SynchronizingAtCore',
'SynchronizingAtOVN',
'Synchronized',
'NoHypervisorAtOVN',
'FailedAtCore',
'TemporaryFailedAtCore',
],
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
'display_name',
'subnet',
'created_at',
'updated_at',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
api_params: dict[str, Any] = dict()
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
api_params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
api_params['page'] = aparam_pagination['number']
api_params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
api_params['sort_by'] = aparam_sorting['field']
api_params['sort_order'] = (
'asc' if aparam_sorting['asc'] else 'desc'
)
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/segment/list',
arg_params=api_params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNSegmentList().run()
if __name__ == '__main__':
main()

View File

@@ -10,6 +10,9 @@ 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
import dynamix_sdk.types as sdk_types
class DecortSecurityGroup(DecortController):
id: int = 0
@@ -52,18 +55,18 @@ class DecortSecurityGroup(DecortController):
options=dict(
direction=dict(
type='str',
choices=[
e.name for e in
self.SecurityGroupRuleDirection
],
choices=(
sdk_types.TrafficDirection.
_member_names_
),
required=True,
),
ethertype=dict(
type='str',
choices=[
e.name for e in
self.SecurityGroupRuleEtherType
],
choices=(
sdk_types.SGRuleEthertype.
_member_names_
),
),
id=dict(
type='int',
@@ -81,12 +84,11 @@ class DecortSecurityGroup(DecortController):
),
protocol=dict(
type='str',
choices=[
e.name for e in
self.SecurityGroupRuleProtocol
],
choices=(
sdk_types.SGRuleProtocol._member_names_
),
),
remote_ip_prefix=dict(
remote_net_cidr=dict(
type='str',
),
),
@@ -101,16 +103,17 @@ class DecortSecurityGroup(DecortController):
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_group = self.security_group_find(
security_groups = self.api.cloudapi.security_group.list(
account_id=self.aparams['account_id'],
name=self.aparams['name'],
)
if security_group:
self.id = security_group['id']
if security_groups.data:
self.id = security_groups.data[0].id
if self.id:
self.get_info()
@@ -127,14 +130,25 @@ class DecortSecurityGroup(DecortController):
self.exit()
def get_info(self):
self.facts: dict = self.security_group_get(id=self.id)
self.facts['created_timestamp'] = self.facts.pop('created_at')
self.facts['updated_timestamp'] = self.facts.pop('updated_at')
for rule in self.facts['rules']:
rule['port_range'] = {
'min': rule.pop('port_range_min'),
'max': rule.pop('port_range_max'),
}
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 is not None
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
@@ -242,16 +256,13 @@ class DecortSecurityGroup(DecortController):
return not check_errors
def create(self):
security_groups_by_account_id = self.user_security_groups(
account_id=self.aparams['account_id']
id = self.sdk_checkmode(self.api.cloudapi.security_group.create)(
account_id=self.aparams['account_id'],
name=self.aparams['name'],
description=self.aparams['description'],
)
sg_names = [sg['name'] for sg in security_groups_by_account_id]
if self.aparams['name'] not in sg_names:
self.id = self.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()
@@ -277,7 +288,7 @@ class DecortSecurityGroup(DecortController):
):
new_description = aparam_description
if new_name or new_description:
self.security_group_update(
self.sdk_checkmode(self.api.cloudapi.security_group.update)(
security_group_id=self.id,
name=new_name,
description=new_description,
@@ -317,7 +328,9 @@ class DecortSecurityGroup(DecortController):
self.create_rule(rule=rule)
def delete(self):
self.security_group_detele(security_group_id=self.id)
self.sdk_checkmode(self.api.cloudapi.security_group.delete)(
security_group_id=self.id,
)
self.facts = {}
self.exit()
@@ -326,20 +339,22 @@ class DecortSecurityGroup(DecortController):
if rule.get('port_range'):
port_range_min = rule['port_range'].get('min')
port_range_max = rule['port_range'].get('max')
self.security_group_create_rule(
self.sdk_checkmode(self.api.cloudapi.security_group.create_rule)(
security_group_id=self.id,
direction=self.SecurityGroupRuleDirection[rule['direction']],
traffic_direction=(
sdk_types.TrafficDirection[rule['direction']]
),
ethertype=(
self.SecurityGroupRuleEtherType[rule['ethertype']]
if rule.get('ethertype') else None
sdk_types.SGRuleEthertype[rule['ethertype']]
if rule.get('ethertype') else sdk_types.SGRuleEthertype.IPV4
),
protocol=(
self.SecurityGroupRuleProtocol[rule['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_ip_prefix=rule.get('remote_ip_prefix'),
remote_net_cidr=rule.get('remote_net_cidr'),
)

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

@@ -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 DecortStoragePolicy(DecortController):
def __init__(self):
@@ -28,18 +30,31 @@ class DecortStoragePolicy(DecortController):
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
self.facts = self.storage_policy_get(id=self.id)
self.facts['sep_pools'] = self.facts.pop('access_seps_pools')
self.facts['iops_limit'] = self.facts.pop('limit_iops')
self.facts['usage']['account_ids'] = self.facts['usage'].pop(
'accounts'
)
self.facts['usage']['rg_ids'] = self.facts['usage'].pop('resgroups')
try:
storage_policy_model = self.api.cloudapi.storage_policy.get(
id=self.id
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response is not None
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():

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 is not None
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()

89
library/decort_user.py Normal file
View File

@@ -0,0 +1,89 @@
#!/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
from dynamix_sdk import exceptions as sdk_exceptions
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']
try:
user_model = self.api.cloudapi.user.get(user_name=self.id)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='user',
)
)
self.exit(fail=True)
raise e
self.facts.update(user_model.model_dump())
if self.aparams['resource_consumption']:
self.facts.update(
self.api.ca.user.get_resource_consumption().model_dump()
)
# Delete duplicate self.facts['name']
del self.facts['user_name']
if self.aparams['api_methods']:
self.facts['api_methods'] = (
self.api.cloudapi.user.api_list(user_name=self.id).model_dump()
)
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,871 +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
],
),
zone_id=dict(
type='int',
),
),
),
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,
),
),
),
),
),
storage_policies=dict(
type='dict',
options=dict(
filter=dict(
type='dict',
options=dict(
account_id=dict(
type='int',
),
description=dict(
type='str',
),
id=dict(
type='int',
),
iops_limit=dict(
type='int',
),
name=dict(
type='str',
),
pool_name=dict(
type='str',
),
rg_id=dict(
type='int',
),
sep_id=dict(
type='int',
),
status=dict(
type='str',
choices=[
e.value for e
in self.StoragePolicyStatus
],
),
),
),
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.StoragePoliciesSortableField
],
required=True,
),
),
),
),
),
security_groups=dict(
type='dict',
options=dict(
filter=dict(
type='dict',
options=dict(
account_id=dict(
type='int',
),
created_timestamp_max=dict(
type='int',
),
created_timestamp_min=dict(
type='int',
),
description=dict(
type='str',
),
id=dict(
type='int',
),
name=dict(
type='str',
),
updated_timestamp_max=dict(
type='int',
),
updated_timestamp_min=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=[
e.name for e
in self.SecurityGroupSortableField
],
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)
)
mapped_args['zone_id'] = input_args_filter['zone_id']
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
@property
def mapped_storage_policies_args(self):
"""
Map the module argument `storage_policies` to
arguments dictionary for the method
`DecortController.user_storage_policies`.
"""
input_args = self.aparams['storage_policies']
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.StoragePolicyStatus(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.StoragePoliciesSortableField(input_args_sorting_field)
)
return mapped_args
@property
def mapped_security_groups_args(self):
"""
Map the module argument `security_groups` to
arguments dictionary for the method
`DecortController.user_security_groups`.
"""
input_args = self.aparams['security_groups']
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_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.SecurityGroupSortableField[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')
if self.aparams['storage_policies']:
self.facts['storage_policies'] = self.user_storage_policies(
**self.mapped_storage_policies_args
)
for storage_policy_facts in self.facts['storage_policies']:
storage_policy_facts['sep_pools'] = storage_policy_facts.pop(
'access_seps_pools'
)
storage_policy_facts['iops_limit'] = storage_policy_facts.pop(
'limit_iops'
)
storage_policy_facts['usage']['account_ids'] = (
storage_policy_facts['usage'].pop('accounts')
)
storage_policy_facts['usage']['rg_ids'] = (
storage_policy_facts['usage'].pop('resgroups')
)
if self.aparams['security_groups']:
self.facts['security_groups'] = self.user_security_groups(
**self.mapped_security_groups_args
)
for security_groups_facts in self.facts['security_groups']:
for rule in security_groups_facts.get('rules', []):
rule['port_range'] = {
'min': rule.pop('port_range_min'),
'max': rule.pop('port_range_max'),
}
security_groups_facts['created_timestamp'] = (
security_groups_facts.pop('created_at')
)
security_groups_facts['created_timestamp_readable'] = (
self.sec_to_dt_str(security_groups_facts[
'created_timestamp'
])
)
security_groups_facts['updated_timestamp'] = (
security_groups_facts.pop('updated_at')
)
security_groups_facts['updated_timestamp_readable'] = (
self.sec_to_dt_str(security_groups_facts[
'updated_timestamp'
])
)
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

@@ -20,7 +20,6 @@ class decort_vins(DecortController):
self.vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
vins_facts = None # will hold ViNS facts
validated_rg_id = 0
rg_facts = None # will hold RG facts
validated_acc_id = 0
@@ -28,15 +27,28 @@ class decort_vins(DecortController):
if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified ID
# This call to vins_find will abort the module if no ViNS with such ID is present
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False)
self.vins_id, self._vins_info = self.vins_find(
arg_amodule.params['vins_id'],
check_state=False,
)
if self.vins_id == 0:
if arg_amodule.params['state'] == 'absent':
self.exit()
else:
self.message(
self.MESSAGES.obj_not_found(
obj='VINS',
id=arg_amodule.params['vins_id'],
)
)
self.exit(fail=True)
if self._vins_info is None:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.amodule.fail_json(**self.result)
self.vins_level = "ID"
#raise Exception(self.vins_facts)
validated_acc_id = self.vins_facts['accountId']
validated_rg_id = self.vins_facts['rgId']
validated_acc_id = self._vins_info.account_id
validated_rg_id = self._vins_info.rg_id
elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID
@@ -44,14 +56,16 @@ class decort_vins(DecortController):
# This call to rg_find will abort the module if no RG with such ID is present
validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID
arg_amodule.params['rg_id'], arg_rg_name="")
validated_acc_id = rg_facts['accountId']
validated_acc_id = rg_facts.account_id
# This call to vins_find may return vins_id=0 if no ViNS found
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=0,
rg_id=arg_amodule.params['rg_id'],
rg_facts=rg_facts,
check_state=False)
self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=0,
rg_id=arg_amodule.params['rg_id'],
check_state=False,
)
# TODO: add checks and setup ViNS presence flags accordingly
pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
@@ -66,27 +80,39 @@ class decort_vins(DecortController):
# expect ViNS @ RG level in the RG with specified name under specified account
# RG with the specified name must be present under the account, otherwise abort the module
validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name'])
if (not validated_rg_id or
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
if (not validated_rg_id or rg_facts is None or
rg_facts.status in [
sdk_types.ResourceGroupStatus.DESTROYING,
sdk_types.ResourceGroupStatus.DESTROYED,
sdk_types.ResourceGroupStatus.DELETED,
sdk_types.ResourceGroupStatus.DISABLING,
sdk_types.ResourceGroupStatus.ENABLING,
]
):
self.result['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result)
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=0, # set to 0, as we are looking for ViNS under RG
rg_id=validated_rg_id,
rg_facts=rg_facts,
check_state=False)
# (account_id) set to 0, as we are looking for ViNS under RG
self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=0,
rg_id=validated_rg_id,
check_state=False,
)
self.vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level
# This call to vins_find may return vins_id=0 if no ViNS found
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=validated_acc_id,
rg_id=0,
rg_facts=rg_facts,
check_state=False)
self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=validated_acc_id,
rg_id=0,
check_state=False,
)
self.vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly
else:
@@ -106,73 +132,121 @@ class decort_vins(DecortController):
self.rg_id = validated_rg_id
self.acc_id = validated_acc_id
if self.vins_id and self.vins_facts['status'] != 'DESTROYED':
if (
self._vins_info
and self._vins_info.status != sdk_types.VINSStatus.DESTROYED
):
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
return
def create(self):
self.vins_id = self.vins_provision(self.amodule.params['vins_name'],
self.acc_id, self.rg_id,
self.amodule.params['ipcidr'],
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'],
self.amodule.params['description'],
zone_id=self.amodule.params['zone_id'],
security_group_mode = self.amodule.params['security_group_mode']
if security_group_mode is None:
security_group_mode = False
self.message(
msg=self.MESSAGES.default_value_used(
param_name='security_group_mode',
default_value=security_group_mode
),
warning=True,
)
self.vins_id = self.vins_provision(
vins_name=self.amodule.params['vins_name'],
account_id=self.acc_id,
rg_id=self.rg_id,
ipcidr=self.amodule.params['ipcidr'],
ext_net_id=self.amodule.params['ext_net_id'],
ext_ip_addr=self.amodule.params['ext_ip_addr'],
desc=self.amodule.params['description'],
zone_id=self.amodule.params['zone_id'],
security_group_mode=security_group_mode,
)
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']:
_, self.vins_facts = self.vins_find(self.vins_id)
if self.vins_id:
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
if self.amodule.params['connect_to']:
self.vins_update_ifaces(self.vins_facts,self.amodule.params['connect_to'],)
self.vins_update_ifaces(
self.vins_info,
self.amodule.params['connect_to'],
)
if self.amodule.params['mgmtaddr']:
self.vins_update_mgmt(self.vins_facts,self.amodule.params['mgmtaddr'])
self.vins_update_mgmt(
self.vins_info,
self.amodule.params['mgmtaddr'],
)
return
def action(self,d_state='',restore=False):
if restore == True:
self.vins_restore(arg_vins_id=self.vins_id)
self.vins_state(self.vins_facts, 'enabled')
self.vins_facts['status'] = "ENABLED"
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
self.vins_update_extnet(self.vins_facts,
self.amodule.params['ext_net_id'],
self.amodule.params['ext_ip_addr'],
)
if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED":
self.vins_state(self.vins_facts, d_state)
self.vins_facts['status'] = "ENABLED"
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
def action(self, d_state='', restore=False):
if restore:
self.sdk_checkmode(self.api.cloudapi.vins.restore)(
vins_id=self.vins_info.id,
)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.vins_state(self.vins_info, 'enabled')
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
if (
self.amodule.params['ext_net_id'] is not None
or self.amodule.params['ext_ip_addr'] is not None
):
self.vins_update_extnet(
self.vins_info,
self.amodule.params['ext_net_id'],
self.amodule.params['ext_ip_addr'],
)
if (
d_state == 'enabled'
and self.vins_info.status == sdk_types.VINSStatus.DISABLED
):
self.vins_state(self.vins_info, d_state)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
d_state = ''
if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED":
self.vins_update_ifaces(self.vins_facts,
self.amodule.params['connect_to'],
)
if (
self.vins_info.status == sdk_types.VINSStatus.ENABLED
and self.vins_info.vnfdev.tech_status == (
sdk_types.VNFDevTechStatus.STARTED
)
):
self.vins_update_ifaces(
self.vins_info,
self.amodule.params['connect_to'],
)
if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id)
self.vins_update_mgmt(self.vins_facts,
self.amodule.params['mgmtaddr'],
)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.vins_update_mgmt(
self.vins_info,
self.amodule.params['mgmtaddr'],
)
if d_state != '':
self.vins_state(self.vins_facts, d_state)
self.vins_state(self.vins_info, d_state)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.vins_facts['zoneId']:
if (
aparam_zone_id is not None
and aparam_zone_id != self.vins_info.zone_id
):
self.vins_migrate_to_zone(
net_id=self.vins_id,
net_id=self.vins_info.id,
zone_id=aparam_zone_id,
)
return
def delete(self):
self.vins_delete(self.vins_id, self.amodule.params['permanently'])
self.vins_facts['status'] = 'DESTROYED'
self.sdk_checkmode(self.api.cloudapi.vins.delete)(
vins_id=self.vins_info.id,
permanently=self.amodule.params['permanently'],
)
return
def nop(self):
"""No operation (NOP) handler for ViNS management by decort_vins module.
This function is intended to be called from the main switch construct of the module
@@ -181,23 +255,27 @@ class decort_vins(DecortController):
"""
self.result['failed'] = False
self.result['changed'] = False
if self.vins_id:
self.result['msg'] = ("No state change required for ViNS ID {} because of its "
"current status '{}'.").format(self.vins_id, self.vins_facts['status'])
if self._vins_info:
self.result['msg'] = (
f'No state change required for ViNS ID {self._vins_info.id} '
f'because of its "current status "{self._vins_info.status}".'
)
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent ViNS instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.vins_id:
if self._vins_info:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
"current status '{}'").format(self.vins_id,
self.amodule.params['state'],
self.vins_facts['status'])
self.result['msg'] = (
f'Invalid target state "{self.amodule.params['state']}" '
f'requested for ViNS ID {self._vins_info.id} in the '
f'current status "{self._vins_info.status}"'
)
else:
self.result['failed'] = True
self.result['changed'] = False
@@ -205,6 +283,7 @@ class decort_vins(DecortController):
"ViNS name '{}'").format(self.amodule.params['state'],
self.amodule.params['vins_name'])
return
def package_facts(self, arg_check_mode=False):
"""Package a dictionary of ViNS facts according to the decort_vins module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
@@ -222,40 +301,12 @@ class decort_vins(DecortController):
# in check mode return immediately with the default values
return ret_dict
if self.vins_facts is None:
if self._vins_info is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = self.vins_facts['id']
ret_dict['name'] = self.vins_facts['name']
ret_dict['state'] = self.vins_facts['status']
ret_dict['account_id'] = self.vins_facts['accountId']
ret_dict['rg_id'] = self.vins_facts['rgId']
ret_dict['int_net_addr'] = self.vins_facts['network']
ret_dict['gid'] = self.vins_facts['gid']
custom_interfaces = list(filter(lambda i: i['type']=="CUSTOM",self.vins_facts['VNFDev']['interfaces']))
if custom_interfaces:
ret_dict['custom_net_addr'] = []
for runner in custom_interfaces:
ret_dict['custom_net_addr'].append(runner['ipAddress'])
mgmt_interfaces = list(filter(lambda i: i['listenSsh'] and i['name']!="ens9",self.vins_facts['VNFDev']['interfaces']))
if mgmt_interfaces:
ret_dict['ssh_ipaddr'] = []
for runner in mgmt_interfaces:
ret_dict['ssh_ipaddr'].append(runner['ipAddress'])
ret_dict['ssh_password'] = self.vins_facts['VNFDev']['config']['mgmt']['password']
ret_dict['ssh_port'] = 9022
if self.vins_facts['vnfs'].get('GW'):
gw_config = self.vins_facts['vnfs']['GW']['config']
ret_dict['ext_ip_addr'] = gw_config['ext_net_ip']
ret_dict['ext_net_id'] = gw_config['ext_net_id']
else:
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
ret_dict['zone_id'] = self.vins_facts['zoneId']
return ret_dict
return self._vins_info.model_dump()
@property
@@ -276,11 +327,9 @@ class decort_vins(DecortController):
),
ext_net_id=dict(
type='int',
default=-1,
),
ext_ip_addr=dict(
type='str',
default='',
),
ipcidr=dict(
type='str',
@@ -304,7 +353,6 @@ class decort_vins(DecortController):
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'disabled',
@@ -335,6 +383,9 @@ class decort_vins(DecortController):
zone_id=dict(
type=int,
),
security_group_mode=dict(
type='bool',
),
),
supports_check_mode=True,
required_one_of=[
@@ -347,6 +398,34 @@ class decort_vins(DecortController):
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.amodule.params['ext_ip_addr']
and self.amodule.params['ext_net_id'] is None
and self.vins_info.vnfs.gw is None
):
self.message(
msg=(
'Check for parameter "ext_net_id" failed: '
'the "ext_net_id" parameter must be specified '
'if the "ext_ip_addr" parameter is passed and '
'VINS is not connected to an external network.'
)
)
check_errors = True
if (
self.aparams['security_group_mode'] is not None
and self.vins_info.security_group_mode != self.aparams['security_group_mode']
):
self.message(
msg=(
'Check for parameter "security_group_mode" failed: '
'"security_group_mode" cannot be changed '
'for existing ViNS'
)
)
check_errors = True
if check_errors:
self.exit(fail=True)
@@ -365,102 +444,144 @@ 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
if decon.amodule.params['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_info:
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_info.status in [
sdk_types.VINSStatus.MODELED,
sdk_types.VINSStatus.DISABLING,
sdk_types.VINSStatus.ENABLING,
sdk_types.VINSStatus.DELETING,
sdk_types.VINSStatus.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'] = (
f'No change can be done for existing '
f'ViNS ID {self.vins_id} because of its '
f'current status {self._vins_info.status}'
)
elif self._vins_info.status == sdk_types.VINSStatus.DISABLED:
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif (
amodule.params['state'] is None
or 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_info.status in [
sdk_types.VINSStatus.CREATED,
sdk_types.VINSStatus.ENABLED,
]:
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif (
amodule.params['state'] is None
or 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_info.status == sdk_types.VINSStatus.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_info.status == sdk_types.VINSStatus.DESTROYED:
state = amodule.params['state']
if state is None:
state = 'present'
self.message(
msg=(
f'State not specified, '
f'default value "{state}" will be used.'
),
warning=True,
)
if state in ('present', 'enabled'):
# need to re-provision ViNS;
self.create()
vins_should_exist = True
elif state == 'absent':
self.nop()
vins_should_exist = False
elif state == 'disabled':
self.error()
else:
state = amodule.params['state']
if state is None:
state = 'present'
self.message(
msg=(
f'State not specified, '
f'default value "{state}" will be used.'
),
warning=True,
)
# 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 state == 'absent':
self.nop()
elif 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 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_info = self._vins_get_by_id(vins_id=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()

2533
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

@@ -30,9 +30,10 @@ class DecortVMSnapshot(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 DecortVMSnapshot(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 DecortVMSnapshot(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 DecortVMSnapshot(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 DecortVMSnapshot(DecortController):
label=label,
)
return common_snapshots_usage_info['stored']
def check_amodule_args_for_change(self):
check_errors = False
@@ -171,7 +174,7 @@ class DecortVMSnapshot(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,7 +182,6 @@ class DecortVMSnapshot(DecortController):
self.exit(fail=True)
def main():
DecortVMSnapshot().run()

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 is not None
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.5.latest