3 Commits

47 changed files with 1636 additions and 3788 deletions

View File

@@ -9,4 +9,4 @@ Requirements:
* PyJWT 2.0.0 Python module or higher
* requests Python module
* netaddr Python module
* DECORT cloud platform version 3.8.7 or higher
* DECORT cloud platform version 3.5.0 or higher

Binary file not shown.

View File

View File

@@ -1,6 +1,6 @@
---
#
# DECORT bservice module example
# DECORT vins module example
#
- hosts: localhost

View File

@@ -22,17 +22,17 @@
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
- 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,27 +0,0 @@
---
#
# 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

View File

@@ -1,15 +0,0 @@
---
#
# 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

View File

@@ -1,14 +0,0 @@
---
#
# 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

View File

@@ -1,15 +0,0 @@
---
#
# 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

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

View File

@@ -1,14 +0,0 @@
---
#
# This playbook create engine "test".
#
- hosts: localhost
tasks:
- hashivault_secret_engine:
url: "https://vault.domain.local"
authtype: ldap
username: "user"
password: "p@ssword"
state: present
name: test
backend: generic

View File

@@ -1,17 +0,0 @@
---
#
# This playbook create secret "secret" with data foo:foe. If secret "secret" exists - add data foo:foe.
#
- hosts: localhost
tasks:
- hashivault_secret:
url: "https://vault.domain.local"
authtype: ldap
username: "user"
password: "p@ssword"
mount_point: "kv"
state: present
permanent: true
secret: secret
data:
foo: foe

View File

@@ -1,35 +0,0 @@
---
- hosts: localhost
tasks:
- hashivault_read:
url: "https://vault.domain.local"
authtype: ldap
username: "user"
password: "p@ssword"
mount_point: kv
secret: secrets/myaccount
key: app_secret
version: 2
register: key
- name: create a VM using app_secret from hashicorp vault
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "{{ key }}" # API key from SSO Digital Energy
controller_url: "https://cloud.digitalenergy.online"
name: hashivault_read_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: 99 #VINS id
tags: "Ansible hashivault_read example"
state: present
rg_id: 99 #Resource group id
delegate_to: localhost
register: simple_vm

View File

@@ -1,31 +0,0 @@
- hosts: localhost
tasks:
- name: Read a kv2 secret with kv mount point
vars:
ansible_hashi_vault_auth_method: ldap
ansible_hashi_vault_username: username
ansible_hashi_vault_password: pwd
ansible_hashi_vault_engine_mount_point: kv
ansible.builtin.set_fact:
response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'secret', url='https://vault.domain.local') }}"
- name: create a VM using app_secret from hashicorp vault
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "{{ response.data.password }}" # API key from SSO Digital Energy
controller_url: "https://cloud.digitalenergy.online"
name: hashivault_read_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: 99 #VINS id
tags: "Ansible hashivault_read example"
state: present
rg_id: 99 #Resource group id
delegate_to: localhost
register: simple_vm

View File

@@ -1,16 +0,0 @@
- hosts: localhost
tasks:
- name: Get auth token from vault
set_fact:
login_data: "{{ lookup('community.hashi_vault.vault_login', url='https://vault.domain.local', auth_method='ldap', username='username', password='pwd') }}"
- name: Perform multiple kv2 reads with a single Vault login, showing the secrets
vars:
ansible_hashi_vault_auth_method: token
ansible_hashi_vault_token: '{{ login_data | community.hashi_vault.vault_login_token }}'
ansible_hashi_vault_engine_mount_point: kv
paths:
- secret
- secret2
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_kv2_get', *paths, auth_method='token', url='https://vault.domain.local') }}"

View File

@@ -1,18 +0,0 @@
- hosts: localhost
tasks:
- name: Read a kv2 secret with the default mount point
vars:
ansible_hashi_vault_auth_method: ldap
ansible_hashi_vault_username: username
ansible_hashi_vault_password: pwd
ansible_hashi_vault_engine_mount_point: kv
ansible.builtin.set_fact:
response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'secret', url='https://vault.domain.local') }}"
- name: Display the results
ansible.builtin.debug:
msg:
- "Secret: {{ response.secret }}"
- "Data: {{ response.data }} (contains secret data & metadata in kv2)"
- "Metadata: {{ response.metadata }}"
- "Full response: {{ response.raw }}"

View File

@@ -1,13 +0,0 @@
---
- hosts: localhost
tasks:
- hashivault_read:
url: "https://vault.domain.local"
authtype: ldap
username: "uset"
password: "p@ssword"
mount_point: kv
secret: secret
key: foo
version: 2
register: key

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

View File

@@ -6,23 +6,23 @@
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "https://sso.digitalenergy.online"
oauth2_url: "" #"https://sso.digitalenergy.online"
validity: 1200
verify_ssl: false
register: token
delegate_to: localhost
- name: create a VM named cluster-test
- name: create a VM named cloud-init_example
decort_k8s:
state: present
started: True
getConfig: True
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "https://ds1.digitalenergy.online"
controller_url: "" #"https://mr4.digitalenergy.online"
name: "cluster-test"
rg_id: 125
k8ci_id: 18
rg_id: # Resource group id
k8ci_id: # k8s ci id
workers:
- name: wg1
ram: 1024

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

