230 Commits

Author SHA1 Message Date
e54a9591e4 11.0.0 2026-02-11 13:50:28 +03:00
8c554c8edd 10.0.1 2025-12-02 15:31:36 +03:00
becbe65993 10.0.0 2025-11-14 12:44:02 +03:00
06336697a6 9.0.0 2025-07-24 16:59:28 +03:00
4113719334 8.0.0 2025-05-07 14:08:17 +03:00
f8c32d609b 7.2.0 2025-03-03 17:44:25 +03:00
e537eadda6 7.1.0 2025-02-07 13:04:30 +03:00
5f3df12742 7.0.0 2024-12-26 12:37:38 +03:00
6b102946de 6.1.2 2024-12-23 16:59:51 +03:00
45355b3dd3 6.1.1 2024-12-18 14:08:14 +03:00
54c306b13b 6.1.0 2024-11-29 13:48:11 +03:00
dd2fca15f3 6.0.0 2024-11-13 11:51:38 +03:00
aa3f84095f 5.6.0 2024-09-03 09:20:19 +03:00
ba305a0ccb 5.5.0 2024-08-29 16:58:25 +03:00
Dmitriy Smirnov
7d60e5f97b 5.4.1 2024-08-23 15:16:14 +03:00
bbc352715d Add docs for the modules version 5.4.0 2024-07-24 14:46:43 +03:00
c34f02f7bd Merge branch 'BANS-481' into 'dev_5.4.0'
Implement functionality of the API method `/cloudapi/account/audits` in the...

See merge request rudecs/dev/decort-ansible!107
2024-07-24 11:31:10 +00:00
3c6ce85dba Implement functionality of the API method /cloudapi/account/audits in the module decort_account_info 2024-07-24 14:23:35 +03:00
fe1e8a32f9 Merge branch 'BANS-480' into 'dev_5.4.0'
Implement functionality of the API method `/cloudapi/account/listFlipGroups`

See merge request rudecs/dev/decort-ansible!106
2024-07-24 08:38:40 +00:00
0ca3399026 Implement functionality of the API method /cloudapi/account/listFlipGroups 2024-07-23 17:53:45 +03:00
ce54341a64 Merge branch 'BANS-479' into 'dev_5.4.0'
Implement functionality of the API method `cloudapi/account/listTemplates`

See merge request rudecs/dev/decort-ansible!105
2024-07-23 11:13:58 +00:00
7422464109 Implement functionality of the API method cloudapi/account/listTemplates 2024-07-23 10:46:58 +03:00
0c0fde8470 Add the argument disk_type validation in the method DecortController.account_disks 2024-07-22 16:32:49 +03:00
aefc920e1a Merge branch 'BANS-478' into 'dev_5.4.0'
Implement functionality of the API method `cloudapi/account/listDisks`

See merge request rudecs/dev/decort-ansible!104
2024-07-22 10:49:19 +00:00
b73a57dd0d Implement functionality of the API method cloudapi/account/listDisks 2024-07-22 13:35:59 +03:00
cd663a4a01 Improve style of version compliance in README.md 2024-07-22 11:42:27 +03:00
0cdfa6a0ec Add annotation for result of the method DecortController.decort_api_call and improve its docstring code style 2024-07-21 11:31:47 +03:00
53938d9d94 Improve code style and docstring for the method DecortController.account_find 2024-07-21 11:02:57 +03:00
8e4ce18d8a Improve naming of some methods and variable in library/decort_account_info.py and module_utils/decort_utils.py 2024-07-21 10:11:57 +03:00
ced031bba8 Add docstring for the method DecortAccountInfo.mapped_vinses_params 2024-07-19 18:19:35 +03:00
25795e9fe9 Add docstring for the method DecortAccountInfo.mapped_rg_params 2024-07-19 18:18:45 +03:00
a46ed24168 Add docstring for the method DecortAccountInfo.mapped_computes_params 2024-07-19 18:16:53 +03:00
53ba9a4f02 Add brief docstring for the method DecortController.account_vinses 2024-07-19 16:50:50 +03:00
682f19c4ce Add brief docstring for the method DecortController.account_computes 2024-07-19 16:50:06 +03:00
3516843c41 Add brief docstring for the method DecortController.account_resource_groups 2024-07-19 16:48:50 +03:00
94586345a1 Add brief docstring for the method DecortController.account_resource_consumption 2024-07-19 16:47:10 +03:00
c1dfaccb61 Merge branch 'BANS-477' into 'dev_5.4.0'
Implement functionality of the API method `cloudapi/account/listVins` in...

See merge request rudecs/dev/decort-ansible!103
2024-07-19 13:08:11 +00:00
d99af4498a Implement functionality of the API method cloudapi/account/listVins in decort_account_info module 2024-07-19 15:59:05 +03:00
a0805d45b3 Update docstring and args type hints for the method DecortController.account_find 2024-07-19 13:09:43 +03:00
3b2be18346 Add decorator for adding the name of called method to the string self.result['waypoints']. 2024-07-19 11:42:23 +03:00
f230325968 Remove erroneously added module decort_account_info parameters 2024-07-18 17:55:30 +03:00
1d56940e7e Merge branch 'BANS-476' into 'dev_5.4.0'
Implement functionality of `cloudapi/account/listComputes` API method in...

See merge request rudecs/dev/decort-ansible!102
2024-07-18 13:40:41 +00:00
10dba22834 Implement functionality of cloudapi/account/listComputes API method in decort_account_info module 2024-07-18 16:30:58 +03:00
b6bbc31961 Merge branch 'BANS-475' into 'dev_5.4.0'
Implement functionality of `/cloudapi/account/listRG` API method in `decort_account_info` module

See merge request rudecs/dev/decort-ansible!101
2024-07-17 12:36:08 +00:00
412bd704f1 Implement functionality of /cloudapi/account/listRG API method in decort_account_info module 2024-07-17 15:33:32 +03:00
77a2b6a182 Refactor DecortAccountInfo class (MODULE_ARGS attribute -> module_args property) 2024-07-16 15:49:15 +03:00
f00055e009 Refactor DecortController.account_find method 2024-07-16 15:11:33 +03:00
37de8afbc1 (2)Remove required=False optional setting from decort_account_info module argument specification 2024-07-16 11:28:07 +03:00
53e30105b1 Remove required=False optional setting from decort_account_info module argument specification 2024-07-16 11:19:08 +03:00
734408ab10 Merge branch 'BANS-472' into 'dev_5.4.0'
Implement functionality of `/cloudapi/account/getResourceConsumption` API method

See merge request rudecs/dev/decort-ansible!100
2024-07-15 15:53:30 +00:00
0fcde3f4bd Merge branch 'BANS-474' into 'BANS-472'
Update changelog after implementation functionality of /cloudapi/account/getResourceConsumption

See merge request rudecs/dev/decort-ansible!99
2024-07-15 15:30:50 +00:00
32324ee184 Update changelog after implementation functionality of /cloudapi/account/getResourceConsumption 2024-07-15 18:28:32 +03:00
5161649fe9 Implement functionality of /cloudapi/account/getResourceConsumption API method 2024-07-15 18:19:08 +03:00
ce4ac4630c Merge branch 'BANS-462' into 'dev_5.4.0'
Implement `cloudapi/account/get` API

See merge request rudecs/dev/decort-ansible!98
2024-07-12 15:52:45 +00:00
8ee5bcce52 Update Ansible modules version for 4.0.0 version platform in README.md 2024-07-12 18:47:18 +03:00
1d6141117d Merge branch 'BANS-469' into 'BANS-462'
Update changelog for 5.4.0 version

See merge request rudecs/dev/decort-ansible!97
2024-07-12 15:45:02 +00:00
5d6a278b8f Update changelog for 5.4.0 version 2024-07-12 18:42:50 +03:00
cf4d43d23e Merge branch 'BANS-471' into 'BANS-462'
Add `decort_account_info` module with implementation of `cloudapi/account/get` functionality

See merge request rudecs/dev/decort-ansible!96
2024-07-12 11:49:59 +00:00
11168827e6 Add decort_account_info module with implementation of cloudapi/account/get functionality 2024-07-12 14:46:34 +03:00
2507a65d89 Merge branch 'BANS-467' into 'BANS-462'
Improve `DecortController.account_find` method

See merge request rudecs/dev/decort-ansible!95
2024-07-12 11:08:02 +00:00
22f487b626 Improve DecortController.account_find method 2024-07-12 14:06:25 +03:00
89b03213df Merge branch 'BANS-468' into 'BANS-462'
Add `not_fail_codes` parameter to `DecortController.decort_api_call` method

See merge request rudecs/dev/decort-ansible!94
2024-07-12 10:55:26 +00:00
96b163ba00 Add not_fail_codes parameter to DecortController.decort_api_call method 2024-07-12 13:52:50 +03:00
21470542ea Merge branch 'BANS-466' into 'BANS-462'
Disable dependency on `password`, `user`, `workflow_callback` and...

See merge request rudecs/dev/decort-ansible!92
2024-07-12 10:21:34 +00:00
7eed30d2ab Disable dependency on password, user, workflow_callback and workflow_context module parameters 2024-07-12 13:16:12 +03:00
efa60a5caf Add wiki directory with documentation files 2024-07-09 15:30:44 +03:00
b7b02fdb85 Merge branch 'BANS-463' into 'dev_5.3.0'
Delete decort-ansible.tar file

See merge request rudecs/dev/decort-ansible!90
2024-07-09 12:28:08 +00:00
c5f2e143ba Delete decort-ansible.tar file 2024-07-09 15:27:10 +03:00
68f4bcbcc6 Merge branch 'BANS-460' into 'dev_5.3.0'
Update README.md file

See merge request rudecs/dev/decort-ansible!89
2024-07-09 12:25:12 +00:00
a94a5a2e62 Update README.md file 2024-07-09 15:24:16 +03:00
3b84a5f633 Fix BANS-434 task description in CHANGELOG.md file 2024-07-08 16:58:09 +03:00
2d04cad3d4 Merge branch 'BANS-459' into 'dev_5.3.0'
Add CHANGELOG.md file for 5.3.0 version

See merge request rudecs/dev/decort-ansible!88
2024-07-08 13:47:34 +00:00
f12e6fc941 Merge branch 'rc-5.2.6' into 'dev_5.3.0'
Update dev_5.3.0 branch

See merge request rudecs/dev/decort-ansible!87
2024-07-08 13:45:55 +00:00
02e55e77f4 Add CHANGELOG.md file for 5.3.0 version 2024-07-08 16:28:17 +03:00
dac66fac77 Merge branch 'BANS-457' into 'rc-5.2.6'
Fix access to lb_facts attribute in decort_lb class (in two places)

See merge request rudecs/dev/decort-ansible!86
2024-07-04 14:57:51 +00:00
3ce022a800 Fix access to lb_facts attribute in decort_lb class (in two places) 2024-07-04 17:54:26 +03:00
59000feb00 Merge branch 'BANS-451' into 'rc-5.2.6'
Fix logic of deleting all port forwarding rules for compute in...

See merge request rudecs/dev/decort-ansible!85
2024-07-02 12:10:43 +00:00
614c7d98d9 Fix logic of deleting all port forwarding rules for compute in DecortController.pfw_configure method 2024-07-02 14:52:23 +03:00
3428b74b00 Merge branch 'BANS-445' into 'rc-5.2.6'
Fix ci_user_data param type in decort_kvmvm module

See merge request rudecs/dev/decort-ansible!84
2024-06-26 10:39:37 +00:00
36930bda0d Fix ci_user_data param type in decort_kvmvm module 2024-06-26 13:35:11 +03:00
Алексей Даньков
eb91d5200f Merge branch 'dev_bsgroup_rc-5.2.6' into 'rc-5.2.6'
decort_group module bug fixes

See merge request rudecs/dev/decort-ansible!80
2024-06-21 06:59:17 +00:00
d7711e58ca Merge branch 'BANS-434' into 'rc-5.2.6'
Fix bs groups info keys in decort_bservice.package_facts method

See merge request rudecs/dev/decort-ansible!83
2024-06-19 08:01:40 +00:00
d287c88293 Fix bs groups info keys in decort_bservice.package_facts method 2024-06-19 10:35:36 +03:00
309d5b91eb Merge branch 'BANS-425' into 'rc-5.2.6'
Fix check mode logic for RG creating in decort_rg module

See merge request rudecs/dev/decort-ansible!82
2024-06-17 13:54:58 +00:00
db854acc11 Fix check mode logic for RG creating in decort_rg module 2024-06-17 12:31:04 +03:00
7b682f0340 Merge branch 'BANS-419' into 'dev_bsgroup_rc-5.2.6'
Fix ViNS list comparison in DecortController.group_update_net method

See merge request rudecs/dev/decort-ansible!79
2024-06-14 12:30:14 +00:00
b51136b711 Fix ViNS list comparison in DecortController.group_update_net method 2024-06-14 15:26:23 +03:00
Алексей Даньков
daa91bee95 Merge branch 'dev_bs_rc-5.2.6' into 'rc-5.2.6'
decort_bservice module bug fixes

See merge request rudecs/dev/decort-ansible!72
2024-06-14 07:44:13 +00:00
Алексей Даньков
5227e2be0b Merge branch 'dev_rc-5.2.6' into 'rc-5.2.6'
decort_lb module bug fixes

See merge request rudecs/dev/decort-ansible!67
2024-06-14 07:43:03 +00:00
9ccf2de256 Merge branch 'BANS-399' into 'dev_bsgroup_rc-5.2.6'
Fix logic of decort_group.destroy method

See merge request rudecs/dev/decort-ansible!78
2024-06-11 14:32:05 +00:00
825ce068c8 Fix logic of decort_group.destroy method 2024-06-11 17:30:23 +03:00
efb51ab8b4 Merge branch 'BANS-396' into 'dev_bsgroup_rc-5.2.6'
Add sub-elements specification for 'networks' parameter of decort_group module

See merge request rudecs/dev/decort-ansible!77
2024-06-11 13:21:37 +00:00
190a1d302c Add sub-elements specification for 'networks' parameter of decort_group module 2024-06-11 16:16:35 +03:00
840e4bec21 Merge branch 'BANS-395' into 'dev_bsgroup_rc-5.2.6'
Add default value '' for 'role' parameter in decort_group module.

See merge request rudecs/dev/decort-ansible!76
2024-06-11 11:32:56 +00:00
f22be4fe08 Add default value '' for 'role' parameter in decort_group module. 2024-06-11 14:16:23 +03:00
4b1a7d9d9e Merge branch 'BANS-392' into 'dev_bsgroup_rc-5.2.6'
Update DecortController.group_find method logic for >=3.8.6 Dynamix version.

See merge request rudecs/dev/decort-ansible!75
2024-06-11 09:51:04 +00:00
3a2d9904cf Update DecortController.group_find method logic for >=3.8.6 Dynamix version. 2024-06-11 12:46:57 +03:00
9393bb76cc Merge branch 'BANS-391' into 'dev_bsgroup_rc-5.2.6'
Fix adding networks logic in DecortController.group_provision method

See merge request rudecs/dev/decort-ansible!74
2024-06-10 14:04:40 +00:00
27e7c2749f Fix adding networks logic in DecortController.group_provision method 2024-06-10 17:01:41 +03:00
e578742bb2 Merge branch 'BANS-390' into 'dev_bsgroup_rc-5.2.6'
Fix return value for DecortController.group_provision method.

See merge request rudecs/dev/decort-ansible!73
2024-06-10 13:04:14 +00:00
7998046cfb Fix return value for DecortController.group_provision method. 2024-06-10 16:02:19 +03:00
06b4686e18 Merge branch 'BANS-369' into 'dev_bs_rc-5.2.6'
Bans 369

See merge request rudecs/dev/decort-ansible!71
2024-06-07 11:57:33 +00:00
cb13649586 Fix executing logic of DecortController.bservice_state method call in decort_bservice.create method 2024-06-07 14:54:30 +03:00
7e372511bc Add check mode simple logic in DecortController.bservice_provision method 2024-06-07 14:53:28 +03:00
bb8e9ad6f7 Merge branch 'BANS-368' into 'dev_bs_rc-5.2.6'
Add existing check for 'groupsName' key of self.bservice_info dict in...

See merge request rudecs/dev/decort-ansible!70
2024-06-07 11:16:32 +00:00
3fec6f014b Add existing check for 'groupsName' key of self.bservice_info dict in decort_bservice.package_facts method 2024-06-07 14:12:45 +03:00
4a08cd86f6 Merge branch 'BANS-367' into 'dev_bs_rc-5.2.6'
Add rg_name param value to call of DecortController.rg_find method in...

See merge request rudecs/dev/decort-ansible!69
2024-06-07 09:58:05 +00:00
3dc9cbcbd8 Add rg_name param value to call of DecortController.rg_find method in decort_bservice.__init__ method 2024-06-07 12:56:04 +03:00
0008372e6b Merge branch 'BANS-366' into 'dev_bs_rc-5.2.6'
Fix variable name for if-condition in decort_bservice.nop method

See merge request rudecs/dev/decort-ansible!68
2024-06-07 09:41:12 +00:00
5c3194b94d Fix variable name for if-condition in decort_bservice.nop method 2024-06-07 12:39:29 +03:00
20b9228351 Merge branch 'BANS-357' into 'dev_rc-5.2.6'
Add check mode simple logic to DecortController.lb_update method

See merge request rudecs/dev/decort-ansible!66
2024-06-06 13:31:30 +00:00
9449afa2ac Add check mode simple logic to DecortController.lb_update method 2024-06-06 16:29:22 +03:00
b2477d2035 Merge branch 'BANS-354' into 'dev_rc-5.2.6'
Fix API param values for servers adding in DecortController._lb_update_backends method

See merge request rudecs/dev/decort-ansible!65
2024-06-06 12:37:27 +00:00
ea63959289 Fix API param values for servers adding in DecortController._lb_update_backends method 2024-06-06 15:34:01 +03:00
b3b47c57a1 Merge branch 'BANS-353' into 'dev_rc-5.2.6'
Fix location of lb_front_list formation code in DecortController.lb_update method

See merge request rudecs/dev/decort-ansible!64
2024-06-06 12:00:53 +00:00
bb6394873b Fix location of lb_front_list formation code in DecortController.lb_update method 2024-06-06 14:54:16 +03:00
ff1c43e8de Merge branch 'BANS-352' into 'dev_rc-5.2.6'
Fix deleting element of frontends list in DecortController._lb_delete_fronts method

See merge request rudecs/dev/decort-ansible!63
2024-06-06 11:51:38 +00:00
6b4957f8aa Fix deleting element of frontends list in DecortController._lb_delete_fronts method 2024-06-06 14:46:50 +03:00
d824e599b9 Merge branch 'BANS-346' into 'dev_rc-5.2.6'
Fix logic of LB state changing

See merge request rudecs/dev/decort-ansible!62
2024-06-05 14:41:36 +00:00
b1f2167d00 Fix logic of LB state changing 2024-06-05 17:39:27 +03:00
aad2f89e6d Merge branch 'BANS-341' into 'dev_rc-5.2.6'
Add LB starting logic in LB restoring logic and fix DecortController.lb_state...

See merge request rudecs/dev/decort-ansible!61
2024-06-05 10:25:28 +00:00
4311eee435 Add LB starting logic in LB restoring logic and fix DecortController.lb_state method VALID_TARGET_STATES list 2024-06-05 13:22:05 +03:00
3bdac96760 Merge branch 'BANS-340' into 'dev_rc-5.2.6'
Add lb_facts updating logic after DecortController.lb_restore method call in...

See merge request rudecs/dev/decort-ansible!60
2024-06-05 08:08:58 +00:00
a6a6954d46 Add lb_facts updating logic after DecortController.lb_restore method call in decort_lb.action method 2024-06-05 11:03:33 +03:00
6d003e4541 Merge branch 'BANS-339' into 'dev_rc-5.2.6'
Fix lb_dict argument of DecortController.lb_state method call in decort_lb.action method

See merge request rudecs/dev/decort-ansible!59
2024-06-05 07:42:06 +00:00
0ae16ddc1d Fix lb_dict argument of DecortController.lb_state method call in decort_lb.action method 2024-06-05 10:39:34 +03:00
c5f68fea38 Merge branch 'BANS-338' into 'dev_rc-5.2.6'
Fix argument name of DecortController.lb_restore method call in decort_lb.action method

See merge request rudecs/dev/decort-ansible!58
2024-06-05 07:32:10 +00:00
23ad78b1cf Fix argument name of DecortController.lb_restore method call in decort_lb.action method 2024-06-05 10:30:03 +03:00
93a929aff5 Merge branch 'BANS-334' into 'dev_rc-5.2.6'
Fix logic of LB deleting from Recycle Bin and LB list getting

See merge request rudecs/dev/decort-ansible!57
2024-06-04 13:33:44 +00:00
90ae212d0c Fix logic of LB deleting from Recycle Bin and LB list getting 2024-06-04 16:30:00 +03:00
421d19bfa8 Merge branch 'BANS-331' into 'dev_rc-5.2.6'
Fix logic of DecortController.lb_update method running in decort_lb.create method

See merge request rudecs/dev/decort-ansible!56
2024-06-04 08:51:04 +00:00
41731c3dd7 Fix logic of DecortController.lb_update method running in decort_lb.create method 2024-06-04 11:49:14 +03:00
034aeca3f0 Merge branch 'BANS-324' into 'dev_rc-5.2.6'
Fix logic of LB frontends updating in DecortController._lb_update_fronts method

See merge request rudecs/dev/decort-ansible!55
2024-06-03 16:16:07 +00:00
db67a3b2d2 Fix logic of LB frontends updating in DecortController._lb_update_fronts method 2024-06-03 19:14:02 +03:00
af6eff33f7 Merge branch 'BANS-326' into 'dev_rc-5.2.6'
Fix logic of frontends creating in DecortController._lb_add_fronts method

See merge request rudecs/dev/decort-ansible!54
2024-06-03 15:14:13 +00:00
ca45f49c2e Fix logic of frontends creating in DecortController._lb_add_fronts method 2024-06-03 18:11:40 +03:00
88d9ddcdbe Merge branch 'BANS-325' into 'dev_rc-5.2.6'
Fix API request parameter name for LB description in DecortController.lb_provision method

See merge request rudecs/dev/decort-ansible!53
2024-06-03 13:14:43 +00:00
876ff5b98d Fix API request parameter name for LB description in DecortController.lb_provision method 2024-06-03 16:12:05 +03:00
c46edd4f86 Merge branch 'BANS-323' into 'dev_rc-5.2.6'
Add logic for processing vins_name parameter

See merge request rudecs/dev/decort-ansible!52
2024-06-03 12:13:07 +00:00
9ce5a3d711 Add logic for processing vins_name parameter 2024-06-03 15:09:35 +03:00
38757aa902 Merge branch 'BANS-322' into 'dev_rc-5.2.6'
Fix access to acc_id class attrubute in decort_lb.__init__ method

See merge request rudecs/dev/decort-ansible!51
2024-06-02 19:31:23 +00:00
56f7f354c1 Fix access to acc_id class attrubute in decort_lb.__init__ method 2024-06-02 22:27:52 +03:00
2b09b9449a Merge branch 'BANS-321' into 'dev_rc-5.2.6'
Fix if-condition for rg_name parameter check

See merge request rudecs/dev/decort-ansible!50
2024-06-02 19:20:29 +00:00
eb0766b15f Fix if-condition for rg_name parameter check 2024-06-02 22:18:58 +03:00
d7a32376db Merge branch 'dev_rc-5.2.6' into 'rc-5.2.6'
osimage module bugs fix

See merge request rudecs/dev/decort-ansible!49
2024-05-30 13:50:41 +00:00
8a9c354f20 Merge branch 'BANS-304' into 'dev_rc-5.2.6'
Fix if-condition for executing logic of virtual image link changing

See merge request rudecs/dev/decort-ansible!48
2024-05-30 13:27:57 +00:00
1e994410fe Fix if-condition for executing logic of virtual image link changing 2024-05-30 16:24:28 +03:00
e2bbeb7ffb Merge branch 'BANS-303' into 'dev_rc-5.2.6'
Add logic for virtual image renaming in decort_osimage class

See merge request rudecs/dev/decort-ansible!47
2024-05-30 13:08:15 +00:00
2777059b6b Add logic for virtual image renaming in decort_osimage class 2024-05-30 16:06:25 +03:00
38f11ee480 Merge branch 'BANS-300' into 'dev_rc-5.2.6'
Fix access to API response image list in DecortController.virt_image_find method

See merge request rudecs/dev/decort-ansible!46
2024-05-30 11:44:21 +00:00
dc24c0e7ee Merge branch 'BANS-296' into 'dev_rc-5.2.6'
Exclude failed setting in case when image not found in DecortController.image_find method

See merge request rudecs/dev/decort-ansible!41
2024-05-30 11:36:30 +00:00
9a5fd176f0 Merge branch 'BANS-297' into 'dev_rc-5.2.6'
Fix account_Id parameter value for call of decort_osimage.decort_image_create method

See merge request rudecs/dev/decort-ansible!42
2024-05-30 11:35:47 +00:00
279d60083d Merge branch 'BANS-299' into 'dev_rc-5.2.6'
Fix image renaming logic in decort_osimage.__init__ method

See merge request rudecs/dev/decort-ansible!43
2024-05-30 11:35:14 +00:00
1d53fd3213 Merge branch 'BANS-301' into 'dev_rc-5.2.6'
Exclude 'failed' setting in case when image not found in DecortController.virt_image_find method

See merge request rudecs/dev/decort-ansible!44
2024-05-30 11:34:33 +00:00
a0da034499 Merge branch 'BANS-302' into 'dev_rc-5.2.6'
Fix if-condition logic in main function in decort_osimage.py

See merge request rudecs/dev/decort-ansible!45
2024-05-30 11:34:00 +00:00
e5504b3ac9 Add if-condition logic correction in main function in decort_osimage.py 2024-05-30 14:30:10 +03:00
2eb43815e7 Fix if-condition logic in main function in decort_osimage.py 2024-05-29 19:02:27 +03:00
2f9716a51d Merge branch 'BANS-295' into 'rc-5.2.6'
Add validated_image_id check to if-condition of deleting image logic

