5 Commits

42 changed files with 1144 additions and 2405 deletions

View File

@@ -9,4 +9,4 @@ Requirements:
* PyJWT 2.0.0 Python module or higher * 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.8.6 or higher * DECORT cloud platform version 3.5.0 or higher

View File

View File

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

View File

@@ -1,22 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
name: "example_disk"
sep_id: 1
pool: 0
gid: 0
size: 2
type: "D"
description: "Disk created by decort_disk module"
iops: 2000
state: present
verify_ssl: false
delegate_to: localhost

View File

@@ -1,18 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
name: "example_disk"
permanently: False
force_detach: True
reason: "Just to test module decort_disk"
state: absent
verify_ssl: false
delegate_to: localhost

View File

@@ -1,28 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
id: 111
limitIO:
read_bytes_sec: 100
read_bytes_sec_max: 100
read_iops_sec: 100
read_iops_sec_max: 100
size_iops_sec: 100
write_bytes_sec: 100
write_bytes_sec_max: 100
write_iops_sec: 100
write_iops_sec_max: 100
total_bytes_sec: 0
total_iops_sec: 0
total_bytes_sec_max: 0
total_iops_sec_max: 0
verify_ssl: false
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
id: 111
name: "example_disk2"
verify_ssl: false
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
id: 111
state: present
verify_ssl: false
delegate_to: localhost

View File

@@ -1,20 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
quotas:
cpu: 8
ram: 4096
disk: 20
ext_ips: 10
net_transfer: 200
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,21 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
resType:
- vins
- compute
- k8s
- openshift
- lb
- flipgroup
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,30 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
account_id: 99
owner: "user_1" #Leave blank to set current user as owner.
quotas:
cpu: 8
ram: 4096
disk: 20
ext_ips: 10
net_transfer: 200
access:
action: "grant"
user: "user_2"
right: "RCX"
def_netType: "PRIVATE"
ipcidr: "" "192.168.1.1"
extNetId: 0
extNetIp: "" "10.100.1.10"
resType:
- vins
- compute
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "test_rg"
# or
#rg_id: 999
account_id: 99
state: present
permanently: True
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,12 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_id: 999 # rg can be restored only by rg id
account_id: 99
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,14 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
state: enabled
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,18 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
access:
action: "grant"
user: "new_user"
right: "R"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "old_rg_name"
# or
#rg_id: 1737
account_id: 99
rename: "new_rg_name"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,17 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
access:
action: "revoke"
user: "old_user"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,16 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
def_netType: "PRIVATE"
def_netId: 199
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

2
examples/inventory Normal file
View File

@@ -0,0 +1,2 @@
[all]
ansible_master ansible_host=<ansible host IP address> ansible_port=<SSH port on ansible host> ansible_user=root

18
examples/jwt.yaml Normal file
View File

@@ -0,0 +1,18 @@
#
# More details on how to use DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
- hosts: ansible_master
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "{{ decort_sso }}" # "https://sso.digitalenergy.online"
validity: 1200
register: my_jwt
delegate_to: localhost
- name: print out JWT
debug:
var: my_jwt.jwt
delegate_to: localhost

323
examples/main.yaml Normal file
View File

@@ -0,0 +1,323 @@
#
# More details on how to use DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
- hosts: ansible_master
vars_files:
- vars.yaml
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "{{ decort_sso }}"
validity: 1200
register: token
delegate_to: localhost
- name: obtain OS image
decort_osimage:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
image_name: "{{ os_image_name }}"
account_name: "{{ target_account_name }}"
verify_ssl: false
register: my_image
delegate_to: localhost
- name: print out the result
debug:
var: my_image.facts
delegate_to: localhost
- name: manage RG
decort_rg:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
account_id: 32
rg_name: "{{ target_rg_name }}"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost
- name: print out the result
debug:
var: my_rg.facts
delegate_to: localhost
- name: manage ViNS 01
decort_vins:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
vins_name: "{{ vins01_name }}"
rg_id: "{{ my_rg.facts.id }}"
ext_net_id: "{{ target_ext_net_id }}"
state: present
verify_ssl: false
register: my_vins01
delegate_to: localhost
- name: print out the result
debug:
var: my_vins01.facts
delegate_to: localhost
- name: manage ViNS 02
decort_vins:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
vins_name: "{{ vins02_name }}"
rg_id: "{{ my_rg.facts.id }}"
ext_net_id: -1
state: present
verify_ssl: false
register: my_vins02
delegate_to: localhost
- name: print out the result
debug:
var: my_vins02.facts
delegate_to: localhost
- name: manage data disk 01
decort_disk:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
name: "{{ datadisk01_name }}"
size: "{{ datadisk01_size }}"
account_name: "{{ target_account_name }}"
pool: data01
place_with: "{{ my_image.facts.id }}"
state: present
verify_ssl: false
register: my_disk01
delegate_to: localhost
- name: print out the result
debug:
var: my_disk01.facts
delegate_to: localhost
- name: manage data disk 02
decort_disk:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
name: "{{ datadisk02_name }}"
size: "{{ datadisk02_size }}"
account_name: "{{ target_account_name }}"
pool: data01
place_with: "{{ my_image.facts.id }}"
state: present
verify_ssl: false
register: my_disk02
delegate_to: localhost
- name: print out the result
debug:
var: my_disk02.facts
delegate_to: localhost
- name: manage KVM X86 VM
decort_kvmvm:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
name: "{{ vm01_name }}"
arch: KVM_X86
ram: "{{ vm01_ram }}"
cpu: "{{ vm01_cpu }}"
image_id: "{{ my_image.facts.id }}"
boot_disk: "{{ vm01_boot_disk }}"
data_disks:
- "{{ my_disk01.facts.id }}"
- "{{ my_disk02.facts.id }}"
networks:
- type: VINS
id: "{{ my_vins01.facts.id }}"
- type: VINS
id: "{{ my_vins02.facts.id }}"
- type: EXTNET
id: "{{ target_ext_net_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: present
verify_ssl: false
register: my_kvmvm
delegate_to: localhost
- name: print out the result
debug:
var: my_kvmvm.facts
delegate_to: localhost
- name: manage PFW rules on Compute
decort_pfw:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
compute_id: "{{ my_kvmvm.facts.id }}"
vins_id: "{{ my_vins01.facts.id }}"
rules:
- public_port_start: 30022
local_port: 22
proto: tcp
- public_port_start: 30080
public_port_end: 30085
local_port: 30080
proto: tcp
state: present
verify_ssl: false
register: my_pfw
delegate_to: localhost
- name: print out the result
debug:
var: my_pfw.facts
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

21
examples/prepenv.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# 1. As this file will contain sensitive data (application ID & Secret pair) put this file
# in a directory, where only you will have access to it, e.g. your ~/.ssh directory.
# 2. Make sure this file is not readable by anybody else (chmod 400).
# 3. Paste your Application ID (obtained from DECORT SSO application) to DECORT_APP_ID.
# 4. Paste your Application Secret (obtained from DECORT SSO application) to DECORT_APP_SECRET.
# 5. Paste DECORT SSO application URL to DECORT_OAUTH2_URL.
#
# Source this file into shell to prepare environment for running DECORT Ansible module, e.g.
# . ~/.ssh/prepenv.sh
#
# More informaiton on DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
export DECORT_APP_ID="put your application ID here"
export DECORT_APP_SECRET="put your application secret here"
export DECORT_OAUTH2_URL="put DECORT SSO URL here" # "https://sso.digitalenergy.online"
export ANSIBLE_HOST_KEY_CHEKCING=False

26
examples/vars.yaml Normal file
View File

