Compare commits

...

4 Commits
main ... rc-5.2

@ -5,8 +5,8 @@ Note that this module may produce unreliable results when used with older DECORT
Requirements: Requirements:
* Ansible 2.7 or higher * Ansible 2.7 or higher
* Python 2.6 or higher * Python 3.7 or higher
* PyJWT 1.7.1 Python module * PyJWT 2.0.0 Python module or higher
* requests Python module * requests Python module
* netaddr Python module * netaddr Python module
* DECORT cloud platform version 3.5.0 or higher * DECORT cloud platform version 3.5.0 or higher

@ -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

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

@ -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

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

@ -0,0 +1,31 @@
---
#
# DECORT bservice 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

@ -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: |
<div>
Hello World!
</div>
owner: user:user
path: /var/www/html/index.html
- hostname: test-apache
- ssh_keys:
- rsa_public: ssh-rsa AAAAOasDmLxnD= user@pc
delegate_to: localhost
register: simple_vm

@ -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

@ -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

@ -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

@ -0,0 +1,39 @@
---
#
# DECORT k8s module example
#
- hosts: ansible_master
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "https://sso.digitalenergy.online"
validity: 1200
verify_ssl: false
register: token
delegate_to: localhost
- name: create a VM named cluster-test
decort_k8s:
state: present
started: True
getConfig: True
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "https://ds1.digitalenergy.online"
name: "cluster-test"
rg_id: 125
k8ci_id: 18
workers:
- name: wg1
ram: 1024
cpu: 10
disk: 10
num: 1
- name: wg2
ram: 1024
cpu: 10
disk: 10
num: 2
verify_ssl: false
delegate_to: localhost
register: kube

@ -188,3 +188,136 @@
var: my_pfw.facts var: my_pfw.facts
delegate_to: localhost delegate_to: localhost
- name: Create k8s cluster with params
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
master_count: 1
master_cpu: 2
master_ram_mb: 2048
master_disk_gb: 20
worker_count: 3
worker_cpu: 1
worker_ram_mb: 1024
worker_disk_gb: 20
extnet_id: "{{ target_ext_net_id }}"
with_lb: True
state: present
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Disable k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: disabled
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Delete in trash k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: absent
permanent: False
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Restore from trash deleted k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: enabled
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Enable k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: enabled
register: k8s
delegate_to: localhost
- name: Enable k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: enabled
started: True
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Destroy k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: absent
permanent: True
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost

@ -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

@ -0,0 +1,42 @@
---
#
# 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://cloud.digitalenergy.online"
vins_name: "vins_connected_by_decort_vins_module"
state: present
rg_id: 98
connect_to:
- type: VINS
id: 864
ipaddr: 192.168.5.66
netmask: 24
- type: VINS
id: 196
ipaddr: 192.168.9.133
netmask: 24
register: managed_vins
- name: print VINS facter
debug:
msg: "{{managed_vins.facts.password}}"
when: managed_vins.facts.password is defined