@@ -1,42 +0,0 @@
---
#
# 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

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
the disk on the fly. Downsizing disk is not allowed.`
required: no
limitIO:
description:
- Disk input / output limit, used to limit the speed of interaction with the disk.
required: no
type:
description:
- Type of the disk.
- `Disks can be of the following types: "D"-Data, "B"-Boot, "T"-Tmp.`
default: "D"
required: no
state:
description:
- Specify the desired state of the disk at the exit of the module.
@@ -244,239 +234,103 @@ facts:
gid: 1001
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_disk(DecortController):
def __init__(self,arg_amodule):
super(decort_disk, self).__init__(arg_amodule)
validated_acc_id = 0
validated_acc_info = None
validated_disk_id = 0
self.disk_id = 0
self.account_id = 0
validated_disk_facts = None
# limitIO check for exclusive parameters
if arg_amodule.params['limitIO']:
self.disk_check_iotune_arg(arg_amodule.params['limitIO'])
def decort_disk_package_facts(disk_facts, check_mode=False):
"""Package a dictionary of disk facts according to the decort_disk module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
@param (dict) disk_facts: dictionary with Disk facts as returned by API call to .../disks/get
@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,
name="none",
state="CHECK_MODE",
size=0,
account_id=0,
sep_id=0,
pool="none",
attached_to=0,
gid=0
)
if check_mode or self.disk_info is None:
return ret_dict
# remove io param with zero value
clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0]
for key in clean_io: del self.disk_info['iotune'][key]
ret_dict['id'] = self.disk_info['id']
ret_dict['name'] = self.disk_info['name']
ret_dict['size'] = self.disk_info['sizeMax']
ret_dict['state'] = self.disk_info['status']
ret_dict['account_id'] = self.disk_info['accountId']
ret_dict['sep_id'] = self.disk_info['sepId']
ret_dict['pool'] = self.disk_info['pool']
ret_dict['attached_to'] = self.disk_info['vmid']
ret_dict['gid'] = self.disk_info['gid']
ret_dict['iotune'] = self.disk_info['iotune']
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
size=0,
account_id=0,
sep_id=0,
pool="none",
attached_to=0,
gid=0
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
@staticmethod
def build_parameters():
"""Build and return a dictionary of parameters expected by decort_disk module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default='Disk by decort_disk'),
app_id=dict(type='str',
if disk_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = disk_facts['id']
ret_dict['name'] = disk_facts['name']
ret_dict['size'] = disk_facts['sizeMax']
ret_dict['state'] = disk_facts['status']
ret_dict['account_id'] = disk_facts['accountId']
ret_dict['sep_id'] = disk_facts['sepId']
ret_dict['pool'] = disk_facts['pool']
ret_dict['attached_to'] = disk_facts['vmid']
ret_dict['gid'] = disk_facts['gid']
return ret_dict
def decort_disk_parameters():
"""Build and return a dictionary of parameters expected by decort_disk module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default='Disk by decort_disk'),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
name=dict(type='str', required=False),
force_detach=dict(type='bool', required=False, default=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
place_with=dict(type='int', default=0),
pool=dict(type='str', default=''),
sep_id=dict(type='int', default=0),
size=dict(type='int', default=0),
type=dict(type='str',
required=False,
default="D",
choices=['B', 'D', 'T']),
iops=dict(type='int',required=False,default=2000),
limitIO=dict(type='dict',
options=dict(
total_bytes_sec=dict(required=False,type='int'),
read_bytes_sec=dict(required=False,type='int'),
write_bytes_sec=dict(required=False,type='int'),
total_iops_sec=dict(required=False,type='int'),
read_iops_sec=dict(required=False,type='int'),
write_iops_sec=dict(required=False,type='int'),
total_bytes_sec_max=dict(required=False,type='int'),
read_bytes_sec_max=dict(required=False,type='int'),
write_bytes_sec_max=dict(required=False,type='int'),
total_iops_sec_max=dict(required=False,type='int'),
read_iops_sec_max=dict(required=False,type='int'),
write_iops_sec_max=dict(required=False,type='int'),
size_iops_sec=dict(required=False,type='int'),)),
permanently=dict(type='bool', required=False, default=False),
shareable=dict(type='bool', required=False, default=False),
reason=dict(type='str', required=False,default='Managed by Ansible decort_disk'),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
user=dict(type='str',
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
id=dict(type='int', required=False, default=0),
name=dict(type='str', required=False),
force_detach=dict(type='bool', required=False, default=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
place_with=dict(type='int', required=False, default=0),
pool=dict(type='str', required=False, default=''),
sep_id=dict(type='int', required=False, default=0),
size=dict(type='int', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
def main():
module_parameters = decort_disk.build_parameters()
module_parameters = decort_disk_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
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:
#disk exist
if decon.disk_info['status'] in ["MODELED", "CREATING"]:
# Valid Disk model statii are as follows:
#
# "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['changed'] = False
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(decon.disk_id, decon.disk_info['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent':
decon.delete()
"status '{}'").format(disk_id, disk_facts['status'])
elif disk_facts['status'] in ["CREATED", "ASSIGNED"]:
if amodule.params['state'] == 'absent':
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':
decon.action()
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]:
#re-provision disk
if amodule.params['state'] in ('present'):
decon.create()
else:
decon.nop()
elif decon.disk_info['status'] == "DELETED":
if amodule.params['state'] in ('present'):
decon.action(restore=True)
else:
decon.nop()
# resize Disk as necessary & if possible
if decon.check_amodule_argument('size', False):
decon.disk_resize(disk_facts, amodule.params['size'])
elif disk_facts['status'] == "DELETED":
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:
# no new SEP ID is explicitly specified, and no place_with option - use sepId from the disk_facts
target_sep_id = disk_facts['sepId']
disk_id = decon.disk_provision(disk_name=disk_facts['name'], # as this disk was found, its name is in the facts
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:
# preexisting Disk was not found
# disk_id =0 -> pre-existing Disk was not found.
disk_should_exist = False # we will change it back to True if Disk is created successfully
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
decon.nop()
else:
decon.create()
decon.result['failed'] = False
decon.result['changed'] = False
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']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed'] and amodule.params['state'] in ('present'):
_, decon.disk_info = decon.disk_find(decon.disk_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
# prepare Disk facts to be returned as part of decon.result and then call exit_json(...)
if disk_should_exist:
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)
if __name__ == "__main__":
main()
#SHARE

View File

@@ -29,7 +29,7 @@ class decort_group(DecortController):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find B-service ID {}.").format(arg_amodule.params['bservice_id'])
self.amodule.fail_json(**self.result)
self.fail_json(**self.result)
#find group
self.bservice_id = validated_bservice_id
self.bservice_info = bservice_info

View File

@@ -1,68 +1,19 @@
#!/usr/bin/python
#
# 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)
#
#
# Author: Aleksandr Malyavin (aleksandr.malyavin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'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 env_fallback
from ansible.module_utils.decort_utils import *
@@ -70,23 +21,17 @@ 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
self.k8s_should_exist = False
if not arg_amodule.params['workers']:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "At least one worker group must be present"
self.amodule.fail_json(**self.result)
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.amodule.fail_json(**self.result)
self.fail_json(**self.result)
if not arg_amodule.params['id']:
@@ -99,17 +44,17 @@ class decort_k8s(DecortController):
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.amodule.fail_json(**self.result)
self.fail_json(**self.result)
# fail the module -> exit
# now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'],)
arg_amodule.params['rg_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.amodule.fail_json(**self.result)
self.fail_json(**self.result)
# fail the module - exit
@@ -120,7 +65,7 @@ class decort_k8s(DecortController):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find K8CI ID {}.".format(arg_amodule.params['k8ci_id'])
self.amodule.fail_json(**self.result)
self.fail_json(**self.result)
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
@@ -132,7 +77,6 @@ class decort_k8s(DecortController):
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']
@@ -164,21 +108,16 @@ class decort_k8s(DecortController):
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.rg_id
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id
ret_dict['k8s_Masters'] = self.k8s_info['k8sGroups']['masters']
ret_dict['k8s_Workers'] = self.k8s_info['k8sGroups']['workers']
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 k8s cluster management by decort_k8s module.
"""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 k8s cluster state.
the actual Compute state.
"""
self.result['failed'] = False
self.result['changed'] = False
@@ -207,57 +146,38 @@ class decort_k8s(DecortController):
return
def create(self):
k8s_id = self.k8s_provision(self.amodule.params['name'],
self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'],
self.amodule.params['vins_id'],
self.amodule.params['network_plugin'],
self.amodule.params['master_count'],
self.amodule.params['master_cpu'],
self.amodule.params['master_ram'],
self.amodule.params['master_disk'],
self.amodule.params['master_sepid'],
self.amodule.params['master_pool'],
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['ha_lb'],
self.amodule.params['additionalSANs'],
self.amodule.params['init_conf'],
self.amodule.params['cluster_conf'],
self.amodule.params['kublet_conf'],
self.amodule.params['kubeproxy_conf'],
self.amodule.params['join_conf'],
self.amodule.params['oidc_cert'],
self.amodule.params['description'],
self.amodule.params['extnet_only'],
)
self.amodule.params['description'],)
if not k8s_id:
self.result['failed'] = True
self.amodule.fail_json(**self.result)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id,
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=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 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'])
return
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_should_exist = False
return
def action(self,disared_state,started=True):
#k8s state
self.k8s_state(self.k8s_info, disared_state, started)
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,
@@ -265,11 +185,7 @@ class decort_k8s(DecortController):
if started == True and self.k8s_info['techStatus'] == "STOPPED":
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_info['techStatus'] == "STARTED"
#check groups and modify if needed
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
if self.result['changed'] == True:
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#TODO check workers metadata and modify if needed
return
@staticmethod
@@ -310,34 +226,25 @@ class decort_k8s(DecortController):
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
name=dict(type='str', required=False, default=""),
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=""),
vins_id=dict(type='int', required=False,default=None),
k8ci_id=dict(type='int', required=True),
network_plugin=dict(type='str',required=False,default="flannel"),
wg_name=dict(type='str', required=False),
master_count=dict(type='int', default=1),
master_cpu=dict(type='int', default=2),
master_ram=dict(type='int', default=2048),
master_disk=dict(type='int', default=10),
master_sepid=dict(type='int', required=False, default=None),
master_pool=dict(type='str', required=False, default=None),
workers=dict(type='list',required=True),
workers_metadata=dict(type='bool',required=False,default=False),
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),
ha_lb=dict(type='bool', default=False),
extnet_only=dict(type='bool', default=False),
additionalSANs=dict(type='list',required=False, default=None),
init_conf=dict(type='dict', required=False, default=None),
cluster_conf=dict(type='str', required=False, default=None),
kublet_conf=dict(type='str', required=False, default=None),
kubeproxy_conf=dict(type='str', required=False, default=None),
join_conf=dict(type='str', required=False, default=None),
oidc_cert=dict(type='raw',required=False,default=None),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),)
@@ -412,7 +319,7 @@ def main():
if amodule.params['state'] == 'absent':
subj.nop()
if amodule.params['state'] in ('present','started'):
subj.create()
subj.create()
elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
subj.error()

View File

@@ -1,11 +1,15 @@
#!/usr/bin/python
#
# 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)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
@@ -20,14 +24,15 @@ description: >
network port forwarding rules, restart guest OS and delete a virtual machine thus releasing
corresponding cloud resources.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 3.8
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python 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:
- 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.
@@ -226,7 +231,7 @@ options:
choices: [ present, absent, poweredon, poweredoff, halted, paused, check ]
tags:
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.
required: no
user:
@@ -282,14 +287,24 @@ EXAMPLES = '''
name: SimpleVM
cpu: 2
ram: 4096
boot_disk: 10
boot_disk:
size: 10
model: ovs
pool: boot
image_name: "Ubuntu 16.04 v1.1"
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
tags:
PROJECT:Ansible
STATUS:Test
tags: "PROJECT:Ansible STATUS:Test"
account_name: "Development"
rg_name: "ANewVDC"
delegate_to: localhost
@@ -322,6 +337,18 @@ EXAMPLES = '''
state: poweredoff
delegate_to: localhost
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 = '''
@@ -531,7 +558,9 @@ class decort_kvmvm(DecortController):
"shell": '/bin/bash'}
]}
elif self.amodule.params['ci_user_data']:
cloud_init_params = self.amodule.params['ci_user_data']
cloud_init_params = {}
for ci_param in self.amodule.params['ci_user_data']:
cloud_init_params.update(ci_param)
else:
cloud_init_params = None
# if we get through here, all parameters required to create new Compute instance should be at hand
@@ -544,8 +573,6 @@ class decort_kvmvm(DecortController):
image_id=image_facts['id'],
annotation=self.amodule.params['annotation'],
userdata=cloud_init_params,
sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None,
pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None,
start_on_create=start_compute)
self.comp_should_exist = True
@@ -564,10 +591,6 @@ class decort_kvmvm(DecortController):
'techStatus': "STOPPED",
'interfaces': [], # new compute instance is created network-less
'disks': [], # new compute instance is created without any data disks attached
'tags': {},
'affinityLabel': "",
'affinityRules': [],
'antiAffinityRules': [],
}
#
@@ -629,13 +652,11 @@ class decort_kvmvm(DecortController):
self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'])
label=self.amodule.params['affinity_label'],)
return
def package_facts(self, check_mode=False):
@@ -665,7 +686,6 @@ class decort_kvmvm(DecortController):
public_ips=[], # direct IPs; this list can be empty
private_ips=[], # IPs on ViNSes; usually, at least one IP is listed
nat_ip="", # IP of the external ViNS interface; can be empty.
tags={},
)
if check_mode or self.comp_info is None:
@@ -683,8 +703,6 @@ class decort_kvmvm(DecortController):
ret_dict['tech_status'] = self.comp_info['techStatus']
ret_dict['account_id'] = self.comp_info['accountId']
ret_dict['rg_id'] = self.comp_info['rgId']
if self.comp_info['tags']:
ret_dict['tags'] = self.comp_info['tags']
# if the VM is an imported VM, then the 'accounts' list may be empty,
# so check for this case before trying to access login and passowrd values
if len(self.comp_info['osUsers']):
@@ -746,8 +764,6 @@ class decort_kvmvm(DecortController):
required=True,
choices=['legacy', 'oauth2', 'jwt']),
boot_disk=dict(type='int', required=False),
sep_id=dict(type='int', required=False),
pool=dict(type='str', required=False),
controller_url=dict(type='str', required=True),
# count=dict(type='int', required=False, default=1),
cpu=dict(type='int', required=False),
@@ -774,11 +790,11 @@ class decort_kvmvm(DecortController):
rg_name=dict(type='str', default=""),
ssh_key=dict(type='str', required=False),
ssh_key_user=dict(type='str', required=False),
tag=dict(type='dict', required=False),
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),
ci_user_data=dict(type='list', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']),

View File

@@ -1,335 +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.amodule.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.amodule.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.amodule.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.amodule.fail_json(**self.result)
self.rg_id, self.rg_facts = self.rg_find(self._acc_id,0, arg_rg_name=arg_amodule.params['rg_name'])
if self.rg_id and self.vins_id:
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['ha_lb'],
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['primaryNode'],
self.lb_facts['frontendHAIP'],
self.lb_facts['backendHAIP'],
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['primaryNode'],
self.lb_facts['frontendHAIP'],
self.lb_facts['backendHAIP'],
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),
ha_lb=dict(type='bool', required=False, default=False),
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

@@ -22,7 +22,8 @@ description: >
This module can be used to obtain image ID of an OS image in DECORT cloud to use with subsequent calls to
decort_vm module for batch VM provisioning. It will speed up VM creation and save a bunch of extra calls to
DECORT cloud controller on each VM creation act.
Note that this module is effectively an information provisioner. It is not designed to and does not manage
nor change state of OS image (or any other) objects in DECORT cloud.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
@@ -67,8 +68,8 @@ options:
image_name:
description:
- Name of the OS image to use. Module will return the ID of this image.
- 'The specified image name will be looked up in the target DECORT controller and error will be generated
- if no matching image is found.'
- 'The specified image name will be looked up in the target DECORT controller and error will be generated if
no matching image is found.'
required: yes
jwt:
description:
@@ -108,6 +109,10 @@ options:
- 'This parameter is required when I(authenticator=legacy) and ignored for other authentication modes.'
- If not specified in the playbook, the value will be taken from DECORT_USER environment variable.
required: no
vdc_id:
description:
- ID of the VDC to limit the search of the OS image to.
required: no
verify_ssl:
description:
- 'Controls SSL verification mode when making API calls to DECORT controller. Set it to False if you
@@ -129,143 +134,19 @@ options:
- 'This context data is expected to uniquely identify the task carried out by this module invocation so
that up-level orchestrator could match returned information to the its internal entities.'
required: no
account_name:
description:
- 'Account name. Used to get a unique integer account ID.'
required: no
virt_id:
description:
- 'A unique integer identifier for the virtual image.'
- 'Can be used to obtain information about a virtual image, as well as to create a virtual image and
- bind another operating system image to it.'
required: no
virt_name:
description:
- 'Name of the virtual image. Used to get the `virt_id`, and later information about the virtual image,
- as well as to create a virtual image and bind another operating system image to it.'
required: no
state:
description:
- 'The state of the images. If set to present, operating system images will be created to which
- the account specified in `account_Id` or `account_name` is bound. If set to absent, they will be removed.
required: no
drivers:
description:
- 'A list of compute types (eg virtual servers) that are appropriate for the operating system image.
- Note: `KVM_X86`. Used when creating an operating system image.'
required: no
architecture:
description:
- 'Binary architecture of the image. Note. `X86_64` or `PPC64_LE`. Used when creating
-an operating system image.'
required: no
imagetype:
description:
- 'Image type. `linux`, `windows` or `other`. The default is `linux`. Used when creating
- an operating system image.'
required: no
boottype:
description:
- 'Image upload type. `bios` or `uefi`. The default is `uefi`. Used when creating an operating
-system image.'
required: no
url:
description:
- 'Uniform resource locator (URL) pointing to the iso image of the operating system. Used when
-creating an operating system image.'
required: no
sepId:
description:
- 'The unique integer ID of the storage provider endpoint. Specified in pair with `poolName`.
- Used when creating an operating system image.'
required: no
poolName:
description:
- 'The pool in which the image will be created. Specified in pair with `sepId`. Used when creating
- an operating system image.'
required: no
hotresize:
description:
- 'Whether the image supports "hot" resizing. The default is `false`. Used when creating an operating
- system image.'
required: no
image_username:
description:
- 'An optional username for the image. Used when creating an operating system image.'
required: no
image_password:
description:
- 'An optional password for the image. Used when creating an operating system image. Used when creating
- an operating system image.'
required: no
usernameDL:
description:
- 'The username for loading the binary media. Used in conjunction with `passwordDL`. Used when creating
- an operating system image'
required: no
passwordDL:
description:
- 'The password for loading the binary media. Used in conjunction with `usernameDL`. Used when creating
- an operating system image.'
required: no
permanently:
description:
- 'Whether to permanently delete the image. Used when deleting an image. The default is false.'
required: no
'''
EXAMPLES = '''
- name: create_osimage
- name: locate OS image specified by its name, store result in image_to_use variable.
decort_osimage:
authenticator: oauth2
verify_ssl: False
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://ds1.digitalenergy.online"
state: present
image_name: "alpine_linux3.14.0"
account_Id: 12345
url: "https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-virt-3.14.0-x86_64.iso"
boottype: "uefi"
imagetype: "linux"
hotresize: False
image_username: "test"
image_password: "p@ssw0rd"
usernameDL: "testDL"
passwordDL: "p@ssw0rdDL"
architecture: "X86_64"
drivers: "KVM_X86"
image_name: "Ubuntu 18.04 v1.2.5"
account_name: "GreyseDevelopment"
delegate_to: localhost
register: osimage
- name: get_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
account_Id: 12345
delegate_to: localhost
register: osimage
- name: create_virtual_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0"
virt_name: "alpine_last"
delegate_to: localhost
register: osimage
- name: rename_osimage
decort_osimage:
authenticator: oauth2
controller_url: "https://ds1.digitalenergy.online"
image_name: "alpine_linux_3.14.0v2.0"
image_id: 54321
delegate_to: localhost
register: osimage
register: image_to_use
'''
RETURN = '''
@@ -276,7 +157,6 @@ facts:
sample:
facts:
id: 100
linkto: 80
name: "Ubuntu 16.04 v1.0"
size: 3
sep_id: 1
@@ -291,206 +171,94 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_osimage(DecortController):
def __init__(self,amodule):
super(decort_osimage, self).__init__(amodule)
self.validated_image_id = 0
self.validated_virt_image_id = 0
self.validated_image_name = amodule.params['image_name']
self.validated_virt_image_name = None
self.validated_virt_image_id = amodule.params['virt_id']
if amodule.params['account_name']:
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
else:
self.validated_account_id = amodule.params['account_Id']
if self.validated_account_id == 0:
# we failed either to find or access the specified account - fail the module
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
amodule.fail_json(**self.result)
def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False):
"""Package a dictionary of OS image according to the decort_osimage module specification. This
dictionary will be returned to the upstream Ansible engine at the completion of the module run.
@param arg_osimage_facts: dictionary with OS image facts as returned by API call to .../images/list
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode.
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")
@return: dictionary with OS image specs populated from arg_osimage_facts.
"""
ret_dict = dict(id=0,
name="none",
size=0,
type="none",
state="CHECK_MODE",
)
def decort_image_find(self, amodule):
# function that finds the OS image
image_id, image_facts = self.image_find(image_id=amodule.params['image_id'], image_name=self.validated_image_name,
account_id=self.validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
pool=amodule.params['pool'])
return image_id, image_facts
def decort_virt_image_find(self, amodule):
# function that finds a virtual image
image_id, image_facts = self.virt_image_find(image_id=amodule.params['virt_id'],
account_id=self.validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
virt_name=amodule.params['virt_name'],
pool=amodule.params['pool'])
return image_id, image_facts
def decort_image_create(self,amodule):
# function that creates OS image
image_facts = self.image_create(img_name=self.validated_image_name,
url=amodule.params['url'],
gid=amodule.params['gid'],
boottype=amodule.params['boottype'],
imagetype=amodule.params['imagetype'],
hotresize=amodule.params['hotresize'],
username=amodule.params['image_username'],
password=amodule.params['image_password'],
account_Id=amodule.params['account_Id'],
usernameDL=amodule.params['usernameDL'],
passwordDL=amodule.params['passwordDL'],
sepId=amodule.params['sepId'],
poolName=amodule.params['poolName'],
architecture=amodule.params['architecture'],
drivers=amodule.params['drivers'])
self.result['changed'] = True
return image_facts
def decort_virt_image_link(self,amodule):
# function that links an OS image to a virtual one
self.virt_image_link(imageId=self.validated_virt_image_id, targetId=self.validated_image_id)
image_id, image_facts = decort_osimage.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
self.result['msg'] = ("Image '{}' linked to virtual image '{}'").format(self.validated_image_id,
decort_osimage.decort_osimage_package_facts(image_facts)['id'],)
return image_id, image_facts
def decort_image_delete(self,amodule):
# function that removes an image
self.image_delete(imageId=amodule.image_id_delete, permanently=amodule.params['permanently'])
self.result['changed'] = True
self.result['msg'] = ("Image '{}' deleted").format(amodule.image_id_delete)
def decort_virt_image_create(self,amodule):
# function that creates a virtual image
image_facts = self.virt_image_create(name=amodule.params['virt_name'], targetId=self.validated_image_id)
image_id, image_facts = decort_osimage.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
return image_id, image_facts
def decort_image_rename(self,amodule):
# image renaming function
image_facts = self.image_rename(imageId=self.validated_image_id, name=amodule.params['image_name'])
self.result['msg'] = ("Image renamed successfully")
image_id, image_facts = decort_osimage.decort_image_find(self, amodule)
return image_id, image_facts
def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False):
"""Package a dictionary of OS image according to the decort_osimage module specification. This
dictionary will be returned to the upstream Ansible engine at the completion of the module run.
@param arg_osimage_facts: dictionary with OS image facts as returned by API call to .../images/list
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode.
@return: dictionary with OS image specs populated from arg_osimage_facts.
"""
ret_dict = dict(id=0,
name="none",
size=0,
type="none",
state="CHECK_MODE", )
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
if arg_osimage_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = arg_osimage_facts['id']
ret_dict['name'] = arg_osimage_facts['name']
ret_dict['size'] = arg_osimage_facts['size']
ret_dict['type'] = arg_osimage_facts['type']
# ret_dict['arch'] = arg_osimage_facts['architecture']
ret_dict['sep_id'] = arg_osimage_facts['sepId']
ret_dict['pool'] = arg_osimage_facts['pool']
ret_dict['state'] = arg_osimage_facts['status']
ret_dict['linkto'] = arg_osimage_facts['linkTo']
ret_dict['accountId'] = arg_osimage_facts['accountId']
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
if arg_osimage_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = arg_osimage_facts['id']
ret_dict['name'] = arg_osimage_facts['name']
ret_dict['size'] = arg_osimage_facts['size']
ret_dict['type'] = arg_osimage_facts['type']
# ret_dict['arch'] = arg_osimage_facts['architecture']
ret_dict['sep_id'] = arg_osimage_facts['sepId']
ret_dict['pool'] = arg_osimage_facts['pool']
ret_dict['state'] = arg_osimage_facts['status']
def decort_osimage_parameters():
"""Build and return a dictionary of parameters expected by decort_osimage module in a form accepted
by AnsibleModule utility class."""
return ret_dict
return dict(
app_id=dict(type='str',
def decort_osimage_parameters():
"""Build and return a dictionary of parameters expected by decort_osimage module in a form accepted
by AnsibleModule utility class."""
return dict(
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
pool=dict(type='str', required=False, default=""),
sep_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=False),
account_Id=dict(type='int', required=False),
user=dict(type='str',
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
image_name=dict(type='str', required=True),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
image_name=dict(type='str', required=False),
image_id=dict(type='int', required=False,default=0),
virt_id=dict(type='int', required=False, default=0),
virt_name=dict(type='str', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
drivers=dict(type='str', required=False, default="KVM_X86"),
architecture=dict(type='str', required=False, default="X86_64"),
imagetype=dict(type='str', required=False, default="linux"),
boottype=dict(type='str', required=False, default="uefi"),
url=dict(type='str', required=False),
gid=dict(type='int', required=False, default=0),
sepId=dict(type='int', required=False, default=0),
poolName=dict(type='str', required=False),
hotresize=dict(type='bool', required=False, default=False),
image_username=dict(type='str', required=False),
image_password=dict(type='str', required=False),
usernameDL=dict(type='str', required=False),
passwordDL=dict(type='str', required=False),
permanently=dict(type='bool', required=False, default=False),
)
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
pool=dict(type='str', required=False, default=""),
sep_id=dict(type='int', required=False, default=0),
account_name=dict(type='str', required=True),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
vdc_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when
# creating DecortController
# 2) obtain a list of OS images accessible to the specified account (and optionally - within
# the specified VDC)
# 3) match specified OS image by its name - if image is not found abort the module
# 5) report result to Ansible
def main():
module_parameters = decort_osimage.decort_osimage_parameters()
module_parameters = decort_osimage_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
@@ -505,69 +273,30 @@ def main():
],
)
decon = decort_osimage(amodule)
if amodule.params['image_name'] or amodule.params['image_id']:
image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0:
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
if amodule.params['state'] == "present" and decon.validated_image_id == 0 and amodule.params['image_name'] and amodule.params['url']:
decort_osimage.decort_image_create(decon,amodule)
decon.result['changed'] = True
image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
decon.result['msg'] = ("OS image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id'])
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
elif amodule.params['state'] == "absent":
if amodule.params['image_name'] or amodule.params['image_id'] and\
decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']:
amodule.image_id_delete = decon.validated_image_id
decort_osimage.decort_image_delete(decon,amodule)
if amodule.params['virt_name'] or amodule.params['virt_id']:
image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule)
if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0:
decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
decon.validated_virt_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
decon.validated_virt_image_name = decort_osimage.decort_osimage_package_facts(image_facts)['name']
if decort_osimage.decort_osimage_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and decon.validated_image_id > 0:
image_id, image_facts = decort_osimage.decort_virt_image_create(decon,amodule)
decon.result['msg'] = ("Virtual image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id'])
decon.result['changed'] = True
elif decort_osimage.decort_osimage_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and decon.validated_image_id == 0:
decon.result['msg'] = ("Cannot find OS image")
amodule.fail_json(**decon.result)
if decon.validated_image_id:
if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.validated_image_id:
decort_osimage.decort_virt_image_link(decon,amodule)
decon.result['changed'] = True
amodule.exit_json(**decon.result)
if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent":
decon.result['msg'] = ("Osimage module cannot delete virtual images.")
decon.result['failed'] = True
amodule.exit_json(**decon.result)
decon = DecortController(amodule)
# we need account ID to locate OS images - find the account by the specified name and get its ID
validated_account_id, _ = decon.account_find(amodule.params['account_name'])
if validated_account_id == 0:
# we failed either to find or access the specified account - fail the module
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
amodule.fail_json(**decon.result)
image_id, image_facts = decon.image_find(image_id=0, image_name=amodule.params['image_name'],
account_id=validated_account_id, rg_id=0,
sepid=amodule.params['sep_id'],
pool=amodule.params['pool'])
if decon.result['failed'] == True:
# we failed to find the specified image - fail the module
decon.result['changed'] = False
amodule.fail_json(**decon.result)
decon.result['facts'] = decort_osimage_package_facts(image_facts, amodule.check_mode)
decon.result['changed'] = False # decort_osimage is a read-only module - make sure the 'changed' flag is set to False
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()

View File

@@ -209,255 +209,90 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_rg(DecortController):
def __init__(self,amodule):
super(decort_rg, self).__init__(amodule)
self.validated_acc_id = 0
self.validated_rg_id = 0
self.validated_rg_facts = None
def decort_rg_package_facts(arg_rg_facts, arg_check_mode=False):
"""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.
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)
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
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
be returned to the upstream Ansible engine at the completion of the module run.
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
@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 check_mode:
# in check mode return immediately with the default values
return ret_dict
#if arg_rg_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.rg_facts['id']
ret_dict['name'] = self.rg_facts['name']
ret_dict['state'] = self.rg_facts['status']
ret_dict['account_id'] = self.rg_facts['accountId']
ret_dict['gid'] = self.rg_facts['gid']
ret_dict['quota'] = self.rg_facts['resourceLimits']
ret_dict['resTypes'] = self.rg_facts['resourceTypes']
ret_dict['defNetId'] = self.rg_facts['def_net_id']
ret_dict['defNetType'] = self.rg_facts['def_net_type']
ret_dict['ViNS'] = self.rg_facts['vins']
ret_dict['computes'] = self.rg_facts['vms']
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
def parameters():
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted
by AnsibleModule utility class."""
if arg_rg_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
access=dict(type='dict'),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
ret_dict['id'] = arg_rg_facts['id']
ret_dict['name'] = arg_rg_facts['name']
ret_dict['state'] = arg_rg_facts['status']
ret_dict['account_id'] = arg_rg_facts['accountId']
ret_dict['gid'] = arg_rg_facts['gid']
return ret_dict
def decort_rg_parameters():
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
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_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
def_netType=dict(type='str', choices=['PRIVATE','PUBLIC', 'NONE'], default='PRIVATE'),
def_netId=dict(type='int', default=0),
extNetId=dict(type='int', default=0),
extNetIp=dict(type='str', default=""),
owner=dict(type='str', default=""),
ipcidr=dict(type='str', default=""),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
rename=dict(type='str', default=""),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
quotas=dict(type='dict', required=False),
resType=dict(type='list'),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
permanently=dict(type='bool',
default='False'),
user=dict(type='str',
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_USER'])),
rg_name=dict(type='str', required=False,),
rg_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
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']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_name=dict(type='str', required=True,),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the RG with the specified id or rg_name:name exists
# 3) if RG does not exist -> deploy
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the RG with the specified id or rg_name:name exists
# 3) if RG does not exist -> deploy
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
def main():
module_parameters = decort_rg.parameters()
module_parameters = decort_rg_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
@@ -472,52 +307,157 @@ def main():
],
)
decon = decort_rg(amodule)
#amodule.check_mode=True
if decon.validated_rg_id > 0:
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
decon.error()
elif decon.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == "disabled":
decon.enable()
if amodule.params['state'] in ['present', 'enabled']:
if amodule.params['quotas'] or amodule.params['resType'] or amodule.params['rename'] != "":
decon.update()
if amodule.params['access']:
decon.access()
if amodule.params['def_netId'] > 0:
decon.setDefNet()
elif decon.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
decon.destroy()
elif amodule.params['state'] == 'present':
decon.restore()
elif decon.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == ("enabled"):
decon.enable()
decon = DecortController(amodule)
# 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:
if amodule.params['state'] in ('present', 'enabled'):
decon.create()
if amodule.params['access']:
decon.access()
elif amodule.params['state'] in ('disabled'):
decon.error()
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'):
# need to re-provision RG
decon.check_amodule_argument('rg_name')
# As we already have validated 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'] == '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']:
amodule.fail_json(**decon.result)
else:
if decon.rg_should_exist:
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
else:
amodule.exit_json(**decon.result)
# prepare RG facts to be returned as part of decon.result and then call exit_json(...)
# rg_facts = None
if rg_should_exist:
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)
if __name__ == "__main__":
main()

View File

@@ -6,6 +6,9 @@
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
@@ -20,13 +23,14 @@ description: >
modify its characteristics, and delete it.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 3.8
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python 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:
- 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.
@@ -238,286 +242,109 @@ facts:
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
import paramiko
from ansible.module_utils.decort_utils import *
class decort_vins(DecortController):
def __init__(self,arg_amodule):
super(decort_vins, self).__init__(arg_amodule)
self.vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
vins_facts = None # will hold ViNS facts
validated_rg_id = 0
rg_facts = None # will hold RG facts
validated_acc_id = 0
acc_facts = None # will hold Account facts
def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False):
"""Package a dictionary of ViNS facts according to the decort_vins module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
if arg_amodule.params['vins_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
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False)
if self.vins_id == 0:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.amodule.fail_json(**self.result)
self.vins_level = "ID"
#raise Exception(self.vins_facts)
validated_acc_id = self.vins_facts['accountId']
validated_rg_id = self.vins_facts['rgId']
elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID
self.vins_level = "RG"
# This call to rg_find will abort the module if no RG with such ID is present
validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID
arg_amodule.params['rg_id'], arg_rg_name="")
# This call to vins_find may return vins_id=0 if no ViNS found
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=0,
rg_id=arg_amodule.params['rg_id'],
rg_facts=rg_facts,
check_state=False)
# TODO: add checks and setup ViNS presence flags accordingly
pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
# Specified account must be present and accessible by the user, otherwise abort the module
validated_acc_id, acc_facts = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id'])
if not validated_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.amodule.fail_json(**self.result)
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
# RG with the specified name must be present under the account, otherwise abort the module
validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name'])
if (not validated_rg_id or
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
self.result['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result)
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
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
rg_id=validated_rg_id,
rg_facts=rg_facts,
check_state=False)
self.vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level
# This call to vins_find may return vins_id=0 if no ViNS found
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=validated_acc_id,
rg_id=0,
rg_facts=rg_facts,
check_state=False)
self.vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly
else:
# 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
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'] == '':
self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0."
if arg_amodule.params['rg_name'] == "":
# rg_name without account specified
self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
self.amodule.fail_json(**self.result)
@param arg_vins_facts: dictionary with viNS facts as returned by API call to .../vins/get
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
return
self.rg_id = validated_rg_id
self.acc_id = validated_acc_id
return
def create(self):
self.vins_id = self.vins_provision(self.amodule.params['vins_name'],
self.acc_id, self.rg_id,
self.amodule.params['ipcidr'],
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'],
self.amodule.params['annotation'])
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']:
_, self.vins_facts = self.vins_find(self.vins_id)
if self.amodule.params['connect_to']:
self.vins_update_ifaces(self.vins_facts,self.amodule.params['connect_to'],)
if self.amodule.params['mgmtaddr']:
self.vins_update_mgmt(self.vins_facts,self.amodule.params['mgmtaddr'])
return
def action(self,d_state='',restore=False):
if restore == True:
self.vins_restore(arg_vins_id=self.vins_id)
self.vins_state(self.vins_facts, 'enabled')
self.vins_facts['status'] = "ENABLED"
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
self.vins_update_extnet(self.vins_facts,
self.amodule.params['ext_net_id'],
self.amodule.params['ext_ip_addr'],
)
if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED":
self.vins_state(self.vins_facts, d_state)
self.vins_facts['status'] = "ENABLED"
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
d_state = ''
if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED":
self.vins_update_ifaces(self.vins_facts,
self.amodule.params['connect_to'],
)
if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id)
self.vins_update_mgmt(self.vins_facts,
self.amodule.params['mgmtaddr'],
)
if d_state != '':
self.vins_state(self.vins_facts, d_state)
return
def delete(self):
self.vins_delete(self.vins_id, permanently=True)
self.vins_facts['status'] = 'DESTROYED'
return
def nop(self):
"""No operation (NOP) handler for ViNS management by decort_vins module.
This function is intended to be called from the main switch construct of the module
when current state -> desired state change logic does not require any changes to
the actual ViNS state.
"""
self.result['failed'] = False
self.result['changed'] = False
if self.vins_id:
self.result['msg'] = ("No state change required for ViNS ID {} because of its "
"current status '{}'.").format(self.vins_id, self.vins_facts['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent ViNS 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 ViNS ID {} in the "
"current status '{}'").format(self.vins_id,
self.amodule.params['state'],
self.vins_facts['status'])
else:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"ViNS name '{}'").format(self.amodule.params['state'],
self.amodule.params['vins_name'])
return
def package_facts(self, arg_check_mode=False):
"""Package a dictionary of ViNS facts according to the decort_vins module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
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.vins_facts['id']
ret_dict['name'] = self.vins_facts['name']
ret_dict['state'] = self.vins_facts['status']
ret_dict['account_id'] = self.vins_facts['accountId']
ret_dict['rg_id'] = self.vins_facts['rgId']
ret_dict['int_net_addr'] = self.vins_facts['network']
ret_dict['gid'] = self.vins_facts['gid']
custom_interfaces = list(filter(lambda i: i['type']=="CUSTOM",self.vins_facts['VNFDev']['interfaces']))
if custom_interfaces:
ret_dict['custom_net_addr'] = []
for runner in custom_interfaces:
ret_dict['custom_net_addr'].append(runner['ipAddress'])
mgmt_interfaces = list(filter(lambda i: i['listenSsh'] and i['name']!="ens9",self.vins_facts['VNFDev']['interfaces']))
if mgmt_interfaces:
ret_dict['ssh_ipaddr'] = []
for runner in mgmt_interfaces:
ret_dict['ssh_ipaddr'].append(runner['ipAddress'])
ret_dict['ssh_password'] = self.vins_facts['VNFDev']['config']['mgmt']['password']
ret_dict['ssh_port'] = 9022
if self.vins_facts['vnfs'].get('GW'):
gw_config = self.vins_facts['vnfs']['GW']['config']
ret_dict['ext_ip_addr'] = gw_config['ext_net_ip']
ret_dict['ext_net_id'] = gw_config['ext_net_id']
else:
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if arg_check_mode:
# in check mode return immediately with the default values
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."""
if arg_vins_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
return dict(
account_id=dict(type='int', required=False,default=0),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''),
ipcidr=dict(type='str', required=False, default=''),
mgmtaddr=dict(type='list',required=False, default=[]),
custom_config=dict(type='bool',required=False, default=False),
config_save=dict(type='bool',required=False, default=False),
connect_to=dict(type='list', default=[], required=False),
jwt=dict(type='str',
ret_dict['id'] = arg_vins_facts['id']
ret_dict['name'] = arg_vins_facts['name']
ret_dict['state'] = arg_vins_facts['status']
ret_dict['account_id'] = arg_vins_facts['accountId']
ret_dict['rg_id'] = arg_vins_facts['rgId']
ret_dict['int_net_addr'] = arg_vins_facts['network']
ret_dict['gid'] = arg_vins_facts['gid']
if arg_vins_facts['vnfs'].get('GW'):
gw_config = arg_vins_facts['vnfs']['GW']['config']
ret_dict['ext_ip_addr'] = gw_config['ext_net_ip']
ret_dict['ext_net_id'] = gw_config['ext_net_id']
else:
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
# arg_vins_facts['vnfs']['GW']['config']
# ext_ip_addr -> ext_net_ip
# ??? -> ext_net_id
# tech_status -> techStatus
return ret_dict
def decort_vins_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=''),
app_id=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',
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_id=dict(type='int', required=False, default=0),
rg_name=dict(type='str', required=False, default=''),
verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=False, default=0),
vins_name=dict(type='str', required=False,default=""),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
ext_net_id=dict(type='int', required=False, default=-1),
ext_ip_addr=dict(type='str', required=False, default=''),
ipcidr=dict(type='str', required=False, default=''),
mgmtaddr=dict(type='str',required=False, default=''),
custom_config=dict(type='bool',required=False, default=False),
config_save=dict(type='bool',required=False, default=False),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_id=dict(type='int', required=False, default=0),
rg_name=dict(type='str', required=False, default=''),
verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=False, default=0),
vins_name=dict(type='str', required=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
# Workflow digest:
@@ -528,7 +355,7 @@ class decort_vins(DecortController):
# 5) report result to Ansible
def main():
module_parameters = decort_vins.build_parameters()
module_parameters = decort_vins_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
@@ -541,12 +368,86 @@ def main():
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['vins_id', 'vins_name'],
],
)
decon = decort_vins(amodule)
decon = DecortController(amodule)
vins_id = 0
vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
vins_facts = None # will hold ViNS facts
validated_rg_id = 0
rg_facts = None # will hold RG facts
validated_acc_id = 0
acc_facts = None # will hold Account facts
if amodule.params['vins_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
vins_id, vins_facts = decon.vins_find(amodule.params['vins_id'])
if not vins_id:
decon.result['failed'] = True
decon.result['msg'] = "Specified ViNS ID {} not found.".format(amodule.params['vins_id'])
decon.fail_json(**decon.result)
vins_level = "ID"
validated_acc_id = vins_facts['accountId']
validated_rg_id = vins_facts['rgId']
elif amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID
vins_level = "RG"
# This call to rg_find will abort the module if no RG with such ID is present
validated_rg_id, rg_facts = decon.rg_find(0, # account ID set to 0 as we search for RG by RG ID
amodule.params['rg_id'], arg_rg_name="")
# This call to vins_find may return vins_id=0 if no ViNS found
vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'],
account_id=0,
rg_id=amodule.params['rg_id'],
check_state=False)
# TODO: add checks and setup ViNS presence flags accordingly
pass
elif amodule.params['account_id'] or amodule.params['account_name'] != "":
# 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.")
decon.fail_json(**decon.result)
if 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
# RG with the specified name must be present under the account, otherwise abort the module
validated_rg_id, rg_facts = decon.rg_find(validated_acc_id, 0, amodule.params['rg_name'])
if (not validated_rg_id or
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
decon.result['failed'] = True
decon.result['msg'] = "RG name '{}' not found or has invalid state.".format(amodule.params['rg_name'])
decon.fail_json(**decon.result)
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'],
account_id=0, # set to 0, as we are looking for ViNS under RG
rg_id=validated_rg_id,
check_state=False)
vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level
# This call to vins_find may return vins_id=0 if no ViNS found
vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'],
account_id=validated_acc_id,
rg_id=0,
check_state=False)
vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly
else:
# 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
decon.result['failed'] = True
if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "":
decon.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0."
if amodule.params['rg_name'] == "":
# rg_name without account specified
decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
decon.fail_json(**decon.result)
#
# Initial validation of module arguments is complete
#
@@ -563,70 +464,119 @@ def main():
# if cconfig_save is true, only config save without other updates
vins_should_exist = False
if decon.vins_id:
if vins_id:
vins_should_exist = True
if decon.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
if vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
# error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
"status '{}'").format(decon.vins_id, decon.vins_facts['status'])
elif decon.vins_facts['status'] == "DISABLED":
"status '{}'").format(vins_id, vins_facts['status'])
elif vins_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.delete()
decon.vins_delete(vins_id, permanently=True)
vins_facts['status'] = 'DESTROYED'
vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update ViNS, leave in disabled state
decon.action()
decon.vins_update(vins_facts,
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
elif amodule.params['state'] == 'enabled':
# update ViNS and enable
decon.action('enabled')
elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]:
decon.vins_update(vins_facts,
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
decon.vins_state(vins_facts, 'enabled')
elif vins_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
decon.delete()
decon.vins_delete(vins_id, permanently=True)
vins_facts['status'] = 'DESTROYED'
vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update ViNS
decon.action()
decon.vins_update(vins_facts,
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
amodule.params['mgmtaddr'],
)
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
decon.action('disabled')
elif decon.vins_facts['status'] == "DELETED":
decon.vins_state(vins_facts, 'disabled')
decon.vins_update(vins_facts,
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'])
elif vins_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
decon.action(restore=True)
decon.vins_restore(arg_vins_id=vins_id)
decon.vins_state(vins_facts, 'enabled')
vins_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
decon.delete()
decon.vins_delete(vins_id, permanently=True)
vins_facts['status'] = 'DESTROYED'
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
# error
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
"current status '{}'").format(vins_id,
amodule.params['state'],
vins_facts['status'])
vins_should_exist = False
elif decon.vins_facts['status'] == "DESTROYED":
elif vins_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
# need to re-provision ViNS;
decon.create()
# need to re-provision ViNS; some attributes may be changed, some stay the same.
# account and RG - stays the same
# vins_name - stays the same
# IPcidr - take from module arguments
# ext IP address - take from module arguments
# annotation - take from module arguments
vins_id = decon.vins_provision(vins_facts['name'],
validated_acc_id, validated_rg_id,
amodule.params['ipcidr'],
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
amodule.params['annotation'])
vins_should_exist = True
elif amodule.params['state'] == 'absent':
decon.nop()
# nop
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("No state change required for ViNS ID {} because of its "
"current status '{}'").format(vins_id,
vins_facts['status'])
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
# error
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
"current status '{}'").format(vins_id,
amodule.params['state'],
vins_facts['status'])
else:
# Preexisting ViNS was not found.
vins_should_exist = False # we will change it back to True if ViNS is created or restored
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
decon.nop()
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
"non-existent ViNS name '{}'").format(amodule.params['vins_name'])
elif amodule.params['state'] in ('present', 'enabled'):
decon.check_amodule_argument('vins_name')
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
decon.create()
vins_id = decon.vins_provision(amodule.params['vins_name'],
validated_acc_id, validated_rg_id,
amodule.params['ipcidr'],
amodule.params['ext_net_id'], amodule.params['ext_ip_addr'],
amodule.params['annotation'])
vins_should_exist = True
elif amodule.params['state'] == 'disabled':
decon.error()
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"ViNS name '{}'").format(amodule.params['state'],
amodule.params['vins_name'])
#
# conditional switch end - complete module run
#
@@ -634,9 +584,18 @@ def main():
amodule.fail_json(**decon.result)
else:
# prepare ViNS facts to be returned as part of decon.result and then call exit_json(...)
if decon.result['changed']:
_, decon.vins_facts = decon.vins_find(decon.vins_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
if vins_should_exist:
if decon.result['changed']:
# If we arrive here, there is a good chance that the ViNS is present - get fresh ViNS
# facts from # the cloud by ViNS ID.
# Otherwise, ViNS facts from previous call (when the ViNS was still in existence) will
# be returned.
_, vins_facts = decon.vins_find(vins_id)
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)

File diff suppressed because it is too large Load Diff