@@ -0,0 +1,26 @@
#
# More details on how to use DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
decort_sso: "put DECORT SSO application URL here" # "https://sso.digitalenergy.online"
decort_ctrl: "put DECORT controller URL here" # "https://ds1.digitalenergy.online"
target_account_name: "your account name"
target_rg_name: "target resource group name"
os_image_name: "OS image name"
vins01_name: "Vins01-ansible"
vins02_name: "Vins02-ansible"
target_ext_net_id: 0
datadisk01_name: "Data01-ansible"
datadisk01_size: 5
datadisk02_name: "Data02-ansible"
datadisk02_size: 5
vm01_name: "Vm01-ansible"
vm01_cpu: 1
vm01_ram: 1024
vm01_boot_disk: 10

View File

@@ -157,16 +157,6 @@ options:
- `If specified for an existing disk, and it is greater than current disk size, platform will try to resize - `If specified for an existing disk, and it is greater than current disk size, platform will try to resize
the disk on the fly. Downsizing disk is not allowed.` the disk on the fly. Downsizing disk is not allowed.`
required: no required: no
limitIO:
description:
- Disk input / output limit, used to limit the speed of interaction with the disk.
required: no
type:
description:
- Type of the disk.
- `Disks can be of the following types: "D"-Data, "B"-Boot, "T"-Tmp.`
default: "D"
required: no
state: state:
description: description:
- Specify the desired state of the disk at the exit of the module. - Specify the desired state of the disk at the exit of the module.
@@ -244,135 +234,21 @@ facts:
gid: 1001 gid: 1001
''' '''
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
from ansible.module_utils.decort_utils import * from ansible.module_utils.decort_utils import *
class decort_disk(DecortController):
def __init__(self,arg_amodule):
super(decort_disk, self).__init__(arg_amodule)
validated_acc_id = 0 def decort_disk_package_facts(disk_facts, check_mode=False):
validated_acc_info = None """Package a dictionary of disk facts according to the decort_disk module specification.
validated_disk_id = 0 This dictionary will be returned to the upstream Ansible engine at the completion of
self.disk_id = 0 the module run.
self.account_id = 0
validated_disk_facts = None
# limitIO check for exclusive parameters
if arg_amodule.params['limitIO']: @param (dict) disk_facts: dictionary with Disk facts as returned by API call to .../disks/get
self.disk_check_iotune_arg(arg_amodule.params['limitIO']) @param (bool) check_mode: boolean that tells if this Ansible module is run in check mode
"""
if arg_amodule.params['id'] or arg_amodule.params['name']:
if arg_amodule.params['account_id'] or arg_amodule.params['account_name'] :
validated_acc_id,validated_acc_info = self.account_find(arg_amodule.params['account_name'],arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
else:
self.acc_id = validated_acc_id
self.acc_info = validated_acc_info
validated_disk_id,validated_disk_facts = self.disk_find(
disk_id=arg_amodule.params['id'],
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id,
check_state=False,
)
else:
self.result['failed'] = True
self.result['msg'] = ("Cannot manage Disk when its ID is 0 and name is empty")
self.fail_json(**self.result)
if arg_amodule.params['place_with']:
image_id, image_facts = self.image_find(arg_amodule.params['place_with'], "", 0)
arg_amodule.params['sep_id'] = image_facts['sepId']
self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts
def create(self):
self.disk_id = self.disk_create(accountId=self.acc_id,
name = self.amodule.params['name'],
description=self.amodule.params['annotation'],
size=self.amodule.params['size'],
type=self.amodule.params['type'],
iops=self.amodule.params['iops'],
sep_id=self.amodule.params['sep_id'],
pool=self.amodule.params['pool'],
)
#IO tune
if self.amodule.params['limitIO']:
self.disk_limitIO(self.amodule.params['limitIO'],self.disk_id)
#set share status
if self.amodule.params['shareable'] and self.amodule.params['type'] == "D":
self.dick_share(self.disk_id,self.amodule.params['shareable'])
return
def action(self,restore=False):
#restore never be done
if restore:
self.disk_restore(self.disk_id)
#rename if id present
if self.amodule.params['name'] != self.disk_info['name']:
self.disk_rename(diskId=self.disk_id,
name=self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
#resize
if self.amodule.params['size'] != self.disk_info['sizeMax']:
self.disk_resize(self.disk_info,self.amodule.params['size'])
#IO TUNE
if self.amodule.params['limitIO']:
clean_io = [param for param in self.amodule.params['limitIO'] \
if self.amodule.params['limitIO'][param] == None]
for key in clean_io: del self.amodule.params['limitIO'][key]
if self.amodule.params['limitIO'] != self.disk_info['iotune']:
self.disk_limitIO(self.disk_id,self.amodule.params['limitIO'])
#share check/update
#raise Exception(self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable'] and \
self.amodule.params['type'] == "D":
self.disk_share(self.disk_id,self.amodule.params['shareable'])
return
def delete(self):
self.disk_id = self.disk_delete(disk_id=self.disk_id,
detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'],
reason=self.amodule.params['reason'])
self.disk_info['status'] = "DELETED"
return
def rename(self):
self.disk_rename(diskId = self.disk_id,
name = self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
return
def nop(self):
self.result['failed'] = False
self.result['changed'] = False
if self.disk_id:
self.result['msg'] = ("No state change required for Disk ID {} because of its "
"current status '{}'.").format(self.disk_id, self.disk_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Disk.").format(self.amodule.params['state'])
return
def package_facts(self, check_mode=False):
ret_dict = dict(id=0, ret_dict = dict(id=0,
name="none", name="none",
state="CHECK_MODE", state="CHECK_MODE",
@@ -384,27 +260,28 @@ class decort_disk(DecortController):
gid=0 gid=0
) )
if check_mode or self.disk_info is None: if check_mode:
# in check mode return immediately with the default values
return ret_dict return ret_dict
# remove io param with zero value if disk_facts is None:
clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0] # if void facts provided - change state value to ABSENT and return
for key in clean_io: del self.disk_info['iotune'][key] ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = self.disk_info['id'] ret_dict['id'] = disk_facts['id']
ret_dict['name'] = self.disk_info['name'] ret_dict['name'] = disk_facts['name']
ret_dict['size'] = self.disk_info['sizeMax'] ret_dict['size'] = disk_facts['sizeMax']
ret_dict['state'] = self.disk_info['status'] ret_dict['state'] = disk_facts['status']
ret_dict['account_id'] = self.disk_info['accountId'] ret_dict['account_id'] = disk_facts['accountId']
ret_dict['sep_id'] = self.disk_info['sepId'] ret_dict['sep_id'] = disk_facts['sepId']
ret_dict['pool'] = self.disk_info['pool'] ret_dict['pool'] = disk_facts['pool']
ret_dict['attached_to'] = self.disk_info['vmid'] ret_dict['attached_to'] = disk_facts['vmid']
ret_dict['gid'] = self.disk_info['gid'] ret_dict['gid'] = disk_facts['gid']
ret_dict['iotune'] = self.disk_info['iotune']
return ret_dict return ret_dict
@staticmethod
def build_parameters(): def decort_disk_parameters():
"""Build and return a dictionary of parameters expected by decort_disk module in a form accepted """Build and return a dictionary of parameters expected by decort_disk module in a form accepted
by AnsibleModule utility class.""" by AnsibleModule utility class."""
@@ -437,33 +314,10 @@ class decort_disk(DecortController):
required=False, required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']), fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True), no_log=True),
place_with=dict(type='int', default=0), place_with=dict(type='int', required=False, default=0),
pool=dict(type='str', default=''), pool=dict(type='str', required=False, default=''),
sep_id=dict(type='int', default=0), sep_id=dict(type='int', required=False, default=0),
size=dict(type='int', default=0), size=dict(type='int', required=False),
type=dict(type='str',
required=False,
default="D",
choices=['B', 'D', 'T']),
iops=dict(type='int',required=False,default=2000),
limitIO=dict(type='dict',
options=dict(
total_bytes_sec=dict(required=False,type='int'),
read_bytes_sec=dict(required=False,type='int'),
write_bytes_sec=dict(required=False,type='int'),
total_iops_sec=dict(required=False,type='int'),
read_iops_sec=dict(required=False,type='int'),
write_iops_sec=dict(required=False,type='int'),
total_bytes_sec_max=dict(required=False,type='int'),
read_bytes_sec_max=dict(required=False,type='int'),
write_bytes_sec_max=dict(required=False,type='int'),
total_iops_sec_max=dict(required=False,type='int'),
read_iops_sec_max=dict(required=False,type='int'),
write_iops_sec_max=dict(required=False,type='int'),
size_iops_sec=dict(required=False,type='int'),)),
permanently=dict(type='bool', required=False, default=False),
shareable=dict(type='bool', required=False, default=False),
reason=dict(type='str', required=False,default='Managed by Ansible decort_disk'),
state=dict(type='str', state=dict(type='str',
default='present', default='present',
choices=['absent', 'present']), choices=['absent', 'present']),
@@ -476,7 +330,7 @@ class decort_disk(DecortController):
) )
def main(): def main():
module_parameters = decort_disk.build_parameters() module_parameters = decort_disk_parameters()
amodule = AnsibleModule(argument_spec=module_parameters, amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True, supports_check_mode=True,
@@ -491,52 +345,197 @@ def main():
], ],
) )
decon = decort_disk(amodule) decon = DecortController(amodule)
disk_id = 0
disk_facts = None # will hold Disk facts
validated_acc_id = 0
acc_facts = None # will hold Account facts
if amodule.params['id']:
# expect existing Disk with the specified ID
# This call to disk_find will abort the module if no Disk with such ID is present
disk_id, disk_facts = decon.disk_find(amodule.params['id'])
if not disk_id:
decon.result['failed'] = True
decon.result['msg'] = "Specified Disk ID {} not found.".format(amodule.params['id'])
amodule.fail_json(**decon.result)
validated_acc_id =disk_facts['accountId']
elif amodule.params['account_id'] > 0 or amodule.params['account_name'] != "":
# Make sure disk name is specified, if not - fail the module
if amodule.params['name'] == "":
decon.result['failed'] = True
decon.result['msg'] = ("Cannot manage disk if both ID is 0 and disk name is empty.")
amodule.fail_json(**decon.result)
# Specified account must be present and accessible by the user, otherwise abort the module
validated_acc_id, acc_facts = decon.account_find(amodule.params['account_name'], amodule.params['account_id'])
if not validated_acc_id:
decon.result['failed'] = True
decon.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
amodule.fail_json(**decon.result)
# This call to disk_find may return disk_id=0 if no Disk with this name found in
disk_id, disk_facts = decon.disk_find(disk_id=0, disk_name=amodule.params['name'],
account_id=validated_acc_id,
check_state=False)
else:
# this is "invalid arguments combination" sink
# if we end up here, it means that module was invoked with disk_id=0 and undefined account
decon.result['failed'] = True
if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "":
decon.result['msg'] = "Cannot find Disk by name when account name is empty and account ID is 0."
if amodule.params['name'] == "":
decon.result['msg'] = "Cannot find Disk by empty name."
amodule.fail_json(**decon.result)
# #
#Full range of Disk status is as follows: # Initial validation of module arguments is complete
# #
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED", # At this point non-zero disk_id means that we will be managing pre-existing Disk
# Otherwise we are about to create a new disk
# #
if decon.disk_id: # Valid Disk model statii are as follows:
#disk exist #
if decon.disk_info['status'] in ["MODELED", "CREATING"]: # "CREATED", "ASSIGNED", DELETED", "DESTROYED", "PURGED"
#
disk_should_exist = False
target_sep_id = 0
# target_pool = ""
if disk_id:
disk_should_exist = True
if disk_facts['status'] in ["MODELED", "CREATING" ]:
# error: nothing can be done to existing Disk in the listed statii regardless of
# the requested state
decon.result['failed'] = True decon.result['failed'] = True
decon.result['changed'] = False decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current " decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(decon.disk_id, decon.disk_info['status']) "status '{}'").format(disk_id, disk_facts['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED" elif disk_facts['status'] in ["CREATED", "ASSIGNED"]:
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
decon.delete() decon.disk_delete(disk_id, True, amodule.params['force_detach']) # delete permanently
disk_facts['status'] = 'DESTROYED'
disk_should_exist = False
elif amodule.params['state'] == 'present': elif amodule.params['state'] == 'present':
decon.action() # resize Disk as necessary & if possible
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]: if decon.check_amodule_argument('size', False):
#re-provision disk decon.disk_resize(disk_facts, amodule.params['size'])
if amodule.params['state'] in ('present'): elif disk_facts['status'] == "DELETED":
decon.create() if amodule.params['state'] == 'present':
# restore
decon.disk_restore(disk_id)
_, disk_facts = decon.disk_find(disk_id)
decon.disk_resize(disk_facts, amodule.params['size'])
disk_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
decon.disk_delete(disk_id, permanently=True)
disk_facts['status'] = 'DESTROYED'
disk_should_exist = False
elif disk_facts['status'] in ["DESTROYED", "PURGED"]:
if amodule.params['state'] == 'present':
# Need to re-provision this Disk.
# Some attributes may change, some must stay the same:
# - disk name - stays, take from disk_facts
# - account ID - stays, take from validated account ID
# - size - may change, take from module arguments
# - SEP ID - may change, build based on module arguments
# - pool - may change, take from module arguments
# - annotation - may change, take from module arguments
#
# First validate required parameters:
decon.check_amodule_argument('size') # this will fail the module if size is not specified
target_sep_id = 0
if decon.check_amodule_argument('sep_id', False) and amodule.params['sep_id'] > 0:
# non-zero sep_id is explicitly passed in module arguments
target_sep_id = amodule.params['sep_id']
elif decon.check_amodule_argument('place_with', False) and amodule.params['place_with'] > 0:
# request to place this disk on the same SEP as the specified OS image
# validate specified OS image and assign SEP ID accordingly
image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0)
target_sep_id = image_facts['sepId']
else: else:
decon.nop() # no new SEP ID is explicitly specified, and no place_with option - use sepId from the disk_facts
elif decon.disk_info['status'] == "DELETED": target_sep_id = disk_facts['sepId']
if amodule.params['state'] in ('present'): disk_id = decon.disk_provision(disk_name=disk_facts['name'], # as this disk was found, its name is in the facts
decon.action(restore=True) size=amodule.params['size'],
account_id=validated_acc_id,
sep_id=target_sep_id,
pool=amodule.params['pool'],
desc=amodule.params['annotation'],
location="")
disk_should_exist = True
elif amodule.params['state'] == 'absent':
# nop
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("No state change required for Disk ID {} because of its "
"current status '{}'").format(disk_id,
disk_facts['status'])
disk_should_exist = False
else: else:
decon.nop() # disk_id =0 -> pre-existing Disk was not found.
else: disk_should_exist = False # we will change it back to True if Disk is created successfully
# preexisting Disk was not found # If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
decon.nop() decon.result['failed'] = False
else: decon.result['changed'] = False
decon.create() decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
"non-existent Disk name '{}'").format(amodule.params['name'])
elif amodule.params['state'] == 'present':
decon.check_amodule_argument('name') # if disk name not specified, fail the module
decon.check_amodule_argument('size') # if disk size not specified, fail the module
# as we already have account ID, we can create Disk and get disk id on success
if decon.check_amodule_argument('sep_id', False) and amodule.params['sep_id'] > 0:
# non-zero sep_id is explicitly passed in module arguments
target_sep_id = amodule.params['sep_id']
elif decon.check_amodule_argument('place_with', False) and amodule.params['place_with'] > 0:
# request to place this disk on the same SEP as the specified OS image
# validate specified OS image and assign SEP ID accordingly
image_id, image_facts = decon.image_find(amodule.params['place_with'], "", 0)
target_sep_id = image_facts['sepId']
else:
# no SEP ID is explicitly specified, and no place_with option - we do not know where
# to place the new disk - fail the module
decon.result['failed'] = True
decon.result['msg'] = ("Cannot create new Disk name '{}': no SEP ID specified and "
"no 'place_with' option used.").format(amodule.params['name'])
amodule.fail_json(**decon.result)
disk_id = decon.disk_provision(disk_name=amodule.params['name'],
size=amodule.params['size'],
account_id=validated_acc_id,
sep_id=target_sep_id,
pool_name=amodule.params['pool'],
desc=amodule.params['annotation'],
location="")
disk_should_exist = True
elif amodule.params['state'] == 'disabled':
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"Disk name '{}'").format(amodule.params['state'],
amodule.params['name'])
#
# conditional switch end - complete module run
#
if decon.result['failed']: if decon.result['failed']:
amodule.fail_json(**decon.result) amodule.fail_json(**decon.result)
else: else:
if decon.result['changed'] and amodule.params['state'] in ('present'): # prepare Disk facts to be returned as part of decon.result and then call exit_json(...)
_, decon.disk_info = decon.disk_find(decon.disk_id) if disk_should_exist:
decon.result['facts'] = decon.package_facts(amodule.check_mode) if decon.result['changed']:
# If we arrive here, there is a good chance that the Disk is present - get fresh Disk
# facts by Disk ID.
# Otherwise, Disk facts from previous call (when the Disk was still in existence) will
# be returned.
_, disk_facts = decon.disk_find(disk_id)
decon.result['facts'] = decort_disk_package_facts(disk_facts, amodule.check_mode)
amodule.exit_json(**decon.result) amodule.exit_json(**decon.result)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
#SHARE