@ -0,0 +1,289 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Alexey Dankov (alexey Dankov@digitalenergy.online)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_bservice(DecortController):
def __init__(self,arg_amodule):
super(decort_bservice, self).__init__(arg_amodule)
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
self.bservice_info = None
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage Basic Services when its ID is 0 and name is empty."
self.fail_json(**self.result)
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
self.bservice_id,self.bservice_info = self.bservice_find(
self.acc_id,
validated_rg_id,
arg_amodule.params['name'],
arg_amodule.params['id']
)
if self.bservice_id == 0:
self.bservice_should_exist = False
else:
self.bservice_should_exist = True
def nop(self):
"""No operation (NOP) handler for B-service.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.k8s_id:
self.result['msg'] = ("No state change required for B-service ID {} because of its "
"current status '{}'.").format(self.bservice_id, self.bservice_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent B-service instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.bservice_id:
self.result['msg'] = ("Invalid target state '{}' requested for B-service ID {} in the "
"current status '{}'.").format(self.bservice_id,
self.amodule.params['state'],
self.bservice_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent B-service name '{}' "
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['rg_name'])
return
def create(self):
self.bservice_id = self.bservice_id = self.bservice_provision(
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['sshuser'],
self.amodule.params['sshkey']
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.bservice_state(self.bservice_info,'enabled',self.amodule.params['started'])
return
def action(self,d_state,started=False):
self.bservice_state(self.bservice_info,d_state,started)
return
def restore(self):
self.result['failed'] = True
self.result['msg'] = "Restore B-Service ID {} manualy.".format(self.bservice_id)
pass
def destroy(self):
self.bservice_delete(self.bservice_id)
self.bservice_info['status'] = 'DELETED'
self.bservice_should_exist = False
return
def package_facts(self,check_mode=False):
ret_dict = dict(
name="",
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
ret_dict['id'] = self.bservice_info['id']
ret_dict['name'] = self.bservice_info['name']
ret_dict['techStatus'] = self.bservice_info['techStatus']
ret_dict['state'] = self.bservice_info['status']
ret_dict['rg_id'] = self.bservice_info['rgId']
ret_dict['account_id'] = self.acc_id
ret_dict['groupsName'] = self.bservice_info['groupsName']
ret_dict['groupsIds'] = self.bservice_info['groups']
return ret_dict
@staticmethod
def build_parameters():
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','check']),
started=dict(type='bool', required=False, default=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
sshuser=dict(type='str', required=False,default=None),
sshkey=dict(type='str', required=False,default=None),
id=dict(type='int', required=False, default=0),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
description=dict(type='str', default="Created by decort ansible module"),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
def main():
module_parameters = decort_bservice.build_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
['rg_id','rg_name']
],
)
subj = decort_bservice(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.bservice_id:
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate B-service name '{}'. Other arguments are: B-service ID {}, "
"RG name '{}', RG ID {}, Account '{}'.").format(amodule.params['name'],
amodule.params['id'],
amodule.params['rg_name'],
amodule.params['rg_id'],
amodule.params['account_name'])
amodule.fail_json(**subj.result)
pass
#MAIN MANAGE PART
if subj.bservice_id:
if subj.bservice_info['status'] in ("DELETING","DESTROYNG","RECONFIGURING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.bservice_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
subj.restore(subj.bservice_id)
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.bservice_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'],amodule.params['started'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.bservice_info['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.bservice_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.bservice_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()

@ -0,0 +1,285 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Alexey Dankov (alexey.dankov@digitalenergy.online)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_group(DecortController):
def __init__(self,arg_amodule):
super(decort_group, self).__init__(arg_amodule)
self.group_should_exist = False
validated_bservice_id = None
#find and validate B-Service
validated_bservice_id, bservice_info = self.bservice_get_by_id(arg_amodule.params['bservice_id'])
if not validated_bservice_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find B-service ID {}.").format(arg_amodule.params['bservice_id'])
self.fail_json(**self.result)
#find group
self.bservice_id = validated_bservice_id
self.bservice_info = bservice_info
self.group_id,self.group_info = self.group_find(
bs_id=validated_bservice_id,
bs_info=bservice_info,
group_id=arg_amodule.params['id'],
group_name=arg_amodule.params['name'],
)
if self.group_id:
self.group_should_exist = True
return
def nop(self):
"""No operation (NOP) handler for B-service.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.group_id:
self.result['msg'] = ("No state change required for B-service ID {} because of its "
"current status '{}'.").format(self.group_id, self.group_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent B-service instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.group_id:
self.result['msg'] = ("Invalid target state '{}' requested for Group ID {} in the "
"current status '{}'.").format(self.group_id,
self.amodule.params['state'],
self.group_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent Group name '{}' "
"in B-service {}").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['bservice_id'],
)
return
def create(self):
if self.amodule.params['driver'] not in ["KVM_X86","KVM_PPC"]:
self.result['failed'] = True
self.result['msg'] = ("Unsupported driver '{}' is specified for "
"Group.").format(self.amodule.params['driver'])
self.amodule.fail_json(**self.result)
self.group_id=self.group_provision(
self.bservice_id,
self.amodule.params['name'],
self.amodule.params['count'],
self.amodule.params['cpu'],
self.amodule.params['ram'],
self.amodule.params['boot_disk'],
self.amodule.params['image_id'],
self.amodule.params['driver'],
self.amodule.params['role'],
self.amodule.params['networks'],
self.amodule.params['timeoutStart'],
)
if self.amodule.params['state'] in ('started','present'):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
return
def action(self):
#change desired state
if (
self.group_info['techStatus'] == 'STARTED' and self.amodule.params['state'] == 'stopped') or (
self.group_info['techStatus'] == 'STOPPED' and self.amodule.params['state'] in ('started','present')
):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
self.group_resize_count(self.bservice_id,self.group_info,self.amodule.params['count'])
self.group_update_hw(
self.bservice_id,
self.group_info,
self.amodule.params['cpu'],
self.amodule.params['boot_disk'],
self.amodule.params['name'],
self.amodule.params['role'],
self.amodule.params['ram'],
)
self.group_update_net(
self.bservice_id,
self.group_info,
self.amodule.params['networks']
)
return
def destroy(self):
self.group_delete(
self.bservice_id,
self.group_id
)
return
def package_facts(self,check_mode=False):
ret_dict = dict(
name="",
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
if self.result['changed'] == True:
self.group_id,self.group_info = self.group_find(
self.bservice_id,
self.bservice_info,
self.group_id
)
ret_dict['account_id'] = self.group_info['accountId']
ret_dict['rg_id'] = self.group_info['rgId']
ret_dict['id'] = self.group_info['id']
ret_dict['name'] = self.group_info['name']
ret_dict['techStatus'] = self.group_info['techStatus']
ret_dict['state'] = self.group_info['status']
ret_dict['Computes'] = self.group_info['computes']
return ret_dict
@staticmethod
def build_parameters():
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'started', 'stopped', 'present','check']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
image_id=dict(type='int', required=False),
image_name=dict(type='str', required=False),
driver=dict(type='str', required=False,default="KVM_X86"),
boot_disk=dict(type='int', required=False),
bservice_id=dict(type='int', required=True),
count=dict(type='int', required=True),
timeoutStart=dict(type='int', required=False),
role=dict(type='str', required=False),
cpu=dict(type='int', required=False),
ram=dict(type='int', required=False),
networks=dict(type='list', default=[], required=False),
description=dict(type='str', default="Created by decort ansible module"),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
def main():
module_parameters = decort_group.build_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
],
)
subj = decort_group(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.group_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate Group name '{}'. "
"B-service ID {}").format(amodule.params['name'],
amodule.params['bservice_id'],)
amodule.fail_json(**subj.result)
if subj.group_id:
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED",
"DISABLED","DESTROYED"):
subj.error()
elif subj.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started','stopped'):
subj.create()
elif subj.group_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started','stopped'):
subj.create()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.group_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()