See merge request rudecs/dev/decort-ansible!40
2024-05-29 15:50:14 +00:00
aa96af1455 Exclude 'failed' setting in case when image not found in DecortController.virt_image_find method 2024-05-29 18:11:05 +03:00
a39ab95c1f Fix access to API response image list in DecortController.virt_image_find method 2024-05-29 14:55:47 +03:00
76a1ff1788 Fix image renaming logic in decort_osimage.__init__ method 2024-05-29 13:18:12 +03:00
9dc3a5e780 Fix account_Id parameter value for call of decort_osimage.decort_image_create method 2024-05-28 17:31:44 +03:00
ff32b509f8 Exclude failed setting in case when image not found in DecortController.image_find method 2024-05-28 16:41:17 +03:00
5b731d009b Add validated_image_id check to if-condition of deleting image logic 2024-05-28 16:26:42 +03:00
57dba89b5a Merge branch 'BANS-283' into 'rc-5.2.6'
Fix if-condition in DecortController.pfw_configure method for excluding case of TypeError

See merge request rudecs/dev/decort-ansible!39
2024-05-27 09:41:08 +00:00
007c7f4bad Fix if-condition in DecortController.pfw_configure method for excluding case of TypeError 2024-05-27 12:20:33 +03:00
Алексей Даньков
7f87642b47 Merge branch 'BANS-264' into 'rc-5.2.6'
Remove if-condition for check mode from DecortController.disk_find method

See merge request rudecs/dev/decort-ansible!38
2024-05-27 07:50:33 +00:00
2873e4da82 Remove if-condition for check mode from DecortController.disk_find method 2024-05-24 14:08:43 +03:00
9c97f4b645 Merge branch 'BANS-260' into 'rc-5.2.6'
Fix call of DecortController.disk_rename method in decort_disk.action method

See merge request rudecs/dev/decort-ansible!37
2024-05-23 16:02:04 +00:00
772f389d98 Fix call of DecortController.disk_rename method in decort_disk.action method 2024-05-23 19:00:29 +03:00
36e1383c7e Merge branch 'BANS-255' into 'rc-5.2.6'
Fix disk deleting logic for permanently deleting from recycle bin

See merge request rudecs/dev/decort-ansible!36
2024-05-23 13:51:37 +00:00
35fe2bdf0e Fix disk deleting logic for permanently deleting from recycle bin 2024-05-23 16:41:45 +03:00
7e7a4898ec Merge branch 'BANS-250' into 'rc-5.2.6'
Fix call of DecortController.disk_share method in decort_disk.create method

See merge request rudecs/dev/decort-ansible!35
2024-05-23 10:58:11 +00:00
fde986bb42 Fix call of DecortController.disk_share method in decort_disk.create method 2024-05-23 13:56:35 +03:00
538c7e6f47 Merge branch 'BANS-249' into 'rc-5.2.6'
Fix call of DecortController.disk_limitIO method in decort_disk.create method

See merge request rudecs/dev/decort-ansible!34
2024-05-22 16:36:26 +00:00
53634b7fa5 Fix call of DecortController.disk_limitIO method in decort_disk.create method 2024-05-22 19:34:40 +03:00
b68e562fc1 Merge branch 'BANS-248' into 'rc-5.2.6'
Fix incorrect access to MIN_IOPS variable in DecortController.disk_check_iotune_arg method

See merge request rudecs/dev/decort-ansible!33
2024-05-22 15:03:08 +00:00
df619e7998 Fix incorrect access to MIN_IOPS variable in DecortController.disk_check_iotune_arg method 2024-05-22 17:59:38 +03:00
Алексей Даньков
877b84d650 Merge branch 'BANS-243' into 'rc-5.2.6'
Add check mode logic to DecortController.k8s_workers_modify method.

See merge request rudecs/dev/decort-ansible!31
2024-05-22 11:00:13 +00:00
10b98b7c4a Merge branch 'BANS-246' into 'rc-5.2.6'
Fix call of AnsibleModule.fail_json method in decort_disk.__init__ method.

See merge request rudecs/dev/decort-ansible!32
2024-05-22 10:05:13 +00:00
51707cbb69 Fix call of AnsibleModule.fail_json method in decort_disk.__init__ method. 2024-05-22 12:53:22 +03:00
3f4cfd40d6 Add check mode logic to DecortController.k8s_workers_modify method. 2024-05-22 11:50:06 +03:00
Алексей Даньков
4cf0316dad Merge branch 'BANS-236' into 'rc-5.2.6'
Move execution of getConfig before if-condition of check mode in decort_k8s.package_facts method

See merge request rudecs/dev/decort-ansible!30
2024-05-21 14:59:44 +00:00
abcb7d52f8 Move execution of getConfig before if-condition of check mode in decort_k8s.package_facts method 2024-05-21 16:17:12 +03:00
Алексей Даньков
8a6d624069 Merge branch 'BANS-226' into 'rc-5.2.6'
Fix logic of starting k8s after restore it from recycle bin

See merge request rudecs/dev/decort-ansible!29
2024-05-21 08:08:12 +00:00
Алексей Даньков
2b96881849 Merge branch 'BANS-225' into 'rc-5.2.6'
Fix logic for 'changed' setting in DecortController.k8s_workers_modify method

See merge request rudecs/dev/decort-ansible!28
2024-05-21 08:07:28 +00:00
6725e4342e Fix logic of starting k8s after restore it from recycle bin 2024-05-20 20:45:45 +03:00
5003991cf5 Fix logic for 'changed' setting in DecortController.k8s_workers_modify method 2024-05-20 16:37:41 +03:00
8a99097b6c Merge branch 'BANS-221' into 'rc-5.2.6'
Fix parameter name 'vinsId' for API request in DecortController.k8s_provision method

See merge request rudecs/dev/decort-ansible!27
2024-05-20 09:52:44 +00:00
6a99ea4d85 Fix parameter name 'vinsId' for API request in DecortController.k8s_provision method 2024-05-20 12:41:06 +03:00
236c3b6d26 Merge branch 'BANS-218' into 'rc-5.2.6'
Fix logic for permanently deleting k8s from recycle bin

See merge request rudecs/dev/decort-ansible!26
2024-05-17 13:42:35 +00:00
bcf384a910 Fix logic for permanently deleting k8s from recycle bin 2024-05-17 16:37:35 +03:00
8cf3d05d0b Merge branch 'BANS-216' into 'rc-5.2.6'
Fix logic for the case when DecortController.k8s_provision method returns 0

See merge request rudecs/dev/decort-ansible!25
2024-05-17 11:26:18 +00:00
5da120f2d3 Fix logic for the case when DecortController.k8s_provision method returns 0 2024-05-17 14:21:17 +03:00
6abd78882c Merge branch 'BANS-202' into 'rc-5.2.6'
Add rg_name parameter to the arguments of the DecortController.rg_find method...

See merge request rudecs/dev/decort-ansible!22
2024-05-08 13:29:15 +00:00
7fa2d07ab0 Merge branch 'BANS-203' into 'rc-5.2.6'
Change cluster_conf, kublet_conf, kubeproxy_conf, join_conf decort_k8s parameter types to dict.

See merge request rudecs/dev/decort-ansible!23
2024-05-08 13:29:05 +00:00
8b407c6f69 Change cluster_conf, kublet_conf, kubeproxy_conf, join_conf decort_k8s parameter types to dict. 2024-05-08 16:06:39 +03:00
4c4be07550 Add rg_name parameter to the arguments of the DecortController.rg_find method call in library/decort_k8s.py 2024-05-08 14:34:33 +03:00
Алексей Даньков
eb542fa46c Merge branch 'BANS-197' into 'rc-5.2.6'
Assign upload_files variable outside of if construction in DecortController.k8s_provision method

See merge request rudecs/dev/decort-ansible!21
2024-05-07 07:06:12 +00:00
240e2ce2df Assign upload_files variable outside of if construction in DecortController.k8s_provision method 2024-05-06 18:48:08 +03:00
Алексей Даньков
f4fcf5b7b7 Merge branch 'fix/rc-5.2.6/jira_bans-39' into 'rc-5.2.6'
(Jira BANS-39) Add NoneType check for 'aaff' argument of the 'Decort.Controller.compute_affinity' method.

See merge request rudecs/dev/decort-ansible!20
2024-04-16 11:12:37 +00:00
Алексей Даньков
d2dabdb194 Merge branch 'fix/rc-5.2.6/jira_bans-38' into 'rc-5.2.6'
(Jira BANS-38) Add NoneType check for 'aff' argument of the 'Decort.Controller.compute_affinity' method.

See merge request rudecs/dev/decort-ansible!19
2024-04-16 11:10:56 +00:00
694e68fe22 Add NoneType check for the 'aaff' argument to Affinity Rules management logic of the 'Decort.Controller.compute_affinity' method. 2024-04-15 18:02:50 +03:00
9e7a33a44a Add NoneType check for the 'aff' argument to Affinity Rules management logic of the 'Decort.Controller.compute_affinity' method. 2024-04-15 17:03:51 +03:00
Алексей Даньков
31b72b3806 Merge branch 'fix/rc-5.2.6/decort_utils.py/1' into 'rc-5.2.6'
Enable output of deleted RGs for API request /restmachine/cloudapi/rg/list in...

See merge request rudecs/dev/decort-ansible!18
2024-04-11 12:21:58 +00:00
Алексей Даньков
fa79b90269 Merge branch 'fix/rc-5.2.6/decort_utils.py/2' into 'rc-5.2.6'
Add status DESTROYED for condition excluding of API request...

See merge request rudecs/dev/decort-ansible!17
2024-04-11 12:21:16 +00:00
Алексей Даньков
aa9f26bf1a Merge branch 'fix/rc-5.2.6/decort_rg.py/1' into 'rc-5.2.6'
Add check for "rg_name" parameter in case of resource group creating

See merge request rudecs/dev/decort-ansible!16
2024-04-11 12:20:41 +00:00
6cd828d031 Add status DESTROYED for condition excluding of API request /restmachine/cloudapi/rg/getResourceConsumption in the DecortController._rg_get_by_id method. 2024-04-05 12:17:05 +03:00
d50509e0c3 Enable output of deleted RGs for API request /restmachine/cloudapi/rg/list in the DecortController.rg_find method. 2024-04-05 11:10:40 +03:00
a45ab19d38 Add check for "rg_name" parameter in case of resource group creating 2024-04-04 18:30:07 +03:00
Алексей Даньков
a3c1dcad7a Merge branch 'fix/rc-5.2.6/decort_rg.py/1' into 'rc-5.2.6'
Add execution of restoring in the case of state=disabled and state=enabled

See merge request rudecs/dev/decort-ansible!15
2024-04-04 13:14:18 +00:00
Алексей Даньков
9bf50c958f Merge branch 'fix/decort_utils.py/_rg_get_by_id/1' into 'rc-5.2.6'
Add call of API /cloudapi/rg/getResourceConsumption to...

See merge request rudecs/dev/decort-ansible!14
2024-04-04 13:13:00 +00:00
Алексей Даньков
d769119ade Merge branch 'fix/rc-5.2.6/decort_utils.py/2' into 'rc-5.2.6'
Fix value for query_key_map['disk'] in method DecortController.rg_update: 'CU_D' -> 'CU_DM'

See merge request rudecs/dev/decort-ansible!13
2024-04-04 13:10:56 +00:00
200e8f7151 Add execution of restoring in the case of state=disabled and state=enabled 2024-04-04 15:53:29 +03:00
f11ec8fefb Update logic to only request RG resources info if the RG is not deleted. 2024-04-04 11:11:18 +03:00
19534384a8 Fix value for query_key_map['disk'] in method DecortController.rg_update: 'CU_D' -> 'CU_DM' 2024-04-03 13:18:53 +03:00
1304a0fcbf Add call of API /cloudapi/rg/getResourceConsumption to DecortController._rg_get_by_id and update its logic and code style 2024-04-03 11:00:56 +03:00
Алексей Даньков
a5f03389f2 Merge branch 'fix/rc-5.2.6/decort_utils.py_decort_api_call' into 'rc-5.2.6'
Add response text to error msg of decort_api_call method

See merge request rudecs/dev/decort-ansible!11
2024-03-25 12:34:54 +00:00
Алексей Даньков
6760167e4e Merge branch 'fix/rc-5.2.6/decort_utils.py_compute_resize' into 'rc-5.2.6'
Remove new_ram value conversion from GB to MB

See merge request rudecs/dev/decort-ansible!9
2024-03-25 12:29:56 +00:00
Алексей Даньков
2b4ba7ee55 Merge branch 'fix/rc-5.2.6/decort_kvmvm.py' into 'rc-5.2.6'
Exclude call of compute_bootdisk_size method with new_size=None

See merge request rudecs/dev/decort-ansible!8
2024-03-25 12:25:04 +00:00
Алексей Даньков
5b66c98cc6 Merge branch 'fix/rc-5.2.6/decort_utils.py' into 'rc-5.2.6'
Fix account vins find logic

See merge request rudecs/dev/decort-ansible!7
2024-03-25 12:24:39 +00:00
Алексей Даньков
55268beaad Merge branch 'fix/rc-5.2.6/decort_rg' into 'rc-5.2.6'
bug fix

See merge request rudecs/dev/decort-ansible!6
2024-03-25 12:23:53 +00:00
b18bdef269 fix code style 2024-03-22 18:54:57 +03:00
21e853c1f2 Add response text to error msg of decort_api_call method 2024-03-22 18:42:44 +03:00
1c6b46c535 remove new_ram value conversion from GB to MB 2024-03-22 17:01:50 +03:00
058de4884f exclude call of compute_bootdisk_size method with new_size=None 2024-03-22 13:39:06 +03:00
Dmitriy Smirnov
d622dd8453 fix account vins find logic 2024-03-20 14:58:02 +03:00
Dmitriy Smirnov
e26011ab20 bug fix 2024-03-18 19:27:32 +03:00
Alex_geth
f2e9b550bb cloud-init example 2023-11-07 16:52:17 +03:00
Alex_geth
a8f50bfb6b k8s/lb update && bug fixes 2023-11-07 16:45:37 +03:00
49 changed files with 12771 additions and 5384 deletions

170
CHANGELOG.md Normal file
View File

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

View File

@@ -1,12 +1,23 @@
# decort-ansible
Ansible modules for Digital Energy Orchestration Technology (DECORT) platform v3.6.1 and above.
Модули Ansible для платформы Digital Energy Orchestration Technology (DECORT).
Note that this module may produce unreliable results when used with older DECORT API versions.
## Соответствие версий платформы версиям модулей Ansible
Requirements:
* Ansible 2.7 or higher
* Python 3.7 or higher
* PyJWT 2.0.0 Python module or higher
* requests Python module
* netaddr Python module
* DECORT cloud platform version 3.8.7 or higher
| Версия платформы | Версия модулей Ansible |
|:----------------:|:--------------------------:|
| 4.5.0 | 11.0.x |
| 4.4.0 | 10.0.x |
| 4.4.0 build 963 | 9.0.x |
| 4.3.0 | 8.0.x |
| 4.2.0 | 7.0.x, 7.1.x, 7.2.x |
| 4.1.0 | 6.0.x, 6.1.x |
| 4.0.0 | 5.6.x, 5.5.x, 5.4.x, 5.3.x |
| 3.8.8, 3.8.9 | 5.2.6 |
| 3.8.7 | 5.2.5 |
| 3.8.6 | 5.2.4 |
## Ссылки
- [Документация](https://repository.basistech.ru/BASIS/wiki-decort-ansible/src/branch/main/Home.md)
- [Список изменений](./CHANGELOG.md)

View File

@@ -1,36 +0,0 @@
---
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
name: affinity_example
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
rg_id: # Resource group id
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" # Name of OS image
networks:
- type: VINS
id: # VINS id
tags: "Ansible cloud init example"
aff_lable: "Affinity lable"
tag:
- key: bd
value: main
aff_rule:
- key: app
value: main
topology: compute
policy: REQUIRED
mode: EQ
state: present
delegate_to: localhost
register: simple_vm

View File

@@ -1,36 +0,0 @@
---
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
name: anti-affinity_example
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
rg_id: # Resource group id
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
networks:
- type: VINS
id: #VINS id
tags: "Ansible cloud init example"
aff_lable: "Anti affinity lable"
tag:
- key: bd
value: main
aaff_rule:
- key: app
value: main
topology: compute
policy: REQUIRED
mode: ANY
state: present
delegate_to: localhost
register: simple_vm

View File

@@ -1,38 +0,0 @@
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
name: cloud-init_example
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
networks:
- type: VINS
id: #VINS id
tags: "Ansible cloud init example"
state: present
rg_id: #Resource group id
ci_user_data:
- packages:
- apache2
- write_files:
- content: |
<div>
Hello World!
</div>
owner: user:user
path: /var/www/html/index.html
- hostname: test-apache
- ssh_keys:
- rsa_public: ssh-rsa AAAAOasDmLxnD= user@pc
delegate_to: localhost
register: simple_vm

View File

@@ -1,38 +0,0 @@
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
name: cloud-init_example
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
networks:
- type: VINS
id: #VINS id
tags: "Ansible cloud init example"
state: present
rg_id: #Resource group id
ci_user_data:
- packages:
- apache2
- write_files:
- content: |
<div>
Hello World!
</div>
owner: user:user
path: /var/www/html/index.html
- hostname: test-apache
- ssh_keys:
- rsa_public: ssh-rsa AAAAOasDmLxnD= user@pc
delegate_to: localhost
register: simple_vm

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,4 +32,4 @@
state: present
rg_id: 99 #Resource group id
delegate_to: localhost
register: simple_vm
register: simple_vm

View File

@@ -15,4 +15,4 @@
- "Secret: {{ response.secret }}"
- "Data: {{ response.data }} (contains secret data & metadata in kv2)"
- "Metadata: {{ response.metadata }}"
- "Full response: {{ response.raw }}"
- "Full response: {{ response.raw }}"

View File

@@ -36,4 +36,4 @@
num: 2
verify_ssl: false
delegate_to: localhost
register: kube
register: kube

425
library/decort_account.py Normal file
View File

@@ -0,0 +1,425 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_account
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Iterable
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortAccount(DecortController):
OBJ = 'account'
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.check_amodule_args()
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_emails=dict(
type='bool',
),
acl=dict(
type='dict',
options=dict(
mode=dict(
type='str',
choices=[
'match',
'revoke',
'update',
],
default='update',
),
users=dict(
type='list',
required=True,
elements='dict',
options=dict(
rights=dict(
type='str',
choices=['R', 'RCX', 'ARCXDU'],
default='R',
),
id=dict(
type='str',
required=True,
),
),
),
),
),
id=dict(
type='int',
),
name=dict(
type='str',
),
get_resource_consumption=dict(
type='bool',
default=False,
),
quotas=dict(
type='dict',
options=dict(
cpu=dict(
type='int',
),
disks_size=dict(
type='int',
),
gpu=dict(
type='int',
),
public_ip=dict(
type='int',
),
ram=dict(
type='int',
),
),
),
state=dict(
type='str',
choices=[
'absent',
'absent_permanently',
'confirmed',
'disabled',
'present',
],
),
sep_pools=dict(
type='list',
elements='dict',
options=dict(
sep_id=dict(
type='int',
required=True,
),
pool_names=dict(
type='list',
required=True,
elements='str',
),
),
),
description=dict(
type='str',
),
default_zone_id=dict(
type='int',
),
),
required_one_of=[
('id', 'name')
],
supports_check_mode=True,
)
def check_amodule_args(self):
"""
Additional Ansible Module arguments validation that
cannot be implemented using Ansible Argument spec.
"""
arg_state = self.aparams['state']
if arg_state is not None and 'absent' in arg_state:
# Parameters or combinations of parameters that can
# cause changing the object.
changing_params = [
'access_emails',
'acl',
['id', 'name'],
'quotas',
]
check_error = False
for elem in changing_params:
if isinstance(elem, str):
param = elem
if self.aparams[elem] is not None:
self.message(
f'If the parameter "state" is set to'
f' "{arg_state}", then using the parameter'
f' "{param}" is not allowed.'
)
check_error = True
elif isinstance(elem, Iterable):
params = elem
params_using = map(
lambda x: self.aparams[x] is not None, params
)
if all(params_using):
params_str = ', '.join(f'"{p}"' for p in params)
self.message(
f'If the parameter "state" is set to'
f' "{arg_state}", then using the combination'
f' of parameters {params_str} are not allowed.'
)
check_error = True
if check_error:
self.exit(fail=True)
def check_amodule_args_for_change(self):
check_error = False
if self.check_aparam_default_zone_id() is False:
check_error = True
if check_error:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.check_amodule_args_for_change()
self.change()
self.exit()
def get_info(self):
# If this is the first getting info
if self._acc_info is None:
self.acc_id, self._acc_info = self.account_find(
account_name=self.aparams['name'],
account_id=self.aparams['id'],
resource_consumption=self.aparams['get_resource_consumption'],
)
# If this is a repeated getting info
else:
# If check mode is enabled, there is no needed to
# request info again
if not self.amodule.check_mode:
self.acc_id, self._acc_info = self.account_find(
account_id=self.acc_id,
resource_consumption=(
self.aparams['get_resource_consumption']
),
)
self.facts = self.acc_info
def change(self):
self.change_state()
self.change_acl()
if self.account_update_args:
self.account_update(account_id=self.acc_id,
**self.account_update_args)
self.get_info()
def change_state(self):
match self._acc_info:
case None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
pass
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.exit(fail=True)
case {'status': 'DESTROYED'}:
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_id,
permanently=True,
already=True,
)
)
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.acc_id)
)
self.exit(fail=True)
case {'status': 'DELETED'}:
match self.aparams:
case {'state': 'absent'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_id,
permanently=False,
already=True,
)
)
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
self.restore()
case {'state': 'disabled'}:
self.restore()
self.disable()
case {'status': 'CONFIRMED'}:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
pass
case {'state': 'disabled'}:
self.disable()
case {'status': 'DISABLED'}:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed'}:
self.enable()
case {'state': 'present' | 'disabled'}:
pass
def delete(self, permanently=False):
self.account_delete(account_id=self.acc_id, permanently=permanently)
self.get_info()
def disable(self):
self.account_disable(account_id=self.acc_id)
self.get_info()
def enable(self):
self.account_enable(account_id=self.acc_id)
self.get_info()
def restore(self):
self.account_restore(account_id=self.acc_id)
self.get_info()
def change_acl(self):
if not self.aparams['acl']:
return
actual_users = {
u['userGroupId']: u['right'] for u in self.acc_info['acl']
}
actual_users_ids = set(actual_users.keys())
aparams_acl = self.aparams['acl']
aparams_users = {u['id']: u['rights'] for u in aparams_acl['users']}
aparams_users_ids = set(aparams_users.keys())
del_users_ids = None
upd_users = None
new_users = None
match aparams_acl:
case {'mode': 'revoke'}:
del_users_ids = aparams_users_ids.intersection(
actual_users_ids,
)
case {'mode': 'update' | 'match' as mode}:
new_users_ids = aparams_users_ids.difference(
actual_users_ids,
)
new_users = dict(
u for u in aparams_users.items() if u[0] in new_users_ids
)
upd_users_ids =\
aparams_users_ids.intersection(actual_users_ids)
upd_users = dict()
for id in upd_users_ids:
if actual_users[id] == 'CXDRAU':
actual_user_rights = 'ARCXDU'
else:
actual_user_rights = actual_users[id]
if actual_user_rights != aparams_users[id]:
upd_users[id] = aparams_users[id]
if mode == 'match':
del_users_ids =\
actual_users_ids.difference(aparams_users_ids)
if del_users_ids or new_users or upd_users:
self.account_change_acl(account_id=self.acc_id,
del_users=del_users_ids,
add_users=new_users,
upd_users=upd_users)
self.get_info()
@property
def account_update_args(self) -> dict:
result_args = dict()
aparam_access_emails = self.aparams['access_emails']
if (aparam_access_emails is not None
and self.acc_info['sendAccessEmails'] != aparam_access_emails):
result_args['access_emails'] = aparam_access_emails
aparam_name = self.aparams['name']
if (self.aparams['id'] and aparam_name
and self.acc_info['name'] != aparam_name):
result_args['name'] = aparam_name
aparam_quotas = self.aparams['quotas']
if aparam_quotas:
quotas_naming = [
['cpu', 'CU_C', 'cpu_quota'],
['disks_size', 'CU_DM', 'disks_size_quota'],
['gpu', 'gpu_units', 'gpu_quota'],
['public_ip', 'CU_I', 'public_ip_quota'],
['ram', 'CU_M', 'ram_quota'],
]
for aparam, info_key, result_arg in quotas_naming:
current_value = int(self.acc_info['resourceLimits'][info_key])
if (aparam_quotas[aparam] is not None
and current_value != aparam_quotas[aparam]):
result_args[result_arg] = aparam_quotas[aparam]
aparam_sep_pools = self.aparams['sep_pools']
if aparam_sep_pools is not None:
sep_pools = set()
for sep in aparam_sep_pools:
for pool_name in sep['pool_names']:
sep_pools.add(
f'{sep["sep_id"]}_{pool_name}'
)
if set(self.acc_info['uniqPools']) != sep_pools:
result_args['sep_pools'] = sep_pools
aparam_desc = self.aparams['description']
if (
aparam_desc is not None
and self.acc_info['description'] != aparam_desc
):
result_args['description'] = aparam_desc
aparam_default_zone_id = self.aparams['default_zone_id']
if (
aparam_default_zone_id is not None
and self.acc_info['defaultZoneId'] != aparam_default_zone_id
):
result_args['default_zone_id'] = aparam_default_zone_id
return result_args
def check_aparam_default_zone_id(self) -> bool | None:
aparam_default_zone_id = self.aparams['default_zone_id']
if aparam_default_zone_id is not None:
if aparam_default_zone_id in self.acc_zone_ids:
return True
else:
self.message(
'Check for parameter "default_zone_id" failed: '
f'zone ID {aparam_default_zone_id} not available '
f'for account ID {self.acc_id}.'
)
return False
def main():
DecortAccount().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,54 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_account_info
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
def main():
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__':
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