View File

@@ -1,68 +1,19 @@
#!/usr/bin/python #!/usr/bin/python
# #
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC # Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
# #
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # 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', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
'supported_by': 'community'} 'supported_by': 'community'}
DOCUMENTATION = '''
---
---
'''
EXAMPLES = '''
- name: Create k8s cluster
decort_k8s:
verify_ssl: false
authenticator: jwt
jwt: "{{ run_jwt.jwt }}"
controller_url: "{{CONTROLLER_URL}}"
name: SOME_NAME
rg_id: {{RG_ID}}
k8ci_id: 10
master_count: 3
master_cpu: 2
master_ram: 2048
master_disk: 10
state: present
permanent: True
started: True
getConfig: True
network_plugin: flannel
workers:
- name: wg1
ram: 1024
cpu: 2
disk: 10
num: 1
labels:
- disktype1=ssd1
- disktype2=ssd2
- disktype3=ssd3
taints:
- key1=value1:NoSchedule
- key2=value2:NoSchedule
- key3=value3:NoSchedule
annotations:
- node.deckhouse.io/group1=g1
- node.deckhouse.io/group2=g2
- node.deckhouse.io/group3=g3
- name: wg2
ram: 1024
cpu: 2
disk: 10
num: 1
labels:
- apptype=main
annotations:
- node.mainapp.domen.local/group1=g1
register: some_cluster
'''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import * from ansible.module_utils.decort_utils import *
@@ -75,12 +26,6 @@ class decort_k8s(DecortController):
validated_rg_id = 0 validated_rg_id = 0
validated_rg_facts = None validated_rg_facts = None
validated_k8ci_id = 0 validated_k8ci_id = 0
self.k8s_should_exist = False
if not arg_amodule.params['workers']:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "At least one worker group must be present"
self.fail_json(**self.result)
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0: if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True self.result['failed'] = True
@@ -163,11 +108,9 @@ class decort_k8s(DecortController):
ret_dict['techStatus'] = self.k8s_info['techStatus'] ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status'] ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.rg_id ret_dict['rg_id'] = self.rg_id
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id ret_dict['account_id'] = self.acc_id
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED": if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig() ret_dict['config'] = self.k8s_getConfig()
return ret_dict return ret_dict
def nop(self): def nop(self):
@@ -206,11 +149,10 @@ class decort_k8s(DecortController):
self.k8s_provision(self.amodule.params['name'], self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'], self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'], self.amodule.params['rg_id'],
self.amodule.params['network_plugin'],
self.amodule.params['master_count'], self.amodule.params['master_count'],
self.amodule.params['master_cpu'], self.amodule.params['master_cpu'],
self.amodule.params['master_ram'], self.amodule.params['master_ram_mb'],
self.amodule.params['master_disk'], self.amodule.params['master_disk_gb'],
self.amodule.params['workers'][0], self.amodule.params['workers'][0],
self.amodule.params['extnet_id'], self.amodule.params['extnet_id'],
self.amodule.params['with_lb'], self.amodule.params['with_lb'],
@@ -223,12 +165,12 @@ class decort_k8s(DecortController):
if self.k8s_id: if self.k8s_id:
self.k8s_should_exist = True self.k8s_should_exist = True
if self.k8s_id and len(self.amodule.params['workers'])>1 : if self.k8s_id and self.amodule.params['workers'][1]:
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers']) self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
return return
def destroy(self): def destroy(self):
self.k8s_delete(self.k8s_id,self.amodule.params['permanent']) self.k8s_delete(self.k8s_id)
self.k8s_info['status'] = 'DELETED' self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False self.k8s_should_exist = False
return return
@@ -290,17 +232,16 @@ class decort_k8s(DecortController):
rg_id=dict(type='int', default=0), rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""), rg_name=dict(type='str',default=""),
k8ci_id=dict(type='int', required=True), k8ci_id=dict(type='int', required=True),
network_plugin=dict(type='str',required=False,default="flannel"),
wg_name=dict(type='str', required=False), wg_name=dict(type='str', required=False),
master_count=dict(type='int', default=1), master_count=dict(type='int', default=1),
master_cpu=dict(type='int', default=2), master_cpu=dict(type='int', default=2),
master_ram=dict(type='int', default=2048), master_ram_mb=dict(type='int', default=2048),
master_disk=dict(type='int', default=10), master_disk_gb=dict(type='int', default=10),
worker_count=dict(type='int', default=1), worker_count=dict(type='int', default=1),
worker_cpu=dict(type='int', default=1), worker_cpu=dict(type='int', default=1),
worker_ram_mb=dict(type='int', default=1024), worker_ram_mb=dict(type='int', default=1024),
worker_disk_gb=dict(type='int', default=10), worker_disk_gb=dict(type='int', default=10),
workers=dict(type='list',required=True), workers=dict(type='list'),
extnet_id=dict(type='int', default=0), extnet_id=dict(type='int', default=0),
description=dict(type='str', default="Created by decort ansible module"), description=dict(type='str', default="Created by decort ansible module"),
with_lb=dict(type='bool', default=True), with_lb=dict(type='bool', default=True),

