From d9ad1fee2198bdf26605d9677b192d0e245253bd Mon Sep 17 00:00:00 2001 From: msbolshakov Date: Wed, 29 Jun 2022 20:13:09 +0700 Subject: [PATCH 1/9] examples --- example/VINS.yaml | 40 +++++++++++++++++++ example/annotations.yaml | 40 +++++++++++++++++++ example/basicservices.yaml | 31 ++++++++++++++ ...loud_init-example.yaml => cloud_init.yaml} | 0 4 files changed, 111 insertions(+) create mode 100644 example/VINS.yaml create mode 100644 example/annotations.yaml create mode 100644 example/basicservices.yaml rename example/{cloud_init-example.yaml => cloud_init.yaml} (100%) diff --git a/example/VINS.yaml b/example/VINS.yaml new file mode 100644 index 0000000..929bf96 --- /dev/null +++ b/example/VINS.yaml @@ -0,0 +1,40 @@ +--- +# +# DECORT vins module example +# + +- hosts: localhost + tasks: + - name: obtain JWT + decort_jwt: + oauth2_url: "https://sso.digitalenergy.online" + validity: 1200 + register: my_jwt + delegate_to: localhost + + - name: print out JWT + debug: + var: my_jwt.jwt + delegate_to: localhost + + - name: Manage ViNS at resource group level + decort_vins: + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + vins_name: "vins_created_by_decort_VINS_module" + state: present + rg_id: 198 + ext_net_id: -1 + ipcidr: "10.20.30.0/24" + mgmtaddr: "10.20.30.1" + custom_config: false + config_save: false + verify_ssl: false + + register: managed_vins + + - name: print VINS facter + debug: + msg: "{{managed_vins.facts.password}}" + when: managed_vins.facts.password is defined diff --git a/example/annotations.yaml b/example/annotations.yaml new file mode 100644 index 0000000..6177f74 --- /dev/null +++ b/example/annotations.yaml @@ -0,0 +1,40 @@ +--- +# +# DECORT k8s module labels, taints, annotations example +# + +- hosts: localhost + tasks: + - name: obtain JWT + decort_jwt: + oauth2_url: "https://sso.digitalenergy.online" + validity: 1200 + register: my_jwt + delegate_to: localhost + + - name: print out JWT + debug: + var: my_jwt.jwt + delegate_to: localhost + + - name: Create k8s cluster + decort_k8s: + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://mr4.digitalenergy.online" + name: "example_kubernetes" + rg_id: 199 + k8ci_id: 4 + state: present + workers: + - name: workgroup1 + labels: + - disktype1=ssd1 + - disktype2=ssd2 + taints: + - key1=value1:NoSchedule + - key2=value2:NoSchedule + annotations: + - node.deckhouse.io/group1=g1 + - node.deckhouse.io/group2=g2 + register: kube diff --git a/example/basicservices.yaml b/example/basicservices.yaml new file mode 100644 index 0000000..f5862fa --- /dev/null +++ b/example/basicservices.yaml @@ -0,0 +1,31 @@ +--- +# +# DECORT vins module example +# + +- hosts: localhost + tasks: + - name: obtain JWT + decort_jwt: + oauth2_url: "https://sso.digitalenergy.online" + validity: 1200 + register: my_jwt + delegate_to: localhost + + - name: print out JWT + debug: + var: my_jwt.jwt + delegate_to: localhost + + - name: Manage bservice at RG + decort_bservice: + account_id: 98 + verify_ssl: false + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + rg_id: 1629 + state: present + name: databases + started: True + register: db_bservice diff --git a/example/cloud_init-example.yaml b/example/cloud_init.yaml similarity index 100% rename from example/cloud_init-example.yaml rename to example/cloud_init.yaml From ebe1a9194fc206ae3b6efeb41d29606b8670cae0 Mon Sep 17 00:00:00 2001 From: Aleksandr Malyavin Date: Wed, 29 Jun 2022 18:26:58 +0300 Subject: [PATCH 2/9] change directory name example>examples --- {example => examples}/.gitkeep | 0 {example => examples}/VINS.yaml | 0 {example => examples}/affinity.yaml | 0 {example => examples}/annotations.yaml | 0 {example => examples}/anti_affinity.yaml | 0 {example => examples}/basicservices.yaml | 0 .../cloud-init.yaml | 0 examples/cloud_init.yaml | 38 +++++++++++++++++++ {example => examples}/kubernetes.yaml | 0 9 files changed, 38 insertions(+) rename {example => examples}/.gitkeep (100%) rename {example => examples}/VINS.yaml (100%) rename {example => examples}/affinity.yaml (100%) rename {example => examples}/annotations.yaml (100%) rename {example => examples}/anti_affinity.yaml (100%) rename {example => examples}/basicservices.yaml (100%) rename example/cloud_init.yaml => examples/cloud-init.yaml (100%) create mode 100644 examples/cloud_init.yaml rename {example => examples}/kubernetes.yaml (100%) diff --git a/example/.gitkeep b/examples/.gitkeep similarity index 100% rename from example/.gitkeep rename to examples/.gitkeep diff --git a/example/VINS.yaml b/examples/VINS.yaml similarity index 100% rename from example/VINS.yaml rename to examples/VINS.yaml diff --git a/example/affinity.yaml b/examples/affinity.yaml similarity index 100% rename from example/affinity.yaml rename to examples/affinity.yaml diff --git a/example/annotations.yaml b/examples/annotations.yaml similarity index 100% rename from example/annotations.yaml rename to examples/annotations.yaml diff --git a/example/anti_affinity.yaml b/examples/anti_affinity.yaml similarity index 100% rename from example/anti_affinity.yaml rename to examples/anti_affinity.yaml diff --git a/example/basicservices.yaml b/examples/basicservices.yaml similarity index 100% rename from example/basicservices.yaml rename to examples/basicservices.yaml diff --git a/example/cloud_init.yaml b/examples/cloud-init.yaml similarity index 100% rename from example/cloud_init.yaml rename to examples/cloud-init.yaml diff --git a/examples/cloud_init.yaml b/examples/cloud_init.yaml new file mode 100644 index 0000000..4163c0b --- /dev/null +++ b/examples/cloud_init.yaml @@ -0,0 +1,38 @@ +# +# 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: | +
+ Hello World! +
+ 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 diff --git a/example/kubernetes.yaml b/examples/kubernetes.yaml similarity index 100% rename from example/kubernetes.yaml rename to examples/kubernetes.yaml From 28876ae38d38347a29b844b12514f8c687736475 Mon Sep 17 00:00:00 2001 From: msbolshakov Date: Fri, 8 Jul 2022 19:12:17 +0700 Subject: [PATCH 3/9] decort_osimage update --- library/decort_osimage.py | 473 +++++++++++++++++++++------ module_utils/decort_utils.py | 598 +++++++++-------------------------- 2 files changed, 517 insertions(+), 554 deletions(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index 9c91b30..46c485b 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -22,8 +22,7 @@ description: > This module can be used to obtain image ID of an OS image in DECORT cloud to use with subsequent calls to decort_vm module for batch VM provisioning. It will speed up VM creation and save a bunch of extra calls to DECORT cloud controller on each VM creation act. - Note that this module is effectively an information provisioner. It is not designed to and does not manage - nor change state of OS image (or any other) objects in DECORT cloud. + version_added: "2.2" author: - Sergey Shubin @@ -109,10 +108,6 @@ options: - 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.' - If not specified in the playbook, the value will be taken from DECORT_USER environment variable. required: no - vdc_id: - description: - - ID of the VDC to limit the search of the OS image to. - required: no verify_ssl: description: - 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you @@ -134,19 +129,138 @@ options: - '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: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + virt_id: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + virt_name: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + state: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + drivers: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + architecture: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + imagetype: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + boottype: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + url: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + sepId: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + poolName: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + hotresize: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + image_username: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + image_password: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + usernameDL: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + passwordDL: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + permanently: + description: + - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + required: no + + + + + + + + ''' EXAMPLES = ''' -- name: locate OS image specified by its name, store result in image_to_use variable. + - 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 - app_id: "{{ MY_APP_ID }}" - app_secret: "{{ MY_APP_SECRET }}" controller_url: "https://ds1.digitalenergy.online" - image_name: "Ubuntu 18.04 v1.2.5" - account_name: "GreyseDevelopment" + image_name: "alpine_linux_3.14.0" + virt_name: "alpine_last" delegate_to: localhost - register: image_to_use + 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 = ''' @@ -157,6 +271,7 @@ facts: sample: facts: id: 100 + linkto: 80 name: "Ubuntu 16.04 v1.0" size: 3 sep_id: 1 @@ -171,94 +286,205 @@ 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) -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. + 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) - @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. - """ + 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") - 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 + 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 - 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'] - - 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), - image_name=dict(type='str', required=True), - jwt=dict(type='str', - required=False, - fallback=(env_fallback, ['DECORT_JWT']), - no_log=True), - oauth2_url=dict(type='str', + 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'] + 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_OAUTH2_URL'])), - password=dict(type='str', + 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_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=True), - user=dict(type='str', - required=False, - fallback=(env_fallback, ['DECORT_USER'])), - vdc_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), - ) - -# Workflow digest: -# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when -# creating DecortController -# 2) obtain a list of OS images accessible to the specified account (and optionally - within -# the specified VDC) -# 3) match specified OS image by its name - if image is not found abort the module -# 5) report result to Ansible + 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_parameters() + module_parameters = decort_osimage.decort_osimage_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -273,30 +499,67 @@ def main(): ], ) - decon = DecortController(amodule) + 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" and 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) + + + + 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) - # we need account ID to locate OS images - find the account by the specified name and get its ID - validated_account_id, _ = decon.account_find(amodule.params['account_name']) - if validated_account_id == 0: - # we failed either to find or access the specified account - fail the module - decon.result['failed'] = True - decon.result['changed'] = False - decon.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name']) - amodule.fail_json(**decon.result) - image_id, image_facts = decon.image_find(image_id=0, image_name=amodule.params['image_name'], - account_id=validated_account_id, rg_id=0, - sepid=amodule.params['sep_id'], - pool=amodule.params['pool']) if decon.result['failed'] == True: # we failed to find the specified image - fail the module decon.result['changed'] = False amodule.fail_json(**decon.result) - decon.result['facts'] = decort_osimage_package_facts(image_facts, amodule.check_mode) - decon.result['changed'] = False # decort_osimage is a read-only module - make sure the 'changed' flag is set to False amodule.exit_json(**decon.result) - + if __name__ == "__main__": main() diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index fd8bb7d..b5f1f87 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1266,7 +1266,6 @@ class DecortController(object): @return: image ID and dictionary with image specs. If no matching image found, 0 for ID and None for dictionary are returned, and self.result['failed']=True. """ - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_find") if image_id > 0: @@ -1302,7 +1301,8 @@ class DecortController(object): full_match = False if full_match: return image_record['id'], image_record - + self.result['failed'] = False + self.result['failed'] = True self.result['msg'] = ("Failed to find OS image by name '{}', SEP ID {}, pool '{}' for " "account ID '{}'.").format(image_name, @@ -1310,6 +1310,138 @@ class DecortController(object): account_id) return 0, None + def virt_image_find(self, image_id, virt_name, account_id, rg_id=0, sepid=0, pool=""): + """Locates virtual image specified by name and returns its facts as dictionary. + Primary use of this function is to obtain the ID of the image identified by its name and, + optionally SEP ID and/or pool name. Also note that only virtual images in status CREATED are + returned. + + @param (string) image_id: ID of the OS image to find. If non-zero ID is specified, then + virt_name is ignored. + @param (string) virt_name: name of the OS image to find. This argument is ignored if non-zero + image ID is passed. + @param (int) account_id: ID of the account for which the image will be looked up. If set to 0, + the account ID will be obtained from the specified RG ID. + @param (int) rg_id: ID of the RG to use as a reference when listing OS images. This argument is + ignored if non-zero image id and/or non-zero account_id are specified. + @param (int) sepid: ID of the SEP where the image should be present. If set to 0, there will be no + filtering by SEP ID and the first matching image will be returned. + @param (string) pool: name of the pool where the image should be present. If set to empty string, there + will be no filtering by pool name and first matching image will be returned. + + @return: image ID and dictionary with image specs. If no matching image found, 0 for ID and None for + dictionary are returned, and self.result['failed']=True. + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "virt_image_find") + + + + if image_id > 0: + ret_image_id, ret_image_dict = self._image_get_by_id(image_id) + if (ret_image_id and + (sepid == 0 or sepid == ret_image_dict['sepId']) and + (pool == "" or pool == ret_image_dict['pool'])): + return ret_image_id, ret_image_dict + else: + validated_acc_id = account_id + if account_id == 0: + validated_rg_id, rg_facts = self._rg_get_by_id(rg_id) + if not validated_rg_id: + self.result['failed'] = True + self.result['msg'] = ("Failed to find RG ID {}, and account ID is zero.").format(rg_id) + return 0, None + validated_acc_id = rg_facts['accountId'] + + api_params = dict(accountId=validated_acc_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/list", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + images_list = json.loads(api_resp.content.decode('utf8')) + for image_record in images_list: + if image_record['name'] == virt_name and image_record['status'] == "CREATED" and image_record['type'] == "virtual": + if sepid == 0 and pool == "": + # if no filtering by SEP ID or pool name is requested, return the first match + return image_record['id'], image_record + full_match = True + if full_match: + return image_record['id'], image_record + + self.result['failed'] = True + self.result['msg'] = ("Failed to find virtual OS image by name '{}', SEP ID {}, pool '{}' for " + "account ID '{}'.").format(virt_name, + sepid, pool, + account_id) + + return 0, None + + def virt_image_create(self, name, targetId): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "virt_image_create") + + api_params = dict(name=name, targetId=targetId,) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/createVirtual", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + virt_image_dict = json.loads(api_resp.content.decode('utf8')) + + self.result['failed'] = False + self.result['changed'] = True + return 0, None + + def image_delete(self, imageId, permanently): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_delete") + + api_params = dict(imageId=imageId, permanently=permanently,) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/delete", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + image_dict = json.loads(api_resp.content.decode('utf8')) + + self.result['changed'] = True + return 0, None + + + def image_create(self,img_name,url,gid,boottype,imagetype,architecture,drivers,hotresize,username,password,account_Id,usernameDL,passwordDL,sepId,poolName): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_create") + + api_params = dict(name=img_name, url=url, + gid=gid, boottype=boottype, + imagetype=imagetype, architecture=architecture, + drivers=drivers, accountId=account_Id, + hotresize=hotresize, username=username, + password=password, usernameDL=usernameDL, + passwordDL=passwordDL, sepId=sepId, + poolName=poolName, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/create", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + virt_image_dict = json.loads(api_resp.content.decode('utf8')) + self.result['failed'] = False + self.result['changed'] = True + return 0, None + + def virt_image_link(self, imageId, targetId): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "virt_image_link") + + api_params = dict(imageId=imageId, targetId=targetId,) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/link", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + link_image_dict = json.loads(api_resp.content.decode('utf8')) + self.result['failed'] = False + self.result['changed'] = True + + + return 0, None + + def image_rename(self, imageId, name): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_rename") + api_params = dict(imageId=imageId, name=name,) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/rename", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + link_image_dict = json.loads(api_resp.content.decode('utf8')) + self.result['failed'] = False + self.result['changed'] = True + + + ################################### # Resource Group (RG) manipulation methods ################################### @@ -2146,10 +2278,10 @@ class DecortController(object): expected_state = "" if vins_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled': - vinsstate_api = "/restmachine/cloudapi/vins/disable" + rgstate_api = "/restmachine/cloudapi/vins/disable" expected_state = "DISABLED" elif vins_dict['status'] == "DISABLED" and desired_state == 'enabled': - vinsstate_api = "/restmachine/cloudapi/vins/enable" + rgstate_api = "/restmachine/cloudapi/vins/enable" expected_state = "ENABLED" if vinsstate_api != "": @@ -2166,7 +2298,7 @@ class DecortController(object): desired_state) return - def vins_update(self, vins_dict, ext_net_id, ext_ip_addr="", mgmtaddr=""): + def vins_update(self, vins_dict, ext_net_id, ext_ip_addr=""): """Update ViNS. Currently only updates to the external network connection settings and external IP address assignment are implemented. Note that as ViNS created at account level cannot have external connections, attempt @@ -2191,11 +2323,6 @@ class DecortController(object): self.result['msg'] = ("vins_update() in check mode: updating ViNS ID {}, name '{}' " "was requested.").format(vins_dict['id'], vins_dict['name']) return - if self.amodule.params['config_save'] and vins_dict['VNFDev']['customPrecfg']: - # only save config,no other modifictaion - self.result['changed'] = True - self._vins_vnf_config_save(vins_dict['VNFDev']['id']) - return if not vins_dict['rgId']: # this ViNS exists at account level - no updates are possible @@ -2247,78 +2374,7 @@ class DecortController(object): "no reconnection to default network will be done.").format(vins_dict['id'], gw_config[ 'ext_net_id']) - for iface in vins_dict['VNFDev']['interfaces']: - if iface['ipAddress'] == mgmtaddr: - if not iface['listenSsh']: - self._vins_vnf_addmgmtaddr(vins_dict['VNFDev']['id'],mgmtaddr) - elif mgmtaddr =="": - if iface['listenSsh'] and iface['name'] != "ens9": - self._vins_vnf_delmgmtaddr(vins_dict['VNFDev']['id'],iface['ipAddress']) - - if self.amodule.params['custom_config']: - if not vins_dict['VNFDev']['customPrecfg']: - self._vins_vnf_config_save(vins_dict['VNFDev']['id']) - self._vins_vnf_customconfig_set(vins_dict['VNFDev']['id']) - else: - if vins_dict['VNFDev']['customPrecfg']: - self._vins_vnf_customconfig_set(vins_dict['VNFDev']['id'],False) - - return - - def _vins_vnf_addmgmtaddr(self,dev_id,mgmtip): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_addmgmtaddr") - api_params = dict(devId=dev_id,ip=mgmtip) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/addMgmtAddr", api_params) - if api_resp.status_code == 200: - self.result['changed'] = True - self.result['failed'] = False - else: - self.result['warning'] = ("_vins_vnf_addmgmtaddr(): failed to add MGMT addr VNFID {} iface ADDR {}. HTTP code {}, " - "response {}.").format(dev_id,mgmtip,api_resp.status_code, api_resp.reason) - return - - def _vins_vnf_delmgmtaddr(self,dev_id,mgmtip): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_delmgmtaddr") - api_params = dict(devId=dev_id,ip=mgmtip) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/delMgmtAddr", api_params) - if api_resp.status_code == 200: - self.result['changed'] = True - self.result['failed'] = False - else: - self.result['warning'] = ("_vins_vnf_delmgmtaddr(): failed to delete MGMT addr VNFID {} iface ADDR {}. HTTP code {}, " - "response {}.").format(dev_id,mgmtip,api_resp.status_code, api_resp.reason) - return - def _vins_vnf_customconfig_set(self,dev_id,arg_mode=True): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_customconfig_set") - api_params = dict(devId=dev_id,mode=arg_mode) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/customSet", api_params) - if api_resp.status_code == 200: - self.result['changed'] = True - self.result['failed'] = False - else: - self.result['warning'] = ("_vins_vnf_customconfig_set(): failed to enable or disable Custom pre-config mode on the VNF device. {}. HTTP code {}, " - "response {}.").format(dev_id,api_resp.status_code, api_resp.reason) - return - - def _vins_vnf_config_save(self,dev_id): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_config_save") - api_params = dict(devId=dev_id) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/configSave", api_params) - if api_resp.status_code == 200: - self.result['changed'] = True - self.result['failed'] = False - else: - self.result['warning'] = ("_vins_vnf_config_set(): failed to Save configuration on the VNF device. {}. HTTP code {}, " - "response {}.").format(dev_id,api_resp.status_code, api_resp.reason) - return - - def vins_vnf_ifaceadd(self): - return - - def vins_vnf_ifaceremove(self): return ############################## @@ -2967,9 +3023,12 @@ class DecortController(object): return def k8s_provision(self, k8s_name, - k8ci_id,rg_id, master_count, + wg_name, k8ci_id, + rg_id, master_count, master_cpu, master_ram, - master_disk, default_worker, extnet_id, + master_disk, worker_count, + worker_cpu, worker_ram, + worker_disk, extnet_id, with_lb, annotation, ): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_provision") @@ -2979,31 +3038,20 @@ class DecortController(object): self.result['msg'] = ("k8s_provision() in check mode. Provision k8s '{}' in RG ID {} " "was requested.").format(k8s_name, rg_id) return 0 - def_wg_name = default_worker['name'] - def_wg_count = default_worker['num'] - def_wg_cpu = default_worker['cpu'] - def_wg_ram = default_worker['ram'] - def_wg_disk = default_worker['disk'] - def_wg_lab = default_worker['labels'] if "labels" in default_worker else None - def_wg_taints = default_worker['taints'] if "taints" in default_worker else None - def_wg_ann = default_worker['annotations'] if "annotations" in default_worker else None api_url = "/restmachine/cloudapi/k8s/create" api_params = dict(name=k8s_name, rgId=rg_id, k8ciId=k8ci_id, - workerGroupName=def_wg_name, + workerGroupName=wg_name, masterNum=master_count, masterCpu=master_cpu, masterRam=master_ram, masterDisk=master_disk, - workerNum=def_wg_count, - workerCpu=def_wg_cpu, - workerRam=def_wg_ram, - workerDisk=def_wg_disk, - labels=def_wg_lab, - taints=def_wg_taints, - annotations=def_wg_ann, + workerNum=worker_count, + workerCpu=worker_cpu, + workerRam=worker_ram, + workerDisk=worker_disk, extnetId=extnet_id, withLB=with_lb, desc=annotation, @@ -3065,7 +3113,7 @@ class DecortController(object): for rec_inn in arg_k8swg['k8sGroups']['workers']: for rec_out in arg_modwg: - if rec_inn['num'] != rec_out['num'] and rec_out['num'] != 0: + if rec_inn['num'] != rec_out['num']: count = rec_inn['num']-rec_out['num'] cmp_list = [] if count > 0: @@ -3088,9 +3136,6 @@ class DecortController(object): workerCpu=wg['cpu'], workerRam=wg['ram'], workerDisk=wg['disk'], - labels=wg['labels'] if "labels" in wg else None, - taints=wg['taints'] if "taints" in wg else None, - annotations=wg['annotations'] if "annotations" in wg else None, ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/workersGroupAdd", api_params) self.result['changed'] = True @@ -3140,349 +3185,4 @@ class DecortController(object): api_params = dict(k8sId=self.k8s_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/getConfig", api_params) ret_conf = api_resp.content.decode('utf8') - return ret_conf - - ############################## - # - # Bservice management - # - ############################## - def bservice_get_by_id(self,bs_id): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_get_by_id") - - ret_bs_id = 0 - ret_bs_dict = dict() - - if not bs_id: - self.result['failed'] = True - self.result['msg'] = "bservice_get_by_id(): zero B-Service ID specified." - self.amodule.fail_json(**self.result) - - api_params = dict(serviceId=bs_id, ) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/get", api_params) - if api_resp.status_code == 200: - ret_bs_id = bs_id - ret_bs_dict = json.loads(api_resp.content.decode('utf8')) - else: - self.result['warning'] = ("bservice_get_by_id(): failed to get B-service by ID {}. HTTP code {}, " - "response {}.").format(bs_id, api_resp.status_code, api_resp.reason) - - return ret_bs_id, ret_bs_dict - def _bservice_rg_list(self,acc_id,rg_id): - ret_bs_dict=dict() - api_params = dict(accountId=acc_id,rgId=rg_id ) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/list", api_params) - if api_resp.status_code == 200: - ret_bs_dict = json.loads(api_resp.content.decode('utf8')) - else: - self.result['warning'] = ("bservice_rg_list(): failed to get B-service list. HTTP code {}, " - "response {}.").format(api_resp.status_code, api_resp.reason) - return ret_bs_dict - - def bservice_find(self,account_id,rg_id,bservice_name="",bservice_id = 0,check_state=True): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_find") - - if bservice_id == 0: - bs_rg_list = self._bservice_rg_list(account_id,rg_id) - for srv in bs_rg_list: - if bservice_name == srv['name']: - bservice_id = int(srv['id']) - - if bservice_id > 0: - ret_bs_id,ret_bs_dict = self.bservice_get_by_id(bservice_id) - return ret_bs_id,ret_bs_dict - else: - return bservice_id,None - - def bservice_provision(self,bs_name,rgid,sshuser=None,sshkey=None): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_provision") - - api_url = "/restmachine/cloudapi/bservice/create" - api_params = dict( - name = bs_name, - rgId = rgid, - sshUser = sshuser, - sshKey = sshkey, - ) - api_resp = self.decort_api_call(requests.post, api_url, api_params) - self.result['failed'] = False - self.result['changed'] = True - ret_bservice_id = int(api_resp.content.decode('utf8')) - return ret_bservice_id - - def bservice_state(self,bs_dict,desired_state,started=True): - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_state") - - if desired_state == "present": - desired_state = "enabled" - - NOP_STATES_FOR_BS_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", - "DESTROYED","RESTORYNG","RECONFIGURING"] - VALID_TARGET_STATES = ["enabled", "disabled"] - - if bs_dict['status'] in NOP_STATES_FOR_BS_CHANGE: - self.result['failed'] = False - self.result['msg'] = ("bservice_state(): no state change possible for ViNS ID {} " - "in its current state '{}'.").format(bs_dict['id'], bs_dict['status']) - return - - if desired_state not in VALID_TARGET_STATES: - self.result['failed'] = False - self.result['warning'] = ("bservice_state(): unrecognized desired state '{}' requested " - "for B-service ID {}. No B-service state change will be done.").format(desired_state, - bs_dict['id']) - return - - if self.amodule.check_mode: - self.result['failed'] = False - self.result['msg'] = ("bservice_state() in check mode: setting state of B-service ID {}, name '{}' to " - "'{}' was requested.").format(bs_dict['id'], bs_dict['name'], - desired_state) - return - - bsstate_api = "" # this string will also be used as a flag to indicate that API call is necessary - api_params = dict(serviceId=bs_dict['id']) - expected_state = "" - - if bs_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled': - bsstate_api = "/restmachine/cloudapi/bservice/disable" - expected_state = "DISABLED" - elif bs_dict['status'] in ["CREATED", "DISABLED"] and desired_state == 'enabled': - bsstate_api = "/restmachine/cloudapi/bservice/enable" - expected_state = "ENABLED" - - if bsstate_api != "": - self.decort_api_call(requests.post, bsstate_api, api_params) - # On success the above call will return here. On error it will abort execution by calling fail_json. - self.result['failed'] = False - self.result['changed'] = True - bs_dict['status'] = expected_state - else: - self.result['failed'] = False - self.result['msg'] = ("bservice_state(): no state change required for B-service ID {} from current " - "state '{}' to desired state '{}'.").format(bs_dict['id'], - bs_dict['status'], - desired_state) - - start_api = "" - - if bs_dict['status'] in ["ENABLED","enabled"]: - if started == True and bs_dict['techStatus'] == "STOPPED": - start_api = "/restmachine/cloudapi/bservice/start" - t_state_expected = "STARTED" - if started == False and bs_dict['techStatus'] == "STARTED": - start_api = "/restmachine/cloudapi/bservice/stop" - t_state_expected = "STOPPED" - - if start_api != "": - self.decort_api_call(requests.post, start_api, api_params) - # On success the above call will return here. On error it will abort execution by calling fail_json. - self.result['failed'] = False - self.result['changed'] = True - bs_dict['techStatus'] = t_state_expected - else: - self.result['failed'] = False - self.result['msg'] = ("bservice_state(): no start/stop action required for B-service ID {} from current " - "techStatus '{}' to desired started = '{}'.").format(bs_dict['id'], - bs_dict['techStatus'], - started) - return - - def bservice_delete(self,bs_id,permanently=True): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_delete") - - if self.amodule.check_mode: - self.result['failed'] = False - self.result['msg'] = "bservice_delete() in check mode: delete B-Service ID {} was requested.".format(bs_id) - return - - api_params = dict(serviceId=bs_id,permanently=permanently) - self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/delete", api_params) - # On success the above call will return here. On error it will abort execution by calling fail_json. - self.result['failed'] = False - self.result['msg'] = "bservice_delete() B-Service ID {} was deleted.".format(bs_id) - self.result['changed'] = True - - return - -# -# GROUP MANAGE -# - def _group_get_by_id(self,bs_id,g_id): - - api_params = dict(serviceId=bs_id,compgroupId=g_id) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/groupGet", api_params) - if api_resp.status_code == 200: - ret_gr_id = g_id - ret_gr_dict = json.loads(api_resp.content.decode('utf8')) - else: - self.result['warning'] = ("group_get_by_id(): failed to get Group by ID {}. HTTP code {}, " - "response {}.").format(g_id, api_resp.status_code, api_resp.reason) - return ret_gr_id,ret_gr_dict - - def group_find(self,bs_id,bs_info,group_id=0,group_name=""): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_find") - - if group_id == 0: - try: - i = bs_info['groupsName'].index(group_name) - except: - return 0,None - group_id = int(bs_info['groups'][i]) - return self._group_get_by_id(bs_id,group_id) - - def group_state(self,bs_id,gr_id,desired_state): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_state") - - group_api="" - if desired_state == 'stopped': - group_api = "/restmachine/cloudapi/bservice/groupStop" - state_expected = "STOPPED" - else: - group_api = "/restmachine/cloudapi/bservice/groupStart" - state_expected = "STARTED" - api_params = dict( - serviceId=bs_id, - compgroupId=gr_id - ) - if group_api != "": - self.decort_api_call(requests.post, group_api, api_params) - # On success the above call will return here. On error it will abort execution by calling fail_json. - self.result['failed'] = False - self.result['changed'] = True - else: - self.result['failed'] = False - self.result['msg'] = ("group_state(): no start/stop action required for B-service ID {} " - "to desired state '{}'.").format(bs_id,desired_state) - return - def group_resize_count(self,bs_id,gr_dict,desired_count): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_resize_count") - - count = len(gr_dict['computes']) - if desired_count != count: - api_params=dict( - serviceId=bs_id, - compgroupId=gr_dict['id'], - count=desired_count, - mode="ABSOLUTE" - ) - api_url = "/restmachine/cloudapi/bservice/groupResize" - self.decort_api_call(requests.post, api_url, api_params) - self.result['failed'] = False - self.result['changed'] = True - self.need_update_group_info = True - else: - self.result['failed'] = False - self.result['msg'] = ("group_resize_count(): no need resize Group ID {}.").format(gr_dict['id']) - return - def group_update_hw(self,bs_id,gr_dict,arg_cpu,arg_disk,arg_name,arg_role,arg_ram): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_update_hw") - - api_params=dict( - serviceId=bs_id, - compgroupId=gr_dict['id'], - force=True, - ) - - if gr_dict['cpu'] != arg_cpu: - api_params.update({'cpu': arg_cpu}) - if gr_dict['ram'] != arg_ram: - api_params.update({'ram': arg_ram}) - if gr_dict['role'] != arg_role: - api_params.update({'role': arg_role}) - if gr_dict['disk'] != arg_disk: - api_params.update({'disk': arg_disk}) - if gr_dict['name'] != arg_name: - api_params.update({'name': arg_name}) - - api_url = "/restmachine/cloudapi/bservice/groupUpdate" - if len(api_params) > 3: - # - self.decort_api_call(requests.post, api_url, api_params) - self.result['failed'] = False - self.result['changed'] = True - self.need_update_group_info = True - else: - self.result['failed'] = False - self.result['msg'] = ("group_update_hw(): no need update Group ID {}.").format(gr_dict['id']) - return - - def group_update_net(self,bs_id,gr_dict,arg_net): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_update_net") - - list_vins= list() - list_extnet= list() - for net in arg_net: - if net['type'] == 'VINS': - list_vins.append(net['id']) - else: - list_extnet.append(net['id']) - - if gr_dict['vinses'] != list_vins: - api_url = "/restmachine/cloudapi/bservice/groupUpdateVins" - api_params = dict( - serviceId=bs_id, - compgroupId=gr_dict['id'], - vinses=list_vins - ) - self.decort_api_call(requests.post, api_url, api_params) - self.result['failed'] = False - self.result['changed'] = True - - #extnet connection need stoped status of group - #rly need connect group to extnet ? - return - def group_provision( - self,bs_id,arg_name,arg_count=1,arg_cpu=1,arg_ram=1024, - arg_boot_disk=10,arg_image_id=0,arg_driver="KVM_X86",arg_role="", - arg_network=None,arg_timeout=0 - ): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_provision") - - list_vins= list() - for net in arg_network: - if net['type'] == 'VINS': - list_vins.append(net['id']) - api_url = "/restmachine/cloudapi/bservice/groupAdd" - api_params = dict( - serviceId = bs_id, - name = arg_name, - count = arg_count, - cpu = arg_cpu, - ram = arg_ram, - disk = arg_boot_disk, - imageId = arg_image_id, - driver = arg_driver, - role = arg_role, - vinses = list_vins, - timeoutStart = arg_timeout - ) - self.decort_api_call(requests.post, api_url, api_params) - self.result['failed'] = False - self.result['changed'] = True - return - - def group_delete(self,bs_id,gr_id): - - self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_delete") - - api_url = "/restmachine/cloudapi/bservice/groupRemove" - api_params=dict( - serviceId = bs_id, - compgroupId = gr_id - ) - self.decort_api_call(requests.post, api_url, api_params) - self.result['failed'] = False - self.result['msg'] = "group_delete() Group ID {} was deleted.".format(gr_id) - self.result['changed'] = True - return + return ret_conf \ No newline at end of file From c497979efaed98fa2c1c41be69e22f4a2f85de14 Mon Sep 17 00:00:00 2001 From: msbolshakov Date: Mon, 11 Jul 2022 15:43:53 +0700 Subject: [PATCH 4/9] osimage update --- library/decort_osimage.py | 63 +++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index 46c485b..f09eee7 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -67,8 +67,8 @@ options: 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.' + - '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: @@ -129,85 +129,90 @@ options: - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Account name. Used to get a unique integer account ID.' required: no virt_id: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Binary architecture of the image. Note. `X86_64` or `PPC64_LE`. Used when creating + -an operating system image.' required: no imagetype: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Image type. `linux`, `windows` or `other`. The default is `linux`. Used when creating + - an operating system image.' required: no boottype: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Image upload type. `bios` or `uefi`. The default is `uefi`. Used when creating an operating + -system image.' required: no url: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Uniform resource locator (URL) pointing to the iso image of the operating system. Used when + -creating an operating system image.' required: no sepId: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - '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: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Whether the image supports "hot" resizing. The default is `false`. Used when creating an operating + - system image.' required: no image_username: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'An optional username for the image. Used when creating an operating system image.' required: no image_password: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'An optional password for the image. Used when creating an operating system image. Used when creating + - an operating system image.' required: no usernameDL: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'The username for loading the binary media. Used in conjunction with `passwordDL`. Used when creating + - an operating system image' required: no passwordDL: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'The password for loading the binary media. Used in conjunction with `usernameDL`. Used when creating + - an operating system image.' required: no permanently: description: - - 'Context data that will be included into the payload of the API call directed at I(workflow_callback) URL.' + - 'Whether to permanently delete the image. Used when deleting an image. The default is false.' required: no - - - - - - - ''' EXAMPLES = ''' From 5e5b6f6b8a01d5127f64bcc8a8c4808d04f9bf14 Mon Sep 17 00:00:00 2001 From: msbolshakov Date: Mon, 11 Jul 2022 15:48:02 +0700 Subject: [PATCH 5/9] osimage examples --- examples/create-osimage.yaml | 27 +++++++++++++++++++++++++++ examples/create-virtual-osimage.yaml | 15 +++++++++++++++ examples/get-osimage.yaml | 14 ++++++++++++++ examples/rename-osimage.yaml | 15 +++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 examples/create-osimage.yaml create mode 100644 examples/create-virtual-osimage.yaml create mode 100644 examples/get-osimage.yaml create mode 100644 examples/rename-osimage.yaml diff --git a/examples/create-osimage.yaml b/examples/create-osimage.yaml new file mode 100644 index 0000000..ae616e4 --- /dev/null +++ b/examples/create-osimage.yaml @@ -0,0 +1,27 @@ +--- +# +# DECORT osimage module example +# +- hosts: localhost + tasks: + - name: create + decort_osimage: + authenticator: oauth2 + verify_ssl: False + controller_url: "https://ds1.digitalenergy.online" + state: present + image_name: "alpine_linux3.14.0" + account_Id: 12345 + url: "https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-virt-3.14.0-x86_64.iso" + boottype: "uefi" + imagetype: "linux" + hotresize: False + image_username: "test" + image_password: "p@ssword" + usernameDL: "testDL" + passwordDL: "p@sswordDL" + architecture: "X86_64" + drivers: "KVM_X86" + + delegate_to: localhost + register: simple_vm diff --git a/examples/create-virtual-osimage.yaml b/examples/create-virtual-osimage.yaml new file mode 100644 index 0000000..015c21e --- /dev/null +++ b/examples/create-virtual-osimage.yaml @@ -0,0 +1,15 @@ +--- +# +# DECORT osimage module example +# +- hosts: localhost + tasks: + - name: create_virtual_osimage + decort_osimage: + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: "alpine_linux_3.14.0" + virt_name: "alpine_last" + delegate_to: localhost + register: osimage + diff --git a/examples/get-osimage.yaml b/examples/get-osimage.yaml new file mode 100644 index 0000000..3e61612 --- /dev/null +++ b/examples/get-osimage.yaml @@ -0,0 +1,14 @@ +--- +# +# DECORT osimage module example +# +- hosts: localhost + tasks: + - name: get_osimage + decort_osimage: + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: "alpine_linux_3.14.0" + account_Id: 79349 + delegate_to: localhost + register: simple_vm diff --git a/examples/rename-osimage.yaml b/examples/rename-osimage.yaml new file mode 100644 index 0000000..eb13642 --- /dev/null +++ b/examples/rename-osimage.yaml @@ -0,0 +1,15 @@ +--- +# +# DECORT osimage module example +# +- hosts: localhost + tasks: + - name: rename_osimage + decort_osimage: + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: "alpine_linux_3.14.0v2.0" + image_id: 54321 + delegate_to: localhost + register: osimage + From ff4273cbce6bfe7bfa5b239e8822ad3799a52736 Mon Sep 17 00:00:00 2001 From: msbolshakov Date: Mon, 11 Jul 2022 15:59:04 +0700 Subject: [PATCH 6/9] module_utils_fix --- library/decort_osimage.py | 397 ++++++++------------------- module_utils/decort_utils.py | 504 ++++++++++++++++++++++++++++++++--- 2 files changed, 575 insertions(+), 326 deletions(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index f09eee7..d44e87c 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -22,7 +22,8 @@ description: > This module can be used to obtain image ID of an OS image in DECORT cloud to use with subsequent calls to decort_vm module for batch VM provisioning. It will speed up VM creation and save a bunch of extra calls to DECORT cloud controller on each VM creation act. - + Note that this module is effectively an information provisioner. It is not designed to and does not manage + nor change state of OS image (or any other) objects in DECORT cloud. version_added: "2.2" author: - Sergey Shubin @@ -108,6 +109,10 @@ options: - 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.' - If not specified in the playbook, the value will be taken from DECORT_USER environment variable. required: no + vdc_id: + description: + - ID of the VDC to limit the search of the OS image to. + required: no verify_ssl: description: - 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you @@ -129,6 +134,7 @@ options: - '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 +<<<<<<< HEAD account_name: description: - 'Account name. Used to get a unique integer account ID.' @@ -213,59 +219,21 @@ options: - 'Whether to permanently delete the image. Used when deleting an image. The default is false.' required: no +======= +>>>>>>> parent of 28876ae (decort_osimage update) ''' 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 +- name: locate OS image specified by its name, store result in image_to_use variable. decort_osimage: authenticator: oauth2 + app_id: "{{ MY_APP_ID }}" + app_secret: "{{ MY_APP_SECRET }}" controller_url: "https://ds1.digitalenergy.online" - image_name: "alpine_linux_3.14.0v2.0" - image_id: 54321 + image_name: "Ubuntu 18.04 v1.2.5" + account_name: "GreyseDevelopment" delegate_to: localhost - register: osimage - - - + register: image_to_use ''' RETURN = ''' @@ -276,7 +244,6 @@ facts: sample: facts: id: 100 - linkto: 80 name: "Ubuntu 16.04 v1.0" size: 3 sep_id: 1 @@ -291,205 +258,94 @@ 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. - 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. + @return: dictionary with OS image specs populated from arg_osimage_facts. + """ - @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. + ret_dict = dict(id=0, + name="none", + size=0, + type="none", + state="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'] + if arg_check_mode: + # in check mode return immediately with the default values 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', + 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'] + + 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_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', + 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), + image_name=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_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), - ) - + 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=True), + user=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_USER'])), + vdc_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), + ) + +# Workflow digest: +# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when +# creating DecortController +# 2) obtain a list of OS images accessible to the specified account (and optionally - within +# the specified VDC) +# 3) match specified OS image by its name - if image is not found abort the module +# 5) report result to Ansible def main(): - module_parameters = decort_osimage.decort_osimage_parameters() + module_parameters = decort_osimage_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -504,67 +360,30 @@ def main(): ], ) - 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" and 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) - - - - 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) + decon = DecortController(amodule) + # we need account ID to locate OS images - find the account by the specified name and get its ID + validated_account_id, _ = decon.account_find(amodule.params['account_name']) + if validated_account_id == 0: + # we failed either to find or access the specified account - fail the module + decon.result['failed'] = True + decon.result['changed'] = False + decon.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name']) + amodule.fail_json(**decon.result) + image_id, image_facts = decon.image_find(image_id=0, image_name=amodule.params['image_name'], + account_id=validated_account_id, rg_id=0, + sepid=amodule.params['sep_id'], + pool=amodule.params['pool']) if decon.result['failed'] == True: # we failed to find the specified image - fail the module decon.result['changed'] = False amodule.fail_json(**decon.result) + decon.result['facts'] = decort_osimage_package_facts(image_facts, amodule.check_mode) + decon.result['changed'] = False # decort_osimage is a read-only module - make sure the 'changed' flag is set to False amodule.exit_json(**decon.result) - + if __name__ == "__main__": main() diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index b5f1f87..c53317d 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1247,23 +1247,23 @@ class DecortController(object): def image_find(self, image_id, image_name, account_id, rg_id=0, sepid=0, pool=""): """Locates image specified by name and returns its facts as dictionary. Primary use of this function is to obtain the ID of the image identified by its name and, - optionally SEP ID and/or pool name. Also note that only images in status CREATED are + optionally SEP ID and/or pool name. Also note that only images in status CREATED are returned. @param (string) image_id: ID of the OS image to find. If non-zero ID is specified, then image_name is ignored. @param (string) image_name: name of the OS image to find. This argument is ignored if non-zero image ID is passed. - @param (int) account_id: ID of the account for which the image will be looked up. If set to 0, + @param (int) account_id: ID of the account for which the image will be looked up. If set to 0, the account ID will be obtained from the specified RG ID. @param (int) rg_id: ID of the RG to use as a reference when listing OS images. This argument is ignored if non-zero image id and/or non-zero account_id are specified. - @param (int) sepid: ID of the SEP where the image should be present. If set to 0, there will be no + @param (int) sepid: ID of the SEP where the image should be present. If set to 0, there will be no filtering by SEP ID and the first matching image will be returned. - @param (string) pool: name of the pool where the image should be present. If set to empty string, there + @param (string) pool: name of the pool where the image should be present. If set to empty string, there will be no filtering by pool name and first matching image will be returned. - @return: image ID and dictionary with image specs. If no matching image found, 0 for ID and None for + @return: image ID and dictionary with image specs. If no matching image found, 0 for ID and None for dictionary are returned, and self.result['failed']=True. """ self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_find") @@ -1291,7 +1291,7 @@ class DecortController(object): for image_record in images_list: if image_record['name'] == image_name and image_record['status'] == "CREATED": if sepid == 0 and pool == "": - # if no filtering by SEP ID or pool name is requested, return the first match + # if no filtering by SEP ID or pool name is requested, return the first match return image_record['id'], image_record # if positive SEP ID and/or non-emtpy pool name are passed, match by them full_match = True @@ -1302,7 +1302,7 @@ class DecortController(object): if full_match: return image_record['id'], image_record self.result['failed'] = False - + self.result['failed'] = True self.result['msg'] = ("Failed to find OS image by name '{}', SEP ID {}, pool '{}' for " "account ID '{}'.").format(image_name, @@ -1313,29 +1313,29 @@ class DecortController(object): def virt_image_find(self, image_id, virt_name, account_id, rg_id=0, sepid=0, pool=""): """Locates virtual image specified by name and returns its facts as dictionary. Primary use of this function is to obtain the ID of the image identified by its name and, - optionally SEP ID and/or pool name. Also note that only virtual images in status CREATED are + optionally SEP ID and/or pool name. Also note that only virtual images in status CREATED are returned. @param (string) image_id: ID of the OS image to find. If non-zero ID is specified, then virt_name is ignored. @param (string) virt_name: name of the OS image to find. This argument is ignored if non-zero image ID is passed. - @param (int) account_id: ID of the account for which the image will be looked up. If set to 0, + @param (int) account_id: ID of the account for which the image will be looked up. If set to 0, the account ID will be obtained from the specified RG ID. @param (int) rg_id: ID of the RG to use as a reference when listing OS images. This argument is ignored if non-zero image id and/or non-zero account_id are specified. - @param (int) sepid: ID of the SEP where the image should be present. If set to 0, there will be no + @param (int) sepid: ID of the SEP where the image should be present. If set to 0, there will be no filtering by SEP ID and the first matching image will be returned. - @param (string) pool: name of the pool where the image should be present. If set to empty string, there + @param (string) pool: name of the pool where the image should be present. If set to empty string, there will be no filtering by pool name and first matching image will be returned. - @return: image ID and dictionary with image specs. If no matching image found, 0 for ID and None for + @return: image ID and dictionary with image specs. If no matching image found, 0 for ID and None for dictionary are returned, and self.result['failed']=True. """ self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "virt_image_find") - + if image_id > 0: ret_image_id, ret_image_dict = self._image_get_by_id(image_id) if (ret_image_id and @@ -1359,7 +1359,7 @@ class DecortController(object): for image_record in images_list: if image_record['name'] == virt_name and image_record['status'] == "CREATED" and image_record['type'] == "virtual": if sepid == 0 and pool == "": - # if no filtering by SEP ID or pool name is requested, return the first match + # if no filtering by SEP ID or pool name is requested, return the first match return image_record['id'], image_record full_match = True if full_match: @@ -1381,11 +1381,11 @@ class DecortController(object): api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/createVirtual", api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. virt_image_dict = json.loads(api_resp.content.decode('utf8')) - + self.result['failed'] = False self.result['changed'] = True return 0, None - + def image_delete(self, imageId, permanently): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_delete") @@ -1393,7 +1393,7 @@ class DecortController(object): api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/delete", api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. image_dict = json.loads(api_resp.content.decode('utf8')) - + self.result['changed'] = True return 0, None @@ -1401,9 +1401,9 @@ class DecortController(object): def image_create(self,img_name,url,gid,boottype,imagetype,architecture,drivers,hotresize,username,password,account_Id,usernameDL,passwordDL,sepId,poolName): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_create") - api_params = dict(name=img_name, url=url, - gid=gid, boottype=boottype, - imagetype=imagetype, architecture=architecture, + api_params = dict(name=img_name, url=url, + gid=gid, boottype=boottype, + imagetype=imagetype, architecture=architecture, drivers=drivers, accountId=account_Id, hotresize=hotresize, username=username, password=password, usernameDL=usernameDL, @@ -1440,8 +1440,6 @@ class DecortController(object): self.result['failed'] = False self.result['changed'] = True - - ################################### # Resource Group (RG) manipulation methods ################################### @@ -2278,10 +2276,10 @@ class DecortController(object): expected_state = "" if vins_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled': - rgstate_api = "/restmachine/cloudapi/vins/disable" + vinsstate_api = "/restmachine/cloudapi/vins/disable" expected_state = "DISABLED" elif vins_dict['status'] == "DISABLED" and desired_state == 'enabled': - rgstate_api = "/restmachine/cloudapi/vins/enable" + vinsstate_api = "/restmachine/cloudapi/vins/enable" expected_state = "ENABLED" if vinsstate_api != "": @@ -2298,7 +2296,7 @@ class DecortController(object): desired_state) return - def vins_update(self, vins_dict, ext_net_id, ext_ip_addr=""): + def vins_update(self, vins_dict, ext_net_id, ext_ip_addr="", mgmtaddr=""): """Update ViNS. Currently only updates to the external network connection settings and external IP address assignment are implemented. Note that as ViNS created at account level cannot have external connections, attempt @@ -2323,6 +2321,11 @@ class DecortController(object): self.result['msg'] = ("vins_update() in check mode: updating ViNS ID {}, name '{}' " "was requested.").format(vins_dict['id'], vins_dict['name']) return + if self.amodule.params['config_save'] and vins_dict['VNFDev']['customPrecfg']: + # only save config,no other modifictaion + self.result['changed'] = True + self._vins_vnf_config_save(vins_dict['VNFDev']['id']) + return if not vins_dict['rgId']: # this ViNS exists at account level - no updates are possible @@ -2374,7 +2377,78 @@ class DecortController(object): "no reconnection to default network will be done.").format(vins_dict['id'], gw_config[ 'ext_net_id']) + for iface in vins_dict['VNFDev']['interfaces']: + if iface['ipAddress'] == mgmtaddr: + if not iface['listenSsh']: + self._vins_vnf_addmgmtaddr(vins_dict['VNFDev']['id'],mgmtaddr) + elif mgmtaddr =="": + if iface['listenSsh'] and iface['name'] != "ens9": + self._vins_vnf_delmgmtaddr(vins_dict['VNFDev']['id'],iface['ipAddress']) + + if self.amodule.params['custom_config']: + if not vins_dict['VNFDev']['customPrecfg']: + self._vins_vnf_config_save(vins_dict['VNFDev']['id']) + self._vins_vnf_customconfig_set(vins_dict['VNFDev']['id']) + else: + if vins_dict['VNFDev']['customPrecfg']: + self._vins_vnf_customconfig_set(vins_dict['VNFDev']['id'],False) + + return + + def _vins_vnf_addmgmtaddr(self,dev_id,mgmtip): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_addmgmtaddr") + api_params = dict(devId=dev_id,ip=mgmtip) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/addMgmtAddr", api_params) + if api_resp.status_code == 200: + self.result['changed'] = True + self.result['failed'] = False + else: + self.result['warning'] = ("_vins_vnf_addmgmtaddr(): failed to add MGMT addr VNFID {} iface ADDR {}. HTTP code {}, " + "response {}.").format(dev_id,mgmtip,api_resp.status_code, api_resp.reason) + return + + def _vins_vnf_delmgmtaddr(self,dev_id,mgmtip): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_delmgmtaddr") + api_params = dict(devId=dev_id,ip=mgmtip) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/delMgmtAddr", api_params) + if api_resp.status_code == 200: + self.result['changed'] = True + self.result['failed'] = False + else: + self.result['warning'] = ("_vins_vnf_delmgmtaddr(): failed to delete MGMT addr VNFID {} iface ADDR {}. HTTP code {}, " + "response {}.").format(dev_id,mgmtip,api_resp.status_code, api_resp.reason) + return + def _vins_vnf_customconfig_set(self,dev_id,arg_mode=True): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_customconfig_set") + api_params = dict(devId=dev_id,mode=arg_mode) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/customSet", api_params) + if api_resp.status_code == 200: + self.result['changed'] = True + self.result['failed'] = False + else: + self.result['warning'] = ("_vins_vnf_customconfig_set(): failed to enable or disable Custom pre-config mode on the VNF device. {}. HTTP code {}, " + "response {}.").format(dev_id,api_resp.status_code, api_resp.reason) + return + + def _vins_vnf_config_save(self,dev_id): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_vnf_config_save") + api_params = dict(devId=dev_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudbroker/vnfdev/configSave", api_params) + if api_resp.status_code == 200: + self.result['changed'] = True + self.result['failed'] = False + else: + self.result['warning'] = ("_vins_vnf_config_set(): failed to Save configuration on the VNF device. {}. HTTP code {}, " + "response {}.").format(dev_id,api_resp.status_code, api_resp.reason) + return + + def vins_vnf_ifaceadd(self): + return + + def vins_vnf_ifaceremove(self): return ############################## @@ -3023,12 +3097,9 @@ class DecortController(object): return def k8s_provision(self, k8s_name, - wg_name, k8ci_id, - rg_id, master_count, + k8ci_id,rg_id, master_count, master_cpu, master_ram, - master_disk, worker_count, - worker_cpu, worker_ram, - worker_disk, extnet_id, + master_disk, default_worker, extnet_id, with_lb, annotation, ): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_provision") @@ -3038,20 +3109,31 @@ class DecortController(object): self.result['msg'] = ("k8s_provision() in check mode. Provision k8s '{}' in RG ID {} " "was requested.").format(k8s_name, rg_id) return 0 + def_wg_name = default_worker['name'] + def_wg_count = default_worker['num'] + def_wg_cpu = default_worker['cpu'] + def_wg_ram = default_worker['ram'] + def_wg_disk = default_worker['disk'] + def_wg_lab = default_worker['labels'] if "labels" in default_worker else None + def_wg_taints = default_worker['taints'] if "taints" in default_worker else None + def_wg_ann = default_worker['annotations'] if "annotations" in default_worker else None api_url = "/restmachine/cloudapi/k8s/create" api_params = dict(name=k8s_name, rgId=rg_id, k8ciId=k8ci_id, - workerGroupName=wg_name, + workerGroupName=def_wg_name, masterNum=master_count, masterCpu=master_cpu, masterRam=master_ram, masterDisk=master_disk, - workerNum=worker_count, - workerCpu=worker_cpu, - workerRam=worker_ram, - workerDisk=worker_disk, + workerNum=def_wg_count, + workerCpu=def_wg_cpu, + workerRam=def_wg_ram, + workerDisk=def_wg_disk, + labels=def_wg_lab, + taints=def_wg_taints, + annotations=def_wg_ann, extnetId=extnet_id, withLB=with_lb, desc=annotation, @@ -3113,7 +3195,7 @@ class DecortController(object): for rec_inn in arg_k8swg['k8sGroups']['workers']: for rec_out in arg_modwg: - if rec_inn['num'] != rec_out['num']: + if rec_inn['num'] != rec_out['num'] and rec_out['num'] != 0: count = rec_inn['num']-rec_out['num'] cmp_list = [] if count > 0: @@ -3136,6 +3218,9 @@ class DecortController(object): workerCpu=wg['cpu'], workerRam=wg['ram'], workerDisk=wg['disk'], + labels=wg['labels'] if "labels" in wg else None, + taints=wg['taints'] if "taints" in wg else None, + annotations=wg['annotations'] if "annotations" in wg else None, ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/workersGroupAdd", api_params) self.result['changed'] = True @@ -3185,4 +3270,349 @@ class DecortController(object): api_params = dict(k8sId=self.k8s_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/getConfig", api_params) ret_conf = api_resp.content.decode('utf8') - return ret_conf \ No newline at end of file + return ret_conf + + ############################## + # + # Bservice management + # + ############################## + def bservice_get_by_id(self,bs_id): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_get_by_id") + + ret_bs_id = 0 + ret_bs_dict = dict() + + if not bs_id: + self.result['failed'] = True + self.result['msg'] = "bservice_get_by_id(): zero B-Service ID specified." + self.amodule.fail_json(**self.result) + + api_params = dict(serviceId=bs_id, ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/get", api_params) + if api_resp.status_code == 200: + ret_bs_id = bs_id + ret_bs_dict = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("bservice_get_by_id(): failed to get B-service by ID {}. HTTP code {}, " + "response {}.").format(bs_id, api_resp.status_code, api_resp.reason) + + return ret_bs_id, ret_bs_dict + def _bservice_rg_list(self,acc_id,rg_id): + ret_bs_dict=dict() + api_params = dict(accountId=acc_id,rgId=rg_id ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/list", api_params) + if api_resp.status_code == 200: + ret_bs_dict = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("bservice_rg_list(): failed to get B-service list. HTTP code {}, " + "response {}.").format(api_resp.status_code, api_resp.reason) + return ret_bs_dict + + def bservice_find(self,account_id,rg_id,bservice_name="",bservice_id = 0,check_state=True): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_find") + + if bservice_id == 0: + bs_rg_list = self._bservice_rg_list(account_id,rg_id) + for srv in bs_rg_list: + if bservice_name == srv['name']: + bservice_id = int(srv['id']) + + if bservice_id > 0: + ret_bs_id,ret_bs_dict = self.bservice_get_by_id(bservice_id) + return ret_bs_id,ret_bs_dict + else: + return bservice_id,None + + def bservice_provision(self,bs_name,rgid,sshuser=None,sshkey=None): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_provision") + + api_url = "/restmachine/cloudapi/bservice/create" + api_params = dict( + name = bs_name, + rgId = rgid, + sshUser = sshuser, + sshKey = sshkey, + ) + api_resp = self.decort_api_call(requests.post, api_url, api_params) + self.result['failed'] = False + self.result['changed'] = True + ret_bservice_id = int(api_resp.content.decode('utf8')) + return ret_bservice_id + + def bservice_state(self,bs_dict,desired_state,started=True): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_state") + + if desired_state == "present": + desired_state = "enabled" + + NOP_STATES_FOR_BS_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", + "DESTROYED","RESTORYNG","RECONFIGURING"] + VALID_TARGET_STATES = ["enabled", "disabled"] + + if bs_dict['status'] in NOP_STATES_FOR_BS_CHANGE: + self.result['failed'] = False + self.result['msg'] = ("bservice_state(): no state change possible for ViNS ID {} " + "in its current state '{}'.").format(bs_dict['id'], bs_dict['status']) + return + + if desired_state not in VALID_TARGET_STATES: + self.result['failed'] = False + self.result['warning'] = ("bservice_state(): unrecognized desired state '{}' requested " + "for B-service ID {}. No B-service state change will be done.").format(desired_state, + bs_dict['id']) + return + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("bservice_state() in check mode: setting state of B-service ID {}, name '{}' to " + "'{}' was requested.").format(bs_dict['id'], bs_dict['name'], + desired_state) + return + + bsstate_api = "" # this string will also be used as a flag to indicate that API call is necessary + api_params = dict(serviceId=bs_dict['id']) + expected_state = "" + + if bs_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled': + bsstate_api = "/restmachine/cloudapi/bservice/disable" + expected_state = "DISABLED" + elif bs_dict['status'] in ["CREATED", "DISABLED"] and desired_state == 'enabled': + bsstate_api = "/restmachine/cloudapi/bservice/enable" + expected_state = "ENABLED" + + if bsstate_api != "": + self.decort_api_call(requests.post, bsstate_api, api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + self.result['failed'] = False + self.result['changed'] = True + bs_dict['status'] = expected_state + else: + self.result['failed'] = False + self.result['msg'] = ("bservice_state(): no state change required for B-service ID {} from current " + "state '{}' to desired state '{}'.").format(bs_dict['id'], + bs_dict['status'], + desired_state) + + start_api = "" + + if bs_dict['status'] in ["ENABLED","enabled"]: + if started == True and bs_dict['techStatus'] == "STOPPED": + start_api = "/restmachine/cloudapi/bservice/start" + t_state_expected = "STARTED" + if started == False and bs_dict['techStatus'] == "STARTED": + start_api = "/restmachine/cloudapi/bservice/stop" + t_state_expected = "STOPPED" + + if start_api != "": + self.decort_api_call(requests.post, start_api, api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + self.result['failed'] = False + self.result['changed'] = True + bs_dict['techStatus'] = t_state_expected + else: + self.result['failed'] = False + self.result['msg'] = ("bservice_state(): no start/stop action required for B-service ID {} from current " + "techStatus '{}' to desired started = '{}'.").format(bs_dict['id'], + bs_dict['techStatus'], + started) + return + + def bservice_delete(self,bs_id,permanently=True): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_delete") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = "bservice_delete() in check mode: delete B-Service ID {} was requested.".format(bs_id) + return + + api_params = dict(serviceId=bs_id,permanently=permanently) + self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/delete", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + self.result['failed'] = False + self.result['msg'] = "bservice_delete() B-Service ID {} was deleted.".format(bs_id) + self.result['changed'] = True + + return + +# +# GROUP MANAGE +# + def _group_get_by_id(self,bs_id,g_id): + + api_params = dict(serviceId=bs_id,compgroupId=g_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/groupGet", api_params) + if api_resp.status_code == 200: + ret_gr_id = g_id + ret_gr_dict = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("group_get_by_id(): failed to get Group by ID {}. HTTP code {}, " + "response {}.").format(g_id, api_resp.status_code, api_resp.reason) + return ret_gr_id,ret_gr_dict + + def group_find(self,bs_id,bs_info,group_id=0,group_name=""): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_find") + + if group_id == 0: + try: + i = bs_info['groupsName'].index(group_name) + except: + return 0,None + group_id = int(bs_info['groups'][i]) + return self._group_get_by_id(bs_id,group_id) + + def group_state(self,bs_id,gr_id,desired_state): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_state") + + group_api="" + if desired_state == 'stopped': + group_api = "/restmachine/cloudapi/bservice/groupStop" + state_expected = "STOPPED" + else: + group_api = "/restmachine/cloudapi/bservice/groupStart" + state_expected = "STARTED" + api_params = dict( + serviceId=bs_id, + compgroupId=gr_id + ) + if group_api != "": + self.decort_api_call(requests.post, group_api, api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + self.result['failed'] = False + self.result['changed'] = True + else: + self.result['failed'] = False + self.result['msg'] = ("group_state(): no start/stop action required for B-service ID {} " + "to desired state '{}'.").format(bs_id,desired_state) + return + def group_resize_count(self,bs_id,gr_dict,desired_count): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_resize_count") + + count = len(gr_dict['computes']) + if desired_count != count: + api_params=dict( + serviceId=bs_id, + compgroupId=gr_dict['id'], + count=desired_count, + mode="ABSOLUTE" + ) + api_url = "/restmachine/cloudapi/bservice/groupResize" + self.decort_api_call(requests.post, api_url, api_params) + self.result['failed'] = False + self.result['changed'] = True + self.need_update_group_info = True + else: + self.result['failed'] = False + self.result['msg'] = ("group_resize_count(): no need resize Group ID {}.").format(gr_dict['id']) + return + def group_update_hw(self,bs_id,gr_dict,arg_cpu,arg_disk,arg_name,arg_role,arg_ram): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_update_hw") + + api_params=dict( + serviceId=bs_id, + compgroupId=gr_dict['id'], + force=True, + ) + + if gr_dict['cpu'] != arg_cpu: + api_params.update({'cpu': arg_cpu}) + if gr_dict['ram'] != arg_ram: + api_params.update({'ram': arg_ram}) + if gr_dict['role'] != arg_role: + api_params.update({'role': arg_role}) + if gr_dict['disk'] != arg_disk: + api_params.update({'disk': arg_disk}) + if gr_dict['name'] != arg_name: + api_params.update({'name': arg_name}) + + api_url = "/restmachine/cloudapi/bservice/groupUpdate" + if len(api_params) > 3: + # + self.decort_api_call(requests.post, api_url, api_params) + self.result['failed'] = False + self.result['changed'] = True + self.need_update_group_info = True + else: + self.result['failed'] = False + self.result['msg'] = ("group_update_hw(): no need update Group ID {}.").format(gr_dict['id']) + return + + def group_update_net(self,bs_id,gr_dict,arg_net): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_update_net") + + list_vins= list() + list_extnet= list() + for net in arg_net: + if net['type'] == 'VINS': + list_vins.append(net['id']) + else: + list_extnet.append(net['id']) + + if gr_dict['vinses'] != list_vins: + api_url = "/restmachine/cloudapi/bservice/groupUpdateVins" + api_params = dict( + serviceId=bs_id, + compgroupId=gr_dict['id'], + vinses=list_vins + ) + self.decort_api_call(requests.post, api_url, api_params) + self.result['failed'] = False + self.result['changed'] = True + + #extnet connection need stoped status of group + #rly need connect group to extnet ? + return + def group_provision( + self,bs_id,arg_name,arg_count=1,arg_cpu=1,arg_ram=1024, + arg_boot_disk=10,arg_image_id=0,arg_driver="KVM_X86",arg_role="", + arg_network=None,arg_timeout=0 + ): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_provision") + + list_vins= list() + for net in arg_network: + if net['type'] == 'VINS': + list_vins.append(net['id']) + api_url = "/restmachine/cloudapi/bservice/groupAdd" + api_params = dict( + serviceId = bs_id, + name = arg_name, + count = arg_count, + cpu = arg_cpu, + ram = arg_ram, + disk = arg_boot_disk, + imageId = arg_image_id, + driver = arg_driver, + role = arg_role, + vinses = list_vins, + timeoutStart = arg_timeout + ) + self.decort_api_call(requests.post, api_url, api_params) + self.result['failed'] = False + self.result['changed'] = True + return + + def group_delete(self,bs_id,gr_id): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_delete") + + api_url = "/restmachine/cloudapi/bservice/groupRemove" + api_params=dict( + serviceId = bs_id, + compgroupId = gr_id + ) + self.decort_api_call(requests.post, api_url, api_params) + self.result['failed'] = False + self.result['msg'] = "group_delete() Group ID {} was deleted.".format(gr_id) + self.result['changed'] = True + return From 224ac5977937eaaac72d752ab87fd1cb42b5c017 Mon Sep 17 00:00:00 2001 From: msbolshakov Date: Mon, 11 Jul 2022 16:05:07 +0700 Subject: [PATCH 7/9] osimage fix --- library/decort_osimage.py | 397 +++++++++++++++++++++++++++----------- 1 file changed, 289 insertions(+), 108 deletions(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index d44e87c..f09eee7 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -22,8 +22,7 @@ description: > This module can be used to obtain image ID of an OS image in DECORT cloud to use with subsequent calls to decort_vm module for batch VM provisioning. It will speed up VM creation and save a bunch of extra calls to DECORT cloud controller on each VM creation act. - Note that this module is effectively an information provisioner. It is not designed to and does not manage - nor change state of OS image (or any other) objects in DECORT cloud. + version_added: "2.2" author: - Sergey Shubin @@ -109,10 +108,6 @@ options: - 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.' - If not specified in the playbook, the value will be taken from DECORT_USER environment variable. required: no - vdc_id: - description: - - ID of the VDC to limit the search of the OS image to. - required: no verify_ssl: description: - 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you @@ -134,7 +129,6 @@ options: - '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 -<<<<<<< HEAD account_name: description: - 'Account name. Used to get a unique integer account ID.' @@ -219,21 +213,59 @@ options: - 'Whether to permanently delete the image. Used when deleting an image. The default is false.' required: no -======= ->>>>>>> parent of 28876ae (decort_osimage update) ''' EXAMPLES = ''' -- name: locate OS image specified by its name, store result in image_to_use variable. + - 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 - app_id: "{{ MY_APP_ID }}" - app_secret: "{{ MY_APP_SECRET }}" controller_url: "https://ds1.digitalenergy.online" - image_name: "Ubuntu 18.04 v1.2.5" - account_name: "GreyseDevelopment" + image_name: "alpine_linux_3.14.0v2.0" + image_id: 54321 delegate_to: localhost - register: image_to_use + register: osimage + + + ''' RETURN = ''' @@ -244,6 +276,7 @@ facts: sample: facts: id: 100 + linkto: 80 name: "Ubuntu 16.04 v1.0" size: 3 sep_id: 1 @@ -258,94 +291,205 @@ 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) -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. + 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) - @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. - """ + 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") - 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 + 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 - 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'] - - 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), - image_name=dict(type='str', required=True), - jwt=dict(type='str', - required=False, - fallback=(env_fallback, ['DECORT_JWT']), - no_log=True), - oauth2_url=dict(type='str', + 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'] + 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_OAUTH2_URL'])), - password=dict(type='str', + 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_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=True), - user=dict(type='str', - required=False, - fallback=(env_fallback, ['DECORT_USER'])), - vdc_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), - ) - -# Workflow digest: -# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when -# creating DecortController -# 2) obtain a list of OS images accessible to the specified account (and optionally - within -# the specified VDC) -# 3) match specified OS image by its name - if image is not found abort the module -# 5) report result to Ansible + 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_parameters() + module_parameters = decort_osimage.decort_osimage_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -360,30 +504,67 @@ def main(): ], ) - decon = DecortController(amodule) + 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" and 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) + + + + 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) - # we need account ID to locate OS images - find the account by the specified name and get its ID - validated_account_id, _ = decon.account_find(amodule.params['account_name']) - if validated_account_id == 0: - # we failed either to find or access the specified account - fail the module - decon.result['failed'] = True - decon.result['changed'] = False - decon.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name']) - amodule.fail_json(**decon.result) - image_id, image_facts = decon.image_find(image_id=0, image_name=amodule.params['image_name'], - account_id=validated_account_id, rg_id=0, - sepid=amodule.params['sep_id'], - pool=amodule.params['pool']) if decon.result['failed'] == True: # we failed to find the specified image - fail the module decon.result['changed'] = False amodule.fail_json(**decon.result) - decon.result['facts'] = decort_osimage_package_facts(image_facts, amodule.check_mode) - decon.result['changed'] = False # decort_osimage is a read-only module - make sure the 'changed' flag is set to False amodule.exit_json(**decon.result) - + if __name__ == "__main__": main() From ebe1c76a43378e5554759d4e0b29c20792937841 Mon Sep 17 00:00:00 2001 From: Maksim Bolshakov Date: Tue, 12 Jul 2022 11:32:10 +0300 Subject: [PATCH 8/9] Update basicservices.yaml --- examples/basicservices.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basicservices.yaml b/examples/basicservices.yaml index f5862fa..ae9a6b7 100644 --- a/examples/basicservices.yaml +++ b/examples/basicservices.yaml @@ -1,6 +1,6 @@ --- # -# DECORT vins module example +# DECORT bservice module example # - hosts: localhost From 38d24cfa0a6e38ee60a6f59aa0470c606248b68e Mon Sep 17 00:00:00 2001 From: Maksim Bolshakov Date: Tue, 12 Jul 2022 17:17:40 +0300 Subject: [PATCH 9/9] Update kubernetes.yaml --- examples/kubernetes.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/kubernetes.yaml b/examples/kubernetes.yaml index 8e73050..065c5f8 100644 --- a/examples/kubernetes.yaml +++ b/examples/kubernetes.yaml @@ -6,23 +6,23 @@ tasks: - name: obtain JWT decort_jwt: - oauth2_url: "" #"https://sso.digitalenergy.online" + oauth2_url: "https://sso.digitalenergy.online" validity: 1200 verify_ssl: false register: token delegate_to: localhost - - name: create a VM named cloud-init_example + - name: create a VM named cluster-test decort_k8s: state: present started: True getConfig: True authenticator: jwt jwt: "{{ token.jwt }}" - controller_url: "" #"https://mr4.digitalenergy.online" + controller_url: "https://ds1.digitalenergy.online" name: "cluster-test" - rg_id: # Resource group id - k8ci_id: # k8s ci id + rg_id: 125 + k8ci_id: 18 workers: - name: wg1 ram: 1024