6 Commits
10.0.2 ... main

Author SHA1 Message Date
9d78742a10 12.0.1 2026-06-05 14:27:07 +03:00
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 11053 additions and 8744 deletions

View File

@@ -1,11 +1,11 @@
# Список изменений в версии 10.0.2 # Список изменений в версии 12.0.1
## Добавлено ## Добавлено
## Удалено ## Удалено
## Исправлено ## Исправлено
### Модуль decort_k8s ### Модуль decort_sdn_logical_port
| Идентификатор<br>задачи | Описание | | Идентификатор<br>задачи | Описание |
| --- | --- | | --- | --- |
| BANS-1173 | Модуль завершал работу ошибкой запроса к API при попытке добавить группу worker-узлов к существующему кластеру. | | BANS-1157 | Модуль не совершал удаление адресов при переданном параметре `no_addresses: true`. |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
--- ---
# #
# DECORT osimage module example # DECORT image module example
# #
- hosts: localhost - hosts: localhost
tasks: tasks:
- name: rename_osimage - name: rename_image
decort_osimage: decort_image:
authenticator: oauth2 authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online" controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0v2.0" image_name: "alpine_linux_3.14.0v2.0"
image_id: 54321 image_id: 54321
delegate_to: localhost 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 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.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController from ansible.module_utils.decort_utils import DecortController, sdk_types
class DecortAccount(DecortController): class DecortAccount(DecortController):
@@ -23,7 +23,7 @@ class DecortAccount(DecortController):
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args( return self.pack_amodule_init_args(
argument_spec=dict( argument_spec=dict(
access_emails=dict( send_access_emails=dict(
type='bool', type='bool',
), ),
acl=dict( acl=dict(
@@ -45,8 +45,13 @@ class DecortAccount(DecortController):
options=dict( options=dict(
rights=dict( rights=dict(
type='str', type='str',
choices=['R', 'RCX', 'ARCXDU'], choices=(
default='R', sdk_types.AccessTypeForSet
._member_names_
),
default=(
sdk_types.AccessTypeForSet.R.name
),
), ),
id=dict( id=dict(
type='str', type='str',
@@ -62,25 +67,26 @@ class DecortAccount(DecortController):
name=dict( name=dict(
type='str', type='str',
), ),
get_resource_consumption=dict(
type='bool',
default=False,
),
quotas=dict( quotas=dict(
type='dict', type='dict',
options=dict( options=dict(
cpu=dict( cpu_count=dict(
type='int', type='int',
), ),
disks_size=dict( storage_size_gb=dict(
type='int', type='int',
), ),
ext_traffic=dict( gpu_count=dict(
type='int', type='int',
), ),
gpu=dict( ext_ip_count=dict(
type='int', type='int',
), ),
public_ip=dict( ram_size_mb=dict(
type='int',
),
ram=dict(
type='int', type='int',
), ),
), ),
@@ -94,7 +100,6 @@ class DecortAccount(DecortController):
'disabled', 'disabled',
'present', 'present',
], ],
default='present',
), ),
sep_pools=dict( sep_pools=dict(
type='list', type='list',
@@ -131,11 +136,11 @@ class DecortAccount(DecortController):
""" """
arg_state = self.aparams['state'] 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 # Parameters or combinations of parameters that can
# cause changing the object. # cause changing the object.
changing_params = [ changing_params = [
'access_emails', 'send_access_emails',
'acl', 'acl',
['id', 'name'], ['id', 'name'],
'quotas', 'quotas',
@@ -175,6 +180,7 @@ class DecortAccount(DecortController):
if check_error: if check_error:
self.exit(fail=True) self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
self.get_info() self.get_info()
self.check_amodule_args_for_change() self.check_amodule_args_for_change()
@@ -194,36 +200,231 @@ class DecortAccount(DecortController):
# request info again # request info again
if not self.amodule.check_mode: if not self.amodule.check_mode:
self.acc_id, self._acc_info = self.account_find( 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): def change(self):
self.change_state() self.change_state()
self.change_acl() self.change_acl()
if self.account_update_args: need_account_update_api_call: bool = False
self.account_update(account_id=self.acc_id,
**self.account_update_args) 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() self.get_info()
def change_state(self): def change_state(self):
match self._acc_info: if self._acc_info is None:
case None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ)) self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams: match self.aparams:
case {'state': 'absent' | 'absent_permanently'}: case {'state': 'absent' | 'absent_permanently'}:
pass pass
case {'state': 'confirmed' | 'disabled' | 'present'}: case {'state': 'confirmed' | 'disabled' | 'present'}:
self.exit(fail=True) self.exit(fail=True)
case {'status': 'DESTROYED'}: elif self._acc_info.status == sdk_types.AccountStatus.DESTROYED:
match self.aparams: match self.aparams:
case {'state': 'absent' | 'absent_permanently'}: case {'state': 'absent' | 'absent_permanently'}:
self.message( self.message(
self.MESSAGES.obj_deleted( self.MESSAGES.obj_deleted(
obj=self.OBJ, obj=self.OBJ,
id=self.acc_id, id=self.acc_info.id,
permanently=True, permanently=True,
already=True, already=True,
) )
@@ -231,16 +432,16 @@ class DecortAccount(DecortController):
case {'state': 'confirmed' | 'disabled' | 'present'}: case {'state': 'confirmed' | 'disabled' | 'present'}:
self.message( self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ, self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.acc_id) id=self.acc_info.id)
) )
self.exit(fail=True) self.exit(fail=True)
case {'status': 'DELETED'}: elif self._acc_info.status == sdk_types.AccountStatus.DELETED:
match self.aparams: match self.aparams:
case {'state': 'absent'}: case {'state': 'absent'}:
self.message( self.message(
self.MESSAGES.obj_deleted( self.MESSAGES.obj_deleted(
obj=self.OBJ, obj=self.OBJ,
id=self.acc_id, id=self.acc_info.id,
permanently=False, permanently=False,
already=True, already=True,
) )
@@ -252,7 +453,7 @@ class DecortAccount(DecortController):
case {'state': 'disabled'}: case {'state': 'disabled'}:
self.restore() self.restore()
self.disable() self.disable()
case {'status': 'CONFIRMED'}: elif self._acc_info.status == sdk_types.AccountStatus.CONFIRMED:
match self.aparams: match self.aparams:
case {'state': 'absent'}: case {'state': 'absent'}:
self.delete() self.delete()
@@ -262,7 +463,7 @@ class DecortAccount(DecortController):
pass pass
case {'state': 'disabled'}: case {'state': 'disabled'}:
self.disable() self.disable()
case {'status': 'DISABLED'}: elif self._acc_info.status == sdk_types.AccountStatus.DISABLED:
match self.aparams: match self.aparams:
case {'state': 'absent'}: case {'state': 'absent'}:
self.delete() self.delete()
@@ -274,19 +475,26 @@ class DecortAccount(DecortController):
pass pass
def delete(self, permanently=False): 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() self.get_info()
def disable(self): 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() self.get_info()
def enable(self): 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() self.get_info()
def restore(self): def restore(self):
self.account_restore(account_id=self.acc_id) self.account_restore(account_id=self.acc_info.id)
self.get_info() self.get_info()
def change_acl(self): def change_acl(self):
@@ -294,7 +502,7 @@ class DecortAccount(DecortController):
return return
actual_users = { 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()) actual_users_ids = set(actual_users.keys())
@@ -323,8 +531,8 @@ class DecortAccount(DecortController):
aparams_users_ids.intersection(actual_users_ids) aparams_users_ids.intersection(actual_users_ids)
upd_users = dict() upd_users = dict()
for id in upd_users_ids: for id in upd_users_ids:
if actual_users[id] == 'CXDRAU': if actual_users[id] == sdk_types.AccessType.CXDRAU:
actual_user_rights = 'ARCXDU' actual_user_rights = sdk_types.AccessTypeForSet.ARCXDU
else: else:
actual_user_rights = actual_users[id] actual_user_rights = actual_users[id]
@@ -336,69 +544,12 @@ class DecortAccount(DecortController):
actual_users_ids.difference(aparams_users_ids) actual_users_ids.difference(aparams_users_ids)
if del_users_ids or new_users or upd_users: 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, del_users=del_users_ids,
add_users=new_users, add_users=new_users,
upd_users=upd_users) upd_users=upd_users)
self.get_info() 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: def check_aparam_default_zone_id(self) -> bool | None:
aparam_default_zone_id = self.aparams['default_zone_id'] aparam_default_zone_id = self.aparams['default_zone_id']
if aparam_default_zone_id is not None: 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.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(): 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__': 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) self.fail_json(**self.result)
# fail the module -> exit # fail the module -> exit
# now validate RG # 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_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'], arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name'] 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['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'], self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name']) 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_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name'] arg_amodule.params['rg_name'] = validated_rg_model.name
validated_acc_id = validated_rg_facts['accountId'] validated_acc_id = validated_rg_model.account_id
self.bservice_id, self.bservice_info = self.bservice_find( self.bservice_id, self.bservice_info = self.bservice_find(
validated_acc_id, validated_acc_id,
@@ -104,16 +104,18 @@ class decort_bservice(DecortController):
return return
def create(self): def create(self):
self.bservice_id = self.bservice_id = self.bservice_provision( self.bservice_id = self.sdk_checkmode(self.api.ca.bservice.create)(
self.amodule.params['name'], name=self.amodule.params['name'],
self.amodule.params['rg_id'], rg_id=self.amodule.params['rg_id'],
self.amodule.params['sshuser'], ssh_user_name=self.amodule.params['sshuser'],
self.amodule.params['sshkey'], ssh_public_key=self.amodule.params['sshkey'],
zone_id=self.aparams['zone_id'], zone_id=self.aparams['zone_id'],
) )
if self.bservice_id: if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(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_state(self.bservice_info, self.aparams['state'])
self.bservice_should_exist = True
return return
def action(self,d_state): def action(self,d_state):
@@ -134,7 +136,10 @@ class decort_bservice(DecortController):
pass pass
def destroy(self): 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_info['status'] = 'DELETED'
self.bservice_should_exist = False self.bservice_should_exist = False
return return
@@ -259,76 +264,80 @@ class decort_bservice(DecortController):
if check_errors: if check_errors:
self.exit(fail=True) self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
def main(): if self.amodule.check_mode:
subj = decort_bservice() self.result['changed'] = False
amodule = subj.amodule if self.bservice_id:
self.result['failed'] = False
if subj.amodule.check_mode: self.result['facts'] = self.package_facts(amodule.check_mode)
subj.result['changed'] = False amodule.exit_json(**self.result)
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 # we exit the module at this point
else: else:
subj.result['failed'] = True self.result['failed'] = True
subj.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, " self.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'], "RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'], amodule.params['id'],
amodule.params['rg_name'], amodule.params['rg_name'],
amodule.params['rg_id'], amodule.params['rg_id'],
amodule.params['account_name']) amodule.params['account_name'])
amodule.fail_json(**subj.result) amodule.fail_json(**self.result)
pass pass
#MAIN MANAGE PART #MAIN MANAGE PART
if subj.bservice_id: if self.bservice_id:
if subj.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING", if self.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"): "ENABLING","DISABLING","RESTORING","MODELED"):
subj.error() self.error()
elif subj.bservice_info['status'] == "DELETED": elif self.bservice_info['status'] == "DELETED":
if amodule.params['state'] in ( if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped' 'disabled', 'enabled', 'present', 'started', 'stopped'
): ):
subj.restore(subj.bservice_id) self.restore(self.bservice_id)
subj.action(amodule.params['state']) self.action(amodule.params['state'])
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.nop() self.nop()
elif subj.bservice_info['status'] in ( elif self.bservice_info['status'] in (
'ENABLED', 'DISABLED', 'CREATED', 'ENABLED', 'DISABLED', 'CREATED',
): ):
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.destroy() self.destroy()
else: else:
subj.action(amodule.params['state']) self.action(amodule.params['state'])
elif subj.bservice_info['status'] == "DESTROYED": elif self.bservice_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'): if amodule.params['state'] in ('present','enabled'):
subj.create() self.create()
subj.action(amodule.params['state']) self.action(amodule.params['state'])
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.nop() self.nop()
else: else:
state = amodule.params['state'] state = amodule.params['state']
if state is None: if state is None:
state = 'present' state = 'present'
if state == 'absent': if state == 'absent':
subj.nop() self.nop()
if state in ('present','started'): if state in ('present','started'):
subj.create() self.create()
elif state in ('stopped', 'disabled','enabled'): elif state in ('stopped', 'disabled','enabled'):
subj.error() self.error()
if subj.result['failed']: if self.result['failed']:
amodule.fail_json(**subj.result) amodule.fail_json(**self.result)
else: else:
if subj.bservice_should_exist: if self.bservice_should_exist:
_, subj.bservice_info = subj.bservice_get_by_id(subj.bservice_id) _, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
subj.result['facts'] = subj.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
else: else:
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_bservice().run()
if __name__ == '__main__':
main() main()

View File

@@ -58,6 +58,7 @@ class decort_disk(DecortController):
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "", name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id, account_id=self.acc_id,
check_state=False, check_state=False,
fail_if_not_found=False,
) )
if arg_amodule.params['place_with']: if arg_amodule.params['place_with']:
@@ -67,87 +68,149 @@ class decort_disk(DecortController):
self.disk_id = validated_disk_id self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts self.disk_info = validated_disk_facts
if self.disk_id: if self.disk_id and self.disk_info.status not in [
self.acc_id = validated_disk_facts['accountId'] sdk_types.DiskStatus.DESTROYED,
sdk_types.DiskStatus.PURGED,
]:
self.acc_id = validated_disk_facts.account_id
self.check_amodule_args_for_change() 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() 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): def create(self):
self.disk_id = self.disk_create( self.disk_id = self.sdk_checkmode(self.api.cloudapi.disks.create)(
accountId=self.acc_id, account_id=self.acc_id,
name=self.amodule.params['name'], name=self.amodule.params['name'],
description=self.amodule.params['description'], size_gb=self.amodule.params['size'],
size=self.amodule.params['size'],
sep_id=self.amodule.params['sep_id'],
pool=self.amodule.params['pool'],
storage_policy_id=self.aparams['storage_policy_id'], 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 #IO tune
if self.amodule.params['limitIO']: aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
self.disk_limitIO(disk_id=self.disk_id, if (
limits=self.amodule.params['limitIO']) 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 #set share status
if self.amodule.params['shareable']: 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 return
def action(self,restore=False): def action(self,restore=False):
#restore never be done #restore never be done
if restore: 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 #rename if id present
if ( if (
self.amodule.params['name'] is not None 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, self.rename()
name=self.amodule.params['name'])
#resize #resize
if ( if (
self.amodule.params['size'] is not None 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 #IO TUNE
if self.amodule.params['limitIO']: aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
clean_io = [param for param in self.amodule.params['limitIO'] \ if (
if self.amodule.params['limitIO'][param] == None] aparam_limit_io
for key in clean_io: del self.amodule.params['limitIO'][key] and any(value is not None for value in aparam_limit_io.values())
if self.amodule.params['limitIO'] != self.disk_info['iotune']: ):
self.disk_limitIO(self.disk_id,self.amodule.params['limitIO']) 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 #share check/update
#raise Exception(self.amodule.params['shareable']) #raise Exception(self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable']: if self.amodule.params['shareable'] != self.disk_info.shared:
self.disk_share(self.disk_id,self.amodule.params['shareable']) if self.amodule.params['shareable']:
self.sdk_checkmode(self.api.cloudapi.disks.share)(
disk_id=self.disk_id,
)
else:
self.sdk_checkmode(self.api.cloudapi.disks.unshare)(
disk_id=self.disk_id,
)
aparam_storage_policy_id = self.aparams['storage_policy_id'] aparam_storage_policy_id = self.aparams['storage_policy_id']
if ( if (
aparam_storage_policy_id is not None 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, disk_id=self.disk_id,
storage_policy_id=aparam_storage_policy_id, storage_policy_id=aparam_storage_policy_id,
) )
return return
def delete(self): def delete(self):
self.disk_delete(disk_id=self.disk_id, self.sdk_checkmode(self.api.cloudapi.disks.delete)(
disk_id=self.disk_id,
detach=self.amodule.params['force_detach'], detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'], 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.disk_info = self._disk_get_by_id(disk_id=self.disk_id)
return return
def rename(self): def rename(self):
self.sdk_checkmode(self.api.cloudapi.disks.rename)(
disk_id=self.disk_id,
self.disk_rename(diskId = self.disk_id, name=self.amodule.params['name'],
name = self.amodule.params['name']) )
self.disk_info['name'] = self.amodule.params['name']
return return
def nop(self): def nop(self):
@@ -156,7 +219,7 @@ class decort_disk(DecortController):
self.result['changed'] = False self.result['changed'] = False
if self.disk_id: if self.disk_id:
self.result['msg'] = ("No state change required for Disk ID {} because of its " 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: else:
self.result['msg'] = ("No state change to '{}' can be done for " self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Disk.").format(self.amodule.params['state']) "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: if check_mode or self.disk_info is None:
return ret_dict return ret_dict
# remove io param with zero value return self.disk_info.model_dump()
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
@property @property
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
@@ -240,6 +284,7 @@ class decort_disk(DecortController):
), ),
limitIO=dict( limitIO=dict(
type='dict', type='dict',
apply_defaults=True,
options=dict( options=dict(
total_bytes_sec=dict( total_bytes_sec=dict(
type='int', type='int',
@@ -290,10 +335,6 @@ class decort_disk(DecortController):
type='bool', type='bool',
default=False, default=False,
), ),
reason=dict(
type='str',
default='Managed by Ansible decort_disk',
),
state=dict( state=dict(
type='str', type='str',
default='present', default='present',
@@ -350,7 +391,7 @@ class decort_disk(DecortController):
if ( if (
aparam_storage_policy_id is not None aparam_storage_policy_id is not None
and aparam_storage_policy_id and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids'] not in self.acc_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -379,57 +420,76 @@ class decort_disk(DecortController):
return not check_errors return not check_errors
def main(): @DecortController.handle_sdk_exceptions
decon = decort_disk() def run(self):
amodule = decon.amodule
# #
#Full range of Disk status is as follows: #Full range of Disk status is as follows:
# #
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED", # "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
# #
if decon.disk_id: amodule = self.amodule
if self.disk_id:
#disk exist #disk exist
if decon.disk_info['status'] in ["MODELED", "CREATING"]: if self.disk_info.status in [sdk_types.DiskStatus.MODELED, sdk_types.DiskStatus.CREATING]:
decon.result['failed'] = True self.result['failed'] = True
decon.result['changed'] = False self.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current " self.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(decon.disk_id, decon.disk_info['status']) "status '{}'").format(self.disk_id, self.disk_info.status)
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED" # "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]: elif self.disk_info.status in [sdk_types.DiskStatus.CREATED, sdk_types.DiskStatus.ASSIGNED]:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
decon.delete() self.delete()
elif amodule.params['state'] == 'present': elif amodule.params['state'] == 'present':
decon.action() self.action()
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]: elif self.disk_info.status in [sdk_types.DiskStatus.PURGED, sdk_types.DiskStatus.DESTROYED]:
#re-provision disk #re-provision disk
if amodule.params['state'] in ('present'): if amodule.params['state'] in ('present'):
decon.create() self.create()
else: else:
decon.nop() self.nop()
elif decon.disk_info['status'] == "DELETED": elif self.disk_info.status == sdk_types.DiskStatus.DELETED:
if amodule.params['state'] in ('present'): if amodule.params['state'] in ('present'):
decon.action(restore=True) self.action(restore=True)
elif (amodule.params['state'] == 'absent' and elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']): amodule.params['permanently']):
decon.delete() self.delete()
else: else:
decon.nop() self.nop()
else: else:
# preexisting Disk was not found # preexisting Disk was not found
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
decon.nop() self.exit()
else:
decon.create()
if decon.result['failed']: if (
amodule.fail_json(**decon.result) (
else: amodule.params['state'] == 'present'
if decon.result['changed']: or amodule.params['state'] is None
_, decon.disk_info = decon.disk_find(decon.disk_id) )
decon.result['facts'] = decon.package_facts(amodule.check_mode) and amodule.params['id']
amodule.exit_json(**decon.result) ):
self.message(
f'Disk with ID {amodule.params['id']} not found.'
)
self.exit(fail=True)
if __name__ == "__main__": if (
amodule.params['state'] == 'present'
and not amodule.params['id']
):
self.create()
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() 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): def create(self):
chipset = self.aparams['chipset'] chipset = self.aparams['chipset']
if chipset is None: if chipset is None:
chipset = 'i440fx' chipset = 'Q35'
self.message( self.message(
msg=f'Chipset not specified, ' msg=f'Chipset not specified, '
f'default value "{chipset}" will be used.', f'default value "{chipset}" will be used.',
warning=True, 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( self.group_id=self.group_provision(
bs_id=self.bservice_id, bs_id=self.bservice_id,
arg_name=self.amodule.params['name'], arg_name=self.amodule.params['name'],
@@ -112,7 +101,6 @@ class decort_group(DecortController):
arg_timeout=self.amodule.params['timeoutStart'], arg_timeout=self.amodule.params['timeoutStart'],
chipset=chipset, chipset=chipset,
storage_policy_id=self.aparams['storage_policy_id'], storage_policy_id=self.aparams['storage_policy_id'],
driver=driver,
) )
if self.amodule.params['state'] in ('started','present'): if self.amodule.params['state'] in ('started','present'):
@@ -296,9 +284,6 @@ class decort_group(DecortController):
storage_policy_id=dict( storage_policy_id=dict(
type='int', type='int',
), ),
driver=dict(
type='str',
),
), ),
supports_check_mode=True, supports_check_mode=True,
required_one_of=[ required_one_of=[
@@ -354,17 +339,6 @@ class decort_group(DecortController):
f'disk ID {disk['id']}' 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: if check_errors:
self.exit(fail=True) self.exit(fail=True)
@@ -382,7 +356,7 @@ class decort_group(DecortController):
else: else:
if ( if (
aparam_storage_policy_id aparam_storage_policy_id
not in self.rg_info['storage_policy_ids'] not in self.rg_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -392,7 +366,7 @@ class decort_group(DecortController):
) )
if ( if (
aparam_storage_policy_id aparam_storage_policy_id
not in self.acc_info['storage_policy_ids'] not in self.acc_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -405,54 +379,59 @@ class decort_group(DecortController):
self.exit(fail=True) self.exit(fail=True)
def main(): @DecortController.handle_sdk_exceptions
subj = decort_group() def run(self):
amodule = subj.amodule amodule = self.amodule
if amodule.params['state'] == 'check': if amodule.params['state'] == 'check':
subj.result['changed'] = False self.result['changed'] = False
if subj.group_id: if self.group_id:
# cluster is found - package facts and report success to Ansible # cluster is found - package facts and report success to Ansible
subj.result['failed'] = False self.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
# we exit the module at this point # we exit the module at this point
else: else:
subj.result['failed'] = True self.result['failed'] = True
subj.result['msg'] = ("Cannot locate Group name '{}'. " self.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'], "B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],) amodule.params['bservice_id'],)
amodule.fail_json(**subj.result) amodule.fail_json(**self.result)
if subj.group_id: if self.group_id:
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING", if self.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED","DESTROYED"): "ENABLING","DISABLING","RESTORING","MODELED","DESTROYED"):
subj.error() self.error()
elif subj.group_info['status'] in ("DELETED","DESTROYED"): elif self.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.nop() self.nop()
if amodule.params['state'] in ('present','started','stopped'): if amodule.params['state'] in ('present','started','stopped'):
subj.create() self.create()
elif subj.group_info['techStatus'] in ("STARTED","STOPPED"): elif self.group_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.destroy() self.destroy()
else: else:
subj.action() self.action()
else: else:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.nop() self.nop()
if amodule.params['state'] in ('present','started','stopped'): if amodule.params['state'] in ('present','started','stopped'):
subj.create() self.create()
if subj.result['failed']: if self.result['failed']:
amodule.fail_json(**subj.result) amodule.fail_json(**self.result)
else: else:
if subj.group_should_exist: if self.group_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
else: else:
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_group().run()
if __name__ == '__main__':
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 return amodule_init_args
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
self.result['jwt'] = self.jwt self.result['jwt'] = self.jwt
self.amodule.exit_json(**self.result) self.amodule.exit_json(**self.result)

View File

@@ -20,7 +20,7 @@ class decort_k8s(DecortController):
validated_acc_id = 0 validated_acc_id = 0
validated_rg_id = 0 validated_rg_id = 0
validated_rg_facts = None validated_rg_model = None
validated_k8ci_id = 0 validated_k8ci_id = 0
self.k8s_should_exist = False self.k8s_should_exist = False
self.is_k8s_stopped_or_will_be_stopped: None | bool = None self.is_k8s_stopped_or_will_be_stopped: None | bool = None
@@ -33,10 +33,10 @@ class decort_k8s(DecortController):
'taints': [], 'taints': [],
'annotations': [], 'annotations': [],
'ci_user_data': {}, '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['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = "Cannot manage k8s cluster when its ID is 0 and name is empty." 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) self.amodule.fail_json(**self.result)
# fail the module -> exit # fail the module -> exit
# now validate RG # 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_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'], arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name'] 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['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'], 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 self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name'] arg_amodule.params['rg_name'] = validated_rg_model.name
self.acc_id = validated_rg_facts['accountId'] self.acc_id = validated_rg_model.account_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'], self.k8s_id, self._k8s_info = self.k8s_find(
k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'], k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id, rg_id=validated_rg_id,
check_state=False) check_state=False,
)
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED': if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
self.k8s_should_exist = True 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() self.check_amodule_args_for_change()
else: elif arg_amodule.params['state'] != 'absent':
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
return return
@@ -96,32 +98,14 @@ class decort_k8s(DecortController):
config=None, config=None,
) )
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
if check_mode: if check_mode:
# in check mode return immediately with the default values
return ret_dict return ret_dict
#if self.k8s_facts is None: self.k8s_info['vins_id'] = self.k8s_vins_id
# #if void facts provided - change state value to ABSENT and return self.k8s_info['config'] = None
# ret_dict['state'] = "ABSENT" if self.amodule.params['getConfig'] and self.k8s_info['tech_status'] == "STARTED":
# return ret_dict self.k8s_info['config'] = self.k8s_getConfig()
return self.k8s_info
ret_dict['id'] = self.k8s_info['id']
ret_dict['name'] = self.k8s_info['name']
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.k8s_info['rgId']
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id
ret_dict['k8s_Masters'] = self.k8s_info['k8sGroups']['masters']
ret_dict['k8s_Workers'] = self.k8s_info['k8sGroups']['workers']
ret_dict['lb_id'] = self.k8s_info['lbId']
ret_dict['description'] = self.k8s_info['desc']
ret_dict['zone_id'] = self.k8s_info['zoneId']
return ret_dict
def nop(self): def nop(self):
"""No operation (NOP) handler for k8s cluster management by decort_k8s module. """No operation (NOP) handler for k8s cluster management by decort_k8s module.
@@ -158,7 +142,7 @@ class decort_k8s(DecortController):
def create(self): def create(self):
master_chipset = self.amodule.params['master_chipset'] master_chipset = self.amodule.params['master_chipset']
if master_chipset is None: if master_chipset is None:
master_chipset = 'i440fx' master_chipset = 'Q35'
target_wgs = deepcopy(self.amodule.params['workers']) target_wgs = deepcopy(self.amodule.params['workers'])
for wg in target_wgs: for wg in target_wgs:
@@ -204,10 +188,12 @@ class decort_k8s(DecortController):
self.result['failed'] = True self.result['failed'] = True
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id, self.k8s_id, self._k8s_info = self.k8s_find(
k8s_id=k8s_id,
k8s_name=self.amodule.params['name'], k8s_name=self.amodule.params['name'],
rg_id=self.rg_id, rg_id=self.rg_id,
check_state=False) check_state=False,
)
if self.k8s_id: if self.k8s_id:
self.k8s_should_exist = True self.k8s_should_exist = True
@@ -219,19 +205,22 @@ class decort_k8s(DecortController):
self.aparams['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 return
def destroy(self): 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_info['status'] = 'DELETED'
self.k8s_should_exist = False self.k8s_should_exist = False
return return
def action(self, disared_state, preupdate: bool = False): def action(self, disared_state, preupdate: bool = False):
if self.amodule.params['master_chipset'] is not None: if self.amodule.params['master_chipset'] is not None:
for master_node in self.k8s_info['k8sGroups']['masters'][ for master_node in self.k8s_info['node_groups']['master'][
'detailedInfo' 'vms'
]: ]:
_, master_node_info, _ = self._compute_get_by_id( _, master_node_info, _ = self._compute_get_by_id(
comp_id=master_node['id'] comp_id=master_node['id']
@@ -247,17 +236,27 @@ class decort_k8s(DecortController):
self.exit(fail=True) self.exit(fail=True)
if ( if (
(
self.aparams['name'] is not None self.aparams['name'] is not None
and self.aparams['name'] != self.k8s_info['name'] 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: if preupdate:
# K8s info updating # 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 #k8s state
self.k8s_state(self.k8s_info, disared_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 #check groups and modify if needed
if self.aparams['workers'] is not None: if self.aparams['workers'] is not None:
self.k8s_workers_modify( self.k8s_workers_modify(
@@ -266,14 +265,17 @@ class decort_k8s(DecortController):
) )
aparam_zone_id = self.aparams['zone_id'] 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( self.k8s_migrate_to_zone(
k8s_id=self.k8s_id, k8s_id=self.k8s_id,
zone_id=aparam_zone_id, zone_id=aparam_zone_id,
) )
if self.result['changed'] == True: 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 #TODO check workers metadata and modify if needed
return return
@@ -307,7 +309,6 @@ class decort_k8s(DecortController):
), ),
name=dict( name=dict(
type='str', type='str',
default='',
), ),
id=dict( id=dict(
type='int', type='int',
@@ -414,7 +415,6 @@ class decort_k8s(DecortController):
), ),
description=dict( description=dict(
type='str', type='str',
default='Created by decort ansible module',
), ),
with_lb=dict( with_lb=dict(
type='bool', type='bool',
@@ -474,25 +474,25 @@ class decort_k8s(DecortController):
self.is_k8s_stopped_or_will_be_stopped = ( self.is_k8s_stopped_or_will_be_stopped = (
( (
self.k8s_info['techStatus'] == 'STOPPED' self.k8s_info['tech_status'] == 'STOPPED'
and ( and (
self.aparams['state'] is None self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped') or self.aparams['state'] in ('present', 'stopped')
) )
) )
or ( or (
self.k8s_info['techStatus'] != 'STOPPED' self.k8s_info['tech_status'] != 'STOPPED'
and self.aparams['state'] == 'stopped' and self.aparams['state'] == 'stopped'
) )
) )
aparam_sysctl = self.aparams['lb_sysctl'] aparam_sysctl = self.aparams['lb_sysctl']
if aparam_sysctl is not None: 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 = { sysctl_with_str_values = {
k: str(v) for k, v in aparam_sysctl.items() 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( self.message(
'Check for parameter "lb_sysctl" failed: ' 'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster ' 'cannot change lb_sysctl for an existing cluster '
@@ -504,7 +504,7 @@ class decort_k8s(DecortController):
check_errors = True check_errors = True
if ( if (
self.aparams['zone_id'] is not None 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 and not self.is_k8s_stopped_or_will_be_stopped
): ):
check_errors = True check_errors = True
@@ -516,13 +516,11 @@ class decort_k8s(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id'] aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None: if aparam_storage_policy_id is not None:
computes_ids = [] computes_ids = []
for master_node in self.k8s_info['k8sGroups']['masters'][ for master_node in self.k8s_info['node_groups']['master']['vms']:
'detailedInfo'
]:
computes_ids.append(master_node['id']) 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 = [ workers_ids = [
worker['id'] for worker in wg['detailedInfo'] worker['id'] for worker in wg['vms']
] ]
computes_ids.extend(workers_ids) computes_ids.extend(workers_ids)
for compute_id in computes_ids: for compute_id in computes_ids:
@@ -590,7 +588,7 @@ class decort_k8s(DecortController):
) )
elif ( elif (
aparam_storage_policy_id aparam_storage_policy_id
not in self.rg_info['storage_policy_ids'] not in self.rg_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -604,67 +602,74 @@ class decort_k8s(DecortController):
self.exit(fail=True) self.exit(fail=True)
def main(): @DecortController.handle_sdk_exceptions
subj = decort_k8s() def run(self):
amodule = subj.amodule amodule = self.amodule
if subj.amodule.check_mode: if self.amodule.check_mode:
subj.result['changed'] = False self.result['changed'] = False
if subj.k8s_id: if self.k8s_id:
# cluster is found - package facts and report success to Ansible # cluster is found - package facts and report success to Ansible
subj.result['failed'] = False self.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
# we exit the module at this point # we exit the module at this point
else: else:
subj.result['failed'] = True self.result['failed'] = True
subj.result['msg'] = ("Cannot locate K8s cluster name '{}'. " self.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
"RG ID {}").format(amodule.params['name'], "RG ID {}").format(amodule.params['name'],
amodule.params['rg_id'],) amodule.params['rg_id'],)
amodule.fail_json(**subj.result) amodule.fail_json(**self.result)
if subj.k8s_id: if self.k8s_id:
if subj.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING", if self.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"): "ENABLING","DISABLING","RESTORING","MODELED"):
subj.error() self.error()
elif subj.k8s_info['status'] == "DELETED": elif self.k8s_info['status'] == "DELETED":
if amodule.params['state'] in ( if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped' 'disabled', 'enabled', 'present', 'started', 'stopped'
): ):
subj.k8s_restore(subj.k8s_id) self.sdk_checkmode(self.api.ca.k8s.restore)(
subj.action(disared_state=amodule.params['state'], k8s_id=self.k8s_id,
)
self.action(disared_state=amodule.params['state'],
preupdate=True) preupdate=True)
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
if amodule.params['permanent']: if amodule.params['permanent']:
subj.destroy() self.destroy()
else: else:
subj.nop() self.nop()
elif subj.k8s_info['status'] in ('ENABLED', 'DISABLED'): elif self.k8s_info['status'] in ('ENABLED', 'DISABLED'):
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.destroy() self.destroy()
else: else:
subj.action(disared_state=amodule.params['state']) self.action(disared_state=amodule.params['state'])
elif subj.k8s_info['status'] == "DESTROYED": elif self.k8s_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'): if amodule.params['state'] in ('present','enabled'):
subj.create() self.create()
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.nop() self.nop()
else: else:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
subj.nop() self.nop()
if amodule.params['state'] in ('present','started'): if amodule.params['state'] in ('present','started'):
subj.create() self.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'): elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error() self.error()
if subj.result['failed']: if self.result['failed']:
amodule.fail_json(**subj.result) amodule.fail_json(**self.result)
else: else:
if subj.k8s_should_exist: if self.k8s_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
else: else:
amodule.exit_json(**subj.result) amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_k8s().run()
if __name__ == '__main__':
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 arg_amodule = self.amodule
self.lb_id = 0 self.lb_id = 0
self.lb_facts = None
self.vins_id = 0 self.vins_id = 0
self.vins_facts = None
self.rg_id = 0 self.rg_id = 0
self.rg_facts = None self.rg_model = None
self.default_server_check = "enabled" self.default_server_check = "enabled"
self.default_alg = "roundrobin" self.default_alg = "roundrobin"
self.default_settings = { self.default_settings = {
@@ -38,22 +36,34 @@ class decort_lb(DecortController):
self.is_lb_stopped_or_will_be_stopped: None | bool = None self.is_lb_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['lb_id']: if arg_amodule.params['lb_id']:
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id']) _, self._lb_info = self.lb_find(lb_id=arg_amodule.params['lb_id'])
if not self.lb_id: if self._lb_info is None:
self.result['failed'] = True if arg_amodule.params['state'] == 'absent':
self.result['msg'] = "Specified LB ID {} not found."\ self.nop()
.format(arg_amodule.params['lb_id']) self.exit()
self.amodule.fail_json(**self.result) else:
self.rg_id = self.lb_facts['rgId'] self.message(
self.vins_id = self.lb_facts['vinsId'] 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']: elif arg_amodule.params['rg_id']:
self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="") self.rg_id, self.rg_model = self.rg_find(
if not self.rg_id: 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['failed'] = True
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_id']) self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_id'])
self.amodule.fail_json(**self.result) 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'] != "": elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
if not arg_amodule.params['rg_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 " self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.") "or non-existent account specified.")
self.amodule.fail_json(**self.result) 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']: 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'] vins_id=arg_amodule.params['vins_id']
) ).id
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = (
f'Specified ViNS ID {arg_amodule.params["vins_id"]}'
f' not found'
)
self.amodule.fail_json(**self.result)
elif arg_amodule.params['vins_name']: 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_id=arg_amodule.params['vins_id'],
vins_name=arg_amodule.params['vins_name'], vins_name=arg_amodule.params['vins_name'],
rg_id=self.rg_id) rg_id=self.rg_id,
)
if not self.vins_id: if not self.vins_id:
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = ( self.result['msg'] = (
@@ -94,10 +102,17 @@ class decort_lb(DecortController):
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
if self.rg_id and arg_amodule.params['lb_name']: 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': if (
self.acc_id = self.lb_facts['accountId'] 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() self.check_amodule_args_for_change()
else: else:
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
@@ -115,54 +130,73 @@ class decort_lb(DecortController):
zone_id=self.aparams['zone_id'], zone_id=self.aparams['zone_id'],
start=start_after_create, start=start_after_create,
) )
if self.lb_id and (self.amodule.params['backends'] or if (
self.amodule.params['frontends']): self.lb_id and (
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id) 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( self.lb_update(
lb_facts=self.lb_facts, lb_model=self.lb_info,
aparam_backends=self.amodule.params['backends'], aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'], aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'], aparam_servers=self.amodule.params['servers'],
) )
return return
def action(self,d_state='',restore=False): def action(
if restore == True: self,
self.lb_restore(lb_id=self.lb_id) d_state='',
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) restore=False,
self.lb_state(self.lb_facts, 'enabled') ):
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) 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( self.lb_update(
lb_facts=self.lb_facts, lb_model=self.lb_info,
aparam_backends=self.amodule.params['backends'], aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'], aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'], aparam_servers=self.amodule.params['servers'],
aparam_sysctl=self.aparams['sysctl'], aparam_sysctl=self.aparams['sysctl'],
) )
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
if d_state != '': if d_state != '':
self.lb_state(self.lb_facts, d_state) self.lb_state(self.lb_info, d_state)
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
if (d_state == 'enabled' and if (
self.lb_facts.get('status') == 'ENABLED' and d_state == 'enabled'
self.lb_facts.get('techStatus') == 'STOPPED'): and self.lb_info.status == sdk_types.LBStatus.ENABLED
self.lb_state(self.lb_facts, 'started') and self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) ):
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'] 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( self.lb_migrate_to_zone(
lb_id=self.lb_id, lb_id=self.lb_info.id,
zone_id=aparam_zone_id, zone_id=aparam_zone_id,
) )
return return
def delete(self): def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently']) self.sdk_checkmode(self.api.ca.lb.delete)(
self.lb_id, self.lb_facts = self._lb_get_by_id(self.lb_id) lb_id=self.lb_id,
permanently=self.amodule.params['permanently'],
)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
return return
def nop(self): def nop(self):
@@ -173,30 +207,39 @@ class decort_lb(DecortController):
""" """
self.result['failed'] = False self.result['failed'] = False
self.result['changed'] = False self.result['changed'] = False
if self.lb_id: if self._lb_info:
self.result['msg'] = ("No state change required for LB ID {} because of its " self.result['msg'] = (
"current status '{}'.").format(self.lb_id, self.lb_facts['status']) f'No state change required for LB ID {self._lb_info.id} '
f'because of its current status "{self._lb_info.status}".'
)
else: else:
self.result['msg'] = ("No state change to '{}' can be done for " self.result['msg'] = (
"non-existent LB instance.").format(self.amodule.params['state']) f'No state change to "{self.amodule.params['state']}" '
f'can be done for non-existent LB instance.'
)
return return
def error(self): def error(self):
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
if self.vins_id: if self._lb_info:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the " self.result['msg'] = (
"current status '{}'").format(self.lb_id, f'Invalid target state "{self.amodule.params['state']}" '
self.amodule.params['state'], f'requested for LB ID {self._lb_info.id} in the current status '
self.lb_facts['status']) f'"{self._lb_info.status}".'
)
else: else:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for non-existent " self.result['msg'] = (
"LB name '{}'").format(self.amodule.params['state'], f'Invalid target state "{self.amodule.params['state']}" '
self.amodule.params['lb_name']) f'requested for non-existent LB name '
f'"{self.amodule.params['lb_name']}".'
)
return return
def package_facts(self, arg_check_mode=False): def package_facts(self, arg_check_mode=False):
"""Package a dictionary of LB facts according to the decort_lb module specification. """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 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 @param arg_check_mode: boolean that tells if this Ansible module is run in check mode
""" """
ret_dict = dict(id=0, ret_dict = {}
name="none",
state="CHECK_MODE",
sysctl={},
)
if arg_check_mode: if arg_check_mode:
# in check mode return immediately with the default values # in check mode return immediately with the default values
return ret_dict return ret_dict
if self.lb_facts is None: if self._lb_info is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict return ret_dict
ret_dict['id'] = self.lb_facts['id'] return self._lb_info.model_dump()
ret_dict['name'] = self.lb_facts['name']
ret_dict['state'] = self.lb_facts['status']
ret_dict['account_id'] = self.lb_facts['accountId']
ret_dict['rg_id'] = self.lb_facts['rgId']
ret_dict['gid'] = self.lb_facts['gid']
if self.amodule.params['state']!="absent":
ret_dict['backends'] = self.lb_facts['backends']
ret_dict['frontends'] = self.lb_facts['frontends']
ret_dict['sysctl'] = self.lb_facts['sysctlParams']
ret_dict['zone_id'] = self.lb_facts['zoneId']
ret_dict['tech_status'] = self.lb_facts['techStatus']
return ret_dict
@property @property
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
@@ -321,18 +346,16 @@ class decort_lb(DecortController):
def check_amodule_args_for_change(self): def check_amodule_args_for_change(self):
check_errors = False 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 ( and (
self.aparams['state'] is None self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped') or self.aparams['state'] in ('present', 'stopped')
) )
) )
or ( or (
lb_info['techStatus'] != 'STOPPED' self.lb_info.tech_status != sdk_types.LBTechStatus.STOPPED
and self.aparams['state'] == 'stopped' and self.aparams['state'] == 'stopped'
) )
) )
@@ -341,7 +364,7 @@ class decort_lb(DecortController):
check_errors = True check_errors = True
if ( if (
self.aparams['zone_id'] is not None 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 and not self.is_lb_stopped_or_will_be_stopped
): ):
check_errors = True check_errors = True
@@ -361,56 +384,74 @@ class decort_lb(DecortController):
if check_errors: if check_errors:
self.exit(fail=True) self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def main(): def run(self):
decon = decort_lb() amodule = self.amodule
amodule = decon.amodule if self._lb_info:
if decon.lb_id: if self._lb_info.status in [
if decon.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]: sdk_types.LBStatus.MODELED,
decon.result['failed'] = True sdk_types.LBStatus.DISABLING,
decon.result['changed'] = False sdk_types.LBStatus.ENABLING,
decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current " sdk_types.LBStatus.DELETING,
"status '{}'").format(decon.lb_id, decon.lb_facts['status']) sdk_types.LBStatus.DESTROYING,
elif decon.lb_facts['status'] in ('DISABLED', 'ENABLED', 'CREATED'): 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': if amodule.params['state'] == 'absent':
decon.delete() self.delete()
else: else:
decon.action(d_state=amodule.params['state']) self.action(d_state=amodule.params['state'])
elif decon.lb_facts['status'] == "DELETED": elif self._lb_info.status == sdk_types.LBStatus.DELETED:
if amodule.params['state'] == 'present': if amodule.params['state'] == 'present':
decon.action(restore=True) self.action(restore=True)
elif amodule.params['state'] == 'enabled': elif amodule.params['state'] == 'enabled':
decon.action(d_state='enabled', restore=True) self.action(d_state='enabled', restore=True)
elif (amodule.params['state'] == 'absent' and elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']): amodule.params['permanently']):
decon.delete() self.delete()
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
decon.error() self.error()
elif decon.lb_facts['status'] == "DESTROYED": elif self._lb_info.status == sdk_types.LBStatus.DESTROYED:
if amodule.params['state'] in ('present', 'enabled'): if amodule.params['state'] in ('present', 'enabled'):
decon.create() self.create()
elif amodule.params['state'] == 'absent': elif amodule.params['state'] == 'absent':
decon.nop() self.nop()
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
decon.error() self.error()
else: else:
state = amodule.params['state'] state = amodule.params['state']
if state is None: if state is None:
state = 'present' state = 'present'
if state == 'absent': if state == 'absent':
decon.nop() self.nop()
elif state in ('present', 'enabled', 'stopped', 'started'): elif state in ('present', 'enabled', 'stopped', 'started'):
decon.create() self.create()
elif state == 'disabled': elif state == 'disabled':
decon.error() self.error()
if decon.result['failed']: if self.result['failed']:
amodule.fail_json(**decon.result) amodule.fail_json(**self.result)
else: else:
if decon.result['changed']: if self.result['changed']:
_, decon.lb_facts = decon.lb_find(lb_id=decon.lb_id) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result) amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_lb().run()
if __name__ == '__main__':
main() main()

View File

@@ -4,552 +4,59 @@ DOCUMENTATION = r'''
--- ---
module: decort_osimage 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 AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_osimage(DecortController): def main():
def __init__(self): module = AnsibleModule(
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( argument_spec=dict(
pool=dict( app_id=dict(type='raw'),
type='str', app_secret=dict(type='raw'),
default='', authenticator=dict(type='raw'),
), controller_url=dict(type='raw'),
sep_id=dict( domain=dict(type='raw'),
type='int', jwt=dict(type='raw'),
default=0, oauth2_url=dict(type='raw'),
), password=dict(type='raw'),
account_name=dict( username=dict(type='raw'),
type='str', verify_ssl=dict(type='raw'),
), ignore_api_compatibility=dict(type='raw'),
account_id=dict( ignore_sdk_version_check=dict(type='raw'),
type='int', pool=dict(type='raw'),
), sep_id=dict(type='raw'),
image_name=dict( account_name=dict(type='raw'),
type='str', account_id=dict(type='raw'),
), image_name=dict(type='raw'),
image_id=dict( image_id=dict(type='raw'),
type='int', virt_id=dict(type='raw'),
default=0, virt_name=dict(type='raw'),
), state=dict(type='raw'),
virt_id=dict( url=dict(type='raw'),
type='int', sepId=dict(type='raw'),
default=0, poolName=dict(type='raw'),
), hot_resize=dict(type='raw'),
virt_name=dict( image_username=dict(type='raw'),
type='str', image_password=dict(type='raw'),
), usernameDL=dict(type='raw'),
state=dict( passwordDL=dict(type='raw'),
type='str', boot=dict(type='raw'),
default='present', network_interface_naming=dict(type='raw'),
choices=[ storage_policy_id=dict(type='raw'),
'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, supports_check_mode=True,
) )
def check_amodule_args_for_change(self): module.fail_json(
check_errors = False msg=(
'The module "decort_osimage" has been renamed to "decort_image". '
aparam_storage_policy_id = self.aparams['storage_policy_id'] 'Please update your playbook to use "decort_image" '
if ( 'instead of "decort_osimage".'
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): if __name__ == '__main__':
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']:
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)
if __name__ == "__main__":
main() main()

View File

@@ -43,7 +43,13 @@ class decort_pfw(DecortController):
supports_check_mode=True, 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. """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 This dictionary will be returned to the upstream Ansible engine at the completion of
the module run. the module run.
@@ -68,9 +74,13 @@ class decort_pfw(DecortController):
ret_dict['state'] = "ABSENT" ret_dict['state'] = "ABSENT"
return ret_dict return ret_dict
gw_vnf = vins_model.vnfs.gw
ret_dict['compute_id'] = comp_facts['id'] ret_dict['compute_id'] = comp_facts['id']
ret_dict['vins_id'] = vins_facts['id'] ret_dict['vins_id'] = vins_model.id
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip'] 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: if len(pfw_facts) != 0:
ret_dict['state'] = 'PRESENT' ret_dict['state'] = 'PRESENT'
@@ -86,9 +96,9 @@ class decort_pfw(DecortController):
return return
def main(): @DecortController.handle_sdk_exceptions
decon = decort_pfw() def run(self):
amodule = decon.amodule amodule = self.amodule
pfw_facts = None # will hold PFW facts as returned by pfw_configure pfw_facts = None # will hold PFW facts as returned by pfw_configure
@@ -100,23 +110,22 @@ def main():
# 4) Compute is connected to this ViNS # 4) Compute is connected to this ViNS
# #
validated_comp_id, comp_facts, rg_id = decon.compute_find(amodule.params['compute_id']) validated_comp_id, comp_facts, rg_id = self.compute_find(amodule.params['compute_id'])
if not validated_comp_id: if not validated_comp_id:
decon.result['failed'] = True self.result['failed'] = True
decon.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id']) self.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id'])
amodule.fail_json(**decon.result) amodule.fail_json(**self.result)
validated_vins_id, vins_facts = decon.vins_find(amodule.params['vins_id']) vins_model = self._vins_get_by_id(vins_id=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') gw_vnf = vins_model.vnfs.gw
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED": if not gw_vnf or gw_vnf.status == sdk_types.VNFStatus.DESTROYED:
decon.result['failed'] = True self.result['failed'] = True
decon.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id) self.result['msg'] = (
amodule.fail_json(**decon.result) 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 # Initial validation of module arguments is complete
@@ -124,22 +133,40 @@ def main():
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute # ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, None) pfw_facts = self.pfw_configure(
comp_facts,
vins_model,
None,
)
elif amodule.params['rules'] is not None: elif amodule.params['rules'] is not None:
# manage PFW rules accodring to the module arguments # manage PFW rules accodring to the module arguments
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, amodule.params['rules']) pfw_facts = self.pfw_configure(
comp_facts,
vins_model,
amodule.params['rules'],
)
else: else:
pfw_facts = decon._pfw_get(comp_facts['id'], vins_facts['id']) pfw_facts = self._pfw_get(comp_facts['id'], vins_model.id)
# #
# complete module run # complete module run
# #
if decon.result['failed']: if self.result['failed']:
amodule.fail_json(**decon.result) amodule.fail_json(**self.result)
else: else:
# prepare PFW facts to be returned as part of decon.result and then call exit_json(...) # prepare PFW facts to be returned as part of self.result and then call exit_json(...)
decon.result['facts'] = decon.decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode) self.result['facts'] = self.decort_pfw_package_facts(
amodule.exit_json(**decon.result) comp_facts,
vins_model,
pfw_facts,
amodule.check_mode,
)
amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_pfw().run()
if __name__ == '__main__':
main() main()

View File

@@ -19,8 +19,7 @@ class decort_rg(DecortController):
amodule = self.amodule amodule = self.amodule
self.validated_acc_id = 0 self.validated_acc_id = 0
self.validated_rg_id = 0 self.rg_id: int = 0
self.validated_rg_facts = None
if amodule.params['rg_id'] is None: if amodule.params['rg_id'] is None:
if self.amodule.params['account_id']: if self.amodule.params['account_id']:
@@ -42,13 +41,15 @@ class decort_rg(DecortController):
else: else:
self.rg_should_exist = False 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() self.check_amodule_args_for_change()
def get_info(self): def get_info(self):
# If this is the first getting info # If this is the first getting info
if not self.validated_rg_id: if self._rg_info is None:
self.validated_rg_id, self.rg_facts = self.rg_find( self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id, arg_account_id=self.validated_acc_id,
arg_rg_id=self.aparams['rg_id'], arg_rg_id=self.aparams['rg_id'],
arg_rg_name=self.aparams['rg_name'], arg_rg_name=self.aparams['rg_name'],
@@ -61,16 +62,16 @@ class decort_rg(DecortController):
if self.amodule.check_mode: if self.amodule.check_mode:
return 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): def access(self):
should_change_access = False should_change_access = False
acc_granted = False acc_granted = False
for rg_item in self.rg_facts['acl']: for rg_item in self.rg_info.acl:
if rg_item['userGroupId'] == self.amodule.params['access']['user']: if rg_item.user_name == self.amodule.params['access']['user']:
acc_granted = True acc_granted = True
if self.amodule.params['access']['action'] == 'grant': 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 should_change_access = True
if self.amodule.params['access']['action'] == 'revoke': if self.amodule.params['access']['action'] == 'revoke':
should_change_access = True should_change_access = True
@@ -78,19 +79,30 @@ class decort_rg(DecortController):
should_change_access = True should_change_access = True
if should_change_access == True: if should_change_access == True:
self.rg_access(self.validated_rg_id, self.amodule.params['access']) if self.amodule.params['access']['action'] == "grant":
self.rg_facts['access'] = self.amodule.params['access'] 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 self.rg_should_exist = True
return return
def error(self): def error(self):
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False 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 " 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.amodule.params['state'],
self.rg_facts['status']) self.rg_info.status.value)
else: else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' " self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
"in account ID {} ").format(self.amodule.params['state'], "in account ID {} ").format(self.amodule.params['state'],
@@ -99,21 +111,31 @@ class decort_rg(DecortController):
return return
def update(self): 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(), incorrect_quota = dict(Requested=dict(),
Reserved=dict(),) Reserved=dict(),)
query_key_map = dict( query_key_map = dict(
cpu='cpu', cpu='cpu_count',
ram='ram', ram='ram_size_mb',
disk='disksize', disk='storage_size_gb_by_real_usage',
ext_ips='extips', ext_ips='ext_ip_count',
net_transfer='exttraffic', storage_policies='storage_policies',
storage_policies='policies',
) )
if self.amodule.params['quotas']: if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']: for quota_item in self.amodule.params['quotas']:
if quota_item == 'storage_policies': 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'][ aparam_storage_policies = self.amodule.params['quotas'][
quota_item quota_item
] ]
@@ -125,32 +147,35 @@ class decort_rg(DecortController):
if ( if (
rg_storage_policy rg_storage_policy
and aparam_storage_policy['storage_size_gb'] 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] = ( incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item] self.amodule.params['quotas'][quota_item]
) )
incorrect_quota['Reserved'][quota_item] = ( incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]] getattr(
reserved_resources,
query_key_map[quota_item]
)
) )
elif ( elif (
self.amodule.params['quotas'][quota_item] 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] = ( incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item] self.amodule.params['quotas'][quota_item]
) )
incorrect_quota['Reserved'][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['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
self.result['failed'] = True self.result['failed'] = True
if not self.result['failed']: if not self.result['failed']:
self.rg_update( self.rg_update(
arg_rg_dict=self.rg_facts, rg_model=self.rg_info,
arg_quotas=self.amodule.params['quotas'], arg_quotas=self.amodule.params['quotas'],
arg_res_types=self.amodule.params['resType'], arg_res_types=self.amodule.params['resType'],
arg_newname=self.amodule.params['rename'], arg_newname=self.amodule.params['rename'],
@@ -161,13 +186,15 @@ class decort_rg(DecortController):
return return
def setDefNet(self): def setDefNet(self):
rg_def_net_type = self.rg_facts['def_net_type'] rg_def_net_type = self.rg_info.default_net_type.value
rg_def_net_id = self.rg_facts['def_net_id'] rg_def_net_id = self.rg_info.default_net_id
aparam_def_net_type = self.aparams['def_netType'] aparam_def_net_type = self.aparams['def_netType']
aparam_def_net_id = self.aparams['def_netId'] aparam_def_net_id = self.aparams['def_netId']
need_to_reset = (aparam_def_net_type == 'NONE' need_to_reset = (
and rg_def_net_type != aparam_def_net_type) aparam_def_net_type == sdk_types.RGDefaultNetType.NONE
and rg_def_net_type != aparam_def_net_type
)
need_to_change = False need_to_change = False
if aparam_def_net_id is not None: 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) or aparam_def_net_type != rg_def_net_type)
if need_to_reset or need_to_change: if need_to_reset or need_to_change:
self.rg_setDefNet( if aparam_def_net_type == sdk_types.RGDefaultNetType.NONE:
arg_rg_id=self.validated_rg_id, self.sdk_checkmode(self.api.ca.rg.remove_def_net)(
arg_net_type=aparam_def_net_type, rg_id=self.rg_id,
arg_net_id=aparam_def_net_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 self.rg_should_exist = True
return return
def create(self): def create(self):
self.validated_rg_id = self.rg_provision( self.rg_id = self.rg_provision(
self.validated_acc_id, self.validated_acc_id,
self.amodule.params['rg_name'], self.amodule.params['rg_name'],
self.amodule.params['owner'], self.amodule.params['owner'],
@@ -199,10 +236,10 @@ class decort_rg(DecortController):
sdn_access_group_id=self.aparams['sdn_access_group_id'], sdn_access_group_id=self.aparams['sdn_access_group_id'],
) )
if self.validated_rg_id: if self.rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find( self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id, arg_account_id=self.validated_acc_id,
arg_rg_id=self.validated_rg_id, arg_rg_id=self.rg_id,
arg_rg_name="", arg_rg_name="",
arg_check_state=False arg_check_state=False
) )
@@ -210,31 +247,30 @@ class decort_rg(DecortController):
return return
def enable(self): def enable(self):
self.rg_enable(self.validated_rg_id,
self.amodule.params['state'])
if self.amodule.params['state'] == "enabled": if self.amodule.params['state'] == "enabled":
self.rg_facts['status'] = 'CREATED' self.sdk_checkmode(self.api.ca.rg.enable)(
else: rg_id=self.rg_id,
self.rg_facts['status'] = 'DISABLED' )
elif self.amodule.params['state'] == "disabled":
self.sdk_checkmode(self.api.ca.rg.disable)(
rg_id=self.rg_id,
)
self.rg_should_exist = True self.rg_should_exist = True
return return
def restore(self): def restore(self):
self.rg_restore(self.validated_rg_id) self.sdk_checkmode(self.api.ca.rg.restore)(
self.rg_facts['status'] = 'DISABLED' rg_id=self.rg_id,
)
self.rg_should_exist = True self.rg_should_exist = True
return return
def destroy(self): def destroy(self):
self.rg_delete( self.sdk_checkmode(self.api.ca.rg.delete)(
rg_id=self.validated_rg_id, rg_id=self.rg_id,
permanently=self.amodule.params['permanently'], permanently=self.amodule.params['permanently'],
recursively=self.aparams['recursive_deletion'], 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 self.rg_should_exist = False
return return
@@ -260,23 +296,7 @@ class decort_rg(DecortController):
# ret_dict['state'] = "ABSENT" # ret_dict['state'] = "ABSENT"
# return ret_dict # return ret_dict
ret_dict['id'] = self.rg_facts['id'] return self.rg_info.model_dump()
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
@property @property
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
@@ -291,6 +311,24 @@ class decort_rg(DecortController):
), ),
access=dict( access=dict(
type='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( description=dict(
type='str', type='str',
@@ -298,12 +336,8 @@ class decort_rg(DecortController):
), ),
def_netType=dict( def_netType=dict(
type='str', type='str',
choices=[ choices=sdk_types.RGDefaultNetType._member_names_,
'PRIVATE', default=sdk_types.RGDefaultNetType.PRIVATE.name,
'PUBLIC',
'NONE',
],
default='PRIVATE',
), ),
def_netId=dict( def_netId=dict(
type='int', type='int',
@@ -387,13 +421,13 @@ class decort_rg(DecortController):
self.aparams['sdn_access_group_id'] is not None self.aparams['sdn_access_group_id'] is not None
and ( and (
self.aparams['sdn_access_group_id'] self.aparams['sdn_access_group_id']
!= self.rg_facts['sdn_access_group_id'] != self.rg_info.sdn_access_group_id
) )
): ):
self.message( self.message(
'Check for parameter "sdn_access_group_id" failed: ' 'Check for parameter "sdn_access_group_id" failed: '
'cannot change sdn_access_group_id for an existing resource ' '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 check_errors = True
@@ -407,18 +441,23 @@ class decort_rg(DecortController):
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly # 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible # 5) report result to Ansible
def main(): @DecortController.handle_sdk_exceptions
decon = decort_rg() def run(self):
amodule = decon.amodule amodule = self.amodule
#amodule.check_mode=True #amodule.check_mode=True
if decon.validated_rg_id > 0: if self.rg_id:
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]: if self.rg_info.status in [
decon.error() sdk_types.ResourceGroupStatus.MODELED,
elif decon.rg_facts['status'] in ("CREATED"): 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': if amodule.params['state'] == 'absent':
decon.destroy() self.destroy()
elif amodule.params['state'] == "disabled": elif amodule.params['state'] == "disabled":
decon.enable() self.enable()
if amodule.params['state'] in ['present', 'enabled']: if amodule.params['state'] in ['present', 'enabled']:
if ( if (
amodule.params['quotas'] amodule.params['quotas']
@@ -427,53 +466,58 @@ def main():
or amodule.params['sep_pools'] is not None or amodule.params['sep_pools'] is not None
or amodule.params['description'] is not None or amodule.params['description'] is not None
): ):
decon.update() self.update()
if amodule.params['access']: if amodule.params['access']:
decon.access() self.access()
if amodule.params['def_netType'] is not None: if amodule.params['def_netType'] is not None:
decon.setDefNet() self.setDefNet()
elif decon.rg_facts['status'] == "DELETED": elif self.rg_info.status == sdk_types.ResourceGroupStatus.DELETED:
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True: if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
decon.destroy() self.destroy()
elif (amodule.params['state'] == 'present' elif (amodule.params['state'] == 'present'
or amodule.params['state'] == 'disabled'): or amodule.params['state'] == 'disabled'):
decon.restore() self.restore()
elif amodule.params['state'] == 'enabled': elif amodule.params['state'] == 'enabled':
decon.restore() self.restore()
decon.enable() self.enable()
elif decon.rg_facts['status'] in ("DISABLED"): elif self.rg_info.status == sdk_types.ResourceGroupStatus.DISABLED:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
decon.destroy() self.destroy()
elif amodule.params['state'] == ("enabled"): elif amodule.params['state'] == ("enabled"):
decon.enable() self.enable()
else: else:
if amodule.params['state'] in ('present', 'enabled'): if amodule.params['state'] in ('present', 'enabled'):
if not amodule.params['rg_name']: if not amodule.params['rg_name']:
decon.result['failed'] = True self.result['failed'] = True
decon.result['msg'] = ( self.result['msg'] = (
'Resource group could not be created because' 'Resource group could not be created because'
' the "rg_name" parameter was not specified.' ' the "rg_name" parameter was not specified.'
) )
else: else:
decon.create() self.create()
if amodule.params['access'] and not amodule.check_mode: if amodule.params['access'] and not amodule.check_mode:
decon.access() self.access()
elif amodule.params['state'] in ('disabled'): elif amodule.params['state'] in ('disabled'):
decon.error() self.error()
if decon.result['failed']: if self.result['failed']:
amodule.fail_json(**decon.result) amodule.fail_json(**self.result)
else: else:
if decon.rg_should_exist: if self.rg_should_exist:
if decon.result['changed']: if self.result['changed']:
decon.get_info() self.get_info()
decon.result['facts'] = decon.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result) amodule.exit_json(**self.result)
else: else:
amodule.exit_json(**decon.result) amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_rg().run()
if __name__ == '__main__':
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,589 @@
#!/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',
'no_addresses',
)
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.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController 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): class DecortSecurityGroup(DecortController):
id: int = 0 id: int = 0
@@ -52,18 +55,18 @@ class DecortSecurityGroup(DecortController):
options=dict( options=dict(
direction=dict( direction=dict(
type='str', type='str',
choices=[ choices=(
e.name for e in sdk_types.TrafficDirection.
self.SecurityGroupRuleDirection _member_names_
], ),
required=True, required=True,
), ),
ethertype=dict( ethertype=dict(
type='str', type='str',
choices=[ choices=(
e.name for e in sdk_types.SGRuleEthertype.
self.SecurityGroupRuleEtherType _member_names_
], ),
), ),
id=dict( id=dict(
type='int', type='int',
@@ -81,12 +84,11 @@ class DecortSecurityGroup(DecortController):
), ),
protocol=dict( protocol=dict(
type='str', type='str',
choices=[ choices=(
e.name for e in sdk_types.SGRuleProtocol._member_names_
self.SecurityGroupRuleProtocol
],
), ),
remote_ip_prefix=dict( ),
remote_net_cidr=dict(
type='str', type='str',
), ),
), ),
@@ -101,16 +103,17 @@ class DecortSecurityGroup(DecortController):
supports_check_mode=True, supports_check_mode=True,
) )
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
if self.aparams['id'] is not None: if self.aparams['id'] is not None:
self.id = self.aparams['id'] self.id = self.aparams['id']
elif self.aparams['name'] is not None: 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'], account_id=self.aparams['account_id'],
name=self.aparams['name'], name=self.aparams['name'],
) )
if security_group: if security_groups.data:
self.id = security_group['id'] self.id = security_groups.data[0].id
if self.id: if self.id:
self.get_info() self.get_info()
@@ -127,14 +130,25 @@ class DecortSecurityGroup(DecortController):
self.exit() self.exit()
def get_info(self): def get_info(self):
self.facts: dict = self.security_group_get(id=self.id) try:
self.facts['created_timestamp'] = self.facts.pop('created_at') storage_policy_model = self.api.cloudapi.security_group.get(
self.facts['updated_timestamp'] = self.facts.pop('updated_at') security_group_id=self.id
for rule in self.facts['rules']: )
rule['port_range'] = { except sdk_exceptions.RequestException as e:
'min': rule.pop('port_range_min'), if (
'max': rule.pop('port_range_max'), 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): def check_amodule_args_for_create(self):
check_errors = False check_errors = False
@@ -242,16 +256,13 @@ class DecortSecurityGroup(DecortController):
return not check_errors return not check_errors
def create(self): def create(self):
security_groups_by_account_id = self.user_security_groups( id = self.sdk_checkmode(self.api.cloudapi.security_group.create)(
account_id=self.aparams['account_id']
)
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'], account_id=self.aparams['account_id'],
name=self.aparams['name'], name=self.aparams['name'],
description=self.aparams['description'], description=self.aparams['description'],
) )
if id:
self.id = id
def change(self): def change(self):
self.change_state() self.change_state()
@@ -277,7 +288,7 @@ class DecortSecurityGroup(DecortController):
): ):
new_description = aparam_description new_description = aparam_description
if new_name or new_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, security_group_id=self.id,
name=new_name, name=new_name,
description=new_description, description=new_description,
@@ -317,7 +328,9 @@ class DecortSecurityGroup(DecortController):
self.create_rule(rule=rule) self.create_rule(rule=rule)
def delete(self): 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.facts = {}
self.exit() self.exit()
@@ -326,20 +339,22 @@ class DecortSecurityGroup(DecortController):
if rule.get('port_range'): if rule.get('port_range'):
port_range_min = rule['port_range'].get('min') port_range_min = rule['port_range'].get('min')
port_range_max = rule['port_range'].get('max') 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, security_group_id=self.id,
direction=self.SecurityGroupRuleDirection[rule['direction']], traffic_direction=(
sdk_types.TrafficDirection[rule['direction']]
),
ethertype=( ethertype=(
self.SecurityGroupRuleEtherType[rule['ethertype']] sdk_types.SGRuleEthertype[rule['ethertype']]
if rule.get('ethertype') else None if rule.get('ethertype') else sdk_types.SGRuleEthertype.IPV4
), ),
protocol=( protocol=(
self.SecurityGroupRuleProtocol[rule['protocol']] sdk_types.SGRuleProtocol[rule['protocol']]
if rule.get('protocol') else None if rule.get('protocol') else None
), ),
port_range_min=port_range_min, port_range_min=port_range_min,
port_range_max=port_range_max, 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.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortStoragePolicy(DecortController): class DecortStoragePolicy(DecortController):
def __init__(self): def __init__(self):
@@ -28,18 +30,31 @@ class DecortStoragePolicy(DecortController):
supports_check_mode=True, supports_check_mode=True,
) )
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
self.get_info() self.get_info()
self.exit() self.exit()
def get_info(self): def get_info(self):
self.facts = self.storage_policy_get(id=self.id) try:
self.facts['sep_pools'] = self.facts.pop('access_seps_pools') storage_policy_model = self.api.cloudapi.storage_policy.get(
self.facts['iops_limit'] = self.facts.pop('limit_iops') id=self.id
self.facts['usage']['account_ids'] = self.facts['usage'].pop(
'accounts'
) )
self.facts['usage']['rg_ids'] = self.facts['usage'].pop('resgroups') 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(): 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.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortTrunk(DecortController): class DecortTrunk(DecortController):
def __init__(self): def __init__(self):
@@ -28,19 +30,31 @@ class DecortTrunk(DecortController):
supports_check_mode=True, supports_check_mode=True,
) )
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
self.get_info() self.get_info()
self.exit() self.exit()
def get_info(self): def get_info(self):
self.facts = self.trunk_get(id=self.id) try:
self.facts['account_ids'] = self.facts.pop('accountIds') trunk_model = self.api.cloudapi.trunk.get(
self.facts['created_timestamp'] = self.facts.pop('created_at') id=self.id
self.facts['deleted_timestamp'] = self.facts.pop('deleted_at') )
self.facts['updated_timestamp'] = self.facts.pop('updated_at') except sdk_exceptions.RequestException as e:
self.facts['native_vlan_id'] = self.facts.pop('nativeVlanId') if (
self.facts['ovs_bridge'] = self.facts.pop('ovsBridge') e.orig_exception.response is not None
self.facts['vlan_ids'] = self.facts.pop('trunkTags') 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(): 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,872 +8,38 @@ description: See L(Module Documentation,https://repository.basistech.ru/BASIS/de
''' '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortUserInfo(DecortController): def main():
def __init__(self): module = AnsibleModule(
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( argument_spec=dict(
accounts=dict( app_id=dict(type='raw'),
type='dict', app_secret=dict(type='raw'),
options=dict( authenticator=dict(type='raw'),
deleted=dict( controller_url=dict(type='raw'),
type='bool', domain=dict(type='raw'),
default=False, jwt=dict(type='raw'),
), oauth2_url=dict(type='raw'),
filter=dict( password=dict(type='raw'),
type='dict', username=dict(type='raw'),
options=dict( verify_ssl=dict(type='raw'),
rights=dict( ignore_api_compatibility=dict(type='raw'),
type='str', ignore_sdk_version_check=dict(type='raw'),
choices=[ api_methods=dict(type='raw'),
e.value for e in self.AccountUserRights objects_search=dict(type='raw'),
], resource_consumption=dict(type='raw'),
),
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, supports_check_mode=True,
) )
def check_amodule_args(self): module.fail_json(
""" msg=(
Additional validation of Ansible Module arguments. 'The module "decort_user_info" has been renamed to "decort_user". '
This validation cannot be implemented using 'Please update your playbook to use "decort_user" '
Ansible Argument spec. 'instead of "decort_user_info".'
""" ),
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()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -20,7 +20,6 @@ class decort_vins(DecortController):
self.vins_id = 0 self.vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level 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 validated_rg_id = 0
rg_facts = None # will hold RG facts rg_facts = None # will hold RG facts
validated_acc_id = 0 validated_acc_id = 0
@@ -28,15 +27,28 @@ class decort_vins(DecortController):
if arg_amodule.params['vins_id']: if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified 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 # 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 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['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
self.vins_level = "ID" self.vins_level = "ID"
#raise Exception(self.vins_facts) validated_acc_id = self._vins_info.account_id
validated_acc_id = self.vins_facts['accountId'] validated_rg_id = self._vins_info.rg_id
validated_rg_id = self.vins_facts['rgId']
elif arg_amodule.params['rg_id']: elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified 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 # 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 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="") 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 # 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'], self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=0, account_id=0,
rg_id=arg_amodule.params['rg_id'], rg_id=arg_amodule.params['rg_id'],
rg_facts=rg_facts, check_state=False,
check_state=False) )
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
pass pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": 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 # 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 # 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']) validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name'])
if (not validated_rg_id or if (not validated_rg_id or rg_facts is None or
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): 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['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name']) self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result) 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 # 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) set to 0, as we are looking for ViNS under RG
account_id=0, # 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, rg_id=validated_rg_id,
rg_facts=rg_facts, check_state=False,
check_state=False) )
self.vins_level = "RG" self.vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0 else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level # So we expect ViNS @ account level
# This call to vins_find may return vins_id=0 if no ViNS found # 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'], self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=validated_acc_id, account_id=validated_acc_id,
rg_id=0, rg_id=0,
rg_facts=rg_facts, check_state=False,
check_state=False) )
self.vins_level = "ACC" self.vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
else: else:
@@ -106,7 +132,10 @@ class decort_vins(DecortController):
self.rg_id = validated_rg_id self.rg_id = validated_rg_id
self.acc_id = validated_acc_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() self.check_amodule_args_for_change()
else: else:
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
@@ -114,65 +143,110 @@ class decort_vins(DecortController):
return return
def create(self): def create(self):
self.vins_id = self.vins_provision(self.amodule.params['vins_name'], security_group_mode = self.amodule.params['security_group_mode']
self.acc_id, self.rg_id, if security_group_mode is None:
self.amodule.params['ipcidr'], security_group_mode = False
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'], self.message(
self.amodule.params['description'], msg=self.MESSAGES.default_value_used(
zone_id=self.amodule.params['zone_id'], param_name='security_group_mode',
default_value=security_group_mode
),
warning=True,
) )
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']: self.vins_id = self.vins_provision(
_, self.vins_facts = self.vins_find(self.vins_id) 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.vins_id:
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
if self.amodule.params['connect_to']: 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']: 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 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, 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_net_id'],
self.amodule.params['ext_ip_addr'], self.amodule.params['ext_ip_addr'],
) )
if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED": if (
self.vins_state(self.vins_facts, d_state) d_state == 'enabled'
self.vins_facts['status'] = "ENABLED" and self.vins_info.status == sdk_types.VINSStatus.DISABLED
self.vins_facts['VNFDev']['techStatus'] = "STARTED" ):
self.vins_state(self.vins_info, d_state)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
d_state = '' d_state = ''
if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED": if (
self.vins_update_ifaces(self.vins_facts, 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'], self.amodule.params['connect_to'],
) )
if self.result['changed']: if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id) self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.vins_update_mgmt(self.vins_facts,
self.vins_update_mgmt(
self.vins_info,
self.amodule.params['mgmtaddr'], self.amodule.params['mgmtaddr'],
) )
if d_state != '': 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'] 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( self.vins_migrate_to_zone(
net_id=self.vins_id, net_id=self.vins_info.id,
zone_id=aparam_zone_id, zone_id=aparam_zone_id,
) )
return return
def delete(self): def delete(self):
self.vins_delete(self.vins_id, self.amodule.params['permanently']) self.sdk_checkmode(self.api.cloudapi.vins.delete)(
self.vins_facts['status'] = 'DESTROYED' vins_id=self.vins_info.id,
permanently=self.amodule.params['permanently'],
)
return return
def nop(self): def nop(self):
"""No operation (NOP) handler for ViNS management by decort_vins module. """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 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['failed'] = False
self.result['changed'] = False self.result['changed'] = False
if self.vins_id: if self._vins_info:
self.result['msg'] = ("No state change required for ViNS ID {} because of its " self.result['msg'] = (
"current status '{}'.").format(self.vins_id, self.vins_facts['status']) f'No state change required for ViNS ID {self._vins_info.id} '
f'because of its "current status "{self._vins_info.status}".'
)
else: else:
self.result['msg'] = ("No state change to '{}' can be done for " self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent ViNS instance.").format(self.amodule.params['state']) "non-existent ViNS instance.").format(self.amodule.params['state'])
return return
def error(self): def error(self):
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
if self.vins_id: if self._vins_info:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the " self.result['msg'] = (
"current status '{}'").format(self.vins_id, f'Invalid target state "{self.amodule.params['state']}" '
self.amodule.params['state'], f'requested for ViNS ID {self._vins_info.id} in the '
self.vins_facts['status']) f'current status "{self._vins_info.status}"'
)
else: else:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
@@ -205,6 +283,7 @@ class decort_vins(DecortController):
"ViNS name '{}'").format(self.amodule.params['state'], "ViNS name '{}'").format(self.amodule.params['state'],
self.amodule.params['vins_name']) self.amodule.params['vins_name'])
return return
def package_facts(self, arg_check_mode=False): def package_facts(self, arg_check_mode=False):
"""Package a dictionary of ViNS facts according to the decort_vins module specification. """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 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 # in check mode return immediately with the default values
return ret_dict 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 # if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT" ret_dict['state'] = "ABSENT"
return ret_dict return ret_dict
ret_dict['id'] = self.vins_facts['id'] return self._vins_info.model_dump()
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
@property @property
@@ -276,11 +327,9 @@ class decort_vins(DecortController):
), ),
ext_net_id=dict( ext_net_id=dict(
type='int', type='int',
default=-1,
), ),
ext_ip_addr=dict( ext_ip_addr=dict(
type='str', type='str',
default='',
), ),
ipcidr=dict( ipcidr=dict(
type='str', type='str',
@@ -304,7 +353,6 @@ class decort_vins(DecortController):
), ),
state=dict( state=dict(
type='str', type='str',
default='present',
choices=[ choices=[
'absent', 'absent',
'disabled', 'disabled',
@@ -335,6 +383,9 @@ class decort_vins(DecortController):
zone_id=dict( zone_id=dict(
type=int, type=int,
), ),
security_group_mode=dict(
type='bool',
),
), ),
supports_check_mode=True, supports_check_mode=True,
required_one_of=[ required_one_of=[
@@ -347,6 +398,34 @@ class decort_vins(DecortController):
if self.check_aparam_zone_id() is False: if self.check_aparam_zone_id() is False:
check_errors = True 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: if check_errors:
self.exit(fail=True) self.exit(fail=True)
@@ -365,9 +444,9 @@ class decort_vins(DecortController):
# 4) if ViNS exists: check desired state, desired configuration -> initiate action(s) accordingly # 4) if ViNS exists: check desired state, desired configuration -> initiate action(s) accordingly
# 5) report result to Ansible # 5) report result to Ansible
def main(): @DecortController.handle_sdk_exceptions
decon = decort_vins() def run(self):
amodule = decon.amodule amodule = self.amodule
# #
# Initial validation of module arguments is complete # Initial validation of module arguments is complete
# #
@@ -384,83 +463,125 @@ def main():
# if cconfig_save is true, only config save without other updates # if cconfig_save is true, only config save without other updates
vins_should_exist = False vins_should_exist = False
if decon.vins_id: if self._vins_info:
vins_should_exist = True vins_should_exist = True
if decon.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]: 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 # error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state # the requested state
decon.result['failed'] = True self.result['failed'] = True
decon.result['changed'] = False self.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current " self.result['msg'] = (
"status '{}'").format(decon.vins_id, decon.vins_facts['status']) f'No change can be done for existing '
elif decon.vins_facts['status'] == "DISABLED": 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': if amodule.params['state'] == 'absent':
decon.delete() self.delete()
vins_should_exist = False vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'): elif (
amodule.params['state'] is None
or amodule.params['state'] in ('present', 'disabled')
):
# update ViNS, leave in disabled state # update ViNS, leave in disabled state
decon.action() self.action()
elif amodule.params['state'] == 'enabled': elif amodule.params['state'] == 'enabled':
# update ViNS and enable # update ViNS and enable
decon.action('enabled') self.action('enabled')
elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]: elif self._vins_info.status in [
sdk_types.VINSStatus.CREATED,
sdk_types.VINSStatus.ENABLED,
]:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
decon.delete() self.delete()
vins_should_exist = False vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'): elif (
amodule.params['state'] is None
or amodule.params['state'] in ('present', 'enabled')
):
# update ViNS # update ViNS
decon.action() self.action()
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
# disable and update ViNS # disable and update ViNS
decon.action('disabled') self.action('disabled')
elif decon.vins_facts['status'] == "DELETED": elif self._vins_info.status == sdk_types.VINSStatus.DELETED:
if amodule.params['state'] in ['present', 'enabled']: if amodule.params['state'] in ['present', 'enabled']:
# restore and enable # restore and enable
decon.action(restore=True) self.action(restore=True)
vins_should_exist = True vins_should_exist = True
elif amodule.params['state'] == 'absent': elif amodule.params['state'] == 'absent':
# destroy permanently # destroy permanently
if decon.amodule.params['permanently']: if self.amodule.params['permanently']:
decon.delete() self.delete()
vins_should_exist = False vins_should_exist = False
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
decon.error() self.error()
vins_should_exist = False vins_should_exist = False
elif decon.vins_facts['status'] == "DESTROYED": elif self._vins_info.status == sdk_types.VINSStatus.DESTROYED:
if amodule.params['state'] in ('present', 'enabled'): 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; # need to re-provision ViNS;
decon.create() self.create()
vins_should_exist = True vins_should_exist = True
elif amodule.params['state'] == 'absent': elif state == 'absent':
decon.nop() self.nop()
vins_should_exist = False vins_should_exist = False
elif amodule.params['state'] == 'disabled': elif state == 'disabled':
decon.error() self.error()
else: 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. # Preexisting ViNS was not found.
vins_should_exist = False # we will change it back to True if ViNS is created or restored vins_should_exist = False # we will change it back to True if ViNS is created or restored
# If requested state is 'absent' - nothing to do # If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent': if state == 'absent':
decon.nop() self.nop()
elif amodule.params['state'] in ('present', 'enabled'): elif state in ('present', 'enabled'):
decon.check_amodule_argument('vins_name') 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 # as we already have account ID and RG ID we can create ViNS and get vins_id on success
decon.create() self.create()
vins_should_exist = True vins_should_exist = True
elif amodule.params['state'] == 'disabled': elif state == 'disabled':
decon.error() self.error()
# #
# conditional switch end - complete module run # conditional switch end - complete module run
# #
if decon.result['failed']: if self.result['failed']:
amodule.fail_json(**decon.result) amodule.fail_json(**self.result)
else: else:
# prepare ViNS facts to be returned as part of decon.result and then call exit_json(...) # prepare ViNS facts to be returned as part of self.result and then call exit_json(...)
if decon.result['changed']: if self.result['changed']:
_, decon.vins_facts = decon.vins_find(decon.vins_id) self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result) amodule.exit_json(**self.result)
if __name__ == "__main__": def main():
decort_vins().run()
if __name__ == '__main__':
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.exit(fail=True)
self.vm_name = self.vm_facts['name'] 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 = [ 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 self.new_snapshot_label = None
@@ -102,21 +103,23 @@ class DecortVMSnapshot(DecortController):
if check_error: if check_error:
self.exit(fail=True) self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
self.get_info(first_run=True) self.get_info()
self.check_amodule_args_for_change() self.check_amodule_args_for_change()
self.change() self.change()
self.exit() self.exit()
def get_info(self, first_run: bool = False): def get_info(self, update_vm_snapshots: bool = False):
if not first_run: if update_vm_snapshots:
self.vm_snapshots = self.snapshot_list( self.vm_snapshots = self.api.cloudapi.compute.snapshot_list(
compute_id=self.aparams_vm_id, vm_id=self.aparams_vm_id,
) ).data
label = self.new_snapshot_label or self.aparams_label label = self.new_snapshot_label or self.aparams_label
for snapshot in self.vm_snapshots: for snapshot in self.vm_snapshots:
if snapshot['label'] == label: if snapshot.label == label:
self.facts = snapshot self.facts = snapshot.model_dump()
if self.aparams['usage']: if self.aparams['usage']:
self.facts['stored'] = self.get_snapshot_usage() self.facts['stored'] = self.get_snapshot_usage()
self.facts['vm_id'] = self.aparams_vm_id self.facts['vm_id'] = self.aparams_vm_id
@@ -134,11 +137,11 @@ class DecortVMSnapshot(DecortController):
self.abort_merge() self.abort_merge()
def create(self): def create(self):
self.snapshot_create( self.sdk_checkmode(self.api.cloudapi.compute.snapshot_create)(
compute_id=self.aparams_vm_id, vm_id=self.aparams_vm_id,
label=self.new_snapshot_label, label=self.new_snapshot_label,
) )
self.get_info() self.get_info(update_vm_snapshots=True)
def delete(self): def delete(self):
self.snapshot_delete( self.snapshot_delete(
@@ -171,7 +174,7 @@ class DecortVMSnapshot(DecortController):
): ):
check_errors = True check_errors = True
self.message( self.message(
f'Check for parameter "state" failed: ' 'Check for parameter "state" failed: '
'Merge can be aborted only for VM in "MERGE" tech status.' 'Merge can be aborted only for VM in "MERGE" tech status.'
) )
@@ -179,7 +182,6 @@ class DecortVMSnapshot(DecortController):
self.exit(fail=True) self.exit(fail=True)
def main(): def main():
DecortVMSnapshot().run() 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.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortZone(DecortController): class DecortZone(DecortController):
def __init__(self): def __init__(self):
@@ -28,16 +30,29 @@ class DecortZone(DecortController):
supports_check_mode=True, supports_check_mode=True,
) )
@DecortController.handle_sdk_exceptions
def run(self): def run(self):
self.get_info() self.get_info()
self.exit() self.exit()
def get_info(self): def get_info(self):
self.facts = self.zone_get(id=self.id) try:
self.facts['grid_id'] = self.facts.pop('gid') zone_model = self.api.cloudapi.zone.get(id=self.id)
self.facts['created_timestamp'] = self.facts.pop('createdTime') except sdk_exceptions.RequestException as e:
self.facts['updated_timestamp'] = self.facts.pop('updatedTime') if (
self.facts['node_ids'] = self.facts.pop('nodeIds') 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(): 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 ansible==11.6.0
requests==2.32.3 requests==2.32.3
git+https://repository.basistech.ru/BASIS/dynamix-python-sdk.git@1.5.latest