View File

@@ -1,11 +1,15 @@
#!/usr/bin/python #!/usr/bin/python
# #
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC # Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
# #
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
# #
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
'supported_by': 'community'} 'supported_by': 'community'}
@@ -20,14 +24,15 @@ description: >
network port forwarding rules, restart guest OS and delete a virtual machine thus releasing network port forwarding rules, restart guest OS and delete a virtual machine thus releasing
corresponding cloud resources. corresponding cloud resources.
version_added: "2.2" version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements: requirements:
- python >= 3.8 - python >= 2.6
- PyJWT Python module - PyJWT Python module
- requests Python module - requests Python module
- netaddr Python module - netaddr Python module
- decort_utils utility library (module) - decort_utils utility library (module)
- DECORT cloud platform version 3.8.6 or higher - DECORT cloud platform version 3.6.1 or higher
notes: notes:
- Environment variables can be used to pass selected parameters to the module, see details below. - Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used. - Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
@@ -226,7 +231,7 @@ options:
choices: [ present, absent, poweredon, poweredoff, halted, paused, check ] choices: [ present, absent, poweredon, poweredoff, halted, paused, check ]
tags: tags:
description: description:
- Dict of custom tags to be assigned to the VM. - String of custom tags to be assigned to the VM (This feature is not implemented yet!).
- These tags are arbitrary text that can be used for grouping or indexing the VMs by other applications. - These tags are arbitrary text that can be used for grouping or indexing the VMs by other applications.
required: no required: no
user: user:
@@ -282,14 +287,24 @@ EXAMPLES = '''
name: SimpleVM name: SimpleVM
cpu: 2 cpu: 2
ram: 4096 ram: 4096
boot_disk: 10 boot_disk:
size: 10
model: ovs
pool: boot
image_name: "Ubuntu 16.04 v1.1" image_name: "Ubuntu 16.04 v1.1"
data_disks: data_disks:
- {{DISK_ID}} - size: 50
model: ovs
pool: data
port_forwards:
- ext_port: 21022
int_port: 22
proto: tcp
- ext_port: 80
int_port: 80
proto: tcp
state: present state: present
tags: tags: "PROJECT:Ansible STATUS:Test"
PROJECT:Ansible
STATUS:Test
account_name: "Development" account_name: "Development"
rg_name: "ANewVDC" rg_name: "ANewVDC"
delegate_to: localhost delegate_to: localhost
@@ -322,6 +337,18 @@ EXAMPLES = '''
state: poweredoff state: poweredoff
delegate_to: localhost delegate_to: localhost
register: simple_vm register: simple_vm
- name: check if VM exists and read in its specs.
decort_kvmvm:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://ds1.digitalenergy.online"
name: "{{ TARGET_VM_NAME }}"
rg_name: "{{ TARGET_VDC_NAME }}"
account_name: "{{ TRAGET_TENANT }}"
state: check
delegate_to: localhost
register: existing_vm
''' '''
RETURN = ''' RETURN = '''
@@ -546,8 +573,6 @@ class decort_kvmvm(DecortController):
image_id=image_facts['id'], image_id=image_facts['id'],
annotation=self.amodule.params['annotation'], annotation=self.amodule.params['annotation'],
userdata=cloud_init_params, userdata=cloud_init_params,
sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None,
pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None,
start_on_create=start_compute) start_on_create=start_compute)
self.comp_should_exist = True self.comp_should_exist = True
@@ -566,10 +591,6 @@ class decort_kvmvm(DecortController):
'techStatus': "STOPPED", 'techStatus': "STOPPED",
'interfaces': [], # new compute instance is created network-less 'interfaces': [], # new compute instance is created network-less
'disks': [], # new compute instance is created without any data disks attached 'disks': [], # new compute instance is created without any data disks attached
'tags': {},
'affinityLabel': "",
'affinityRules': [],
'antiAffinityRules': [],
} }
# #
@@ -631,13 +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.compute_affinity(self.comp_info,
self.amodule.params['tag'], self.amodule.params['tag'],
self.amodule.params['aff_rule'], self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'], self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label']) label=self.amodule.params['affinity_label'],)
return return
def package_facts(self, check_mode=False): def package_facts(self, check_mode=False):
@@ -667,7 +686,6 @@ class decort_kvmvm(DecortController):
public_ips=[], # direct IPs; this list can be empty public_ips=[], # direct IPs; this list can be empty
private_ips=[], # IPs on ViNSes; usually, at least one IP is listed private_ips=[], # IPs on ViNSes; usually, at least one IP is listed
nat_ip="", # IP of the external ViNS interface; can be empty. nat_ip="", # IP of the external ViNS interface; can be empty.
tags={},
) )
if check_mode or self.comp_info is None: if check_mode or self.comp_info is None:
@@ -685,8 +703,6 @@ class decort_kvmvm(DecortController):
ret_dict['tech_status'] = self.comp_info['techStatus'] ret_dict['tech_status'] = self.comp_info['techStatus']
ret_dict['account_id'] = self.comp_info['accountId'] ret_dict['account_id'] = self.comp_info['accountId']
ret_dict['rg_id'] = self.comp_info['rgId'] ret_dict['rg_id'] = self.comp_info['rgId']
if self.comp_info['tags']:
ret_dict['tags'] = self.comp_info['tags']
# if the VM is an imported VM, then the 'accounts' list may be empty, # if the VM is an imported VM, then the 'accounts' list may be empty,
# so check for this case before trying to access login and passowrd values # so check for this case before trying to access login and passowrd values
if len(self.comp_info['osUsers']): if len(self.comp_info['osUsers']):
@@ -748,8 +764,6 @@ class decort_kvmvm(DecortController):
required=True, required=True,
choices=['legacy', 'oauth2', 'jwt']), choices=['legacy', 'oauth2', 'jwt']),
boot_disk=dict(type='int', required=False), boot_disk=dict(type='int', required=False),
sep_id=dict(type='int', required=False),
pool=dict(type='str', required=False),
controller_url=dict(type='str', required=True), controller_url=dict(type='str', required=True),
# count=dict(type='int', required=False, default=1), # count=dict(type='int', required=False, default=1),
cpu=dict(type='int', required=False), cpu=dict(type='int', required=False),
@@ -776,7 +790,7 @@ 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='dict', required=False), tag=dict(type='list', required=False),
affinity_label=dict(type='str', required=False), affinity_label=dict(type='str', required=False),
aff_rule=dict(type='list', required=False), aff_rule=dict(type='list', required=False),
aaff_rule=dict(type='list', required=False), aaff_rule=dict(type='list', required=False),