@ -0,0 +1,336 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Aleksandr Malyavin (aleksandr.malyavin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_k8s(DecortController):
def __init__(self,arg_amodule):
super(decort_k8s, self).__init__(arg_amodule)
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_k8ci_id = 0
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot manage k8s cluster when its ID is 0 and name is empty."
self.fail_json(**self.result)
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
if not validated_rg_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
# fail the module - exit
#validate k8ci ID
validated_k8ci_id = self.k8s_k8ci_find(arg_amodule.params['k8ci_id'])
if not validated_k8ci_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find K8CI ID {}.".format(arg_amodule.params['k8ci_id'])
self.fail_json(**self.result)
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
arg_amodule.params['k8ci_id'] = validated_k8ci_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
if self.k8s_id:
self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId']
# check workers and groups for add or remove?
return
def package_facts(self,check_mode=False):
ret_dict = dict(
name="",
state="CHECK_MODE",
account_id=0,
rg_id=0,
config=None,
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
#if self.k8s_facts is None:
# #if void facts provided - change state value to ABSENT and return
# ret_dict['state'] = "ABSENT"
# return ret_dict
ret_dict['id'] = self.k8s_info['id']
ret_dict['name'] = self.k8s_info['name']
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.rg_id
ret_dict['account_id'] = self.acc_id
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
return ret_dict
def nop(self):
"""No operation (NOP) handler for Compute management by decort_kvmvm module.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.k8s_id:
self.result['msg'] = ("No state change required for K8s ID {} because of its "
"current status '{}'.").format(self.k8s_id, self.k8s_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent K8s instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.k8s_id:
self.result['msg'] = ("Invalid target state '{}' requested for K8s cluster ID {} in the "
"current status '{}'.").format(self.k8s_id,
self.amodule.params['state'],
self.k8s_info['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent K8s Cluster name '{}' "
"in RG ID {} / name '{}'").format(self.amodule.params['state'],
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['rg_name'])
return
def create(self):
self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'],
self.amodule.params['master_count'],
self.amodule.params['master_cpu'],
self.amodule.params['master_ram_mb'],
self.amodule.params['master_disk_gb'],
self.amodule.params['workers'][0],
self.amodule.params['extnet_id'],
self.amodule.params['with_lb'],
self.amodule.params['description'],)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=self.amodule.params['id'],
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
if self.k8s_id:
self.k8s_should_exist = True
if self.k8s_id and self.amodule.params['workers'][1]:
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
return
def destroy(self):
self.k8s_delete(self.k8s_id)
self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False
return
def action(self,disared_state,started=True):
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=self.amodule.params['id'],
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
if started == True and self.k8s_info['techStatus'] == "STOPPED":
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_info['techStatus'] == "STARTED"
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
return
@staticmethod
def build_parameters():
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
quotas=dict(type='dict', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','check']),
permanent=dict(type='bool', default=False),
started=dict(type='bool', default=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
getConfig=dict(type='bool',required=False, default=False),
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
k8ci_id=dict(type='int', required=True),
wg_name=dict(type='str', required=False),
master_count=dict(type='int', default=1),
master_cpu=dict(type='int', default=2),
master_ram_mb=dict(type='int', default=2048),
master_disk_gb=dict(type='int', default=10),
worker_count=dict(type='int', default=1),
worker_cpu=dict(type='int', default=1),
worker_ram_mb=dict(type='int', default=1024),
worker_disk_gb=dict(type='int', default=10),
workers=dict(type='list'),
extnet_id=dict(type='int', default=0),
description=dict(type='str', default="Created by decort ansible module"),
with_lb=dict(type='bool', default=True),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
def main():
module_parameters = decort_k8s.build_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
mutually_exclusive=[
['oauth2', 'password'],
['password', 'jwt'],
['jwt', 'oauth2'],
],
required_together=[
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['id', 'name'],
['rg_id','rg_name']
],
)
subj = decort_k8s(amodule)
if amodule.params['state'] == 'check':
subj.result['changed'] = False
if subj.k8s_id:
# cluster is found - package facts and report success to Ansible
subj.result['failed'] = False
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
# we exit the module at this point
else:
subj.result['failed'] = True
subj.result['msg'] = ("Cannot locate K8s cluster name '{}'. "
"RG ID {}").format(amodule.params['name'],
amodule.params['rg_id'],)
amodule.fail_json(**subj.result)
if subj.k8s_id:
if subj.k8s_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.k8s_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
subj.k8s_restore(subj.k8s_id)
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.k8s_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.k8s_info['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.k8s_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
if subj.k8s_should_exist:
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()

@ -192,6 +192,9 @@ options:
- If I(ssh_key) is not specified, this parameter is ignored and a warning is generated. - If I(ssh_key) is not specified, this parameter is ignored and a warning is generated.
- This parameter is valid at VM creation time only and ignored for any operation on existing VMs. - This parameter is valid at VM creation time only and ignored for any operation on existing VMs.
required: no required: no
user_data:
description:
- Cloud-init User-Data, exept ssh module
state: state:
description: description:
- Specify the desired state of the virtual machine at the exit of the module. - Specify the desired state of the virtual machine at the exit of the module.
@ -548,15 +551,18 @@ class decort_kvmvm(DecortController):
if self.amodule.params['state'] in ('halted', 'poweredoff'): if self.amodule.params['state'] in ('halted', 'poweredoff'):
start_compute = False start_compute = False
if self.amodule.params['ssh_key'] and self.amodule.params['ssh_key_user']: if self.amodule.params['ssh_key'] and self.amodule.params['ssh_key_user'] and not self.amodule.params['ci_user_data']:
cloud_init_params = {'users': [ cloud_init_params = {'users': [
{"name": self.amodule.params['ssh_key_user'], {"name": self.amodule.params['ssh_key_user'],
"ssh-authorized-keys": [self.amodule.params['ssh_key']], "ssh-authorized-keys": [self.amodule.params['ssh_key']],
"shell": '/bin/bash'} "shell": '/bin/bash'}
]} ]}
elif self.amodule.params['ci_user_data']:
cloud_init_params = {}
for ci_param in self.amodule.params['ci_user_data']:
cloud_init_params.update(ci_param)
else: else:
cloud_init_params = None cloud_init_params = None
# if we get through here, all parameters required to create new Compute instance should be at hand # if we get through here, all parameters required to create new Compute instance should be at hand
# NOTE: KVM VM is created in HALTED state and must be explicitly started # NOTE: KVM VM is created in HALTED state and must be explicitly started
@ -595,6 +601,11 @@ class decort_kvmvm(DecortController):
# Next manage data disks # Next manage data disks
self.compute_data_disks(self.comp_info, self.amodule.params['data_disks']) self.compute_data_disks(self.comp_info, self.amodule.params['data_disks'])
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'],)
# NOTE: see NOTE above regarding libvirt "feature" and new VMs created in HALTED state # NOTE: see NOTE above regarding libvirt "feature" and new VMs created in HALTED state
if self.amodule.params['state'] not in ('halted', 'poweredoff'): if self.amodule.params['state'] not in ('halted', 'poweredoff'):
self.compute_powerstate(self.comp_info, 'started') self.compute_powerstate(self.comp_info, 'started')
@ -641,6 +652,11 @@ class decort_kvmvm(DecortController):
self.compute_resize(self.comp_info, self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'], self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles) wait_for_state_change=arg_wait_cycles)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'],)
return return
def package_facts(self, check_mode=False): def package_facts(self, check_mode=False):
@ -774,6 +790,11 @@ class decort_kvmvm(DecortController):
rg_name=dict(type='str', default=""), rg_name=dict(type='str', default=""),
ssh_key=dict(type='str', required=False), ssh_key=dict(type='str', required=False),
ssh_key_user=dict(type='str', required=False), ssh_key_user=dict(type='str', required=False),
tag=dict(type='list', required=False),
affinity_label=dict(type='str', required=False),
aff_rule=dict(type='list', required=False),
aaff_rule=dict(type='list', required=False),
ci_user_data=dict(type='list',elements='dict', required=False),
state=dict(type='str', state=dict(type='str',
default='present', default='present',
choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']), choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']),

@ -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 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_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. 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" version_added: "2.2"
author: author:
- Sergey Shubin <sergey.shubin@digitalenergy.online> - Sergey Shubin <sergey.shubin@digitalenergy.online>
@ -68,8 +67,8 @@ options:
image_name: image_name:
description: description:
- Name of the OS image to use. Module will return the ID of this image. - 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 - 'The specified image name will be looked up in the target DECORT controller and error will be generated
no matching image is found.' - if no matching image is found.'
required: yes required: yes
jwt: jwt:
description: description:
@ -109,10 +108,6 @@ options:
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.' - '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. - If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no required: no
vdc_id:
description:
- ID of the VDC to limit the search of the OS image to.
required: no
verify_ssl: verify_ssl:
description: description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you - 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
@ -134,19 +129,143 @@ options:
- 'This context data is expected to uniquely identify the task carried out by this module invocation so - '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.' that up-level orchestrator could match returned information to the its internal entities.'
required: no required: no
account_name:
description:
- 'Account name. Used to get a unique integer account ID.'
required: no
virt_id:
description:
- 'A unique integer identifier for the virtual image.'
- 'Can be used to obtain information about a virtual image, as well as to create a virtual image and
- bind another operating system image to it.'
required: no
virt_name:
description:
- 'Name of the virtual image. Used to get the `virt_id`, and later information about the virtual image,
- as well as to create a virtual image and bind another operating system image to it.'
required: no
state:
description:
- 'The state of the images. If set to present, operating system images will be created to which
- the account specified in `account_Id` or `account_name` is bound. If set to absent, they will be removed.
required: no
drivers:
description:
- 'A list of compute types (eg virtual servers) that are appropriate for the operating system image.
- Note: `KVM_X86`. Used when creating an operating system image.'
required: no
architecture:
description:
- 'Binary architecture of the image. Note. `X86_64` or `PPC64_LE`. Used when creating
-an operating system image.'
required: no
imagetype:
description:
- 'Image type. `linux`, `windows` or `other`. The default is `linux`. Used when creating
- an operating system image.'
required: no
boottype:
description:
- 'Image upload type. `bios` or `uefi`. The default is `uefi`. Used when creating an operating
-system image.'
required: no
url:
description:
- 'Uniform resource locator (URL) pointing to the iso image of the operating system. Used when
-creating an operating system image.'
required: no
sepId:
description:
- 'The unique integer ID of the storage provider endpoint. Specified in pair with `poolName`.
- Used when creating an operating system image.'
required: no
poolName:
description:
- 'The pool in which the image will be created. Specified in pair with `sepId`. Used when creating
- an operating system image.'
required: no
hotresize:
description:
- 'Whether the image supports "hot" resizing. The default is `false`. Used when creating an operating
- system image.'
required: no
image_username:
description:
- 'An optional username for the image. Used when creating an operating system image.'
required: no
image_password:
description:
- 'An optional password for the image. Used when creating an operating system image. Used when creating
- an operating system image.'
required: no
usernameDL:
description:
- 'The username for loading the binary media. Used in conjunction with `passwordDL`. Used when creating
- an operating system image'
required: no
passwordDL:
description:
- 'The password for loading the binary media. Used in conjunction with `usernameDL`. Used when creating
- an operating system image.'
required: no
permanently:
description:
- 'Whether to permanently delete the image. Used when deleting an image. The default is false.'
required: no
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: locate OS image specified by its name, store result in image_to_use variable. - name: create_osimage
decort_osimage: decort_osimage:
authenticator: oauth2 authenticator: oauth2
app_id: "{{ MY_APP_ID }}" verify_ssl: False
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://ds1.digitalenergy.online" controller_url: "https://ds1.digitalenergy.online"
image_name: "Ubuntu 18.04 v1.2.5" state: present
account_name: "GreyseDevelopment" 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 delegate_to: localhost
register: image_to_use register: osimage
- name: get_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
account_Id: 12345
delegate_to: localhost
register: osimage
- name: create_virtual_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
virt_name: "alpine_last"
delegate_to: localhost
register: osimage
- name: rename_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0v2.0"
image_id: 54321
delegate_to: localhost
register: osimage
''' '''
RETURN = ''' RETURN = '''
@ -157,6 +276,7 @@ facts:
sample: sample:
facts: facts:
id: 100 id: 100
linkto: 80
name: "Ubuntu 16.04 v1.0" name: "Ubuntu 16.04 v1.0"
size: 3 size: 3
sep_id: 1 sep_id: 1
@ -171,6 +291,104 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import * 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): 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 """Package a dictionary of OS image according to the decort_osimage module specification. This
@ -186,8 +404,7 @@ def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False):
name="none", name="none",
size=0, size=0,
type="none", type="none",
state="CHECK_MODE", state="CHECK_MODE", )
)
if arg_check_mode: if arg_check_mode:
# in check mode return immediately with the default values # in check mode return immediately with the default values
@ -206,9 +423,10 @@ def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False):
ret_dict['sep_id'] = arg_osimage_facts['sepId'] ret_dict['sep_id'] = arg_osimage_facts['sepId']
ret_dict['pool'] = arg_osimage_facts['pool'] ret_dict['pool'] = arg_osimage_facts['pool']
ret_dict['state'] = arg_osimage_facts['status'] ret_dict['state'] = arg_osimage_facts['status']
ret_dict['linkto'] = arg_osimage_facts['linkTo']
return ret_dict return ret_dict
def decort_osimage_parameters(): def decort_osimage_parameters():
"""Build and return a dictionary of parameters expected by decort_osimage module in a form accepted """Build and return a dictionary of parameters expected by decort_osimage module in a form accepted
by AnsibleModule utility class.""" by AnsibleModule utility class."""
@ -225,7 +443,6 @@ def decort_osimage_parameters():
required=True, required=True,
choices=['legacy', 'oauth2', 'jwt']), choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True), controller_url=dict(type='str', required=True),
image_name=dict(type='str', required=True),
jwt=dict(type='str', jwt=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_JWT']), fallback=(env_fallback, ['DECORT_JWT']),
@ -239,26 +456,40 @@ def decort_osimage_parameters():
no_log=True), no_log=True),
pool=dict(type='str', required=False, default=""), pool=dict(type='str', required=False, default=""),
sep_id=dict(type='int', required=False, default=0), sep_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=True), account_name=dict(type='str', required=False),
account_Id=dict(type='int', required=False),
user=dict(type='str', user=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_USER'])), fallback=(env_fallback, ['DECORT_USER'])),
vdc_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True), verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False), workflow_callback=dict(type='str', required=False),
workflow_context=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),
) )
# 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(): def main():
module_parameters = decort_osimage_parameters() module_parameters = decort_osimage.decort_osimage_parameters()
amodule = AnsibleModule(argument_spec=module_parameters, amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True, supports_check_mode=True,
@ -273,28 +504,65 @@ def main():
], ],
) )
decon = DecortController(amodule) decon = decort_osimage(amodule)
# we need account ID to locate OS images - find the account by the specified name and get its ID if amodule.params['image_name'] or amodule.params['image_id']:
validated_account_id, _ = decon.account_find(amodule.params['account_name']) image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
if validated_account_id == 0: decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
# we failed either to find or access the specified account - fail the module if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0:
decon.result['failed'] = True decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
decon.result['changed'] = False
decon.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name']) 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) 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, if decon.validated_image_id:
sepid=amodule.params['sep_id'], if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.validated_image_id:
pool=amodule.params['pool']) decort_osimage.decort_virt_image_link(decon,amodule)
decon.result['changed'] = True
amodule.exit_json(**decon.result)
if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent":
decon.result['msg'] = ("Osimage module cannot delete virtual images.")
decon.result['failed'] = True
amodule.exit_json(**decon.result)
if decon.result['failed'] == True: if decon.result['failed'] == True:
# we failed to find the specified image - fail the module # we failed to find the specified image - fail the module
decon.result['changed'] = False decon.result['changed'] = False
amodule.fail_json(**decon.result) 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) amodule.exit_json(**decon.result)