@@ -1,30 +1,26 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Alexey Dankov (alexey Dankov@digitalenergy.online)
DOCUMENTATION = r'''
---
module: decort_bservice
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_bservice(DecortController):
def __init__(self,arg_amodule):
super(decort_bservice, self).__init__(arg_amodule)
def __init__(self):
super(decort_bservice, self).__init__(AnsibleModule(**self.amodule_init_args))
arg_amodule = self.amodule
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
self.bservice_info = None
self.is_bservice_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
@@ -32,7 +28,7 @@ class decort_bservice(DecortController):
self.fail_json(**self.result)
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
validated_acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
@@ -43,8 +39,11 @@ class decort_bservice(DecortController):
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
validated_rg_id, validated_rg_facts = self.rg_find(
arg_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name']
)
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
@@ -52,21 +51,25 @@ class decort_bservice(DecortController):
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
self.bservice_id,self.bservice_info = self.bservice_find(
self.acc_id,
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
validated_acc_id = validated_rg_facts['accountId']
self.bservice_id, self.bservice_info = self.bservice_find(
validated_acc_id,
validated_rg_id,
arg_amodule.params['name'],
arg_amodule.params['id']
)
if self.bservice_id == 0:
self.bservice_should_exist = False
else:
self.acc_id = validated_acc_id or self.bservice_info['accountId']
if self.bservice_id and self.bservice_info['status'] != 'DESTROYED':
self.bservice_should_exist = True
self.check_amodule_args_for_change()
else:
self.bservice_should_exist = False
self.check_amodule_args_for_create()
def nop(self):
"""No operation (NOP) handler for B-service.
@@ -76,7 +79,7 @@ class decort_bservice(DecortController):
"""
self.result['failed'] = False
self.result['changed'] = False
if self.k8s_id:
if self.bservice_id:
self.result['msg'] = ("No state change required for B-service ID {} because of its "
"current status '{}'.").format(self.bservice_id, self.bservice_info['status'])
else:
@@ -105,15 +108,25 @@ class decort_bservice(DecortController):
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['sshuser'],
self.amodule.params['sshkey']
self.amodule.params['sshkey'],
zone_id=self.aparams['zone_id'],
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.bservice_state(self.bservice_info,'enabled',self.amodule.params['started'])
self.bservice_state(self.bservice_info, self.aparams['state'])
self.bservice_should_exist = True
return
def action(self,d_state,started=False):
self.bservice_state(self.bservice_info,d_state,started)
def action(self,d_state):
self.bservice_state(self.bservice_info,d_state)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.bservice_info['zoneId']:
self.bservice_migrate_to_zone(
bs_id=self.bservice_id,
zone_id=aparam_zone_id,
)
return
def restore(self):
@@ -147,143 +160,181 @@ class decort_bservice(DecortController):
ret_dict['techStatus'] = self.bservice_info['techStatus']
ret_dict['state'] = self.bservice_info['status']
ret_dict['rg_id'] = self.bservice_info['rgId']
ret_dict['account_id'] = self.acc_id
ret_dict['groupsName'] = self.bservice_info['groupsName']
ret_dict['groupsIds'] = self.bservice_info['groups']
ret_dict['account_id'] = self.bservice_info['accountId']
ret_dict['groups'] = self.bservice_info['groups']
ret_dict['zone_id'] = self.bservice_info['zoneId']
return ret_dict
@staticmethod
def build_parameters():
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','check']),
started=dict(type='bool', required=False, default=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
sshuser=dict(type='str', required=False,default=None),
sshkey=dict(type='str', required=False,default=None),
id=dict(type='int', required=False, default=0),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
description=dict(type='str', default="Created by decort ansible module"),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
default='',
),
state=dict(
type='str',
choices=[
'absent',
'disabled',
'enabled',
'present',
'started',
'stopped',
],
),
name=dict(
type='str',
default='',
),
sshuser=dict(
type='str',
),
sshkey=dict(
type='str',
),
id=dict(
type='int',
default=0,
),
rg_id=dict(
type='int',
default=0,
),
rg_name=dict(
type='str',
default='',
),
zone_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
('id', 'name'),
('rg_id', 'rg_name'),
],
)
def check_amodule_args_for_change(self):
check_errors = False
self.is_bservice_stopped_or_will_be_stopped = (
(
self.bservice_info['techStatus'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
self.bservice_info['techStatus'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != self.bservice_info['zoneId']
and not self.is_bservice_stopped_or_will_be_stopped
):
check_errors = True
self.message(
'Check for parameter "zone_id" failed: '
'Basic Service must be stopped to migrate to a zone.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if self.amodule.check_mode:
self.result['changed'] = False
if self.bservice_id:
self.result['failed'] = False
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
# we exit the module at this point
else:
self.result['failed'] = True
self.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'],
amodule.params['rg_name'],
amodule.params['rg_id'],
amodule.params['account_name'])
amodule.fail_json(**self.result)
pass
#MAIN MANAGE PART
if self.bservice_id:
if self.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
self.error()
elif self.bservice_info['status'] == "DELETED":
if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped'
):
self.restore(self.bservice_id)
self.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
self.nop()
elif self.bservice_info['status'] in (
'ENABLED', 'DISABLED', 'CREATED',
):
if amodule.params['state'] == 'absent':
self.destroy()
else:
self.action(amodule.params['state'])
elif self.bservice_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
self.create()
self.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
self.nop()
else:
state = amodule.params['state']
if state is None:
state = 'present'
if state == 'absent':
self.nop()
if state in ('present','started'):
self.create()
elif state in ('stopped', 'disabled','enabled'):
self.error()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.bservice_should_exist:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
def main():
module_parameters = decort_bservice.build_parameters()
decort_bservice().run()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
['rg_id','rg_name']
],
)
subj = decort_bservice(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.bservice_id:
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'],
amodule.params['rg_name'],
amodule.params['rg_id'],
amodule.params['account_name'])
amodule.fail_json(**subj.result)
pass
#MAIN MANAGE PART
if subj.bservice_id:
if subj.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.bservice_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
subj.restore(subj.bservice_id)
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.bservice_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'],amodule.params['started'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.bservice_info['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.bservice_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.bservice_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()
if __name__ == '__main__':
main()

View File

@@ -1,295 +1,64 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_disk
short_description: Manage Disks (virtualized storage resources) in DECORT cloud
description: >
This module can be used to create new disk in DECORT cloud platform, obtain or
modify its characteristics, and delete it.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.6.1 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
options:
account_id:
description:
- ID of the account, which owns this disk. This is the alternative to I(account_name) option.
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
default: 0
required: no
account_name:
description:
- 'Name of the account, which will own this disk.'
- 'This parameter is ignored if I(account_id) is specified.'
default: empty string
required: no
annotation:
description:
- Optional text description of this disk.
default: empty string
required: no
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
controller_url:
description:
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
id:
description:
- `ID of the disk to manage. If I(id) is specified it is assumed, that this disk already
exists. In other words, you cannot create new disk by specifying its ID, use I(name)
when creating new disk.`
- `If non-zero I(id) is specified, then I(name), I(account_id) and I(account_name)
are ignored.`
default: 0
required: no
name:
description:
- `Name of the disk to manage. To manage disk by name you also need to specify either
I(account_id) or I(account_name).`
- If non-zero I(id) is specified, I(name) is ignored.
- `Note that the platform does not enforce uniqueness of disk names, so if more than one
disk with this name exists under the specified account, module will return the first
occurence.`
default: empty string
required: no
force_detach:
description:
- `By default it is not allowed to delete or destroy disk that is currently attached to a compute
instance (e.g. virtual machine or bare metal server). Set this argument to true to change this
behavior.`
- This argument is meaningful for I(state=absent) operations only and ignored otherwise.
default: false
required: no
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
place_with:
description:
- `This argument can be used to simplify data disks creation along with a new compute, by placing
disks in the same storage, where corresponding OS image is deployed.`
- `Specify ID of an OS image, and the newly created disk will be provisioned from the same
storage, where this OS image is located. You may optionally specify I(pool) to control
actual disk placement within that storage, or leave I(pool=default) to let platform manage
it automatically.`
- This parameter is used when creating new disks and ignored for all other operations.
- This is an alternative to specifying I(sep_id).
default: 0
required: no
pool:
description:
- Name of the pool where to place new disk. Once disk is created, its pool cannot be changed.
- This parameter is used when creating new disk and igonred for all other operations.
default: empty string
required: no
sep_id:
description:
- `ID of the Storage Endpoint Provider (SEP) where to place new disk. Once disk is created,
its SEP cannot be changed.`
- `You may think of SEP as an identifier of a storage system connected to DECORT platform. There
may be several different storage systems and, consequently, several SEPs available to choose from.`
- This parameter is used when creating new disk and igonred for all other operations.
- See also I(place_with) for an alternative way to specify disk placement.
default: 0
required: no
size:
description:
- Size of the disk in GB. This parameter is mandatory when creating new disk.
- `If specified for an existing disk, and it is greater than current disk size, platform will try to resize
the disk on the fly. Downsizing disk is not allowed.`
required: no
limitIO:
description:
- Disk input / output limit, used to limit the speed of interaction with the disk.
required: no
type:
description:
- Type of the disk.
- `Disks can be of the following types: "D"-Data, "B"-Boot, "T"-Tmp.`
default: "D"
required: no
state:
description:
- Specify the desired state of the disk at the exit of the module.
- 'If desired I(state=present):'
- ' - Disk does not exist or is in [DESTROYED, PURGED] states, create new disk according to the specifications.'
- ' - Disk is in DELETED state, restore it and change size if necessary.'
- ' - Disk is in one of [CREATED, ASSIGNED] states, do nothing.'
- ' - Disk in any other state, abort with an error.'
- 'If desired I(state=absent):'
- ' - Disk is in one of [CREATED, ASSIGNED, DELETED] states, destroy it.'
- ' - Disk not found or in [DESTROYED, PURGED] states, do nothing.'
- ' - Disk in any other state, abort with an error.'
default: present
choices: [ absent, present ]
user:
description:
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
EXAMPLES = '''
- name: create new Disk named "MyDataDisk01" of size 50 GB, on SEP ID 1, in default pool, under the account "MyAccount".
decort_vins:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://cloud.digitalenergy.online"
name: "MyDataDisk01"
sep_id: 1
size: 50
account_name: "MyAccount"
state: present
delegate_to: localhost
register: my_disk
'''
RETURN = '''
facts:
description: facts about the disk
returned: always
type: dict
sample:
facts:
id: 50
name: data01
size: 10
sep_id: 1
pool: datastore
state: ASSIGNED
account_id: 7
attached_to: 18
gid: 1001
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_disk(DecortController):
def __init__(self,arg_amodule):
super(decort_disk, self).__init__(arg_amodule)
def __init__(self):
super(decort_disk, self).__init__(AnsibleModule(**self.amodule_init_args))
arg_amodule = self.amodule
validated_acc_id = 0
validated_acc_info = None
validated_disk_id = 0
self.disk_id = 0
self.account_id = 0
validated_disk_facts = None
# limitIO check for exclusive parameters
if arg_amodule.params['limitIO']:
self.disk_check_iotune_arg(arg_amodule.params['limitIO'])
if arg_amodule.params['id'] or arg_amodule.params['name']:
if arg_amodule.params['account_id'] or arg_amodule.params['account_name'] :
validated_acc_id,validated_acc_info = self.account_find(arg_amodule.params['account_name'],arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
else:
self.acc_id = validated_acc_id
self.acc_info = validated_acc_info
validated_disk_id,validated_disk_facts = self.disk_find(
disk_id=arg_amodule.params['id'],
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id,
check_state=False,
if not arg_amodule.params['id']:
if (
not arg_amodule.params['account_id']
and not arg_amodule.params['account_name']
):
self.result['changed'] = False
self.result['msg'] = (
'Cannot manage Disk by name without specifying account ID '
'or name.'
)
else:
self.result['failed'] = True
self.result['msg'] = ("Cannot manage Disk when its ID is 0 and name is empty")
self.fail_json(**self.result)
self.amodule.fail_json(**self.result)
validated_acc_id, validated_acc_info = self.account_find(
arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
self.acc_id = validated_acc_id
self._acc_info = validated_acc_info
if not validated_acc_id:
self.result['changed'] = False
self.result['msg'] = (
f"Current user does not have access to the account "
f"ID {arg_amodule.params['account_id']} / "
f"name '{arg_amodule.params['account_name']}' "
f"or non-existent account specified."
)
self.amodule.fail_json(**self.result)
validated_disk_id, validated_disk_facts = self.disk_find(
disk_id=arg_amodule.params['id'],
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id,
check_state=False,
)
if arg_amodule.params['place_with']:
image_id, image_facts = self.image_find(arg_amodule.params['place_with'], "", 0)
@@ -298,66 +67,130 @@ class decort_disk(DecortController):
self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts
if self.disk_id:
self.acc_id = validated_disk_facts['accountId']
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
def compare_iotune_params(self, new_iotune: dict, current_iotune: dict):
io_fields = sdk_types.IOTuneAPIResultNM.model_fields.keys()
for field in io_fields:
new_value = new_iotune.get(field)
current_value = current_iotune.get(field)
if new_value != current_value:
return False
return True
def limit_io(self, aparam_limit_io: dict):
self.sdk_checkmode(self.api.cloudapi.disks.limit_io)(
disk_id=self.disk_id,
read_bytes_sec_max=(aparam_limit_io.get('read_bytes_sec_max')),
read_bytes_sec=(aparam_limit_io.get('read_bytes_sec')),
read_iops_sec_max=(aparam_limit_io.get('read_iops_sec_max')),
read_iops_sec=(aparam_limit_io.get('read_iops_sec')),
size_iops_sec=(aparam_limit_io.get('size_iops_sec')),
total_bytes_sec_max=(aparam_limit_io.get('total_bytes_sec_max')),
total_bytes_sec=(aparam_limit_io.get('total_bytes_sec')),
total_iops_sec_max=(aparam_limit_io.get('total_iops_sec_max')),
total_iops_sec=(aparam_limit_io.get('total_iops_sec')),
write_bytes_sec_max=(aparam_limit_io.get('write_bytes_sec_max')),
write_bytes_sec=(aparam_limit_io.get('write_bytes_sec')),
write_iops_sec_max=(aparam_limit_io.get('write_iops_sec_max')),
write_iops_sec=(aparam_limit_io.get('write_iops_sec')),
)
def create(self):
self.disk_id = self.disk_create(accountId=self.acc_id,
name = self.amodule.params['name'],
description=self.amodule.params['annotation'],
size=self.amodule.params['size'],
type=self.amodule.params['type'],
iops=self.amodule.params['iops'],
sep_id=self.amodule.params['sep_id'],
pool=self.amodule.params['pool'],
self.disk_id = self.sdk_checkmode(self.api.cloudapi.disks.create)(
account_id=self.acc_id,
name=self.amodule.params['name'],
size_gb=self.amodule.params['size'],
storage_policy_id=self.aparams['storage_policy_id'],
description=self.amodule.params['description'],
sep_id=self.amodule.params['sep_id'],
sep_pool_name=self.amodule.params['pool'],
)
#IO tune
if self.amodule.params['limitIO']:
self.disk_limitIO(self.amodule.params['limitIO'],self.disk_id)
aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
self.limit_io(aparam_limit_io=aparam_limit_io)
#set share status
if self.amodule.params['shareable'] and self.amodule.params['type'] == "D":
self.dick_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,
)
return
def action(self,restore=False):
#restore never be done
if restore:
self.disk_restore(self.disk_id)
self.sdk_checkmode(self.api.cloudapi.disks.restore)(
disk_id=self.disk_id,
)
#rename if id present
if self.amodule.params['name'] != self.disk_info['name']:
self.disk_rename(diskId=self.disk_id,
name=self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
if (
self.amodule.params['name'] is not None
and self.amodule.params['name'] != self.disk_info['name']
):
self.rename()
#resize
if self.amodule.params['size'] != self.disk_info['sizeMax']:
self.disk_resize(self.disk_info,self.amodule.params['size'])
if (
self.amodule.params['size'] is not None
and self.amodule.params['size'] != self.disk_info['sizeMax']
):
self.sdk_checkmode(self.api.cloudapi.disks.resize2)(
disk_id=self.disk_id,
disk_size_gb=self.amodule.params['size'],
)
#IO TUNE
if self.amodule.params['limitIO']:
clean_io = [param for param in self.amodule.params['limitIO'] \
if self.amodule.params['limitIO'][param] == None]
for key in clean_io: del self.amodule.params['limitIO'][key]
if self.amodule.params['limitIO'] != self.disk_info['iotune']:
self.disk_limitIO(self.disk_id,self.amodule.params['limitIO'])
aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
if aparam_limit_io:
if not self.compare_iotune_params(
new_iotune=aparam_limit_io,
current_iotune=self.disk_info['iotune'],
):
self.limit_io(aparam_limit_io=aparam_limit_io)
#share check/update
#raise Exception(self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable'] and \
self.amodule.params['type'] == "D":
self.disk_share(self.disk_id,self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable']:
if self.amodule.params['shareable']:
self.sdk_checkmode(self.api.cloudapi.disks.share)(
disk_id=self.disk_id,
)
else:
self.sdk_checkmode(self.api.cloudapi.disks.unshare)(
disk_id=self.disk_id,
)
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id != self.disk_info['storage_policy_id']
):
self.sdk_checkmode(self.api.ca.disks.change_disk_storage_policy)(
disk_id=self.disk_id,
storage_policy_id=aparam_storage_policy_id,
)
return
def delete(self):
self.disk_id = self.disk_delete(disk_id=self.disk_id,
detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'],
reason=self.amodule.params['reason'])
self.disk_info['status'] = "DELETED"
self.sdk_checkmode(self.api.cloudapi.disks.delete)(
disk_id=self.disk_id,
detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'],
)
self.disk_id, self.disk_info = self._disk_get_by_id(self.disk_id)
return
def rename(self):
self.disk_rename(diskId = self.disk_id,
name = self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
self.sdk_checkmode(self.api.cloudapi.disks.rename)(
disk_id=self.disk_id,
name=self.amodule.params['name'],
)
return
def nop(self):
@@ -380,7 +213,6 @@ class decort_disk(DecortController):
account_id=0,
sep_id=0,
pool="none",
attached_to=0,
gid=0
)
@@ -398,145 +230,251 @@ class decort_disk(DecortController):
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['attached_to'] = self.disk_info['vmid']
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']
ret_dict['cache_mode'] = self.disk_info['cache']
ret_dict['blkdiscard'] = self.disk_info['blkdiscard']
return ret_dict
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_disk module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default='Disk by decort_disk'),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
name=dict(type='str', required=False),
force_detach=dict(type='bool', required=False, default=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
place_with=dict(type='int', default=0),
pool=dict(type='str', default=''),
sep_id=dict(type='int', default=0),
size=dict(type='int', default=0),
type=dict(type='str',
required=False,
default="D",
choices=['B', 'D', 'T']),
iops=dict(type='int',required=False,default=2000),
limitIO=dict(type='dict',
options=dict(
total_bytes_sec=dict(required=False,type='int'),
read_bytes_sec=dict(required=False,type='int'),
write_bytes_sec=dict(required=False,type='int'),
total_iops_sec=dict(required=False,type='int'),
read_iops_sec=dict(required=False,type='int'),
write_iops_sec=dict(required=False,type='int'),
total_bytes_sec_max=dict(required=False,type='int'),
read_bytes_sec_max=dict(required=False,type='int'),
write_bytes_sec_max=dict(required=False,type='int'),
total_iops_sec_max=dict(required=False,type='int'),
read_iops_sec_max=dict(required=False,type='int'),
write_iops_sec_max=dict(required=False,type='int'),
size_iops_sec=dict(required=False,type='int'),)),
permanently=dict(type='bool', required=False, default=False),
shareable=dict(type='bool', required=False, default=False),
reason=dict(type='str', required=False,default='Managed by Ansible decort_disk'),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
default=0,
),
account_name=dict(
type='str',
default='',
),
description=dict(
type='str',
),
id=dict(
type='int',
default=0,
),
name=dict(
type='str',
),
force_detach=dict(
type='bool',
default=False,
),
place_with=dict(
type='int',
default=0,
),
pool=dict(
type='str',
default='',
),
sep_id=dict(
type='int',
default=0,
),
size=dict(
type='int',
),
limitIO=dict(
type='dict',
apply_defaults=True,
options=dict(
total_bytes_sec=dict(
type='int',
),
read_bytes_sec=dict(
type='int',
),
write_bytes_sec=dict(
type='int',
),
total_iops_sec=dict(
type='int',
),
read_iops_sec=dict(
type='int',
),
write_iops_sec=dict(
type='int',
),
total_bytes_sec_max=dict(
type='int',
),
read_bytes_sec_max=dict(
type='int',
),
write_bytes_sec_max=dict(
type='int',
),
total_iops_sec_max=dict(
type='int',
),
read_iops_sec_max=dict(
type='int',
),
write_iops_sec_max=dict(
type='int',
),
size_iops_sec=dict(
type='int',
),
),
),
permanently=dict(
type='bool',
default=False,
),
shareable=dict(
type='bool',
default=False,
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'present',
],
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
('id', 'name'),
],
)
def main():
module_parameters = decort_disk.build_parameters()
def check_amodule_args_for_change(self):
check_errors = False
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
)
if self.check_aparam_storage_policy_id() is False:
check_errors = True
if self.check_aparam_size() is False:
check_errors = True
decon = decort_disk(amodule)
#
#Full range of Disk status is as follows:
#
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
#
if decon.disk_id:
#disk exist
if decon.disk_info['status'] in ["MODELED", "CREATING"]:
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(decon.disk_id, decon.disk_info['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] == 'present':
decon.action()
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]:
#re-provision disk
if amodule.params['state'] in ('present'):
decon.create()
else:
decon.nop()
elif decon.disk_info['status'] == "DELETED":
if amodule.params['state'] in ('present'):
decon.action(restore=True)
else:
decon.nop()
else:
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
decon.nop()
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new disk'
)
if self.check_aparam_storage_policy_id() is False:
check_errors = True
if self.check_aparam_size() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_aparam_storage_policy_id(self) -> bool:
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
return not check_errors
def check_aparam_size(self) -> bool:
check_errors = False
aparam_size = self.aparams['size']
if (
aparam_size is not None
and aparam_size <= 0
):
check_errors = True
self.message(
msg=(
'Check for parameter "size" failed: '
f'Disk cannot be size {aparam_size}'
),
)
return not check_errors
@DecortController.handle_sdk_exceptions
def run(self):
#
#Full range of Disk status is as follows:
#
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
#
amodule = self.amodule
if self.disk_id:
#disk exist
if self.disk_info['status'] in ["MODELED", "CREATING"]:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(self.disk_id, self.disk_info['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif self.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent':
self.delete()
elif amodule.params['state'] == 'present':
self.action()
elif self.disk_info['status'] in ["PURGED", "DESTROYED"]:
#re-provision disk
if amodule.params['state'] in ('present'):
self.create()
else:
self.nop()
elif self.disk_info['status'] == "DELETED":
if amodule.params['state'] in ('present'):
self.action(restore=True)
elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']):
self.delete()
else:
self.nop()
else:
decon.create()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed'] and amodule.params['state'] in ('present'):
_, decon.disk_info = decon.disk_find(decon.disk_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
self.nop()
else:
self.create()
if __name__ == "__main__":
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.result['changed']:
_, self.disk_info = self.disk_find(self.disk_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
def main():
decort_disk().run()
if __name__ == '__main__':
main()
#SHARE

155
library/decort_disk_list.py Normal file
View File

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

View File

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

View File

@@ -1,25 +1,22 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Alexey Dankov (alexey.dankov@digitalenergy.online)
DOCUMENTATION = r'''
---
module: decort_group
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_group(DecortController):
def __init__(self,arg_amodule):
super(decort_group, self).__init__(arg_amodule)
def __init__(self):
super(decort_group, self).__init__(AnsibleModule(**self.amodule_init_args))
arg_amodule = self.amodule
self.group_should_exist = False
validated_bservice_id = None
#find and validate B-Service
@@ -29,7 +26,7 @@ class decort_group(DecortController):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find B-service ID {}.").format(arg_amodule.params['bservice_id'])
self.fail_json(**self.result)
self.amodule.fail_json(**self.result)
#find group
self.bservice_id = validated_bservice_id
self.bservice_info = bservice_info
@@ -39,10 +36,15 @@ class decort_group(DecortController):
group_id=arg_amodule.params['id'],
group_name=arg_amodule.params['name'],
)
self.acc_id = self.bservice_info['accountId']
self.rg_id = self.bservice_info['rgId']
if self.group_id:
self.group_should_exist = True
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def nop(self):
"""No operation (NOP) handler for B-service.
@@ -77,29 +79,34 @@ class decort_group(DecortController):
return
def create(self):
if self.amodule.params['driver'] not in ["KVM_X86","KVM_PPC"]:
self.result['failed'] = True
self.result['msg'] = ("Unsupported driver '{}' is specified for "
"Group.").format(self.amodule.params['driver'])
self.amodule.fail_json(**self.result)
chipset = self.aparams['chipset']
if chipset is None:
chipset = 'Q35'
self.message(
msg=f'Chipset not specified, '
f'default value "{chipset}" will be used.',
warning=True,
)
self.group_id=self.group_provision(
self.bservice_id,
self.amodule.params['name'],
self.amodule.params['count'],
self.amodule.params['cpu'],
self.amodule.params['ram'],
self.amodule.params['boot_disk'],
self.amodule.params['image_id'],
self.amodule.params['driver'],
self.amodule.params['role'],
self.amodule.params['networks'],
self.amodule.params['timeoutStart'],
bs_id=self.bservice_id,
arg_name=self.amodule.params['name'],
arg_count=self.amodule.params['count'],
arg_cpu=self.amodule.params['cpu'],
arg_ram=self.amodule.params['ram'],
arg_boot_disk=self.amodule.params['boot_disk'],
arg_image_id=self.amodule.params['image_id'],
arg_role=self.amodule.params['role'],
arg_network=self.amodule.params['networks'],
arg_timeout=self.amodule.params['timeoutStart'],
chipset=chipset,
storage_policy_id=self.aparams['storage_policy_id'],
)
if self.amodule.params['state'] in ('started','present'):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
self.group_should_exist = True
return
def action(self):
@@ -109,22 +116,53 @@ class decort_group(DecortController):
self.group_info['techStatus'] == 'STOPPED' and self.amodule.params['state'] in ('started','present')
):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
self.group_resize_count(self.bservice_id,self.group_info,self.amodule.params['count'])
self.group_update_hw(
self.bservice_id,
self.group_info,
self.amodule.params['cpu'],
self.amodule.params['boot_disk'],
self.amodule.params['name'],
self.amodule.params['role'],
self.amodule.params['ram'],
)
self.group_update_net(
self.bservice_id,
self.group_info,
self.amodule.params['networks']
)
return
aparam_chipset = self.aparams['chipset']
if (
self.aparams['count'] is not None
and self.aparams['count'] != len(self.group_info['computes'])
):
self.group_resize_count(
bs_id=self.bservice_id,
gr_dict=self.group_info,
desired_count=self.aparams['count'],
chipset=aparam_chipset,
)
if aparam_chipset is not None:
for vm in self.group_info['computes']:
if vm['chipset'] != aparam_chipset:
self.compute_update(
compute_id=vm['id'],
chipset=aparam_chipset,
)
for aparam_name, info_key in {'cpu': 'cpu',
'boot_disk': 'disk',
'role': 'role',
'ram': 'ram',
'name': 'name',
}.items():
aparam_value = self.aparams[aparam_name]
group_info_value = self.group_info[info_key]
if aparam_value != None and aparam_value != group_info_value:
self.group_update(
bs_id=self.bservice_id,
gr_dict=self.group_info,
arg_cpu=self.aparams['cpu'],
arg_disk=self.aparams['boot_disk'],
arg_name=self.aparams['name'],
arg_role=self.aparams['role'],
arg_ram=self.aparams['ram'],
)
break
if self.aparams['networks'] != None:
self.group_update_net(
bs_id=self.bservice_id,
gr_dict=self.group_info,
arg_net=self.aparams['networks'],
)
def destroy(self):
@@ -132,6 +170,7 @@ class decort_group(DecortController):
self.bservice_id,
self.group_id
)
self.group_should_exist = False
return
@@ -142,7 +181,6 @@ class decort_group(DecortController):
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
@@ -162,124 +200,238 @@ class decort_group(DecortController):
ret_dict['techStatus'] = self.group_info['techStatus']
ret_dict['state'] = self.group_info['status']
ret_dict['Computes'] = self.group_info['computes']
ret_dict['driver'] = self.group_info['driver']
return ret_dict
@staticmethod
def build_parameters():
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
default='',
),
state=dict(
type='str',
default='present',
choices=['absent', 'started', 'stopped', 'present','check']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
image_id=dict(type='int', required=False),
image_name=dict(type='str', required=False),
driver=dict(type='str', required=False,default="KVM_X86"),
boot_disk=dict(type='int', required=False),
bservice_id=dict(type='int', required=True),
count=dict(type='int', required=True),
timeoutStart=dict(type='int', required=False),
role=dict(type='str', required=False),
cpu=dict(type='int', required=False),
ram=dict(type='int', required=False),
networks=dict(type='list', default=[], required=False),
description=dict(type='str', default="Created by decort ansible module"),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
def main():
module_parameters = decort_group.build_parameters()
choices=[
'absent',
'started',
'stopped',
'present',
'check',
],
),
name=dict(
type='str',
),
id=dict(
type='int',
),
image_id=dict(
type='int',
),
boot_disk=dict(
type='int',
),
bservice_id=dict(
type='int',
required=True,
),
count=dict(
type='int',
),
timeoutStart=dict(
type='int',
),
role=dict(
type='str',
),
cpu=dict(
type='int',
),
ram=dict(
type='int',
),
networks=dict(
type='list',
elements='dict',
options=dict(
type=dict(
type='str',
required=True,
choices=[
'VINS',
'EXTNET',
]
),
id=dict(
type='int',
required=True,
)
)
),
chipset=dict(
type='str',
choices=[
'Q35',
'i440fx',
]
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
('id', 'name'),
],
)
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
],
)
def check_amodule_args_for_change(self):
check_errors = False
subj = decort_group(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.group_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
if (
self.aparams['chipset'] is None
and (self.aparams['count'] if self.aparams['count'] is not None else 0) > len(self.group_info['computes'])
):
check_errors = True
self.message(
msg='Check for parameter "chipset" failed: '
'Chipset must be specified when increasing '
'VM count in group'
)
if (
self.aparams['count'] is None
or self.aparams['count'] == len(self.group_info['computes'])
):
aparam_chipset = self.aparams['chipset']
if aparam_chipset is not None:
for vm in self.group_info['computes']:
if (
vm['chipset'] != aparam_chipset
and self.group_info['techStatus'] != 'STOPPED'
and self.amodule.params['state'] != 'stopped'
):
check_errors = True
self.message(
msg=f'Check for parameter "chipset" failed: '
f'group ID {self.group_id} must be stopped '
f'to change chipset',
)
break
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
for compute in self.group_info['computes']:
_, compute_info, _ = self._compute_get_by_id(compute['id'])
for disk in compute_info['disks']:
if aparam_storage_policy_id != disk['storage_policy_id']:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" '
'failed: storage_policy_id can not be changed '
f'for compute ID {compute['id']} '
f'disk ID {disk['id']}'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new group'
)
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],)
amodule.fail_json(**subj.result)
if (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'RG ID {self.rg_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if (
aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if subj.group_id:
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED",
"DISABLED","DESTROYED"):
subj.error()
elif subj.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started','stopped'):
subj.create()
elif subj.group_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'absent':
subj.destroy()
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if amodule.params['state'] == 'check':
self.result['changed'] = False
if self.group_id:
# cluster is found - package facts and report success to Ansible
self.result['failed'] = False
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
# we exit the module at this point
else:
subj.action()
self.result['failed'] = True
self.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],)
amodule.fail_json(**self.result)
if self.group_id:
if self.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED","DESTROYED"):
self.error()
elif self.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent':
self.nop()
if amodule.params['state'] in ('present','started','stopped'):
self.create()
elif self.group_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'absent':
self.destroy()
else:
self.action()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started','stopped'):
subj.create()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.group_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if amodule.params['state'] == 'absent':
self.nop()
if amodule.params['state'] in ('present','started','stopped'):
self.create()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.group_should_exist:
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
if __name__ == "__main__":
main()
def main():
decort_group().run()
if __name__ == '__main__':
main()

558
library/decort_image.py Normal file
View File

@@ -0,0 +1,558 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_image
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_image(DecortController):
def __init__(self):
super(decort_image, self).__init__(AnsibleModule(**self.amodule_init_args))
amodule = self.amodule
self.validated_image_id = 0
self.validated_virt_image_id = 0
self.validated_image_name = amodule.params['image_name']
self.validated_virt_image_name = None
self.image_info: dict
self.virt_image_info: dict
if amodule.params['account_name']:
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
else:
self.validated_account_id = amodule.params['account_id']
if self.validated_account_id == 0:
# we failed either to find or access the specified account - fail the module
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
amodule.fail_json(**self.result)
self.acc_id = self.validated_account_id
if (
self.aparams['virt_id'] != 0
or self.aparams['virt_name'] is not None
):
self.validated_virt_image_id, self.virt_image_info = (
self.decort_virt_image_find(amodule)
)
if self.virt_image_info:
_, linked_image_info = self._image_get_by_id(
image_id=self.virt_image_info['linkTo']
)
self.acc_id = linked_image_info['accountId']
if (
self.aparams['virt_name'] is not None
and self.aparams['virt_name']
!= self.virt_image_info['name']
):
self.decort_virt_image_rename(amodule)
self.result['msg'] = 'Virtual image renamed successfully'
elif (
self.aparams['image_id'] != 0
or self.aparams['image_name'] is not None
):
self.validated_image_id, self.image_info = (
self.decort_image_find(amodule)
)
if self.image_info:
self.acc_id = self.image_info['accountId']
if (
amodule.params['image_name']
and amodule.params['image_name'] != self.image_info['name']
):
decort_image.decort_image_rename(self,amodule)
self.result['msg'] = ("Image renamed successfully")
if self.validated_image_id:
self.check_amodule_args_for_change()
elif self.validated_virt_image_id:
self.check_amodule_args_for_change_virt_image()
elif self.aparams['virt_name']:
self.check_amodule_args_for_create_virt_image()
else:
self.check_amodule_args_for_create_image()
def decort_image_find(self, amodule):
# function that finds the OS image
image_id, image_facts = self.image_find(image_id=amodule.params['image_id'], image_name=self.validated_image_name,
account_id=self.validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
pool=amodule.params['pool'])
return image_id, image_facts
def decort_virt_image_find(self, amodule):
# function that finds a virtual image
image_id, image_facts = self.virt_image_find(image_id=amodule.params['virt_id'],
account_id=self.validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
virt_name=amodule.params['virt_name'],
pool=amodule.params['pool'])
return image_id, image_facts
def decort_image_create(self,amodule):
aparam_boot = self.aparams['boot']
boot_mode = 'bios'
loader_type = 'unknown'
if aparam_boot is not None:
if aparam_boot['mode'] is None:
self.message(
msg=self.MESSAGES.default_value_used(
param_name='boot.mode',
default_value=boot_mode
),
warning=True,
)
else:
boot_mode = aparam_boot['mode']
if aparam_boot['loader_type'] is None:
self.message(
msg=self.MESSAGES.default_value_used(
param_name='boot.loader_type',
default_value=loader_type
),
warning=True,
)
else:
loader_type = aparam_boot['loader_type']
network_interface_naming = self.aparams['network_interface_naming']
if network_interface_naming is None:
network_interface_naming = 'ens'
self.message(
msg=self.MESSAGES.default_value_used(
param_name='network_interface_naming',
default_value=network_interface_naming
),
warning=True,
)
hot_resize = self.aparams['hot_resize']
if hot_resize is None:
hot_resize = False
self.message(
msg=self.MESSAGES.default_value_used(
param_name='hot_resize',
default_value=hot_resize
),
warning=True,
)
# function that creates OS image
image_facts = self.image_create(
img_name=self.validated_image_name,
url=amodule.params['url'],
boot_mode=boot_mode,
boot_loader_type=loader_type,
hot_resize=hot_resize,
username=amodule.params['image_username'],
password=amodule.params['image_password'],
account_id=self.validated_account_id,
usernameDL=amodule.params['usernameDL'],
passwordDL=amodule.params['passwordDL'],
sepId=amodule.params['sepId'],
poolName=amodule.params['poolName'],
network_interface_naming=network_interface_naming,
storage_policy_id=amodule.params['storage_policy_id'],
)
self.result['changed'] = True
return image_facts
def decort_virt_image_link(self,amodule):
# function that links an OS image to a virtual one
self.virt_image_link(imageId=self.validated_virt_image_id, targetId=self.target_image_id)
image_id, image_facts = decort_image.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
self.result['msg'] = ("Image '{}' linked to virtual image '{}'").format(self.target_image_id,
decort_image.decort_image_package_facts(image_facts)['id'],)
return image_id, image_facts
def decort_image_delete(self,amodule):
# function that removes an image
self.image_delete(imageId=amodule.image_id_delete)
_, image_facts = decort_image._image_get_by_id(self, amodule.image_id_delete)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
return
def decort_virt_image_create(self,amodule):
# function that creates a virtual image
image_facts = self.virt_image_create(
name=amodule.params['virt_name'],
target_id=self.target_image_id,
account_id=self.aparams['account_id'],
)
image_id, image_facts = decort_image.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
return image_id, image_facts
def decort_image_rename(self,amodule):
# image renaming function
image_facts = self.image_rename(imageId=self.validated_image_id, name=amodule.params['image_name'])
self.result['msg'] = ("Image renamed successfully")
image_id, image_facts = decort_image.decort_image_find(self, amodule)
return image_id, image_facts
def decort_virt_image_rename(self, amodule):
image_facts = self.image_rename(imageId=self.validated_virt_image_id,
name=amodule.params['virt_name'])
self.result['msg'] = ("Virtual image renamed successfully")
image_id, image_facts = self.decort_virt_image_find(amodule)
return image_id, image_facts
@staticmethod
def decort_image_package_facts(
arg_image_facts: dict | None,
arg_check_mode=False,
):
"""Package a dictionary of OS image according to the decort_image module specification. This
dictionary will be returned to the upstream Ansible engine at the completion of the module run.
@param arg_image_facts: dictionary with OS image facts as returned by API call to .../images/list
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode.
@return: dictionary with OS image specs populated from arg_image_facts.
"""
ret_dict = dict(id=0,
name="none",
size=0,
type="none",
state="CHECK_MODE", )
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
if arg_image_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = arg_image_facts['id']
ret_dict['name'] = arg_image_facts['name']
ret_dict['size'] = arg_image_facts['size']
# ret_dict['arch'] = arg_image_facts['architecture']
ret_dict['sep_id'] = arg_image_facts['sepId']
ret_dict['pool'] = arg_image_facts['pool']
ret_dict['state'] = arg_image_facts['status']
ret_dict['linkto'] = arg_image_facts['linkTo']
ret_dict['accountId'] = arg_image_facts['accountId']
ret_dict['boot_mode'] = arg_image_facts['bootType']
ret_dict['boot_loader_type'] = ''
match arg_image_facts['type']:
case 'cdrom' | 'virtual' as type:
ret_dict['type'] = type
case _ as boot_loader_type:
ret_dict['type'] = 'template'
ret_dict['boot_loader_type'] = boot_loader_type
ret_dict['network_interface_naming'] = arg_image_facts[
'networkInterfaceNaming'
]
ret_dict['hot_resize'] = arg_image_facts['hotResize']
ret_dict['storage_policy_id'] = arg_image_facts['storage_policy_id']
ret_dict['to_clean'] = arg_image_facts['to_clean']
return ret_dict
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
pool=dict(
type='str',
default='',
),
sep_id=dict(
type='int',
default=0,
),
account_name=dict(
type='str',
),
account_id=dict(
type='int',
),
image_name=dict(
type='str',
),
image_id=dict(
type='int',
default=0,
),
virt_id=dict(
type='int',
default=0,
),
virt_name=dict(
type='str',
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'present',
],
),
url=dict(
type='str',
),
sepId=dict(
type='int',
default=0,
),
poolName=dict(
type='str',
),
hot_resize=dict(
type='bool',
),
image_username=dict(
type='str',
),
image_password=dict(
type='str',
),
usernameDL=dict(
type='str',
),
passwordDL=dict(
type='str',
),
boot=dict(
type='dict',
options=dict(
mode=dict(
type='str',
choices=[
'bios',
'uefi',
],
),
loader_type=dict(
type='str',
choices=[
'windows',
'linux',
'unknown',
],
),
),
),
network_interface_naming=dict(
type='str',
choices=[
'ens',
'eth',
],
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
)
def check_amodule_args_for_change(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_change_virt_image(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and (
aparam_storage_policy_id
!= self.virt_image_info['storage_policy_id']
)
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id can not be changed in virtual image'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create_image(self):
check_errors = False
aparam_account_id = self.aparams['account_id']
if aparam_account_id is None:
check_errors = True
self.message(
msg='Check for parameter "account_id" failed: '
'account_id must be specified when creating '
'a new image'
)
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new image'
)
elif (
aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'Account ID {self.acc_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create_virt_image(self):
check_errors = False
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id can not be specified when creating '
'virtual image'
)
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if amodule.params['virt_name'] or amodule.params['virt_id']:
image_id, image_facts = self.decort_virt_image_find(amodule)
if amodule.params['image_name'] or amodule.params['image_id']:
self.target_image_id, _ = self.decort_image_find(amodule)
else:
self.target_image_id = 0
if self.decort_image_package_facts(image_facts)['id'] > 0:
self.result['facts'] = self.decort_image_package_facts(image_facts, amodule.check_mode)
self.validated_virt_image_id = self.decort_image_package_facts(image_facts)['id']
self.validated_virt_image_name = self.decort_image_package_facts(image_facts)['name']
if self.decort_image_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and self.target_image_id > 0:
image_id, image_facts = self.decort_virt_image_create(amodule)
self.result['msg'] = ("Virtual image '{}' created").format(self.decort_image_package_facts(image_facts)['id'])
self.result['changed'] = True
elif self.decort_image_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and self.target_image_id == 0:
self.result['msg'] = ("Cannot find OS image")
amodule.fail_json(**self.result)
if self.validated_virt_image_id:
if (
self.target_image_id
and self.decort_image_package_facts(image_facts)[
'linkto'
] != self.target_image_id
):
self.decort_virt_image_link(amodule)
self.result['changed'] = True
amodule.exit_json(**self.result)
if (
amodule.params['storage_policy_id'] is not None
and amodule.params['storage_policy_id']
!= image_facts['storage_policy_id']
):
self.image_change_storage_policy(
image_id=self.validated_virt_image_id,
storage_policy_id=amodule.params['storage_policy_id'],
)
if amodule.params['state'] == "absent" and self.validated_virt_image_id:
amodule.image_id_delete = self.validated_virt_image_id
image_id, image_facts = self.decort_virt_image_find(amodule)
if image_facts['status'] != 'PURGED':
self.decort_image_delete(amodule)
elif amodule.params['image_name'] or amodule.params['image_id']:
image_id, image_facts = self.decort_image_find(amodule)
self.validated_image_id = self.decort_image_package_facts(image_facts)['id']
if self.decort_image_package_facts(image_facts)['id'] > 0:
self.result['facts'] = self.decort_image_package_facts(image_facts, amodule.check_mode)
if amodule.params['state'] == "present" and self.validated_image_id == 0 and amodule.params['image_name'] and amodule.params['url']:
self.decort_image_create(amodule)
self.result['changed'] = True
image_id, image_facts = self.decort_image_find(amodule)
self.result['msg'] = ("OS image '{}' created").format(self.decort_image_package_facts(image_facts)['id'])
self.result['facts'] = self.decort_image_package_facts(image_facts, amodule.check_mode)
self.validated_image_id = self.decort_image_package_facts(image_facts)['id']
elif amodule.params['state'] == "absent" and self.validated_image_id:
amodule.image_id_delete = self.validated_image_id
image_id, image_facts = self.decort_image_find(amodule)
if image_facts['status'] != 'DESTROYED':
self.decort_image_delete(amodule)
if self.validated_image_id:
if (
amodule.params['storage_policy_id'] is not None
and amodule.params['storage_policy_id']
!= image_facts['storage_policy_id']
):
self.image_change_storage_policy(
image_id=self.validated_image_id,
storage_policy_id=amodule.params['storage_policy_id'],
)
if self.result['failed'] == True:
# we failed to find the specified image - fail the module
self.result['changed'] = False
amodule.fail_json(**self.result)
else:
if self.validated_image_id:
_, image_facts = self.decort_image_find(amodule=amodule)
elif self.validated_virt_image_id:
_, image_facts = self.decort_virt_image_find(amodule=amodule)
self.result['facts'] = self.decort_image_package_facts(
arg_image_facts=image_facts,
arg_check_mode=amodule.check_mode,
)
amodule.exit_json(**self.result)
def main():
decort_image().run()
if __name__ == '__main__':
main()

View File

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

View File

@@ -1,157 +1,39 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_jwt
short_description: Obtain access token to be used for authentication to DECORT cloud controller
description:
- Obtain JWT (JSON Web Token) from the specified Oauth2 provider. This JWT can be used in subsequent DECS modules'
invocations to authenticate them to the DECS cloud controller.
version_added: "2.4"
author: "Sergey Shubin (sergey.shubin@digitalenergy.online)"
notes:
- Environment variables can be used to pass parameters to the module (see options below for details).
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'If you register module output as I(my_jwt), the JWT value is accessed as I(my_jwt.jwt)'
requirements:
- python >= 2.6
- PyJWT module
- requests module
- decort_utils utility library (module)
- DECORT cloud platform version 3.6.1 or higher
options:
app_id:
description:
- 'Application ID for authenticating to the Oauth2 provider specified in I(oauth2_url).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the Oauth2 provider specified in I(oauth2_url).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to obtain JWT from.'
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
validity:
description:
- Validity of the JWT in seconds. Default value is 3600 (one hour).
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECS controller. Set it to False if you
want to disable SSL certificate verification.'
- `Intended use case is when you run module in a trusted environment that uses self-signed certificates.
Note that disabling SSL verification in any other scenario can lead to security issues, so please use
with caution.'
default: True
required: no
'''
EXAMPLES = '''
- name: Obtain JWT and store it as my_jwt for authenticating subsequent task to DECORT cloud controller
decort_jwt:
app_id: "{{ my_app_id }}"
app_secret: "{{ my_app_secret }}"
oauth2_url: https://sso.decs.online
delegate_to: localhost
register: my_jwt
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
RETURN = '''
jwt:
description: JSON Web Token that can be used to access DECS cloud controller
returned: always
type: string
sample: None
'''
import requests
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import DecortController
def decort_jwt_parameters():
"""Build and return a dictionary of parameters expected by decort_jwt module in a form accepted
by AnsibleModule utility class"""
return dict(
app_id=dict(type='str',
required=True,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=True,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
oauth2_url=dict(type='str',
required=True,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
validity=dict(type='int',
required=False,
default=3600),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
class DecortJWT(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
amodule_init_args = self.common_amodule_init_args
amodule_argument_spec = amodule_init_args['argument_spec']
del amodule_argument_spec['controller_url']
del amodule_argument_spec['jwt']
amodule_argument_spec['authenticator']['choices'].remove('jwt')
return amodule_init_args
@DecortController.handle_sdk_exceptions
def run(self):
self.result['jwt'] = self.jwt
self.amodule.exit_json(**self.result)
def main():
module_parameters = decort_jwt_parameters()
DecortJWT().run()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,)
result = {'failed': False, 'changed': False}
token_get_url = amodule.params['oauth2_url'] + "/v1/oauth/access_token"
req_data = dict(grant_type="client_credentials",
client_id=amodule.params['app_id'],
client_secret=amodule.params['app_secret'],
response_type="id_token",
validity=amodule.params['validity'],)
# TODO: Need standard code snippet to handle server timeouts gracefully
# Consider a few retries before giving up or use requests.Session & requests.HTTPAdapter
# see https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request
# catch requests.exceptions.ConnectionError to handle incorrect oauth2_url case
try:
token_get_resp = requests.post(token_get_url, data=req_data, verify=amodule.params['verify_ssl'])
except requests.exceptions.ConnectionError as errco:
result.update(failed=True)
result['msg'] = "Failed to connect to {}: {}".format(token_get_url, errco)
amodule.fail_json(**result)
except requests.exceptions.Timeout as errti:
result.update(failed=True)
result['msg'] = "Timeout when trying to connect to {}: {}".format(token_get_url, errti)
amodule.fail_json(**result)
# alternative -- if resp == requests.codes.ok
if token_get_resp.status_code != 200:
result.update(failed=True)
result['msg'] = "Failed to obtain JWT access token from oauth2_url {} for app_id {}: {} {}".format(
token_get_url, amodule.params['app_id'],
token_get_resp.status_code, token_get_resp.reason)
amodule.fail_json(**result)
# Common return values: https://docs.ansible.com/ansible/2.3/common_return_values.html
result['jwt'] = token_get_resp.content.decode('utf8')
amodule.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -1,95 +1,48 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
DOCUMENTATION = r'''
---
module: decort_k8s
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
---
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
EXAMPLES = '''
- name: Create k8s cluster
decort_k8s:
verify_ssl: false
authenticator: jwt
jwt: "{{ run_jwt.jwt }}"
controller_url: "{{CONTROLLER_URL}}"
name: SOME_NAME
rg_id: {{RG_ID}}
k8ci_id: 10
master_count: 3
master_cpu: 2
master_ram: 2048
master_disk: 10
state: present
permanent: True
started: True
getConfig: True
network_plugin: flannel
workers:
- name: wg1
ram: 1024
cpu: 2
disk: 10
num: 1
labels:
- disktype1=ssd1
- disktype2=ssd2
- disktype3=ssd3
taints:
- key1=value1:NoSchedule
- key2=value2:NoSchedule
- key3=value3:NoSchedule
annotations:
- node.deckhouse.io/group1=g1
- node.deckhouse.io/group2=g2
- node.deckhouse.io/group3=g3
- name: wg2
ram: 1024
cpu: 2
disk: 10
num: 1
labels:
- apptype=main
annotations:
- node.mainapp.domen.local/group1=g1
register: some_cluster
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
from copy import deepcopy
class decort_k8s(DecortController):
def __init__(self,arg_amodule):
super(decort_k8s, self).__init__(arg_amodule)
def __init__(self):
super(decort_k8s, self).__init__(AnsibleModule(**self.amodule_init_args))
arg_amodule = self.amodule
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_k8ci_id = 0
self.k8s_should_exist = False
if not arg_amodule.params['workers']:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "At least one worker group must be present"
self.amodule.fail_json(**self.result)
self.is_k8s_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.wg_default_params = {
'num': 1,
'cpu': 1,
'ram': 1024,
'labels': [],
'taints': [],
'annotations': [],
'ci_user_data': {},
'chipset': 'Q35',
}
if arg_amodule.params['name'] is None and arg_amodule.params['id'] is None:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage k8s cluster when its ID is 0 and name is empty."
self.amodule.fail_json(**self.result)
if not arg_amodule.params['id']:
if arg_amodule.params['id'] is None:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
@@ -102,8 +55,11 @@ class decort_k8s(DecortController):
self.amodule.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
validated_rg_id, validated_rg_facts = self.rg_find(
arg_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name']
)
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
@@ -111,32 +67,23 @@ class decort_k8s(DecortController):
arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result)
# fail the module - exit
#validate k8ci ID
validated_k8ci_id = self.k8s_k8ci_find(arg_amodule.params['k8ci_id'])
if not validated_k8ci_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find K8CI ID {}.".format(arg_amodule.params['k8ci_id'])
self.amodule.fail_json(**self.result)
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
arg_amodule.params['k8ci_id'] = validated_k8ci_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
if self.k8s_id:
self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId']
# check workers and groups for add or remove?
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId']
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def package_facts(self,check_mode=False):
@@ -149,6 +96,9 @@ class decort_k8s(DecortController):
config=None,
)
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
if check_mode:
# in check mode return immediately with the default values
return ret_dict
@@ -162,12 +112,15 @@ class decort_k8s(DecortController):
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.rg_id
ret_dict['rg_id'] = self.k8s_info['rgId']
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
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):
@@ -203,34 +156,67 @@ class decort_k8s(DecortController):
return
def create(self):
k8s_id = self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'],
self.amodule.params['network_plugin'],
self.amodule.params['master_count'],
self.amodule.params['master_cpu'],
self.amodule.params['master_ram'],
self.amodule.params['master_disk'],
self.amodule.params['master_sepid'],
self.amodule.params['master_pool'],
self.amodule.params['workers'][0],
self.amodule.params['extnet_id'],
self.amodule.params['with_lb'],
self.amodule.params['description'],)
master_chipset = self.amodule.params['master_chipset']
if master_chipset is None:
master_chipset = 'Q35'
target_wgs = deepcopy(self.amodule.params['workers'])
for wg in target_wgs:
for param, default_value in self.wg_default_params.items():
if wg[param] is None:
wg[param] = default_value
k8s_id = self.k8s_provision(
self.amodule.params['name'],
self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'],
self.amodule.params['vins_id'],
self.amodule.params['network_plugin'],
self.amodule.params['master_count'],
self.amodule.params['master_cpu'],
self.amodule.params['master_ram'],
self.amodule.params['master_disk'],
self.amodule.params['master_sepid'],
self.amodule.params['master_pool'],
target_wgs[0],
self.amodule.params['extnet_id'],
self.amodule.params['with_lb'],
self.amodule.params['ha_lb'],
self.amodule.params['additionalSANs'],
self.amodule.params['init_conf'],
self.amodule.params['cluster_conf'],
self.amodule.params['kublet_conf'],
self.amodule.params['kubeproxy_conf'],
self.amodule.params['join_conf'],
self.amodule.params['oidc_cert'],
self.amodule.params['description'],
self.amodule.params['extnet_only'],
master_chipset=master_chipset,
lb_sysctl=self.amodule.params['lb_sysctl'],
zone_id=self.aparams['zone_id'],
storage_policy_id=self.aparams['storage_policy_id'],
)
if not k8s_id:
self.result['failed'] = True
self.amodule.fail_json(**self.result)
if k8s_id == 0:
return
else:
self.result['failed'] = True
self.amodule.fail_json(**self.result)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id,
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
if self.k8s_id:
self.k8s_should_exist = True
if self.k8s_id and len(self.amodule.params['workers'])>1 :
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
self.k8s_should_exist = True
if len(target_wgs) > 1:
self.k8s_workers_modify(
arg_k8swg=self.k8s_info,
arg_modwg=target_wgs,
)
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
return
def destroy(self):
@@ -239,165 +225,444 @@ class decort_k8s(DecortController):
self.k8s_should_exist = False
return
def action(self,disared_state,started=True):
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=self.amodule.params['id'],
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
if started == True and self.k8s_info['techStatus'] == "STOPPED":
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_info['techStatus'] == "STARTED"
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
def action(self, disared_state, preupdate: bool = False):
if self.amodule.params['master_chipset'] is not None:
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
]:
_, master_node_info, _ = self._compute_get_by_id(
comp_id=master_node['id']
)
if (
master_node_info['chipset']
!= self.amodule.params['master_chipset']
):
self.result['msg'] = (
'"master_chipset" cannot be changed '
'for existing K8s cluster.'
)
self.exit(fail=True)
if (
self.aparams['name'] is not None
and self.aparams['name'] != self.k8s_info['name']
):
self.k8s_update(id=self.k8s_id, name=self.aparams['name'])
if preupdate:
# K8s info updating
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#k8s state
self.k8s_state(self.k8s_info, disared_state)
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#check groups and modify if needed
if self.aparams['workers'] is not None:
self.k8s_workers_modify(self.k8s_info, self.amodule.params['workers'])
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.k8s_info['zoneId']:
self.k8s_migrate_to_zone(
k8s_id=self.k8s_id,
zone_id=aparam_zone_id,
)
if self.result['changed'] == True:
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#TODO check workers metadata and modify if needed
return
@staticmethod
def build_parameters():
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
quotas=dict(type='dict', required=False),
state=dict(type='str',
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
default='',
),
state=dict(
type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','check']),
permanent=dict(type='bool', default=False),
started=dict(type='bool', default=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=False, default=""),
id=dict(type='int', required=False, default=0),
getConfig=dict(type='bool',required=False, default=False),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
k8ci_id=dict(type='int', required=True),
network_plugin=dict(type='str',required=False,default="flannel"),
wg_name=dict(type='str', required=False),
master_count=dict(type='int', default=1),
master_cpu=dict(type='int', default=2),
master_ram=dict(type='int', default=2048),
master_disk=dict(type='int', default=10),
master_sepid=dict(type='int', required=False, default=None),
master_pool=dict(type='str', required=False, default=None),
worker_count=dict(type='int', default=1),
worker_cpu=dict(type='int', default=1),
worker_ram_mb=dict(type='int', default=1024),
worker_disk_gb=dict(type='int', default=10),
workers=dict(type='list',required=True),
extnet_id=dict(type='int', default=0),
description=dict(type='str', default="Created by decort ansible module"),
with_lb=dict(type='bool', default=True),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
choices=[
'absent',
'disabled',
'enabled',
'present',
'started',
'stopped',
],
),
permanent=dict(
type='bool',
default=False,
),
name=dict(
type='str',
),
id=dict(
type='int',
),
getConfig=dict(
type='bool',
default=False,
),
rg_id=dict(
type='int',
default=0,
),
rg_name=dict(
type='str',
default='',
),
vins_id=dict(
type='int',
),
k8ci_id=dict(
type='int',
),
network_plugin=dict(
type='str',
default='flannel',
),
master_count=dict(
type='int',
default=1,
choices=[1, 3, 5, 7],
),
master_cpu=dict(
type='int',
default=2,
),
master_ram=dict(
type='int',
default=2048,
),
master_disk=dict(
type='int',
default=10,
),
master_sepid=dict(
type='int',
),
master_pool=dict(
type='str',
),
workers=dict(
type='list',
elements='dict',
options=dict(
name=dict(
type='str',
required=True,
),
num=dict(
type='int',
),
cpu=dict(
type='int',
),
ram=dict(
type='int',
),
disk=dict(
type='int',
),
sep_id=dict(
type='int',
),
pool=dict(
type='str',
),
annotations=dict(
type='list',
elements='str',
),
ci_user_data=dict(
type='dict',
),
labels=dict(
type='list',
elements='str',
),
taints=dict(
type='list',
elements='str',
),
chipset=dict(
type='str',
choices=['Q35', 'i440fx'],
),
),
),
workers_metadata=dict(
type='bool',
default=False,
),
extnet_id=dict(
type='int',
default=0,
),
description=dict(
type='str',
default='Created by decort ansible module',
),
with_lb=dict(
type='bool',
default=True,
),
ha_lb=dict(
type='bool',
default=False,
),
extnet_only=dict(
type='bool',
default=False,
),
additionalSANs=dict(
type='list',
),
init_conf=dict(
type='dict',
),
cluster_conf=dict(
type='dict',
),
kublet_conf=dict(
type='dict',
),
kubeproxy_conf=dict(
type='dict',
),
join_conf=dict(
type='dict',
),
oidc_cert=dict(
type='raw',
),
master_chipset=dict(
type='str',
choices=['Q35', 'i440fx'],
),
lb_sysctl=dict(
type='dict',
),
zone_id=dict(
type='int',
),
storage_policy_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
('id', 'name'),
],
)
def check_amodule_args_for_change(self):
check_errors = False
self.is_k8s_stopped_or_will_be_stopped = (
(
self.k8s_info['techStatus'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
self.k8s_info['techStatus'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
aparam_sysctl = self.aparams['lb_sysctl']
if aparam_sysctl is not None:
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId'])
sysctl_with_str_values = {
k: str(v) for k, v in aparam_sysctl.items()
}
if sysctl_with_str_values != lb_info['sysctlParams']:
self.message(
'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster '
'load balancer.'
)
check_errors = True
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != self.k8s_info['zoneId']
and not self.is_k8s_stopped_or_will_be_stopped
):
check_errors = True
self.message(
'Check for parameter "zone_id" failed: '
'K8s cluster must be stopped to migrate to a zone.'
)
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
computes_ids = []
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
]:
computes_ids.append(master_node['id'])
for wg in self.k8s_info['k8sGroups']['workers']:
workers_ids = [
worker['id'] for worker in wg['detailedInfo']
]
computes_ids.extend(workers_ids)
for compute_id in computes_ids:
_, compute_info, _ = self._compute_get_by_id(
comp_id=compute_id
)
for disk in compute_info['disks']:
if aparam_storage_policy_id != disk['storage_policy_id']:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" '
'failed: storage_policy_id can not be changed '
f'for k8s cluster ID {self.k8s_id} compute ID '
f'{compute_id} disk ID {disk['id']}'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
validated_k8ci_id = self.k8s_k8ci_find(self.aparams['k8ci_id'])
if not validated_k8ci_id:
self.message(f'Cannot find K8CI ID {"k8ci_id"}.')
check_errors = True
if not self.aparams['workers']:
self.message('At least one worker group must be present.')
check_errors = True
if (
self.aparams['lb_sysctl'] is not None
and not self.aparams['with_lb']
):
self.message(
'Check for parameter "lb_sysctl" failed: '
'"lb_sysctl" can only be set if the parameter "with_lb" '
'is set to True.'
)
check_errors = True
if (
self.aparams['master_count'] is not None
and self.aparams['master_count'] > 1
and not self.aparams['with_lb']
):
self.message(
'Check for parameter "master_count" failed: '
'master_count can be more than 1 only if the parameter '
'"with_lb" is set to True.'
)
check_errors = True
if self.check_aparam_zone_id() is False:
check_errors = True
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is None:
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
'storage_policy_id must be specified when creating '
'a new cluster'
)
elif (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
):
check_errors = True
self.message(
msg='Check for parameter "storage_policy_id" failed: '
f'RG ID {self.rg_id} does not have access to '
f'storage_policy_id {aparam_storage_policy_id}'
)
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if self.amodule.check_mode:
self.result['changed'] = False
if self.k8s_id:
# cluster is found - package facts and report success to Ansible
self.result['failed'] = False
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
# we exit the module at this point
else:
self.result['failed'] = True
self.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
"RG ID {}").format(amodule.params['name'],
amodule.params['rg_id'],)
amodule.fail_json(**self.result)
if self.k8s_id:
if self.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
self.error()
elif self.k8s_info['status'] == "DELETED":
if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped'
):
self.k8s_restore(self.k8s_id)
self.action(disared_state=amodule.params['state'],
preupdate=True)
if amodule.params['state'] == 'absent':
if amodule.params['permanent']:
self.destroy()
else:
self.nop()
elif self.k8s_info['status'] in ('ENABLED', 'DISABLED'):
if amodule.params['state'] == 'absent':
self.destroy()
else:
self.action(disared_state=amodule.params['state'])
elif self.k8s_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
self.create()
if amodule.params['state'] == 'absent':
self.nop()
else:
if amodule.params['state'] == 'absent':
self.nop()
if amodule.params['state'] in ('present','started'):
self.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
self.error()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.k8s_should_exist:
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
def main():
module_parameters = decort_k8s.build_parameters()
decort_k8s().run()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
['rg_id','rg_name']
],
)
subj = decort_k8s(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.k8s_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
"RG ID {}").format(amodule.params['name'],
amodule.params['rg_id'],)
amodule.fail_json(**subj.result)
if subj.k8s_id:
if subj.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.k8s_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
subj.k8s_restore(subj.k8s_id)
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.k8s_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.k8s_info['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.k8s_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.k8s_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,913 +1,87 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_kvmvm
short_description: Manage KVM virtual machine in DECORT cloud
description: >
This module can be used to create a KVM based virtual machine in Digital Energy cloud platform from a
specified OS image, modify virtual machine's CPU and RAM allocation, change its power state, configure
network port forwarding rules, restart guest OS and delete a virtual machine thus releasing
corresponding cloud resources.
version_added: "2.2"
requirements:
- python >= 3.8
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.8.6 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
options:
account_id:
description:
- 'ID of the account in which this VM will be created (for new VMs) or is located (for already
existing VMs). This is the alternative to I(account_name) option.'
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
- If any one of I(vm_id) or I(rg_id) specified, I(account_id) is ignored.
required: no
account_name:
description:
- 'Name of the account in which this VM will be created (for new VMs) or is located (for already
existing VMs).'
- This parameter is ignored if I(account_id) is specified.
- If any one of I(vm_id) or I(rg_id) specified, I(account_name) is ignored.
required: no
annotation:
description:
- Optional text description of this VM.
default: empty string
required: no
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
arch:
description:
- Architecture of the KVM VM. DECORT supports KVM hosts based on Intel x86 and IBM PowerPC hardware.
- This parameter is used when new KVM VM is created and ignored for all other operations.
- Module may fail if your DECORT installation does not have physical nodes of specified architecture.
default: X86_64
choices: [ X86_64, PPC64_LE ]
required: yes
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
boot_disk:
description:
- 'Boot disk size in GB. If this parameter is not specified for a new VM, the size of the boot disk
will be set to the size of the OS image, which this VM is based on.'
- Boot disk is always created in the same storage and pool, as the OS image, which this VM is based on.
- Boot disk cannot be detached from VM.
required: no
controller_url:
description:
- URL of the DECORT controller that will be contacted to manage the VM according to the specification.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
cpu:
description:
- Number of virtual CPUs to allocate for the VM.
- This parameter is required for creating new VM and optional for other operations.
- 'If you set this parameter for an existing VM, then the module will check if VM resize is necessary and do
it accordingly. Note that resize operation on a running VM may generate errors as not all OS images support
hot resize feature.'
required: no
data_disks:
description:
- Optional list of integer IDs of the pre-existing disks that will be attached to this VM.
- These are additional disks (aka data disks) besides boot disk, which is created and attached automatically.
required: no
id:
description:
- ID of the KVM VM to manage.
- 'Either I(id) or a combination of VM name I(name) and RG related parameters (either I(rg_id) or a pair of
I(account_name) and I(rg_name) is required to manage an existing VM.'
- 'This parameter is not required (and ignored) when creating new VM as VM ID is assigned by cloud platform
automatically and cannot be changed afterwards. If existing VM is identified by I(id), then I(account_id),
I(account_name), I(rg_name) or I(rg_id) parameters will be ignored.'
required: no
image_id:
description:
- ID of the OS image to use for VM provisioning.
- 'This parameter is valid at VM creation time only and is ignored for operations on existing VMs.'
- 'You need to know image ID, e.g. by extracting it with decort_osimage module and storing
in a variable prior to calling decort_kvmvm.'
- 'If both I(image_id) and I(image_name) are specified, I(image_name) will be ignored.'
required: no
image_name:
description:
- Name of the OS image to use for a new VM provisioning.
- 'This parameter is valid at VM creation time only and is ignored for operations on existing VMs.'
- 'The specified image name will be looked up in the target DECORT controller and error will be generated if
no matching image is found.'
- 'If both I(image_id) and I(image_name) are specified, I(image_name) will be ignored.'
required: no
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
name:
description:
- Name of the VM.
- 'To manage VM by I(name) you also need to specify either I(rg_id) or a pair of I(rg_name) and I(account_name).'
- 'If both I(name) and I(id) are specified, I(name) will be ignored and I(id) used to locate the VM.'
required: no
networks:
description:
- List of dictionaries that specifies network connections for this VM.
- Structure of each element is as follows:
- ' - (string) type - type of the network connection. Supported types are VINS and EXTNET.'
- ' - (int) id - ID of the target network segment. It is ViNS ID for I(net_type=VINS) and
external network segment ID for I(net_type=EXTNET)'
- ' - (string) ip_addr - optional IP address to request for this connection. If not specified, the
platform will assign valid IP address automatically.'
- 'If you call decort_kvmvm module for an existing VM, the module will try to reconfigure existing network
connections according to the new specification.'
- If this parameter is not specified, the VM will have no connections to the network(s).
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
ram:
description:
- Size of RAM in MB to allocate to the VM.
- This parameter is required for creating new VM and optional for other operations.
- 'If you set this parameter for an existing VM, then the module will check if VM resize is necessary and do
it accordingly. Note that resize operation on a running VM may generate errors as not all OS images support
hot resize feature.'
required: no
ssh_key:
description:
- 'SSH public key to be deployed on to the new VM for I(ssh_key_user). If I(ssh_key_user) is not specified,
the key will not be deployed, and a warning is generated.'
- This parameter is valid at VM creation time only and ignored for any operation on existing VMs.
required: no
ssh_key_user:
description:
- User for which I(ssh_key) should be deployed.
- If I(ssh_key) is not specified, this parameter is ignored and a warning is generated.
- This parameter is valid at VM creation time only and ignored for any operation on existing VMs.
required: no
user_data:
description:
- Cloud-init User-Data, exept ssh module
state:
description:
- Specify the desired state of the virtual machine at the exit of the module.
- 'Regardless of I(state), if VM exists and is in one of [MIGRATING, DESTROYING, ERROR] states, do nothing.'
- 'If desired I(state=check):'
- ' - Just check if VM exists in any state and return its current specifications.'
- ' - If VM does not exist, fail the task.'
- 'If desired I(state=present):'
- ' - VM does not exist, create the VM according to the specifications and start it.'
- ' - VM in one of [RUNNING, PAUSED, HALTED] states, attempt resize if necessary, change network if necessary.'
- ' - VM in DELETED state, restore and start it.'
- ' - VM in DESTROYED state, recreate the VM according to the specifications and start it.'
- 'If desired I(state=poweredon):'
- ' - VM does not exist, create it according to the specifications.'
- ' - VM in RUNNING state, attempt resize if necessary, change network if necessary.'
- ' - VM in one of [PAUSED, HALTED] states, attempt resize if necessary, change network if necessary, next
start the VM.'
- ' - VM in DELETED state, restore it.'
- ' - VM in DESTROYED state, create it according to the specifications.'
- 'If desired I(state=absent):'
- ' - VM in one of [RUNNING, PAUSED, HALTED] states, destroy it.'
- ' - VM in one of [DELETED, DESTROYED] states, do nothing.'
- 'If desired I(state=paused):'
- ' - VM in RUNNING state, pause the VM, resize if necessary, change network if necessary.'
- ' - VM in one of [PAUSED, HALTED] states, resize if necessary, change network if necessary.'
- ' - VM in one of [DELETED, DESTROYED] states, abort with an error.'
- 'If desired I(state=poweredoff) or I(state=halted):'
- ' - VM does not exist, create the VM according to the specifications and leave it in HALTED state.'
- ' - VM in RUNNING state, stop the VM, resize if necessary, change network if necessary.'
- ' - VM in one of [PAUSED, HALTED] states, resize if necessary, change network if necessary.'
- ' - VM in DELETED state, abort with an error.'
- ' - VM in DESTROYED state, recreate the VM according to the specifications and leave it in HALTED state.'
default: present
choices: [ present, absent, poweredon, poweredoff, halted, paused, check ]
tags:
description:
- Dict of custom tags to be assigned to the VM.
- These tags are arbitrary text that can be used for grouping or indexing the VMs by other applications.
required: no
user:
description:
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no
rg_id:
description:
- ID of the Resource Group where a new VM will be deployed or an existing VM can be found.
- 'This parameter may be required when managing VM by its I(name). If you specify I(rg_id), then
I(account_name), I(account_id) and I(rg_name) will be ignored.'
required: no
rg_name:
description:
- Name of the RG where the VM will be deployed (for new VMs) or can be found (for existing VMs).
- This parameter is required when managing VM by its I(name).
- If both I(rg_id) and I(rg_name) are specified, I(rg_name) will be ignored.
- If I(rg_name) is specified, then either I(account_name) or I(account_id) must also be set.
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
'''
EXAMPLES = '''
- name: create a VM named "SimpleVM" in the DECORT cloud along with VDC named "ANewVDC" if it does not exist yet.
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://ds1.digitalenergy.online"
name: SimpleVM
cpu: 2
ram: 4096
boot_disk: 10
image_name: "Ubuntu 16.04 v1.1"
data_disks:
- {{DISK_ID}}
state: present
tags:
PROJECT:Ansible
STATUS:Test
account_name: "Development"
rg_name: "ANewVDC"
delegate_to: localhost
register: simple_vm
- name: resize the above VM to CPU 4 and remove port forward rule for port number 80.
decort_kvmvm:
authenticator: jwt
jwt: "{{ MY_JWT }}"
controller_url: "https://ds1.digitalenergy.online"
name: SimpleVM
cpu: 4
ram: 4096
port_forwards:
- ext_port: 21022
int_port: 22
proto: tcp
state: present
account_name: "Development"
rg_name: "ANewVDC"
delegate_to: localhost
register: simple_vm
- name: stop existing VM identified by the VM ID and down size it to CPU:RAM 1:2048 along the way.
decort_kvmvm:
authenticator: jwt
jwt: "{{ MY_JWT }}"
controller_url: "https://ds1.digitalenergy.online"
id: "{{ TARGET_VM_ID }}"
cpu: 1
ram: 2048
state: poweredoff
delegate_to: localhost
register: simple_vm
'''
RETURN = '''
facts:
description: facts about the virtual machine that may be useful in the playbook
returned: always
type: dict
sample:
facts:
id: 9454
name: TestVM
state: RUNNING
username: testuser
password: Yab!tWbyPF
int_ip: 192.168.103.253
rg_name: SandboxVDC
rg_id: 2883
vdc_ext_ip: 185.193.143.151
ext_ip: 185.193.143.106
ext_netmask: 24
ext_gateway: 185.193.143.1
ext_mac: 52:54:00:00:1a:24
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_kvmvm(DecortController):
def __init__(self, arg_amodule):
# call superclass constructor first
super(decort_kvmvm, self).__init__(arg_amodule)
self.comp_should_exist = False
# This following flag is used to avoid extra (and unnecessary) get of compute details prior to
# packaging facts before the module completes. As ""
self.skip_final_get = False
self.comp_id = 0
self.comp_info = None
self.acc_id = 0
self.rg_id = 0
validated_acc_id =0
validated_rg_id = 0
validated_rg_facts = None
# Analyze Compute name & ID, RG name & ID and build arguments to compute_find accordingly.
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage Compute when its ID is 0 and name is empty."
self.fail_json(**self.result)
# fail the module - exit
if not arg_amodule.params['id']: # manage Compute by name -> need RG identity
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
# fail the module - exit
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
# at this point we are ready to locate Compute, and if anything fails now, then it must be
# because this Compute does not exist or something goes wrong in the upstream API
# We call compute_find with check_state=False as we also consider the case when a Compute
# specified by account / RG / compute name never existed and will be created for the first time.
self.comp_id, self.comp_info, self.rg_id = self.compute_find(comp_id=arg_amodule.params['id'],
comp_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
if self.comp_id:
if self.comp_info['status'] != 'DESTROYED' and self.comp_info['arch'] not in ["X86_64", "PPC64_LE"]:
# If we found a Compute in a non-DESTROYED state and it is not of type valid arch, abort the module
self.result['failed'] = True
self.result['msg'] = ("Compute ID {} architecture '{}' is not supported by "
"decort_kvmvm module.").format(self.comp_id,
self.amodule.params['arch'])
self.amodule.fail_json(**self.result)
# fail the module - exit
self.comp_should_exist = True
self.acc_id = self.comp_info['accountId']
return
def nop(self):
"""No operation (NOP) handler for Compute management by decort_kvmvm module.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.comp_id:
self.result['msg'] = ("No state change required for Compute ID {} because of its "
"current status '{}'.").format(self.comp_id, self.comp_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Compute instance.").format(self.amodule.params['state'])
return
def error(self):
"""Error handler for Compute instance management by decort_kvmvm module.
This function is intended to be called when an invalid state change is requested.
Invalid means that the current is invalid for any operations on the Compute or the
transition from current to desired state is not technically possible.
"""
self.result['failed'] = True
self.result['changed'] = False
if self.comp_id:
self.result['msg'] = ("Invalid target state '{}' requested for Compute ID {} in the "
"current status '{}'.").format(self.comp_id,
self.amodule.params['state'],
self.comp_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent Compute name '{}' "
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['rg_name'])
return
def create(self):
"""New Compute instance creation handler for decort_kvmvm module.
This function checks for the presence of required parameters and deploys a new KVM VM
Compute instance with the specified characteristics into the target Resource Group.
The target RG must exist.
"""
# the following parameters must be present: cpu, ram, image_id or image_name
# each of the following calls will abort if argument is missing
self.check_amodule_argument('cpu')
self.check_amodule_argument('ram')
if self.amodule.params['arch'] not in ["X86_64", "PPC64_LE"]:
self.result['failed'] = True
self.result['msg'] = ("Unsupported architecture '{}' is specified for "
"KVM VM create.").format(self.amodule.params['arch'])
self.amodule.fail_json(**self.result)
# fail the module - exit
validated_bdisk_size = 0
image_facts = None
# either image_name or image_id must be present
if self.check_amodule_argument('image_id', abort=False) and self.amodule.params['image_id'] > 0 :
# find image by image ID and account ID
# image_find(self, image_id, image_name, account_id, rg_id=0, sepid=0, pool=""):
_, image_facts = self.image_find(image_id=self.amodule.params['image_id'],
image_name="",
account_id=self.acc_id)
elif self.check_amodule_argument('image_name', abort=False) and self.amodule.params['image_name'] != "":
# find image by image name and account ID
_, image_facts = self.image_find(image_id=0,
image_name=self.amodule.params['image_name'],
account_id=self.acc_id)
else:
# neither image_name nor image_id are set - abort the script
self.result['failed'] = True
self.result['msg'] = "Missing both 'image_name' and 'image_id'. You need to specify one to create a Compute."
self.amodule.fail_json(**self.result)
# fail the module - exit
if ((not self.check_amodule_argument('boot_disk', False)) or
self.amodule.params['boot_disk'] <= image_facts['size']):
# adjust disk size to the minimum allowed by OS image, which will be used to spin off this Compute
validated_bdisk_size = image_facts['size']
else:
validated_bdisk_size =self.amodule.params['boot_disk']
# NOTE: due to a libvirt "feature", that impacts management of a VM created without any network interfaces,
# we create KVM VM in HALTED state.
# Consequently, if desired state is different from 'halted' or 'porewedoff", we should explicitly start it
# in the upstream code.
# See corresponding NOTE below for another place where this "feature" is redressed for.
#
# Once this "feature" is fixed, make sure VM is created according to the actual desired state
#
start_compute = False # change this once a workaround for the aforementioned libvirt "feature" is implemented
if self.amodule.params['state'] in ('halted', 'poweredoff'):
start_compute = False
if self.amodule.params['ssh_key'] and self.amodule.params['ssh_key_user'] and not self.amodule.params['ci_user_data']:
cloud_init_params = {'users': [
{"name": self.amodule.params['ssh_key_user'],
"ssh-authorized-keys": [self.amodule.params['ssh_key']],
"shell": '/bin/bash'}
]}
elif self.amodule.params['ci_user_data']:
cloud_init_params = {}
for ci_param in self.amodule.params['ci_user_data']:
cloud_init_params.update(ci_param)
else:
cloud_init_params = None
# if we get through here, all parameters required to create new Compute instance should be at hand
# NOTE: KVM VM is created in HALTED state and must be explicitly started
self.comp_id = self.kvmvm_provision(rg_id=self.rg_id,
comp_name=self.amodule.params['name'], arch=self.amodule.params['arch'],
cpu=self.amodule.params['cpu'], ram=self.amodule.params['ram'],
boot_disk=validated_bdisk_size,
image_id=image_facts['id'],
annotation=self.amodule.params['annotation'],
userdata=cloud_init_params,
sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None,
pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None,
start_on_create=start_compute)
self.comp_should_exist = True
# Originally we would have had to re-read comp_info after VM was provisioned
# _, self.comp_info, _ = self.compute_find(self.comp_id)
# However, to avoid extra call to compute/get API we need to construct comp_info so that
# the below calls to compute_networks and compute_data_disks work properly.
#
# Here we are imitating comp_info structure as if it has been returned by a real call
# to API compute/get
self.comp_info = {
'id': self.comp_id,
'accountId': self.acc_id,
'status': "ENABLED",
'techStatus': "STOPPED",
'interfaces': [], # new compute instance is created network-less
'disks': [], # new compute instance is created without any data disks attached
'tags': {},
'affinityLabel': "",
'affinityRules': [],
'antiAffinityRules': [],
}
#
# Compute was created
#
# Setup network connections
self.compute_networks(self.comp_info, self.amodule.params['networks'])
# Next manage data disks
self.compute_data_disks(self.comp_info, self.amodule.params['data_disks'])
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'],)
# NOTE: see NOTE above regarding libvirt "feature" and new VMs created in HALTED state
if self.amodule.params['state'] not in ('halted', 'poweredoff'):
self.compute_powerstate(self.comp_info, 'started')
# read in Compute facts once more after all initial setup is complete
_, self.comp_info, _ = self.compute_find(comp_id=self.comp_id)
self.skip_final_get = True
return
def destroy(self):
"""Compute destroy handler for VM management by decort_kvmvm module.
Note that this handler deletes the VM permanently together with all assigned disk resources.
"""
self.compute_delete(comp_id=self.comp_id, permanently=True)
self.comp_info['status'] = 'DESTROYED'
self.comp_should_exist = False
return
def restore(self):
"""Compute restore handler for Compute instance management by decort_kvmvm module.
Note that restoring Compute is only possible if it is in DELETED state. If called on a
Compute instance in any other state, the method will throw an error and abort the execution
of the module.
"""
self.compute_restore(comp_id=self.comp_id)
# TODO - do we need updated comp_info to manage port forwards and size after VM is restored?
_, self.comp_info, _ = self.compute_find(comp_id=self.comp_id)
self.modify()
self.comp_should_exist = True
return
def modify(self, arg_wait_cycles=0):
"""Compute modify handler for KVM VM management by decort_kvmvm module.
This method is a convenience wrapper that calls individual Compute modification functions from
DECORT utility library (module).
Note that it does not modify power state of KVM VM.
"""
self.compute_networks(self.comp_info, self.amodule.params['networks'])
self.compute_bootdisk_size(self.comp_info, self.amodule.params['boot_disk'])
self.compute_data_disks(self.comp_info, self.amodule.params['data_disks'])
self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'])
return
def package_facts(self, check_mode=False):
"""Package a dictionary of KVM VM facts according to the decort_kvmvm module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of decort_kvmvm
module run.
@param check_mode: boolean that tells if this Ansible module is run in check mode
@return: dictionary of KVM VM facts, containing suffucient information to manage the KVM VM in
subsequent Ansible tasks.
"""
ret_dict = dict(id=0,
name="",
arch="",
cpu="",
ram="",
disk_size=0,
data_disks=[], # IDs of attached data disks; this list can be emty
state="CHECK_MODE",
tech_status="",
account_id=0,
rg_id=0,
username="",
password="",
public_ips=[], # direct IPs; this list can be empty
private_ips=[], # IPs on ViNSes; usually, at least one IP is listed
nat_ip="", # IP of the external ViNS interface; can be empty.
tags={},
)
if check_mode or self.comp_info is None:
# if in check mode (or void facts provided) return immediately with the default values
return ret_dict
# if not self.comp_should_exist:
# ret_dict['state'] = "ABSENT"
# return ret_dict
ret_dict['id'] = self.comp_info['id']
ret_dict['name'] = self.comp_info['name']
ret_dict['arch'] = self.comp_info['arch']
ret_dict['state'] = self.comp_info['status']
ret_dict['tech_status'] = self.comp_info['techStatus']
ret_dict['account_id'] = self.comp_info['accountId']
ret_dict['rg_id'] = self.comp_info['rgId']
if self.comp_info['tags']:
ret_dict['tags'] = self.comp_info['tags']
# if the VM is an imported VM, then the 'accounts' list may be empty,
# so check for this case before trying to access login and passowrd values
if len(self.comp_info['osUsers']):
ret_dict['username'] = self.comp_info['osUsers'][0]['login']
ret_dict['password'] = self.comp_info['osUsers'][0]['password']
if self.comp_info['interfaces']:
# We need a list of all ViNSes in the account, which owns this Compute
# to find a ViNS, which may have active external connection. Then
# we will save external IP address of that connection in ret_dict['nat_ip']
for iface in self.comp_info['interfaces']:
if iface['connType'] == "VXLAN": # This is ViNS connection
ret_dict['private_ips'].append(iface['ipAddress'])
# if iface['connId']
# Now we need to check if this ViNS has GW function and external connection.
# If it does - save public IP address of GW VNF in ret_dict['nat_ip']
elif iface['connType'] == "VLAN": # This is direct external network connection
ret_dict['public_ips'].append(iface['ipAddress'])
ret_dict['cpu'] = self.comp_info['cpus']
ret_dict['ram'] = self.comp_info['ram']
ret_dict['image_id'] = self.comp_info['imageId']
for ddisk in self.comp_info['disks']:
if ddisk['type'] == 'B':
# if it is a boot disk - store its size
ret_dict['disk_size'] = ddisk['sizeMax']
elif ddisk['type'] == 'D':
# if it is a data disk - append its ID to the list of data disks IDs
ret_dict['data_disks'].append(ddisk['id'])
return ret_dict
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_kvmvm module in a form
accepted by AnsibleModule utility class.
This dictionary is then used y AnsibleModule class instance to parse and validate parameters
passed to the module from the playbook.
"""
return dict(
account_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str',
default='',
required=False),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
arch=dict(type='str', choices=['X86_64', 'PPC64_LE'], default='X86_64'),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
boot_disk=dict(type='int', required=False),
sep_id=dict(type='int', required=False),
pool=dict(type='str', required=False),
controller_url=dict(type='str', required=True),
# count=dict(type='int', required=False, default=1),
cpu=dict(type='int', required=False),
# datacenter=dict(type='str', required=False, default=''),
data_disks=dict(type='list', default=[], required=False), # list of integer disk IDs
id=dict(type='int', required=False, default=0),
image_id=dict(type='int', required=False),
image_name=dict(type='str', required=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
name=dict(type='str'),
networks=dict(type='list', default=[], required=False), # list of dictionaries
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
ram=dict(type='int', required=False),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str', default=""),
ssh_key=dict(type='str', required=False),
ssh_key_user=dict(type='str', required=False),
tag=dict(type='dict', required=False),
affinity_label=dict(type='str', required=False),
aff_rule=dict(type='list', required=False),
aaff_rule=dict(type='list', required=False),
ci_user_data=dict(type='list',elements='dict', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']),
tags=dict(type='str', required=False),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
# wait_for_ip_address=dict(type='bool', required=False, default=False),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECSController
# 2) check if the VM with the specified id or rg_name:name exists
# 3) if VM does not exist, check if there is enough resources to deploy it in the target account / vdc
# 4) if VM exists: check desired state, desired configuration -> initiate action accordingly
# 5) VM does not exist: check desired state -> initiate action accordingly
# - create VM: check if target VDC exists, create VDC as necessary, create VM
# - delete VM: delete VM
# - change power state: change as required
# - change guest OS state: change as required
# 6) report result to Ansible
def main():
module_parameters = decort_kvmvm.build_parameters()
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'),
account_id=dict(type='raw'),
account_name=dict(type='raw'),
description=dict(type='raw'),
boot=dict(type='raw'),
sep_id=dict(type='raw'),
pool=dict(type='raw'),
cpu=dict(type='raw'),
disks=dict(type='raw'),
id=dict(type='raw'),
image_id=dict(type='raw'),
name=dict(type='raw'),
networks=dict(type='raw'),
network_order_changing=dict(type='raw'),
ram=dict(type='raw'),
rg_id=dict(type='raw'),
rg_name=dict(type='raw'),
ssh_key=dict(type='raw'),
ssh_key_user=dict(type='raw'),
tag=dict(type='raw'),
affinity_label=dict(type='raw'),
aff_rule=dict(type='raw'),
aaff_rule=dict(type='raw'),
ci_user_data=dict(type='raw'),
state=dict(type='raw'),
tags=dict(type='raw'),
chipset=dict(type='raw'),
cpu_pin=dict(type='raw'),
hp_backed=dict(type='raw'),
numa_affinity=dict(type='raw'),
custom_fields=dict(type='raw'),
auto_start=dict(type='raw'),
rollback_to=dict(type='raw'),
preferred_cpu_cores=dict(type='raw'),
get_console_url=dict(type='raw'),
clone_from=dict(type='raw'),
network_interface_naming=dict(type='raw'),
hot_resize=dict(type='raw'),
zone_id=dict(type='raw'),
guest_agent=dict(type='raw'),
get_snapshot_merge_status=dict(type='raw'),
cdrom=dict(type='raw'),
storage_policy_id=dict(type='raw'),
os_version=dict(type='raw'),
get_cloning_status=dict(type='raw'),
abort_cloning=dict(type='raw'),
),
supports_check_mode=True,
)
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
],
)
# Initialize DECORT KVM VM instance object
# This object does not necessarily represent an existing KVM VM
subj = decort_kvmvm(amodule)
# handle state=check before any other logic
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.comp_id:
# Compute is found - package facts and report success to Ansible
subj.result['failed'] = False
# _, subj.comp_info, _ = subj.compute_find(comp_id=subj.comp_id)
# _, rg_facts = subj.rg_find(arg_account_id=0, arg_rg_id=subj.rg_id)
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate Compute name '{}'. Other arguments are: Compute ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'],
amodule.params['rg_name'],
amodule.params['rg_id'],
amodule.params['account_name'])
amodule.fail_json(**subj.result)
pass
if subj.comp_id:
if subj.comp_info['status'] in ("DISABLED", "MIGRATING", "DELETING", "DESTROYING", "ERROR", "REDEPLOYING"):
# cannot do anything on the existing Compute in the listed states
subj.error() # was subj.nop()
elif subj.comp_info['status'] in ("ENABLED", "DISABLED"):
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present', 'paused', 'poweredon', 'poweredoff', 'halted'):
subj.compute_powerstate(subj.comp_info, amodule.params['state'])
subj.modify(arg_wait_cycles=7)
elif subj.comp_info['status'] == "DELETED":
if amodule.params['state'] in ('present', 'poweredon'):
# TODO - check if restore API returns VM ID (similarly to VM create API)
subj.compute_restore(comp_id=subj.comp_id)
# TODO - do we need updated comp_info to manage port forwards and size after VM is restored?
_, subj.comp_info, _ = subj.compute_find(comp_id=subj.comp_id)
subj.modify()
elif amodule.params['state'] == 'absent':
# subj.nop()
# subj.comp_should_exist = False
subj.destroy()
elif amodule.params['state'] in ('paused', 'poweredoff', 'halted'):
subj.error()
elif subj.comp_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'poweredon', 'poweredoff', 'halted'):
subj.create() # this call will also handle data disk & network connection
elif amodule.params['state'] == 'absent':
subj.nop()
subj.comp_should_exist = False
elif amodule.params['state'] == 'paused':
subj.error()
else:
# Preexisting Compute of specified identity was not found.
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
subj.nop()
elif amodule.params['state'] in ('present', 'poweredon', 'poweredoff', 'halted'):
subj.create() # this call will also handle data disk & network connection
elif amodule.params['state'] == 'paused':
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
# prepare Compute facts to be returned as part of decon.result and then call exit_json(...)
rg_facts = None
if subj.comp_should_exist:
if subj.result['changed'] and not subj.skip_final_get:
# There were changes to the Compute - refresh Compute facts.
_, subj.comp_info, _ = subj.compute_find(comp_id=subj.comp_id)
#
# We no longer need to re-read RG facts, as all network info is now available inside
# compute structure
# _, rg_facts = subj.rg_find(arg_account_id=0, arg_rg_id=subj.rg_id)
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
module.fail_json(
msg=(
'The module "decort_kvmvm" has been renamed to "decort_vm". '
'Please update your playbook to use "decort_vm" '
'instead of "decort_kvmvm".'
),
)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,32 +1,21 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: decort_lb
DOCUMENTATION = '''
TODO
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
EXAMPLES = '''
TODO
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_lb(DecortController):
def __init__(self,arg_amodule) -> None:
super(decort_lb,self).__init__(arg_amodule)
def __init__(self) -> None:
super(decort_lb,self).__init__(AnsibleModule(**self.amodule_init_args))
arg_amodule = self.amodule
self.lb_id = 0
self.lb_facts = None
@@ -34,8 +23,6 @@ class decort_lb(DecortController):
self.vins_facts = None
self.rg_id = 0
self.rg_facts = None
self.acc_id = 0
self.acc_facts = None
self.default_server_check = "enabled"
self.default_alg = "roundrobin"
self.default_settings = {
@@ -47,91 +34,137 @@ class decort_lb(DecortController):
"rise": 2,
"slowstart": 60000,
"weight": 100,
}
}
self.is_lb_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['lb_id']:
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id'])
if not self.lb_id:
self.result['failed'] = True
self.result['msg'] = "Specified LB ID {} not found."\
.format(arg_amodule.params['lb _id'])
self.fail_json(**self.result)
self.acc_id = self.lb_facts['accountId']
.format(arg_amodule.params['lb_id'])
self.amodule.fail_json(**self.result)
self.rg_id = self.lb_facts['rgId']
self.vins_id = self.lb_facts['vinsId']
return
if 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="")
if not self.rg_id:
self.result['failed'] = True
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result)
if arg_amodule.params['vins_id']:
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'])
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result)
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_id'])
self.amodule.fail_json(**self.result)
self.acc_id = self.rg_facts['accountId']
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
if arg_amodule.params['rg_name']:
if not arg_amodule.params['rg_name']:
self.result['failed'] = True
self.result['msg'] = ("RG name must be specified with account present")
self.fail_json(**self.result)
self.acc_id, self.acc_facts = self.account_find(arg_amodule.params['account_name'],
self.amodule.fail_json(**self.result)
self.acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not self.acc_id:
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.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.amodule.fail_json(**self.result)
self.rg_id, self.rg_facts = self.rg_find(self.acc_id,0, arg_rg_name=arg_amodule.params['rg_name'])
if self.rg_id and self.vins_id:
if arg_amodule.params['vins_id']:
self.vins_id, self.vins_facts = self.vins_find(
vins_id=arg_amodule.params['vins_id']
)
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = (
f'Specified ViNS ID {arg_amodule.params["vins_id"]}'
f' not found'
)
self.amodule.fail_json(**self.result)
elif arg_amodule.params['vins_name']:
self.vins_id, self.vins_facts = self.vins_find(
vins_id=arg_amodule.params['vins_id'],
vins_name=arg_amodule.params['vins_name'],
rg_id=self.rg_id)
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = (
f'Specified ViNS name {arg_amodule.params["vins_name"]}'
f' not found in RG ID {self.rg_id}'
)
self.amodule.fail_json(**self.result)
if self.rg_id and arg_amodule.params['lb_name']:
self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id)
if self.lb_id and self.lb_facts['status'] != 'DESTROYED':
self.acc_id = self.lb_facts['accountId']
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def create(self):
start_after_create = self.aparams['state'] != 'stopped'
self.lb_id = self.lb_provision(self.amodule.params['lb_name'],
self.rg_id,self.vins_id,
self.amodule.params['ext_net_id'],
self.amodule.params['annotation'])
if self.amodule.params['backends'] or self.amodule.params['frontends']:
self.amodule.params['ha_lb'],
self.amodule.params['description'],
sysctl=self.amodule.params['sysctl'],
zone_id=self.aparams['zone_id'],
start=start_after_create,
)
if self.lb_id and (self.amodule.params['backends'] or
self.amodule.params['frontends']):
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id)
self.lb_update(
self.lb_facts['backends'],
self.lb_facts['frontends'],
self.amodule.params['backends'],
self.amodule.params['servers'],
self.amodule.params['frontends']
)
lb_facts=self.lb_facts,
aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'],
)
return
def action(self,d_state='',restore=False):
if restore == True:
self.lb_restore(arg_vins_id=self.lb_id)
self.lb_state(self.vins_facts, 'enabled')
self.lb_facts['status'] = "ENABLED"
self.lb_facts['techStatus'] = "STARTED"
self.lb_restore(lb_id=self.lb_id)
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_state(self.lb_facts, 'enabled')
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_update(
self.lb_facts['backends'],
self.lb_facts['frontends'],
self.amodule.params['backends'],
self.amodule.params['servers'],
self.amodule.params['frontends']
lb_facts=self.lb_facts,
aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'],
aparam_sysctl=self.aparams['sysctl'],
)
if d_state != '':
self.lb_state(self.lb_facts, d_state)
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
if (d_state == 'enabled' and
self.lb_facts.get('status') == 'ENABLED' and
self.lb_facts.get('techStatus') == 'STOPPED'):
self.lb_state(self.lb_facts, 'started')
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.lb_facts['zoneId']:
self.lb_migrate_to_zone(
lb_id=self.lb_id,
zone_id=aparam_zone_id,
)
return
def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently'])
self.lb_facts['status'] = 'DESTROYED'
self.lb_id, self.lb_facts = self._lb_get_by_id(self.lb_id)
return
def nop(self):
"""No operation (NOP) handler for LB management by decort_lb module.
This function is intended to be called from the main switch construct of the module
@@ -142,7 +175,7 @@ class decort_lb(DecortController):
self.result['changed'] = False
if self.lb_id:
self.result['msg'] = ("No state change required for LB ID {} because of its "
"current status '{}'.").format(self.lb_id, self.vins_facts['status'])
"current status '{}'.").format(self.lb_id, self.lb_facts['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent LB instance.").format(self.amodule.params['state'])
@@ -175,13 +208,14 @@ class decort_lb(DecortController):
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
sysctl={},
)
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
if self.vins_facts is None:
if self.lb_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
@@ -189,140 +223,198 @@ class decort_lb(DecortController):
ret_dict['id'] = self.lb_facts['id']
ret_dict['name'] = self.lb_facts['name']
ret_dict['state'] = self.lb_facts['status']
#ret_dict['account_id'] = self.lb_facts['accountId']
ret_dict['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
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_vins module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default='Managed by Ansible module decort_lb'),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','restart']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_id=dict(type='int', required=False, default=0),
rg_name=dict(type='str', required=False, default=''),
vins_name=dict(type='str', required=False, default=''),
vins_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True),
lb_id=dict(type='int', required=False, default=0),
lb_name=dict(type='str', required=True),
backends=dict(type='list',required=False,default=[]),
frontends=dict(type='list',required=False,default=[]),
servers=dict(type='list',required=False,default=[]),
permanently=dict(type='bool', required=False, default=False),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
default='',
),
description=dict(
type='str',
),
ext_net_id=dict(
type='int',
default=0,
),
state=dict(
type='str',
choices=[
'absent',
'disabled',
'enabled',
'present',
'restart',
'started',
'stopped',
],
),
rg_id=dict(
type='int',
default=0,
),
rg_name=dict(
type='str',
default='',
),
vins_name=dict(
type='str',
default='',
),
vins_id=dict(
type='int',
default=0,
),
lb_id=dict(
type='int',
default=0,
),
lb_name=dict(
type='str',
),
ha_lb=dict(
type='bool',
default=False,
),
backends=dict(
type='list',
),
frontends=dict(
type='list',
),
servers=dict(
type='list',
),
permanently=dict(
type='bool',
default=False,
),
sysctl=dict(
type='dict',
),
zone_id=dict(
type=int,
),
),
supports_check_mode=True,
required_one_of=[
('rg_id', 'rg_name'),
('lb_id', 'lb_name'),
('vins_id', 'vins_name'),
],
)
def main():
module_parameters = decort_lb.build_parameters()
def check_amodule_args_for_change(self):
check_errors = False
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password']
],
required_one_of=[
['rg_id','rg_name'],
['lb_id','lb_name'],
['vins_id','vins_name']
]
)
decon = decort_lb(amodule)
if decon.lb_id:
if decon.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]:
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current "
"status '{}'").format(decon.lb_id, decon.lb_facts['status'])
elif decon.lb_facts['status'] == "DISABLED":
lb_info: dict = self.lb_facts
self.is_lb_stopped_or_will_be_stopped = (
(
lb_info['techStatus'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
lb_info['techStatus'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != lb_info['zoneId']
and not self.is_lb_stopped_or_will_be_stopped
):
check_errors = True
self.message(
'Check for parameter "zone_id" failed: '
'Load balancer must be stopped to migrate to a zone.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
if self.lb_id:
if self.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing LB ID {} because of its current "
"status '{}'").format(self.lb_id, self.lb_facts['status'])
elif self.lb_facts['status'] in ('DISABLED', 'ENABLED', 'CREATED'):
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] in ('present', 'disabled'):
decon.action()
self.delete()
else:
self.action(d_state=amodule.params['state'])
elif self.lb_facts['status'] == "DELETED":
if amodule.params['state'] == 'present':
self.action(restore=True)
elif amodule.params['state'] == 'enabled':
decon.action('enabled')
elif decon.lb_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] in ('present', 'enabled'):
decon.action()
self.action(d_state='enabled', restore=True)
elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']):
self.delete()
elif amodule.params['state'] == 'disabled':
decon.action('disabled')
elif amodule.params['state'] in ('stopped', 'started','restart'):
decon.action(amodule.params['state'])
elif decon.lb_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
decon.action(restore=True)
elif amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] == 'disabled':
decon.error()
elif decon.lb_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
decon.create()
elif amodule.params['state'] == 'absent':
decon.nop()
elif amodule.params['state'] == 'disabled':
decon.error()
else:
if amodule.params['state'] == 'absent':
decon.nop()
elif amodule.params['state'] in ('present', 'enabled'):
decon.create()
elif amodule.params['state'] == 'disabled':
decon.error()
self.error()
elif self.lb_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
self.create()
elif amodule.params['state'] == 'absent':
self.nop()
elif amodule.params['state'] == 'disabled':
self.error()
else:
state = amodule.params['state']
if state is None:
state = 'present'
if state == 'absent':
self.nop()
elif state in ('present', 'enabled', 'stopped', 'started'):
self.create()
elif state == 'disabled':
self.error()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed'] and amodule.params['state'] != 'absent':
_, decon.lb_facts = decon.lb_find(decon.lb_id)
if decon.lb_id:
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.result['changed']:
_, self.lb_facts = self.lb_find(lb_id=self.lb_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
def main():
decort_lb().run()
if __name__ == '__main__':
main()

View File

@@ -1,573 +1,62 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_osimage
short_description: Locate OS image in DCORT cloud by its name and return image ID
description: >
This module can be used to obtain image ID of an OS image in DECORT cloud to use with subsequent calls to
decort_vm module for batch VM provisioning. It will speed up VM creation and save a bunch of extra calls to
DECORT cloud controller on each VM creation act.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.6.1 or higher.
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
options:
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
controller_url:
description:
- URL of the DECORT controller that will be contacted to obtain OS image details.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
image_name:
description:
- Name of the OS image to use. Module will return the ID of this image.
- 'The specified image name will be looked up in the target DECORT controller and error will be generated
- if no matching image is found.'
required: yes
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
pool:
description:
- 'Name of the storage pool, where the image should be found.'
- 'Omit this option if no matching by pool name is required. The first matching image will be returned."
required: no
sep_id:
description:
- 'ID of the SEP (Storage End-point Provider), where the image should be found.'
- 'Omit this option if no matching by SEP ID is required. The first matching image will be returned."
required: no
account_name:
description:
- 'Name of the account for which the specified OS image will be looked up.'
- 'This parameter is required for listing OS images.'
required: yes
user:
description:
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
account_name:
description:
- 'Account name. Used to get a unique integer account ID.'
required: no
virt_id:
description:
- 'A unique integer identifier for the virtual image.'
- 'Can be used to obtain information about a virtual image, as well as to create a virtual image and
- bind another operating system image to it.'
required: no
virt_name:
description:
- 'Name of the virtual image. Used to get the `virt_id`, and later information about the virtual image,
- as well as to create a virtual image and bind another operating system image to it.'
required: no
state:
description:
- 'The state of the images. If set to present, operating system images will be created to which
- the account specified in `account_Id` or `account_name` is bound. If set to absent, they will be removed.
required: no
drivers:
description:
- 'A list of compute types (eg virtual servers) that are appropriate for the operating system image.
- Note: `KVM_X86`. Used when creating an operating system image.'
required: no
architecture:
description:
- 'Binary architecture of the image. Note. `X86_64` or `PPC64_LE`. Used when creating
-an operating system image.'
required: no
imagetype:
description:
- 'Image type. `linux`, `windows` or `other`. The default is `linux`. Used when creating
- an operating system image.'
required: no
boottype:
description:
- 'Image upload type. `bios` or `uefi`. The default is `uefi`. Used when creating an operating
-system image.'
required: no
url:
description:
- 'Uniform resource locator (URL) pointing to the iso image of the operating system. Used when
-creating an operating system image.'
required: no
sepId:
description:
- 'The unique integer ID of the storage provider endpoint. Specified in pair with `poolName`.
- Used when creating an operating system image.'
required: no
poolName:
description:
- 'The pool in which the image will be created. Specified in pair with `sepId`. Used when creating
- an operating system image.'
required: no
hotresize:
description:
- 'Whether the image supports "hot" resizing. The default is `false`. Used when creating an operating
- system image.'
required: no
image_username:
description:
- 'An optional username for the image. Used when creating an operating system image.'
required: no
image_password:
description:
- 'An optional password for the image. Used when creating an operating system image. Used when creating
- an operating system image.'
required: no
usernameDL:
description:
- 'The username for loading the binary media. Used in conjunction with `passwordDL`. Used when creating
- an operating system image'
required: no
passwordDL:
description:
- 'The password for loading the binary media. Used in conjunction with `usernameDL`. Used when creating
- an operating system image.'
required: no
permanently:
description:
- 'Whether to permanently delete the image. Used when deleting an image. The default is false.'
required: no
'''
EXAMPLES = '''
- name: create_osimage
decort_osimage:
authenticator: oauth2
verify_ssl: False
controller_url: "https://ds1.digitalenergy.online"
state: present
image_name: "alpine_linux3.14.0"
account_Id: 12345
url: "https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-virt-3.14.0-x86_64.iso"
boottype: "uefi"
imagetype: "linux"
hotresize: False
image_username: "test"
image_password: "p@ssw0rd"
usernameDL: "testDL"
passwordDL: "p@ssw0rdDL"
architecture: "X86_64"
drivers: "KVM_X86"
delegate_to: localhost
register: osimage
- name: get_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
account_Id: 12345
delegate_to: localhost
register: osimage
- name: create_virtual_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
virt_name: "alpine_last"
delegate_to: localhost
register: osimage
- name: rename_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0v2.0"
image_id: 54321
delegate_to: localhost
register: osimage
'''
RETURN = '''
facts:
description: facts about the specified OS image
returned: always
type: dict
sample:
facts:
id: 100
linkto: 80
name: "Ubuntu 16.04 v1.0"
size: 3
sep_id: 1
pool: "vmstore"
type: Linux
arch: x86_64
state: CREATED
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_osimage(DecortController):
def __init__(self,amodule):
super(decort_osimage, self).__init__(amodule)
self.validated_image_id = 0
self.validated_virt_image_id = 0
self.validated_image_name = amodule.params['image_name']
self.validated_virt_image_name = None
self.validated_virt_image_id = amodule.params['virt_id']
if amodule.params['account_name']:
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
else:
self.validated_account_id = amodule.params['account_Id']
if self.validated_account_id == 0:
# we failed either to find or access the specified account - fail the module
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
amodule.fail_json(**self.result)
if amodule.params['image_id'] != 0 and amodule.params['image_name']:
self.validated_image_id = amodule.params['image_id']
if amodule.params['image_name']:
decort_osimage.decort_image_rename(self,amodule)
self.result['msg'] = ("Image renamed successfully")
def decort_image_find(self, amodule):
# function that finds the OS image
image_id, image_facts = self.image_find(image_id=amodule.params['image_id'], image_name=self.validated_image_name,
account_id=self.validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
pool=amodule.params['pool'])
return image_id, image_facts
def decort_virt_image_find(self, amodule):
# function that finds a virtual image
image_id, image_facts = self.virt_image_find(image_id=amodule.params['virt_id'],
account_id=self.validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
virt_name=amodule.params['virt_name'],
pool=amodule.params['pool'])
return image_id, image_facts
def decort_image_create(self,amodule):
# function that creates OS image
image_facts = self.image_create(img_name=self.validated_image_name,
url=amodule.params['url'],
gid=amodule.params['gid'],
boottype=amodule.params['boottype'],
imagetype=amodule.params['imagetype'],
hotresize=amodule.params['hotresize'],
username=amodule.params['image_username'],
password=amodule.params['image_password'],
account_Id=amodule.params['account_Id'],
usernameDL=amodule.params['usernameDL'],
passwordDL=amodule.params['passwordDL'],
sepId=amodule.params['sepId'],
poolName=amodule.params['poolName'],
architecture=amodule.params['architecture'],
drivers=amodule.params['drivers'])
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.validated_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.validated_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, permanently=amodule.params['permanently'])
self.result['changed'] = True
self.result['msg'] = ("Image '{}' deleted").format(amodule.image_id_delete)
def decort_virt_image_create(self,amodule):
# function that creates a virtual image
image_facts = self.virt_image_create(name=amodule.params['virt_name'], targetId=self.validated_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)
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_osimage_package_facts(arg_osimage_facts, 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['type'] = arg_osimage_facts['type']
# 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']
return ret_dict
def decort_osimage_parameters():
"""Build and return a dictionary of parameters expected by decort_osimage module in a form accepted
by AnsibleModule utility class."""
return dict(
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
pool=dict(type='str', required=False, default=""),
sep_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False),
account_Id=dict(type='int', required=False),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
image_name=dict(type='str', required=False),
image_id=dict(type='int', required=False,default=0),
virt_id=dict(type='int', required=False, default=0),
virt_name=dict(type='str', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
drivers=dict(type='str', required=False, default="KVM_X86"),
architecture=dict(type='str', required=False, default="X86_64"),
imagetype=dict(type='str', required=False, default="linux"),
boottype=dict(type='str', required=False, default="uefi"),
url=dict(type='str', required=False),
gid=dict(type='int', required=False, default=0),
sepId=dict(type='int', required=False, default=0),
poolName=dict(type='str', required=False),
hotresize=dict(type='bool', required=False, default=False),
image_username=dict(type='str', required=False),
image_password=dict(type='str', required=False),
usernameDL=dict(type='str', required=False),
passwordDL=dict(type='str', required=False),
permanently=dict(type='bool', required=False, default=False),
)
def main():
module_parameters = decort_osimage.decort_osimage_parameters()
module = AnsibleModule(
argument_spec=dict(
app_id=dict(type='raw'),
app_secret=dict(type='raw'),
authenticator=dict(type='raw'),
controller_url=dict(type='raw'),
domain=dict(type='raw'),
jwt=dict(type='raw'),
oauth2_url=dict(type='raw'),
password=dict(type='raw'),
username=dict(type='raw'),
verify_ssl=dict(type='raw'),
ignore_api_compatibility=dict(type='raw'),
ignore_sdk_version_check=dict(type='raw'),
pool=dict(type='raw'),
sep_id=dict(type='raw'),
account_name=dict(type='raw'),
account_id=dict(type='raw'),
image_name=dict(type='raw'),
image_id=dict(type='raw'),
virt_id=dict(type='raw'),
virt_name=dict(type='raw'),
state=dict(type='raw'),
url=dict(type='raw'),
sepId=dict(type='raw'),
poolName=dict(type='raw'),
hot_resize=dict(type='raw'),
image_username=dict(type='raw'),
image_password=dict(type='raw'),
usernameDL=dict(type='raw'),
passwordDL=dict(type='raw'),
boot=dict(type='raw'),
network_interface_naming=dict(type='raw'),
storage_policy_id=dict(type='raw'),
),
supports_check_mode=True,
)
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
)
decon = decort_osimage(amodule)
if 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":
if amodule.params['image_name'] or amodule.params['image_id'] and\
decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']:
amodule.image_id_delete = decon.validated_image_id
decort_osimage.decort_image_delete(decon,amodule)
module.fail_json(
msg=(
'The module "decort_osimage" has been renamed to "decort_image". '
'Please update your playbook to use "decort_image" '
'instead of "decort_osimage".'
),
)
if amodule.params['virt_name'] or amodule.params['virt_id']:
image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule)
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.validated_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.validated_image_id == 0:
decon.result['msg'] = ("Cannot find OS image")
amodule.fail_json(**decon.result)
if decon.validated_image_id:
if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.validated_image_id:
decort_osimage.decort_virt_image_link(decon,amodule)
decon.result['changed'] = True
amodule.exit_json(**decon.result)
if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent":
decon.result['msg'] = ("Osimage module cannot delete virtual images.")
decon.result['failed'] = True
amodule.exit_json(**decon.result)
if decon.result['failed'] == True:
# we failed to find the specified image - fail the module
decon.result['changed'] = False
amodule.fail_json(**decon.result)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,332 +1,150 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_pfw
short_description: Manage network Port Forward rules for Compute instances in DECORT cloud
description: >
This module can be used to create new port forwarding rules in DECORT cloud platform,
modify and delete them.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.6.1 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
options:
account_id:
description:
- ID of the account, which owns this disk. This is the alternative to I(account_name) option.
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
default: 0
required: no
account_name:
description:
- 'Name of the account, which will own this disk.'
- 'This parameter is ignored if I(account_id) is specified.'
default: empty string
required: no
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
controller_url:
description:
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
compute_id:
description:
- ID of the Compute instance to manage network port forwarding rules for.
required: yes
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
rules:
description:
- 'Set of rules to configure for the Compute instance identidied by I(compute_id) in the virtual
network segment identidied by I(vins_id).'
- The set is specified as a list of dictionaries with the following structure:
- ' - (int) public_port_start - starting port number on the ViNS external interface.'
- ' - (int) public_port_end - optional end port number of the ViNS external interface. If not specified
or set equal to I(public_port_start), a one-to-one rule is created. Otherwise a ranged rule will
be created, which maps specified external port range to local ports starting from I(local_port).'
- ' - (int) local_port - port number on the local interface of the Compute. For ranged rule it is
interpreted as a base port to translate public port range to internal port range.'
- ' - (string) proto - protocol, specify either I(tcp) or I(udp).'
- 'Note that rules are meaningful only if I(state=present). If I(state=absent) is specified, rules set
will be ignored, and all rules for the specified Compute will be deleted.'
state:
description:
- 'Specify the desired state of the port forwarding rules set for the Compute instance identified by
I(compute_id).'
- 'If I(state=present), the rules will be applied according to the I(rules) parameter.'
- 'If I(state=absent), all rules for the specified Compute instance will be deleted regardless of
I(rules) parameter.'
default: present
choices: [ absent, present ]
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
vins_id:
description:
- ID of the virtual network segment (ViNS), where port forwarding rules will be set up.
- This ViNS must have connection to external network.
- Compute instance specified by I(compute_id) must be connected to this ViNS.
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
'''
EXAMPLES = '''
- name: configure one-toone rule for SSH protocol on Compute ID 100 connected to ViNS ID 5.
decort_pfw:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://cloud.digitalenergy.online"
compute_id: 100
vins_id: 5
rules:
- public_port_start: 10022
local_port: 22
proto: tcp
state: present
delegate_to: localhost
register: my_pfw
'''
RETURN = '''
facts:
description: facts about created PFW rules
returned: always
type: dict
sample:
facts:
compute_id: 100
vins_id: 5
rules:
-
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 *
def decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, check_mode=False):
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
class decort_pfw(DecortController):
def __init__(self):
super(decort_pfw, self).__init__(AnsibleModule(**self.amodule_init_args))
@param (dict) pfw_facts: dictionary with PFW facts as returned by API call to .../???/get
@param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
"""
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
compute_id=dict(
type='int',
required=True,
),
rules=dict(
type='list',
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'present',
],
),
vins_id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
)
ret_dict = dict(state="CHECK_MODE",
compute_id=0,
public_ip="",
rules=[],
vins_id=0,
)
def decort_pfw_package_facts(self, comp_facts, vins_facts, pfw_facts, check_mode=False):
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
@param (dict) pfw_facts: dictionary with PFW facts as returned by API call to .../???/get
@param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
"""
ret_dict = dict(state="CHECK_MODE",
compute_id=0,
public_ip="",
rules=[],
vins_id=0,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
if pfw_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['compute_id'] = comp_facts['id']
ret_dict['vins_id'] = vins_facts['id']
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip']
if len(pfw_facts) != 0:
ret_dict['state'] = 'PRESENT'
ret_dict['rules'] = pfw_facts
else:
ret_dict['state'] = 'ABSENT'
if check_mode:
# in check mode return immediately with the default values
return ret_dict
if pfw_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
def decort_pfw_parameters(self):
"""Build and return a dictionary of parameters expected by decort_pfw module in a form accepted
by AnsibleModule utility class."""
ret_dict['compute_id'] = comp_facts['id']
ret_dict['vins_id'] = vins_facts['id']
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip']
if len(pfw_facts) != 0:
ret_dict['state'] = 'PRESENT'
ret_dict['rules'] = pfw_facts
else:
ret_dict['state'] = 'ABSENT'
return
return ret_dict
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
def decort_pfw_parameters():
"""Build and return a dictionary of parameters expected by decort_pfw module in a form accepted
by AnsibleModule utility class."""
pfw_facts = None # will hold PFW facts as returned by pfw_configure
#
# Validate module arguments:
# 1) specified Compute instance exists in correct state
# 2) specified ViNS exists
# 3) ViNS has GW function
# 4) Compute is connected to this ViNS
#
validated_comp_id, comp_facts, rg_id = self.compute_find(amodule.params['compute_id'])
if not validated_comp_id:
self.result['failed'] = True
self.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id'])
amodule.fail_json(**self.result)
validated_vins_id, vins_facts = self.vins_find(amodule.params['vins_id'])
if not validated_vins_id:
self.result['failed'] = True
self.result['msg'] = "Cannot find specified ViNS ID {}.".format(amodule.params['vins_id'])
amodule.fail_json(**self.result)
gw_vnf_facts = vins_facts['vnfs'].get('GW')
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED":
self.result['failed'] = True
self.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id)
amodule.fail_json(**self.result)
#
# Initial validation of module arguments is complete
#
if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = self.pfw_configure(comp_facts, vins_facts, None)
elif amodule.params['rules'] is not None:
# manage PFW rules accodring to the module arguments
pfw_facts = self.pfw_configure(comp_facts, vins_facts, amodule.params['rules'])
else:
pfw_facts = self._pfw_get(comp_facts['id'], vins_facts['id'])
#
# complete module run
#
if self.result['failed']:
amodule.fail_json(**self.result)
else:
# prepare PFW facts to be returned as part of self.result and then call exit_json(...)
self.result['facts'] = self.decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode)
amodule.exit_json(**self.result)
return dict(
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
compute_id=dict(type='int', required=True),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
rules=dict(type='list', required=False, default=[]),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
def main():
module_parameters = decort_pfw_parameters()
decort_pfw().run()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
)
decon = DecortController(amodule)
pfw_facts = None # will hold PFW facts as returned by pfw_configure
#
# Validate module arguments:
# 1) specified Compute instance exists in correct state
# 2) specified ViNS exists
# 3) ViNS has GW function
# 4) Compute is connected to this ViNS
#
validated_comp_id, comp_facts, rg_id = decon.compute_find(amodule.params['compute_id'])
if not validated_comp_id:
decon.result['failed'] = True
decon.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id'])
amodule.fail_json(**decon.result)
validated_vins_id, vins_facts = decon.vins_find(amodule.params['vins_id'])
if not validated_vins_id:
decon.result['failed'] = True
decon.result['msg'] = "Cannot find specified ViNS ID {}.".format(amodule.params['vins_id'])
amodule.fail_json(**decon.result)
gw_vnf_facts = vins_facts['vnfs'].get('GW')
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED":
decon.result['failed'] = True
decon.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id)
amodule.fail_json(**decon.result)
#
# Initial validation of module arguments is complete
#
if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, None)
else:
# manage PFW rules accodring to the module arguments
pfw_facts = decon.pfw_configure(comp_facts, vins_facts, amodule.params['rules'])
#
# complete module run
#
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
# prepare PFW facts to be returned as part of decon.result and then call exit_json(...)
decon.result['facts'] = decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,207 +1,10 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_rg
short_description: Manage resource groups (RGs) in DECORT cloud
description: >
This module can be used to create a new resource group in DECORT cloud platform, modify its
characteristics, and delete it.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.6.1 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
- New RGs provisioned with this module will be deployed to the first location under specified DECORT
controller (if there is more than one location).
options:
account_id:
description:
- ID of the account under which this RG will be created. This is the alternative to I(account_name)
option. If both I(account_id) and I(account_name) specified, the latter is ignored.
account_name:
description:
- 'Name of the account under which this RG will be created (for new RGs) or is located.'
- 'This parameter is ignored if I(account_id) is specified.'
required: no
annotation:
description:
- Optional text description of this resource group.
default: empty string
required: no
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
controller_url:
description:
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
quotas:
description:
- Dictionary that defines resource quotas to be set on a newly created RG.
- 'This parameter is optional and only used when creating new RG. It is ignored for any operations on an
existing RG.'
- 'The following keys are valid to set the resource quotas:'
- ' - I(cpu) (integer) - limit on the total number of CPUs that can be consumed by all compute instances
in this RG.'
- ' - I(ram) (integer) - limit on the total amount of RAM in GB that can be consumed by compute instances
in this RG.'
- ' - I(disk) (integer) - limit on the total volume of disk space in GB that can be consumed by all
compute instances in this RG.'
- ' - I(ext_ips) (integer) - maximum number of external IP addresses that can be allocated to the compute
instances and virtual network segments (ViNS) in this RG.'
- 'Each of the above keys is optional. For example, you may specify I(cpu) and I(ram) while omitting the
other two keys. Then the quotas will be set on RAM and CPU leaving disk volume and the number of external
IP addresses unlimited.'
required: no
rg_name:
description:
- Name of the RG to manage.
required: yes
state:
description:
- Specify the desired state of the resource group at the exit of the module.
- 'Regardless of I(state), if RG exists and is in one of [DEPLOYING, DESTROYING, MIGRATING, ] states,
do nothing.'
- 'If desired I(state=present):'
- ' - RG does not exist or is in DESTROYED state, create new RG according to the specifications.'
- ' - RG is in one of [CREATED, DISABLED] states, change quotas if necessary.'
- ' - RG is in DELETED state, restore it and change quotas if necessary. RG will be left in DISABLED state.'
- ' - RG in any other state, abort with an error.'
- 'If desired I(state=enabled):'
- ' - RG does not exist or is in DESTROYED state, create new RG according to the specifications.'
- ' - RG is in CREATED state, change quotas if necessary.'
- ' - RG is in DELETED state, restore it, change quotas if necessary and enable.'
- ' - RG is in any other state, abort with an error.'
- 'If desired I(state=absent):'
- ' - RG is in one of [CREATED, DISABLED, DELETED] states, destroy it.'
- ' - RG in DESTROYED state, do nothing.'
- ' - RG in any other state, abort with an error.'
- 'If desired I(state=disabled):'
- ' - RG does not exist or is in one of [ENABLING, DISABLING, DELETING, DELETED, DESTROYING, DESTROYED]
states, abort with an error.'
- ' - RG is DISABLED state, change quotas if necessary.'
- ' - RG is in CREATED state, change quotas if necessary and disable the RG.'
default: present
choices: [ absent, disabled, enabled, present ]
user:
description:
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
'''
EXAMPLES = '''
- name: create a new RG named "MyFirstRG" if it does not exist yet, set quotas on CPU and the number of exteranl IPs.
decort_rg:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://cloud.digitalenergy.online"
rg_name: "MyFirstRG"
account_name: "MyMainAccount"
quotas:
cpu: 16
ext_ips: 4
annotation: "My first RG created with Ansible module"
state: present
delegate_to: localhost
register: my_rg
'''
RETURN = '''
facts:
description: facts about the resource group
returned: always
type: dict
sample:
facts:
id: 100
name: MyFirstRG
state: CREATED
account_id: 10
gid: 1001
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
@@ -209,39 +12,57 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_rg(DecortController):
def __init__(self,amodule):
super(decort_rg, self).__init__(amodule)
def __init__(self):
super(decort_rg, self).__init__(AnsibleModule(**self.amodule_init_args))
amodule = self.amodule
self.validated_acc_id = 0
self.validated_rg_id = 0
self.validated_rg_facts = None
if self.amodule.params['account_id']:
self.validated_acc_id, _ = self.account_find("", amodule.params['account_id'])
elif amodule.params['account_name']:
self.validated_acc_id, _ = self.account_find(amodule.params['account_name'])
if not self.validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.fail_json(**self.result)
if amodule.params['rg_id'] > 0:
self.validated_rg_id = amodule.params['rg_id']
if amodule.params['rg_id'] is None:
if self.amodule.params['account_id']:
self.validated_acc_id, _ = self.account_find("", amodule.params['account_id'])
elif amodule.params['account_name']:
self.validated_acc_id, _ = self.account_find(amodule.params['account_name'])
if not self.validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.amodule.fail_json(**self.result)
# Check if the RG with the specified parameters already exists
self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id,
arg_rg_id = self.validated_rg_id,
arg_rg_name=amodule.params['rg_name'],
arg_check_state=False)
self.get_info()
if amodule.params['state'] != "absent":
self.rg_should_exist = True
else:
self.rg_should_exist = False
if self.validated_rg_id and self.rg_facts['status'] != 'DESTROYED':
self.check_amodule_args_for_change()
def get_info(self):
# If this is the first getting info
if not self.validated_rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find(
arg_account_id=self.validated_acc_id,
arg_rg_id=self.aparams['rg_id'],
arg_rg_name=self.aparams['rg_name'],
arg_check_state=False,
)
# If this is a repeated getting info
else:
# If check mode is enabled, there is no needed to
# request info again
if self.amodule.check_mode:
return
_, self.rg_facts = self.rg_find(arg_rg_id=self.validated_rg_id)
def access(self):
should_change_access = False
acc_granted = False
@@ -276,59 +97,115 @@ class decort_rg(DecortController):
self.amodule.params['rg_name'],
self.validated_acc_id)
return
def update(self):
resources = self.rg_facts['Resources']['Reserved']
incorrect_quota = dict(Requested=dict(),
Reserved=dict(),)
query_key_map = dict(cpu='cpu',
ram='ram',
disk='disksize',
ext_ips='extips',
net_transfer='exttraffic',)
query_key_map = dict(
cpu='cpu',
ram='ram',
disk='disksize',
ext_ips='extips',
storage_policies='policies',
)
if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']:
if self.amodule.params['quotas'][quota_item] < resources[query_key_map[quota_item]]:
incorrect_quota['Requested'][quota_item]=self.amodule.params['quotas'][quota_item]
incorrect_quota['Reserved'][quota_item]=resources[query_key_map[quota_item]]
if quota_item == 'storage_policies':
rg_storage_policies = resources[query_key_map[quota_item]]
aparam_storage_policies = self.amodule.params['quotas'][
quota_item
]
for aparam_storage_policy in aparam_storage_policies:
aparam_storage_policy_id = aparam_storage_policy['id']
rg_storage_policy = rg_storage_policies.get(
str(aparam_storage_policy_id)
)
if (
rg_storage_policy
and aparam_storage_policy['storage_size_gb']
< rg_storage_policy['disksize']
):
incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item]
)
incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]]
)
elif (
self.amodule.params['quotas'][quota_item]
< resources[query_key_map[quota_item]]
):
incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item]
)
incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]]
)
if incorrect_quota['Requested']:
self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
self.result['failed'] = True
if self.result['failed'] != True:
self.rg_update(self.rg_facts, self.amodule.params['quotas'],
self.amodule.params['resType'], self.amodule.params['rename'])
if not self.result['failed']:
self.rg_update(
arg_rg_dict=self.rg_facts,
arg_quotas=self.amodule.params['quotas'],
arg_res_types=self.amodule.params['resType'],
arg_newname=self.amodule.params['rename'],
arg_sep_pools=self.amodule.params['sep_pools'],
arg_desc=self.amodule.params['description'],
)
self.rg_should_exist = True
return
def setDefNet(self):
if self.amodule.params['def_netId'] != self.rg_facts['def_net_id']:
self.rg_setDefNet(self.validated_rg_id,
self.amodule.params['def_netType'],
self.amodule.params['def_netId'])
rg_def_net_type = self.rg_facts['def_net_type']
rg_def_net_id = self.rg_facts['def_net_id']
aparam_def_net_type = self.aparams['def_netType']
aparam_def_net_id = self.aparams['def_netId']
need_to_reset = (aparam_def_net_type == 'NONE'
and rg_def_net_type != aparam_def_net_type)
need_to_change = False
if aparam_def_net_id is not None:
need_to_change = (aparam_def_net_id != rg_def_net_id
or aparam_def_net_type != rg_def_net_type)
if need_to_reset or need_to_change:
self.rg_setDefNet(
arg_rg_id=self.validated_rg_id,
arg_net_type=aparam_def_net_type,
arg_net_id=aparam_def_net_id,
)
self.rg_should_exist = True
return
def create(self):
self.validated_rg_id = self.rg_provision(self.validated_acc_id,
self.amodule.params['rg_name'],
self.amodule.params['owner'],
self.amodule.params['annotation'],
self.amodule.params['resType'],
self.amodule.params['def_netType'],
self.amodule.params['ipcidr'],
self.amodule.params['extNetId'],
self.amodule.params['extNetIp'],
self.amodule.params['quotas'],
"", # this is location code. TODO: add module argument
)
self.validated_rg_id = self.rg_provision(
self.validated_acc_id,
self.amodule.params['rg_name'],
self.amodule.params['owner'],
self.amodule.params['description'],
self.amodule.params['resType'],
self.amodule.params['def_netType'],
self.amodule.params['ipcidr'],
self.amodule.params['extNetId'],
self.amodule.params['extNetIp'],
self.amodule.params['quotas'],
"", # this is location code. TODO: add module argument
sdn_access_group_id=self.aparams['sdn_access_group_id'],
)
self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id,
self.validated_rg_id,
arg_rg_name="",
arg_check_state=False)
self.rg_should_exist = True
if self.validated_rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find(
arg_account_id=self.validated_acc_id,
arg_rg_id=self.validated_rg_id,
arg_rg_name="",
arg_check_state=False
)
self.rg_should_exist = True
return
def enable(self):
@@ -348,8 +225,11 @@ class decort_rg(DecortController):
return
def destroy(self):
self.rg_delete(self.validated_rg_id, self.amodule.params['permanently'])
self.rg_delete(
rg_id=self.validated_rg_id,
permanently=self.amodule.params['permanently'],
recursively=self.aparams['recursive_deletion'],
)
if self.amodule.params['permanently'] == True:
self.rg_facts['status'] = 'DESTROYED'
else:
@@ -390,65 +270,135 @@ class decort_rg(DecortController):
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
def parameters():
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
access=dict(type='dict'),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
def_netType=dict(type='str', choices=['PRIVATE','PUBLIC', 'NONE'], default='PRIVATE'),
def_netId=dict(type='int', default=0),
extNetId=dict(type='int', default=0),
extNetIp=dict(type='str', default=""),
owner=dict(type='str', default=""),
ipcidr=dict(type='str', default=""),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
rename=dict(type='str', default=""),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
quotas=dict(type='dict', required=False),
resType=dict(type='list'),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
permanently=dict(type='bool',
default='False'),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_name=dict(type='str', required=False,),
rg_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
default='',
),
access=dict(
type='dict',
),
description=dict(
type='str',
default='',
),
def_netType=dict(
type='str',
choices=[
'PRIVATE',
'PUBLIC',
'NONE',
],
default='PRIVATE',
),
def_netId=dict(
type='int',
),
extNetId=dict(
type='int',
default=0,
),
extNetIp=dict(
type='str',
default='',
),
owner=dict(
type='str',
default='',
),
ipcidr=dict(
type='str',
default='',
),
rename=dict(
type='str',
default='',
),
quotas=dict(
type='dict',
),
resType=dict(
type='list',
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'disabled',
'enabled',
'present',
],
),
permanently=dict(
type='bool',
default='False',
),
rg_name=dict(
type='str',
),
rg_id=dict(
type='int',
),
sep_pools=dict(
type='list',
elements='dict',
options=dict(
sep_id=dict(
type='int',
required=True,
),
pool_names=dict(
type='list',
required=True,
elements='str',
),
),
),
recursive_deletion=dict(
type='bool',
default=False,
),
sdn_access_group_id=dict(
type='str',
),
),
supports_check_mode=True,
)
def check_amodule_args_for_change(self):
check_errors = False
if (
self.aparams['sdn_access_group_id'] is not None
and (
self.aparams['sdn_access_group_id']
!= self.rg_facts['sdn_access_group_id']
)
):
self.message(
'Check for parameter "sdn_access_group_id" failed: '
'cannot change sdn_access_group_id for an existing resource '
f'group ID {self.validated_rg_id}.'
)
check_errors = True
if check_errors:
self.exit(fail=True)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the RG with the specified id or rg_name:name exists
@@ -456,68 +406,78 @@ class decort_rg(DecortController):
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
def main():
module_parameters = decort_rg.parameters()
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
#amodule.check_mode=True
if self.validated_rg_id > 0:
if self.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
self.error()
elif self.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
self.destroy()
elif amodule.params['state'] == "disabled":
self.enable()
if amodule.params['state'] in ['present', 'enabled']:
if (
amodule.params['quotas']
or amodule.params['resType']
or amodule.params['rename'] != ""
or amodule.params['sep_pools'] is not None
or amodule.params['description'] is not None
):
self.update()
if amodule.params['access']:
self.access()
if amodule.params['def_netType'] is not None:
self.setDefNet()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
)
elif self.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
self.destroy()
elif (amodule.params['state'] == 'present'
or amodule.params['state'] == 'disabled'):
self.restore()
elif amodule.params['state'] == 'enabled':
self.restore()
self.enable()
elif self.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
self.destroy()
elif amodule.params['state'] == ("enabled"):
self.enable()
decon = decort_rg(amodule)
#amodule.check_mode=True
if decon.validated_rg_id > 0:
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
decon.error()
elif decon.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == "disabled":
decon.enable()
if amodule.params['state'] in ['present', 'enabled']:
if amodule.params['quotas'] or amodule.params['resType'] or amodule.params['rename'] != "":
decon.update()
if amodule.params['access']:
decon.access()
if amodule.params['def_netId'] > 0:
decon.setDefNet()
elif decon.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
decon.destroy()
elif amodule.params['state'] == 'present':
decon.restore()
elif decon.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == ("enabled"):
decon.enable()
else:
if amodule.params['state'] in ('present', 'enabled'):
decon.create()
if amodule.params['access']:
decon.access()
elif amodule.params['state'] in ('disabled'):
decon.error()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.rg_should_exist:
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
else:
amodule.exit_json(**decon.result)
if amodule.params['state'] in ('present', 'enabled'):
if not amodule.params['rg_name']:
self.result['failed'] = True
self.result['msg'] = (
'Resource group could not be created because'
' the "rg_name" parameter was not specified.'
)
else:
self.create()
if amodule.params['access'] and not amodule.check_mode:
self.access()
elif amodule.params['state'] in ('disabled'):
self.error()
if __name__ == "__main__":
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.rg_should_exist:
if self.result['changed']:
self.get_info()
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
def main():
decort_rg().run()
if __name__ == '__main__':
main()

148
library/decort_rg_list.py Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_storage_policy
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortStoragePolicy(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.id: int = self.aparams['id']
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
try:
storage_policy_model = self.api.cloudapi.storage_policy.get(
id=self.id
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='storage_policy',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = storage_policy_model.model_dump()
def main():
DecortStoragePolicy().run()
if __name__ == '__main__':
main()

View File

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

65
library/decort_trunk.py Normal file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_trunk
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortTrunk(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.id: int = self.aparams['id']
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
try:
trunk_model = self.api.cloudapi.trunk.get(
id=self.id
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='trunk',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = trunk_model.model_dump()
def main():
DecortTrunk().run()
if __name__ == '__main__':
main()

View File

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

68
library/decort_user.py Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_user
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortUser(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
api_methods=dict(
type='bool',
default=False,
),
objects_search=dict(
type='str',
),
resource_consumption=dict(
type='bool',
default=False,
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
self.facts = self.usermanager_whoami_result
self.id = self.facts['name']
user_get = self.user_get(id=self.id)
for key in ['emailaddresses', 'data']:
self.facts[key] = user_get[key]
if self.aparams['resource_consumption']:
self.facts.update(self.user_resource_consumption())
if self.aparams['api_methods']:
self.facts['api_methods'] = self.user_api_methods(id=self.id)
search_string = self.aparams['objects_search']
if search_string:
self.facts['objects_search'] = self.user_objects_search(
search_string=search_string,
)
def main():
DecortUser().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,45 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_user_info
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(
app_id=dict(type='raw'),
app_secret=dict(type='raw'),
authenticator=dict(type='raw'),
controller_url=dict(type='raw'),
domain=dict(type='raw'),
jwt=dict(type='raw'),
oauth2_url=dict(type='raw'),
password=dict(type='raw'),
username=dict(type='raw'),
verify_ssl=dict(type='raw'),
ignore_api_compatibility=dict(type='raw'),
ignore_sdk_version_check=dict(type='raw'),
api_methods=dict(type='raw'),
objects_search=dict(type='raw'),
resource_consumption=dict(type='raw'),
),
supports_check_mode=True,
)
module.fail_json(
msg=(
'The module "decort_user_info" has been renamed to "decort_user". '
'Please update your playbook to use "decort_user" '
'instead of "decort_user_info".'
),
)
if __name__ == '__main__':
main()

View File

@@ -1,239 +1,10 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
DOCUMENTATION = r'''
---
module: decort_vins
short_description: Manage Virtual Network Segments (ViNS) in DECORT cloud
description: >
This module can be used to create new ViNS in DECORT cloud platform, obtain or
modify its characteristics, and delete it.
version_added: "2.2"
author:
requirements:
- python >= 3.8
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.8.6 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
- 'Similarly, JWT supplied in I(authenticator=jwt) mode should be received from Oauth2 provider trusted by
the DECORT cloud controller on which this JWT will be used.'
options:
account_id:
description:
- 'ID of the account under which this ViNS will be created (for new ViNS) or is located (for already
existing ViNS). This is the alternative to I(account_name) option.'
- If both I(account_id) and I(account_name) specified, then I(account_name) is ignored.
required: no
account_name:
description:
- 'Name of the account under which this ViNS will be created (for new RGs) or is located (for already
existing ViNS).'
- 'This parameter is ignored if I(account_id) is specified.'
required: no
annotation:
description:
- Optional text description of this virtual network segment.
default: empty string
required: no
app_id:
description:
- 'Application ID for authenticating to the DECORT controller when I(authenticator=oauth2).'
- 'Required if I(authenticator=oauth2).'
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_ID
environment variable.'
required: no
app_secret:
description:
- 'Application API secret used for authenticating to the DECORT controller when I(authenticator=oauth2).'
- This parameter is required when I(authenticator=oauth2) and ignored in other modes.
- 'If not found in the playbook or command line arguments, the value will be taken from DECORT_APP_SECRET
environment variable.'
required: no
authenticator:
description:
- Authentication mechanism to be used when accessing DECORT controller and authorizing API call.
default: jwt
choices: [ jwt, oauth2, legacy ]
required: yes
controller_url:
description:
- URL of the DECORT controller that will be contacted to manage the RG according to the specification.
- 'This parameter is always required regardless of the specified I(authenticator) type.'
required: yes
ext_net_id:
description:
- 'Controls ViNS connection to an external network. This argument is optional with default value of -1,
which means no external connection.'
- Specify 0 to connect ViNS to external network and let platform select external network Id automatically.
- Specify positive value to request ViNS connection to the external network with corresponding ID.
- You may also control external IP address selection with I(ext_ip_addr) argument.
default: -1
required: no
ext_ip_addr:
description:
- IP address to assign to the external interface of this ViNS when connecting to the external net.
- If empty string is passed, the platform will assign free IP address automatically.
- 'Note that if invalid IP address or an address already occupied by another client is specified,
the module will abort with an error.'
- 'This argument is used only for new connection to the specified network. You cannot select another
external IP address without changing external network ID.'
- ViNS connection to the external network is controlled by I(ext_net_id) argument.
default: empty string
required: no
ipcidr:
description:
- Internal ViNS network address in a format XXX.XXX.XXX.XXX/XX (includes address and netmask).
- If empty string is passed, the platform will assign network address automatically.
- 'When selecting this address manually, note that this address must be unique amomng all ViNSes in
the target account.'
default: empty string
required: no
jwt:
description:
- 'JWT (access token) for authenticating to the DECORT controller when I(authenticator=jwt).'
- 'This parameter is required if I(authenticator=jwt) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_JWT environment variable.
required: no
oauth2_url:
description:
- 'URL of the oauth2 authentication provider to use when I(authenticator=oauth2).'
- 'This parameter is required when when I(authenticator=oauth2).'
- 'If not specified in the playbook, the value will be taken from DECORT_OAUTH2_URL environment variable.'
password:
description:
- 'Password for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required if I(authenticator=legacy) and ignored in other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_PASSWORD environment variable.
required: no
rg_id:
description:
- 'ID of the resource group (RG), where this ViNS will be created (for a new ViNS) or located
(for already existing ViNS).'
- If ViNS is created at the account level, I(rg_id) should be omitted or set to 0.
- If both I(rg_id) and I(rg_name) are specified, then I(rg_name) is ignored.
default: 0
required: no
rg_name:
description:
- 'Name of the resource group (RG), where this ViNS will be created (for new ViNS) or
located (for already existing ViNS).'
- If ViNS is created at the account level, I(rg_name) should be omitted or set to emtpy string.
- If both I(rg_name) and I(rg_id) are specified, then I(rg_name) is ignored.
default: empty string
required: no
state:
description:
- Specify the desired state of the ViNS at the exit of the module.
- 'Regardless of I(state), if ViNS exists and is in one of [DEPLOYING, DESTROYING, MIGRATING] states,
do nothing.'
- 'If desired I(state=present):'
- ' - ViNS does not exist or is in DESTROYED state, create new ViNS according to the specifications.'
- ' - ViNS is in DELETED state, restore it and change quotas if necessary. Note that on successful
restore ViNS will be left in DISABLED state.'
- ' - ViNS is in one of [CREATED, ENABLED, DISABLED] states, do nothing.'
- ' - ViNS in any other state, abort with an error.'
- 'If desired I(state=enabled):'
- ' - ViNS does not exist or is in DESTROYED state, create new ViNS according to the specifications.'
- ' - ViNS is in DELETED state, restore and enable it.'
- ' - ViNS is in one of [CREATED, ENABLED] states, do nothing.'
- ' - viNS is in any other state, abort with an error.'
- 'If desired I(state=absent):'
- ' - ViNS is in one of [CREATED, ENABLED, DISABLED, DELETED] states, destroy it.'
- ' - ViNS in DESTROYED state, do nothing.'
- ' - ViNS in any other state, abort with an error.'
- 'If desired I(state=disabled):'
- ' - ViNS is in one of [CREATED, ENABLED] states, disable it.'
- ' - ViNS is DISABLED state, do nothing.'
- ' - ViNS does not exist or is in one of [ENABLING, DISABLING, DELETING, DELETED, DESTROYING, DESTROYED]
states, abort with an error.'
default: present
choices: [ absent, disabled, enabled, present ]
user:
description:
- 'Name of the legacy user for authenticating to the DECORT controller when I(authenticator=legacy).'
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
want to disable SSL certificate verification. Intended use case is when you run module in a trusted
environment that uses self-signed certificates. Note that disabling SSL verification in any other
scenario can lead to security issues, so please know what you are doing.'
default: True
required: no
vins_id:
description:
- ID of the ViNs to manage. If ViNS is identified by ID it must be present.
- If ViNS ID is specified, I(account_id), I(account_name), I(rg_id) and I(rg_name) are ignored.
vins_name:
description:
- Name of the ViNS.
- ViNS can exist at either account or resource group level.
- ViNS name is unique only within its parent (i.e. account or resource group).
- 'To create ViNS at account level omit both I(rg_id) and I(rg_name), or set them to 0 and empty
string respectively.'
required: yes
workflow_callback:
description:
- 'Callback URL that represents an application, which invokes this module (e.g. up-level orchestrator or
end-user portal) and may except out-of-band updates on progress / exit status of the module run.'
- API call at this URL will be used to relay such information to the application.
- 'API call payload will include module-specific details about this module run and I(workflow_context).'
required: no
workflow_context:
description:
- 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.'
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
'''
EXAMPLES = '''
- name: create a new ViNS named "MyViNS" if it does not exist yet under RG "MyRG" in the account "MyAccount".
decort_vins:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://cloud.digitalenergy.online"
vins_name: "MyViNS"
rg_name: "MyRG"
account_name: "MyAccount"
state: present
delegate_to: localhost
register: my_vins
'''
RETURN = '''
facts:
description: facts about the virtual network segment
returned: always
type: dict
sample:
facts:
id: 5
name: MyViNS
int_net_addr: 192.168.1.0
ext_net_addr: 10.50.11.118
state: CREATED
account_id: 7
rg_id: 19
gid: 1001
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
@@ -241,9 +12,11 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_vins(DecortController):
def __init__(self,arg_amodule):
super(decort_vins, self).__init__(arg_amodule)
def __init__(self):
super(decort_vins, self).__init__(AnsibleModule(**self.amodule_init_args))
arg_amodule = self.amodule
self.vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
@@ -251,7 +24,6 @@ class decort_vins(DecortController):
validated_rg_id = 0
rg_facts = None # will hold RG facts
validated_acc_id = 0
acc_facts = None # will hold Account facts
if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified ID
@@ -260,7 +32,7 @@ class decort_vins(DecortController):
if self.vins_id == 0:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result)
self.amodule.fail_json(**self.result)
self.vins_level = "ID"
#raise Exception(self.vins_facts)
validated_acc_id = self.vins_facts['accountId']
@@ -272,6 +44,7 @@ class decort_vins(DecortController):
# This call to rg_find will abort the module if no RG with such ID is present
validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID
arg_amodule.params['rg_id'], arg_rg_name="")
validated_acc_id = rg_facts['accountId']
# 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'],
@@ -283,7 +56,7 @@ class decort_vins(DecortController):
pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
# Specified account must be present and accessible by the user, otherwise abort the module
validated_acc_id, acc_facts = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id'])
validated_acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
@@ -329,15 +102,25 @@ class decort_vins(DecortController):
self.amodule.fail_json(**self.result)
return
self.rg_id = validated_rg_id
self.acc_id = validated_acc_id
if self.vins_id and self.vins_facts['status'] != 'DESTROYED':
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def create(self):
self.vins_id = self.vins_provision(self.amodule.params['vins_name'],
self.acc_id, self.rg_id,
self.amodule.params['ipcidr'],
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'],
self.amodule.params['annotation'])
self.amodule.params['description'],
zone_id=self.amodule.params['zone_id'],
)
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']:
_, self.vins_facts = self.vins_find(self.vins_id)
@@ -377,9 +160,17 @@ class decort_vins(DecortController):
if d_state != '':
self.vins_state(self.vins_facts, d_state)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.vins_facts['zoneId']:
self.vins_migrate_to_zone(
net_id=self.vins_id,
zone_id=aparam_zone_id,
)
return
def delete(self):
self.vins_delete(self.vins_id, permanently=True)
self.vins_delete(self.vins_id, self.amodule.params['permanently'])
self.vins_facts['status'] = 'DESTROYED'
return
def nop(self):
@@ -462,63 +253,110 @@ class decort_vins(DecortController):
else:
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
ret_dict['zone_id'] = self.vins_facts['zoneId']
return ret_dict
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_vins module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False,default=0),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''),
ipcidr=dict(type='str', required=False, default=''),
mgmtaddr=dict(type='list',required=False, default=[]),
custom_config=dict(type='bool',required=False, default=False),
config_save=dict(type='bool',required=False, default=False),
connect_to=dict(type='list', default=[], required=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
default=0,
),
account_name=dict(
type='str',
default='',
),
description=dict(
type='str',
default='',
),
ext_net_id=dict(
type='int',
default=-1,
),
ext_ip_addr=dict(
type='str',
default='',
),
ipcidr=dict(
type='str',
default='',
),
mgmtaddr=dict(
type='list',
default=[],
),
custom_config=dict(
type='bool',
default=False,
),
config_save=dict(
type='bool',
default=False,
),
connect_to=dict(
type='list',
default=[],
),
state=dict(
type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_id=dict(type='int', required=False, default=0),
rg_name=dict(type='str', required=False, default=''),
verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=False, default=0),
vins_name=dict(type='str', required=False,default=""),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
choices=[
'absent',
'disabled',
'enabled',
'present',
],
),
rg_id=dict(
type='int',
default=0,
),
rg_name=dict(
type='str',
default='',
),
permanently=dict(
type='bool',
default=False,
),
vins_id=dict(
type='int',
default=0,
),
vins_name=dict(
type='str',
default='',
),
zone_id=dict(
type=int,
),
),
supports_check_mode=True,
required_one_of=[
('vins_id', 'vins_name'),
],
)
def check_amodule_args_for_change(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
@@ -527,118 +365,106 @@ class decort_vins(DecortController):
# 4) if ViNS exists: check desired state, desired configuration -> initiate action(s) accordingly
# 5) report result to Ansible
def main():
module_parameters = decort_vins.build_parameters()
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
#
# Initial validation of module arguments is complete
#
# At this point non-zero vins_id means that we will be managing pre-existing ViNS
# Otherwise we are about to create a new one as follows:
# - if validated_rg_id is non-zero, create ViNS @ RG level
# - if validated_rg_id is zero, create ViNS @ account level
#
# When managing existing ViNS we need to account for both "static" and "transient"
# status. Full range of ViNS statii is as follows:
#
# "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING"
#
# if cconfig_save is true, only config save without other updates
vins_should_exist = False
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['vins_id', 'vins_name'],
],
)
decon = decort_vins(amodule)
#
# Initial validation of module arguments is complete
#
# At this point non-zero vins_id means that we will be managing pre-existing ViNS
# Otherwise we are about to create a new one as follows:
# - if validated_rg_id is non-zero, create ViNS @ RG level
# - if validated_rg_id is zero, create ViNS @ account level
#
# When managing existing ViNS we need to account for both "static" and "transient"
# status. Full range of ViNS statii is as follows:
#
# "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING"
#
# if cconfig_save is true, only config save without other updates
vins_should_exist = False
if decon.vins_id:
vins_should_exist = True
if decon.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
# error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
"status '{}'").format(decon.vins_id, decon.vins_facts['status'])
elif decon.vins_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update ViNS, leave in disabled state
decon.action()
elif amodule.params['state'] == 'enabled':
# update ViNS and enable
decon.action('enabled')
elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
decon.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS
decon.action()
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
decon.action('disabled')
elif decon.vins_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
decon.action(restore=True)
vins_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
decon.delete()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
vins_should_exist = False
elif decon.vins_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
# need to re-provision ViNS;
decon.create()
vins_should_exist = True
elif amodule.params['state'] == 'absent':
decon.nop()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
else:
# Preexisting ViNS was not found.
vins_should_exist = False # we will change it back to True if ViNS is created or restored
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
decon.nop()
elif amodule.params['state'] in ('present', 'enabled'):
decon.check_amodule_argument('vins_name')
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
decon.create()
if self.vins_id:
vins_should_exist = True
elif amodule.params['state'] == 'disabled':
decon.error()
#
# conditional switch end - complete module run
#
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
# prepare ViNS facts to be returned as part of decon.result and then call exit_json(...)
if decon.result['changed']:
_, decon.vins_facts = decon.vins_find(decon.vins_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
if self.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
# error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
"status '{}'").format(self.vins_id, self.vins_facts['status'])
elif self.vins_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update ViNS, leave in disabled state
self.action()
elif amodule.params['state'] == 'enabled':
# update ViNS and enable
self.action('enabled')
elif self.vins_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS
self.action()
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
self.action('disabled')
elif self.vins_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
self.action(restore=True)
vins_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
if self.amodule.params['permanently']:
self.delete()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
self.error()
vins_should_exist = False
elif self.vins_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
# need to re-provision ViNS;
self.create()
vins_should_exist = True
elif amodule.params['state'] == 'absent':
self.nop()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
self.error()
else:
# Preexisting ViNS was not found.
vins_should_exist = False # we will change it back to True if ViNS is created or restored
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
self.nop()
elif amodule.params['state'] in ('present', 'enabled'):
self.check_amodule_argument('vins_name')
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
self.create()
vins_should_exist = True
elif amodule.params['state'] == 'disabled':
self.error()
#
# conditional switch end - complete module run
#
if self.result['failed']:
amodule.fail_json(**self.result)
else:
# prepare ViNS facts to be returned as part of self.result and then call exit_json(...)
if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
if __name__ == "__main__":
def main():
decort_vins().run()
if __name__ == '__main__':
main()