View File

@@ -1,328 +0,0 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
TODO
'''
EXAMPLES = '''
TODO
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_lb(DecortController):
def __init__(self,arg_amodule) -> None:
super(decort_lb,self).__init__(arg_amodule)
self.lb_id = 0
self.lb_facts = None
self.vins_id = 0
self.vins_facts = None
self.rg_id = 0
self.rg_facts = None
self.acc_id = 0
self.acc_facts = None
self.default_server_check = "enabled"
self.default_alg = "roundrobin"
self.default_settings = {
"downinter": 10000,
"fall": 2,
"inter": 5000,
"maxconn": 250,
"maxqueue": 256,
"rise": 2,
"slowstart": 60000,
"weight": 100,
}
if arg_amodule.params['lb_id']:
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id'])
if not self.lb_id:
self.result['failed'] = True
self.result['msg'] = "Specified LB ID {} not found."\
.format(arg_amodule.params['lb _id'])
self.fail_json(**self.result)
self.acc_id = self.lb_facts['accountId']
self.rg_id = self.lb_facts['rgId']
self.vins_id = self.lb_facts['vinsId']
return
if arg_amodule.params['rg_id']:
self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="")
if not self.rg_id:
self.result['failed'] = True
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result)
if arg_amodule.params['vins_id']:
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'])
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result)
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
if arg_amodule.params['rg_name']:
self.result['failed'] = True
self.result['msg'] = ("RG name must be specified with account present")
self.fail_json(**self.result)
self.acc_id, self.acc_facts = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not self.acc_id:
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.fail_json(**self.result)
self.rg_id, self.rg_facts = self.rg_find(self._acc_id,0, arg_rg_name=arg_amodule.params['rg_name'])
if self.rg_id and self.vins_id:
self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id)
return
def create(self):
self.lb_id = self.lb_provision(self.amodule.params['lb_name'],
self.rg_id,self.vins_id,
self.amodule.params['ext_net_id'],
self.amodule.params['annotation'])
if self.amodule.params['backends'] or self.amodule.params['frontends']:
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id)
self.lb_update(
self.lb_facts['backends'],
self.lb_facts['frontends'],
self.amodule.params['backends'],
self.amodule.params['servers'],
self.amodule.params['frontends']
)
return
def action(self,d_state='',restore=False):
if restore == True:
self.lb_restore(arg_vins_id=self.lb_id)
self.lb_state(self.vins_facts, 'enabled')
self.lb_facts['status'] = "ENABLED"
self.lb_facts['techStatus'] = "STARTED"
self.lb_update(
self.lb_facts['backends'],
self.lb_facts['frontends'],
self.amodule.params['backends'],
self.amodule.params['servers'],
self.amodule.params['frontends']
)
if d_state != '':
self.lb_state(self.lb_facts, d_state)
return
def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently'])
self.lb_facts['status'] = 'DESTROYED'
return
def nop(self):
"""No operation (NOP) handler for LB management by decort_lb module.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual LB state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.lb_id:
self.result['msg'] = ("No state change required for LB ID {} because of its "
"current status '{}'.").format(self.lb_id, self.vins_facts['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent LB instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.vins_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the "
"current status '{}'").format(self.lb_id,
self.amodule.params['state'],
self.lb_facts['status'])
else:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"LB name '{}'").format(self.amodule.params['state'],
self.amodule.params['lb_name'])
return
def package_facts(self, arg_check_mode=False):
"""Package a dictionary of LB facts according to the decort_lb module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
if self.vins_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.lb_facts['id']
ret_dict['name'] = self.lb_facts['name']
ret_dict['state'] = self.lb_facts['status']
#ret_dict['account_id'] = self.lb_facts['accountId']
ret_dict['rg_id'] = self.lb_facts['rgId']
ret_dict['gid'] = self.lb_facts['gid']
if self.amodule.params['state']!="absent":
ret_dict['backends'] = self.lb_facts['backends']
ret_dict['frontends'] = self.lb_facts['frontends']
return ret_dict
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_vins module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default='Managed by Ansible module decort_lb'),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present','restart']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_id=dict(type='int', required=False, default=0),
rg_name=dict(type='str', required=False, default=''),
vins_name=dict(type='str', required=False, default=''),
vins_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True),
lb_id=dict(type='int', required=False, default=0),
lb_name=dict(type='str', required=True),
backends=dict(type='list',required=False,default=[]),
frontends=dict(type='list',required=False,default=[]),
servers=dict(type='list',required=False,default=[]),
permanently=dict(type='bool', required=False, default=False),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
def main():
module_parameters = decort_lb.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=[
['rg_id','rg_name'],
['lb_id','lb_name'],
['vins_id','vins_name']
]
)
decon = decort_lb(amodule)
if decon.lb_id:
if decon.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]:
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current "
"status '{}'").format(decon.lb_id, decon.lb_facts['status'])
elif decon.lb_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] in ('present', 'disabled'):
decon.action()
elif amodule.params['state'] == 'enabled':
decon.action('enabled')
elif decon.lb_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] in ('present', 'enabled'):
decon.action()
elif amodule.params['state'] == 'disabled':
decon.action('disabled')
elif amodule.params['state'] in ('stopped', 'started','restart'):
decon.action(amodule.params['state'])
elif decon.lb_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
decon.action(restore=True)
elif amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] == 'disabled':
decon.error()
elif decon.lb_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
decon.create()
elif amodule.params['state'] == 'absent':
decon.nop()
elif amodule.params['state'] == 'disabled':
decon.error()
else:
if amodule.params['state'] == 'absent':
decon.nop()
elif amodule.params['state'] in ('present', 'enabled'):
decon.create()
elif amodule.params['state'] == 'disabled':
decon.error()
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed'] and amodule.params['state'] != 'absent':
_, decon.lb_facts = decon.lb_find(decon.lb_id)
if decon.lb_id:
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()