@ -242,6 +242,7 @@ facts:
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
import paramiko
from ansible.module_utils.decort_utils import * from ansible.module_utils.decort_utils import *
@ -290,9 +291,9 @@ def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False):
# ??? -> ext_net_id # ??? -> ext_net_id
# tech_status -> techStatus # tech_status -> techStatus
return ret_dict return ret_dict
def decort_vins_parameters(): def decort_vins_parameters():
"""Build and return a dictionary of parameters expected by decort_vins module in a form accepted """Build and return a dictionary of parameters expected by decort_vins module in a form accepted
by AnsibleModule utility class.""" by AnsibleModule utility class."""
@ -316,6 +317,10 @@ def decort_vins_parameters():
ext_net_id=dict(type='int', required=False, default=-1), ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''), ext_ip_addr=dict(type='str', required=False, default=''),
ipcidr=dict(type='str', required=False, default=''), ipcidr=dict(type='str', required=False, default=''),
mgmtaddr=dict(type='str',required=False, default=''),
custom_config=dict(type='bool',required=False, default=False),
config_save=dict(type='bool',required=False, default=False),
connect_to=dict(type='list', default=[], required=False),
jwt=dict(type='str', jwt=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_JWT']), fallback=(env_fallback, ['DECORT_JWT']),
@ -342,6 +347,7 @@ def decort_vins_parameters():
workflow_context=dict(type='str', required=False), workflow_context=dict(type='str', required=False),
) )
# Workflow digest: # Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController # 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the ViNS with this id or name exists under specified account / resource group # 2) check if the ViNS with this id or name exists under specified account / resource group
@ -386,6 +392,7 @@ def main():
vins_level = "ID" vins_level = "ID"
validated_acc_id = vins_facts['accountId'] validated_acc_id = vins_facts['accountId']
validated_rg_id = vins_facts['rgId'] validated_rg_id = vins_facts['rgId']
elif amodule.params['rg_id']: elif amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID # expect ViNS @ RG level in the RG with specified ID
vins_level = "RG" vins_level = "RG"
@ -442,7 +449,6 @@ def main():
# rg_name without account specified # rg_name without account specified
decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
decon.fail_json(**decon.result) decon.fail_json(**decon.result)
# #
# Initial validation of module arguments is complete # Initial validation of module arguments is complete
# #
@ -456,7 +462,7 @@ def main():
# #
# "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING" # "MODELED", "CREATED", "ENABLED", "ENABLING", "DISABLED", "DISABLING", "DELETED", "DELETING", "DESTROYED", "DESTROYING"
# #
# if cconfig_save is true, only config save without other updates
vins_should_exist = False vins_should_exist = False
if vins_id: if vins_id:
@ -490,7 +496,17 @@ def main():
elif amodule.params['state'] in ('present', 'enabled'): elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS # update ViNS
decon.vins_update(vins_facts, decon.vins_update(vins_facts,
amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
)
decon.vins_update_mgmt(
vins_facts,
amodule.params['mgmtaddr'],
)
decon.vins_update_ifaces(
vins_facts,
amodule.params['connect_to'],
)
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
# disable and update ViNS # disable and update ViNS
decon.vins_state(vins_facts, 'disabled') decon.vins_state(vins_facts, 'disabled')
@ -570,7 +586,6 @@ def main():
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent " decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"ViNS name '{}'").format(amodule.params['state'], "ViNS name '{}'").format(amodule.params['state'],
amodule.params['vins_name']) amodule.params['vins_name'])
# #
# conditional switch end - complete module run # conditional switch end - complete module run
# #
@ -586,6 +601,10 @@ def main():
# be returned. # be returned.
_, vins_facts = decon.vins_find(vins_id) _, vins_facts = decon.vins_find(vins_id)
decon.result['facts'] = decort_vins_package_facts(vins_facts, amodule.check_mode) decon.result['facts'] = decort_vins_package_facts(vins_facts, amodule.check_mode)
# add password to facts if mgmtaddr is present
# need reworking
if amodule.params['mgmtaddr'] != "":
decon.result['facts'].update({'password': vins_facts['VNFDev']['config']['mgmt']['password']})
amodule.exit_json(**decon.result) amodule.exit_json(**decon.result)

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save