141
library/decort_vins_list.py Normal file
View File

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

2510
library/decort_vm.py Normal file

File diff suppressed because it is too large Load Diff

158
library/decort_vm_list.py Normal file
View File

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

View File

@@ -0,0 +1,190 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_vm_snapshot
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortVMSnapshot(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.check_amodule_args()
self.vm_id: int
self.vm_facts: dict
self.aparams_label = self.aparams['label']
self.aparams_vm_id = self.aparams['vm_id']
self.vm_id, self.vm_facts, _ = self._compute_get_by_id(
comp_id=self.aparams_vm_id,
)
if not self.vm_id:
self.message(f'VM {self.aparams_vm_id} not found')
self.exit(fail=True)
self.vm_name = self.vm_facts['name']
self.vm_snapshots = self.api.ca.compute.snapshot_list(
vm_id=self.vm_id).data
self.vm_snapshot_labels = [
snapshot.label for snapshot in self.vm_snapshots
]
self.new_snapshot_label = None
if self.aparams['state'] == 'present':
if self.aparams_label is None:
self.new_snapshot_label = (
f'{self.vm_name}_{self.sec_to_dt_str(time.time())}'
)
elif self.aparams_label not in self.vm_snapshot_labels:
self.new_snapshot_label = self.aparams_label
if (
self.new_snapshot_label is None
and self.aparams_label is not None
and self.aparams_label not in self.vm_snapshot_labels
):
self.message(
f'Snapshot {self.aparams_label} '
f'not found for VM {self.aparams_vm_id}'
)
self.exit(fail=True)
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
label=dict(
type='str',
),
state=dict(
type='str',
choices=(
'absent',
'present',
'merge_aborted',
),
),
usage=dict(
type='bool',
default=False,
),
vm_id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
required_if=[
('state', 'absent', ('label',)),
],
required_one_of=[
('label', 'state'),
],
)
def check_amodule_args(self):
check_error = False
if (
self.aparams['state'] == 'absent'
and self.aparams['usage']
):
self.message(
'Parameter "usage" is not supported when deleting snapshot'
)
check_error = True
if check_error:
self.exit(fail=True)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.check_amodule_args_for_change()
self.change()
self.exit()
def get_info(self, update_vm_snapshots: bool = False):
if update_vm_snapshots:
self.vm_snapshots = self.api.cloudapi.compute.snapshot_list(
vm_id=self.aparams_vm_id,
).data
label = self.new_snapshot_label or self.aparams_label
for snapshot in self.vm_snapshots:
if snapshot.label == label:
self.facts = snapshot.model_dump()
if self.aparams['usage']:
self.facts['stored'] = self.get_snapshot_usage()
self.facts['vm_id'] = self.aparams_vm_id
break
def change(self):
match self.aparams['state']:
case 'present':
if self.new_snapshot_label:
self.create()
case 'absent':
if self.aparams_label in self.vm_snapshot_labels:
self.delete()
case 'merge_aborted':
self.abort_merge()
def create(self):
self.sdk_checkmode(self.api.cloudapi.compute.snapshot_create)(
vm_id=self.aparams_vm_id,
label=self.new_snapshot_label,
)
self.get_info(update_vm_snapshots=True)
def delete(self):
self.snapshot_delete(
compute_id=self.aparams_vm_id,
label=self.aparams_label,
)
self.facts = {}
def abort_merge(self):
self.snapshot_abort_merge(
vm_id=self.aparams_vm_id,
label=self.aparams_label,
)
self.get_info()
def get_snapshot_usage(self) -> int:
label = self.new_snapshot_label or self.aparams_label
common_snapshots_usage_info, _ = self.snapshot_usage(
compute_id=self.aparams_vm_id,
label=label,
)
return common_snapshots_usage_info['stored']
def check_amodule_args_for_change(self):
check_errors = False
if (
self.aparams['state'] == 'merge_aborted'
and self.vm_facts['techStatus'] != 'MERGE'
):
check_errors = True
self.message(
'Check for parameter "state" failed: '
'Merge can be aborted only for VM in "MERGE" tech status.'
)
if check_errors:
self.exit(fail=True)
def main():
DecortVMSnapshot().run()
if __name__ == '__main__':
main()

63
library/decort_zone.py Normal file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_zone
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortZone(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.id: int = self.aparams['id']
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
)
@DecortController.handle_sdk_exceptions
def run(self):
self.get_info()
self.exit()
def get_info(self):
try:
zone_model = self.api.cloudapi.zone.get(id=self.id)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='zone',
id=self.id,
)
)
self.exit(fail=True)
raise e
self.facts = zone_model.model_dump()
def main():
DecortZone().run()
if __name__ == '__main__':
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

3
requirements.txt Normal file
View File

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