View File

@@ -424,7 +424,6 @@ class decort_osimage(DecortController):
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'] ret_dict['linkto'] = arg_osimage_facts['linkTo']
ret_dict['accountId'] = arg_osimage_facts['accountId']
return ret_dict return ret_dict
@@ -522,9 +521,7 @@ def main():
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
elif amodule.params['state'] == "absent": 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']:
if amodule.params['image_name'] or amodule.params['image_id'] and\
decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']:
amodule.image_id_delete = decon.validated_image_id amodule.image_id_delete = decon.validated_image_id
decort_osimage.decort_image_delete(decon,amodule) decort_osimage.decort_image_delete(decon,amodule)

View File

@@ -209,155 +209,8 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import * from ansible.module_utils.decort_utils import *
class decort_rg(DecortController):
def __init__(self,amodule):
super(decort_rg, self).__init__(amodule)
self.validated_acc_id = 0 def decort_rg_package_facts(arg_rg_facts, arg_check_mode=False):
self.validated_rg_id = 0
self.validated_rg_facts = None
if self.amodule.params['account_id']:
self.validated_acc_id, _ = self.account_find("", amodule.params['account_id'])
elif amodule.params['account_name']:
self.validated_acc_id, _ = self.account_find(amodule.params['account_name'])
if not self.validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.fail_json(**self.result)
if amodule.params['rg_id'] > 0:
self.validated_rg_id = amodule.params['rg_id']
# Check if the RG with the specified parameters already exists
self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id,
arg_rg_id = self.validated_rg_id,
arg_rg_name=amodule.params['rg_name'],
arg_check_state=False)
if amodule.params['state'] != "absent":
self.rg_should_exist = True
else:
self.rg_should_exist = False
def access(self):
should_change_access = False
acc_granted = False
for rg_item in self.rg_facts['acl']:
if rg_item['userGroupId'] == self.amodule.params['access']['user']:
acc_granted = True
if self.amodule.params['access']['action'] == 'grant':
if rg_item['right'] != self.amodule.params['access']['right']:
should_change_access = True
if self.amodule.params['access']['action'] == 'revoke':
should_change_access = True
if acc_granted == False and self.amodule.params['access']['action'] == 'grant':
should_change_access = True
if should_change_access == True:
self.rg_access(self.validated_rg_id, self.amodule.params['access'])
self.rg_facts['access'] = self.amodule.params['access']
self.rg_should_exist = True
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.validated_rg_id > 0:
self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the "
"current status '{}'.").format(self.validated_rg_id,
self.amodule.params['state'],
self.rg_facts['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
"in account ID {} ").format(self.amodule.params['state'],
self.amodule.params['rg_name'],
self.validated_acc_id)
return
def update(self):
resources = self.rg_facts['Resources']['Reserved']
incorrect_quota = dict(Requested=dict(),
Reserved=dict(),)
query_key_map = dict(cpu='cpu',
ram='ram',
disk='disksize',
ext_ips='extips',
net_transfer='exttraffic',)
if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']:
if self.amodule.params['quotas'][quota_item] < resources[query_key_map[quota_item]]:
incorrect_quota['Requested'][quota_item]=self.amodule.params['quotas'][quota_item]
incorrect_quota['Reserved'][quota_item]=resources[query_key_map[quota_item]]
if incorrect_quota['Requested']:
self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
self.result['failed'] = True
if self.result['failed'] != True:
self.rg_update(self.rg_facts, self.amodule.params['quotas'],
self.amodule.params['resType'], self.amodule.params['rename'])
self.rg_should_exist = True
return
def setDefNet(self):
if self.amodule.params['def_netId'] != self.rg_facts['def_net_id']:
self.rg_setDefNet(self.validated_rg_id,
self.amodule.params['def_netType'],
self.amodule.params['def_netId'])
self.rg_should_exist = True
return
def create(self):
self.validated_rg_id = self.rg_provision(self.validated_acc_id,
self.amodule.params['rg_name'],
self.amodule.params['owner'],
self.amodule.params['annotation'],
self.amodule.params['resType'],
self.amodule.params['def_netType'],
self.amodule.params['ipcidr'],
self.amodule.params['extNetId'],
self.amodule.params['extNetIp'],
self.amodule.params['quotas'],
"", # this is location code. TODO: add module argument
)
self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id,
self.validated_rg_id,
arg_rg_name="",
arg_check_state=False)
self.rg_should_exist = True
return
def enable(self):
self.rg_enable(self.validated_rg_id,
self.amodule.params['state'])
if self.amodule.params['state'] == "enabled":
self.rg_facts['status'] = 'CREATED'
else:
self.rg_facts['status'] = 'DISABLED'
self.rg_should_exist = True
return
def restore(self):
self.rg_restore(self.validated_rg_id)
self.rg_facts['status'] = 'DISABLED'
self.rg_should_exist = True
return
def destroy(self):
self.rg_delete(self.validated_rg_id, self.amodule.params['permanently'])
if self.amodule.params['permanently'] == True:
self.rg_facts['status'] = 'DESTROYED'
else:
self.rg_facts['status'] = 'DELETED'
self.rg_should_exist = False
return
def package_facts(self, check_mode=False):
"""Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will """Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will
be returned to the upstream Ansible engine at the completion of the module run. be returned to the upstream Ansible engine at the completion of the module run.
@@ -370,37 +223,30 @@ class decort_rg(DecortController):
state="CHECK_MODE", state="CHECK_MODE",
) )
if check_mode: if arg_check_mode:
# in check mode return immediately with the default values # in check mode return immediately with the default values
return ret_dict return ret_dict
#if arg_rg_facts is None: if arg_rg_facts is None:
# # if void facts provided - change state value to ABSENT and return # if void facts provided - change state value to ABSENT and return
# ret_dict['state'] = "ABSENT" ret_dict['state'] = "ABSENT"
# return ret_dict return ret_dict
ret_dict['id'] = self.rg_facts['id'] ret_dict['id'] = arg_rg_facts['id']
ret_dict['name'] = self.rg_facts['name'] ret_dict['name'] = arg_rg_facts['name']
ret_dict['state'] = self.rg_facts['status'] ret_dict['state'] = arg_rg_facts['status']
ret_dict['account_id'] = self.rg_facts['accountId'] ret_dict['account_id'] = arg_rg_facts['accountId']
ret_dict['gid'] = self.rg_facts['gid'] ret_dict['gid'] = arg_rg_facts['gid']
ret_dict['quota'] = self.rg_facts['resourceLimits']
ret_dict['resTypes'] = self.rg_facts['resourceTypes']
ret_dict['defNetId'] = self.rg_facts['def_net_id']
ret_dict['defNetType'] = self.rg_facts['def_net_type']
ret_dict['ViNS'] = self.rg_facts['vins']
ret_dict['computes'] = self.rg_facts['vms']
return ret_dict return ret_dict
def parameters(): def decort_rg_parameters():
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted """Build and return a dictionary of parameters expected by decort_rg module in a form accepted
by AnsibleModule utility class.""" by AnsibleModule utility class."""
return dict( return dict(
account_id=dict(type='int', required=False), account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''), account_name=dict(type='str', required=False, default=''),
access=dict(type='dict'),
annotation=dict(type='str', required=False, default=''), annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str', app_id=dict(type='str',
required=False, required=False,
@@ -414,12 +260,6 @@ class decort_rg(DecortController):
choices=['legacy', 'oauth2', 'jwt']), choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True), controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''), # datacenter=dict(type='str', required=False, default=''),
def_netType=dict(type='str', choices=['PRIVATE','PUBLIC', 'NONE'], default='PRIVATE'),
def_netId=dict(type='int', default=0),
extNetId=dict(type='int', default=0),
extNetIp=dict(type='str', default=""),
owner=dict(type='str', default=""),
ipcidr=dict(type='str', default=""),
jwt=dict(type='str', jwt=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_JWT']), fallback=(env_fallback, ['DECORT_JWT']),
@@ -427,37 +267,32 @@ class decort_rg(DecortController):
oauth2_url=dict(type='str', oauth2_url=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])), fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
rename=dict(type='str', default=""),
password=dict(type='str', password=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']), fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True), no_log=True),
quotas=dict(type='dict', required=False), quotas=dict(type='dict', required=False),
resType=dict(type='list'),
state=dict(type='str', state=dict(type='str',
default='present', default='present',
choices=['absent', 'disabled', 'enabled', 'present']), choices=['absent', 'disabled', 'enabled', 'present']),
permanently=dict(type='bool',
default='False'),
user=dict(type='str', user=dict(type='str',
required=False, required=False,
fallback=(env_fallback, ['DECORT_USER'])), fallback=(env_fallback, ['DECORT_USER'])),
rg_name=dict(type='str', required=False,), rg_name=dict(type='str', required=True,),
rg_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),
) )
# 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 RG with the specified id or rg_name:name exists # 2) check if the RG with the specified id or rg_name:name exists
# 3) if RG does not exist -> deploy # 3) if RG does not exist -> deploy
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly # 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible # 5) report result to Ansible
def main(): def main():
module_parameters = decort_rg.parameters() module_parameters = decort_rg_parameters()
amodule = AnsibleModule(argument_spec=module_parameters, amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True, supports_check_mode=True,
@@ -472,52 +307,157 @@ def main():
], ],
) )
decon = decort_rg(amodule) decon = DecortController(amodule)
#amodule.check_mode=True
if decon.validated_rg_id > 0:
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
decon.error()
elif decon.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == "disabled":
decon.enable()
if amodule.params['state'] in ['present', 'enabled']:
if amodule.params['quotas'] or amodule.params['resType'] or amodule.params['rename'] != "":
decon.update()
if amodule.params['access']:
decon.access()
if amodule.params['def_netId'] > 0:
decon.setDefNet()
elif decon.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
decon.destroy()
elif amodule.params['state'] == 'present':
decon.restore()
elif decon.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == ("enabled"):
decon.enable()
# We need valid Account ID to manage RG.
# Account may be specified either by account_id or account_name. In both cases we
# have to validate account presence and accesibility by the current user.
validated_acc_id = 0
if decon.check_amodule_argument('account_id', False):
validated_acc_id, _ = decon.account_find("", amodule.params['account_id'])
else: else:
decon.check_amodule_argument('account_name') # if no account_name, this function will abort module
validated_acc_id, _ = decon.account_find(amodule.params['account_name'])
if not validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
decon.result['failed'] = True
decon.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
decon.fail_json(**decon.result)
# Check if the RG with the specified parameters already exists
rg_id, rg_facts = decon.rg_find(validated_acc_id,
0, arg_rg_name=amodule.params['rg_name'],
arg_check_state=False)
rg_should_exist = True
if rg_id:
if rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
# error: nothing can be done to existing RG in the listed statii regardless of
# the requested state
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing RG ID {} because of its current "
"status '{}'").format(rg_id, rg_facts['status'])
elif rg_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
rg_facts['status'] = 'DESTROYED'
rg_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update quotas
decon.rg_quotas(rg_facts, amodule.params['quotas'])
elif amodule.params['state'] == 'enabled':
# update quotas and enable
decon.rg_quotas(rg_facts, amodule.params['quotas'])
decon.rg_state(rg_facts, 'enabled')
elif rg_facts['status'] == "CREATED":
if amodule.params['state'] == 'absent':
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
rg_facts['status'] = 'DESTROYED'
rg_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update quotas
decon.rg_quotas(rg_facts, amodule.params['quotas'])
elif amodule.params['state'] == 'disabled':
# disable and update quotas
decon.rg_state(rg_facts, 'disabled')
decon.rg_quotas(rg_facts, amodule.params['quotas'])
elif rg_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
# TODO: check if restore RG API returns the new RG ID of the restored RG instance.
decon.rg_restore(arg_rg_id=rg_id)
decon.rg_state(rg_facts, 'enabled')
# TODO: Not sure what to do with the quotas after RG is restored. May need to update rg_facts.
rg_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
rg_facts['status'] = 'DESTROYED'
rg_should_exist = False
elif amodule.params['state'] == 'disabled':
# error
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for RG ID {} in the "
"current status '{}'").format(rg_id,
amodule.params['state'],
rg_facts['status'])
rg_should_exist = False
elif rg_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'): if amodule.params['state'] in ('present', 'enabled'):
decon.create() # need to re-provision RG
if amodule.params['access']: decon.check_amodule_argument('rg_name')
decon.access() # As we already have validated account ID we can create RG and get rg_id on success
elif amodule.params['state'] in ('disabled'): # pass empty string for location code, rg_provision will select the 1st location
decon.error() rg_id = decon.rg_provision(validated_acc_id,
amodule.params['rg_name'], decon.decort_username,
amodule.params['quotas'],
"", # this is location code. TODO: add module argument
amodule.params['annotation'])
rg_should_exist = True
elif amodule.params['state'] == 'absent':
# nop
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("No state change required for RG ID {} because of its "
"current status '{}'").format(rg_id,
rg_facts['status'])
rg_should_exist = False
elif amodule.params['state'] == 'disabled':
# error
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for RG ID {} in the "
"current status '{}'").format(rg_id,
amodule.params['state'],
rg_facts['status'])
else:
# Preexisting RG was not found.
rg_should_exist = False # we will change it back to True if RG is explicitly created or restored
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
"non-existent RG name '{}'").format(amodule.params['rg_name'])
elif amodule.params['state'] in ('present', 'enabled'):
# Target RG does not exist yet - create it and store the returned ID in rg_id variable for later use
# To create RG we need account name (or account ID) and RG name - check
# that these parameters are present and proceed.
decon.check_amodule_argument('rg_name')
# as we already have account ID we can create RG and get rg_id on success
# pass empty string for location code, rg_provision will select the 1st location
rg_id = decon.rg_provision(validated_acc_id,
amodule.params['rg_name'], decon.decort_username,
amodule.params['quotas'],
"", # this is location code. TODO: add module argument
amodule.params['annotation'])
rg_should_exist = True
elif amodule.params['state'] == 'disabled':
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"RG name '{}' ").format(amodule.params['state'],
amodule.params['rg_name'])
#
# conditional switch end - complete module run
if decon.result['failed']: if decon.result['failed']:
amodule.fail_json(**decon.result) amodule.fail_json(**decon.result)
else: else:
if decon.rg_should_exist: # prepare RG facts to be returned as part of decon.result and then call exit_json(...)
decon.result['facts'] = decon.package_facts(amodule.check_mode) # rg_facts = None
amodule.exit_json(**decon.result) if rg_should_exist:
else: if decon.result['changed']:
# If we arrive here, there is a good chance that the RG is present - get fresh RG facts from
# the cloud by RG ID.
# Otherwise, RG facts from previous call (when the RG was still in existence) will be returned.
_, rg_facts = decon.rg_find(arg_account_id=0, arg_rg_id=rg_id)
decon.result['facts'] = decort_rg_package_facts(rg_facts, amodule.check_mode)
amodule.exit_json(**decon.result) amodule.exit_json(**decon.result)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -6,6 +6,9 @@
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
# #
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
@@ -20,13 +23,14 @@ description: >
modify its characteristics, and delete it. modify its characteristics, and delete it.
version_added: "2.2" version_added: "2.2"
author: author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements: requirements:
- python >= 3.8 - python >= 2.6
- PyJWT Python module - PyJWT Python module
- requests Python module - requests Python module
- netaddr Python module - netaddr Python module
- decort_utils utility library (module) - decort_utils utility library (module)
- DECORT cloud platform version 3.8.6 or higher - DECORT cloud platform version 3.6.1 or higher
notes: notes:
- Environment variables can be used to pass selected parameters to the module, see details below. - Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used. - Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
@@ -245,8 +249,8 @@ class decort_vins(DecortController):
def __init__(self,arg_amodule): def __init__(self,arg_amodule):
super(decort_vins, self).__init__(arg_amodule) super(decort_vins, self).__init__(arg_amodule)
self.vins_id = 0 vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
vins_facts = None # will hold ViNS facts vins_facts = None # will hold ViNS facts
validated_rg_id = 0 validated_rg_id = 0
rg_facts = None # will hold RG facts rg_facts = None # will hold RG facts
@@ -256,19 +260,18 @@ class decort_vins(DecortController):
if arg_amodule.params['vins_id']: if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified ID # expect existing ViNS with the specified ID
# This call to vins_find will abort the module if no ViNS with such ID is present # This call to vins_find will abort the module if no ViNS with such ID is present
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False) self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'])
if self.vins_id == 0: if not vins_id:
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result) self.fail_json(**self.result)
self.vins_level = "ID" vins_level = "ID"
#raise Exception(self.vins_facts) validated_acc_id = vins_facts['accountId']
validated_acc_id = self.vins_facts['accountId'] validated_rg_id = vins_facts['rgId']
validated_rg_id = self.vins_facts['rgId']
elif arg_amodule.params['rg_id']: elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID # expect ViNS @ RG level in the RG with specified ID
self.vins_level = "RG" vins_level = "RG"
# This call to rg_find will abort the module if no RG with such ID is present # This call to rg_find will abort the module if no RG with such ID is present
validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID
arg_amodule.params['rg_id'], arg_rg_name="") arg_amodule.params['rg_id'], arg_rg_name="")
@@ -288,7 +291,7 @@ class decort_vins(DecortController):
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account " self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.") "or non-existent account specified.")
self.amodule.fail_json(**self.result) self.fail_json(**self.result)
if arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0 if arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0
# expect ViNS @ RG level in the RG with specified name under specified account # expect ViNS @ RG level in the RG with specified name under specified account
# RG with the specified name must be present under the account, otherwise abort the module # RG with the specified name must be present under the account, otherwise abort the module
@@ -297,14 +300,14 @@ class decort_vins(DecortController):
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name']) self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result) self.fail_json(**self.result)
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG # This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=0, # set to 0, as we are looking for ViNS under RG account_id=0, # set to 0, as we are looking for ViNS under RG
rg_id=validated_rg_id, rg_id=validated_rg_id,
rg_facts=rg_facts, rg_facts=rg_facts,
check_state=False) check_state=False)
self.vins_level = "RG" vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0 else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level # So we expect ViNS @ account level
@@ -314,19 +317,18 @@ class decort_vins(DecortController):
rg_id=0, rg_id=0,
rg_facts=rg_facts, rg_facts=rg_facts,
check_state=False) check_state=False)
self.vins_level = "ACC" vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
else: else:
# this is "invalid arguments combination" sink # this is "invalid arguments combination" sink
# if we end up here, it means that module was invoked with vins_id=0 and rg_id=0 # if we end up here, it means that module was invoked with vins_id=0 and rg_id=0
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = "Cannot find ViNS by name" if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == "":
if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == '':
self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0." self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0."
if arg_amodule.params['rg_name'] == "": if arg_amodule.params['rg_name'] == "":
# rg_name without account specified # rg_name without account specified
self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
self.amodule.fail_json(**self.result) self.fail_json(**self.result)
return return
self.rg_id = validated_rg_id self.rg_id = validated_rg_id
@@ -463,6 +465,11 @@ class decort_vins(DecortController):
ret_dict['ext_ip_addr'] = "" ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1 ret_dict['ext_net_id'] = -1
# arg_vins_facts['vnfs']['GW']['config']
# ext_ip_addr -> ext_net_ip
# ??? -> ext_net_id
# tech_status -> techStatus
return ret_dict return ret_dict
@staticmethod @staticmethod
@@ -471,7 +478,7 @@ class decort_vins(DecortController):
by AnsibleModule utility class.""" by AnsibleModule utility class."""
return dict( return dict(
account_id=dict(type='int', required=False,default=0), account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''), account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''), annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str', app_id=dict(type='str',
@@ -514,7 +521,7 @@ class decort_vins(DecortController):
rg_name=dict(type='str', required=False, default=''), rg_name=dict(type='str', required=False, default=''),
verify_ssl=dict(type='bool', required=False, default=True), verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=False, default=0), vins_id=dict(type='int', required=False, default=0),
vins_name=dict(type='str', required=False,default=""), vins_name=dict(type='str', required=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),
) )
@@ -541,9 +548,6 @@ def main():
['app_id', 'app_secret'], ['app_id', 'app_secret'],
['user', 'password'], ['user', 'password'],
], ],
required_one_of=[
['vins_id', 'vins_name'],
],
) )
decon = decort_vins(amodule) decon = decort_vins(amodule)

File diff suppressed because it is too large Load Diff