@ -29,48 +29,49 @@
import os
import os
import time
import time
from ansible . module_utils . six import iteritems
try :
try :
from cs import CloudStack , CloudStackException , read_config
from cs import CloudStack , CloudStackException , read_config
has_lib_cs = True
HAS_LIB_CS = True
except ImportError :
except ImportError :
has_lib_cs = False
HAS_LIB_CS = False
CS_HYPERVISORS = [
CS_HYPERVISORS = [
" KVM " , " kvm " ,
' KVM ' , ' kvm ' ,
" VMware " , " vmware " ,
' VMware ' , ' vmware ' ,
" BareMetal " , " baremetal " ,
' BareMetal ' , ' baremetal ' ,
" XenServer " , " xenserver " ,
' XenServer ' , ' xenserver ' ,
" LXC " , " lxc " ,
' LXC ' , ' lxc ' ,
" HyperV " , " hyperv " ,
' HyperV ' , ' hyperv ' ,
" UCS " , " ucs " ,
' UCS ' , ' ucs ' ,
" OVM " , " ovm " ,
' OVM ' , ' ovm ' ,
" Simulator " , " simulator " ,
' Simulator ' , ' simulator ' ,
]
]
def cs_argument_spec ( ) :
def cs_argument_spec ( ) :
return dict (
return dict (
api_key = dict ( default = None ) ,
api_key = dict ( default = os . environ . get ( ' CLOUDSTACK_KEY ' ) ) ,
api_secret = dict ( default = None , no_log = True ) ,
api_secret = dict ( default = os . environ . get ( ' CLOUDSTACK_SECRET ' ) , no_log = True ) ,
api_url = dict ( default = None ) ,
api_url = dict ( default = os . environ . get ( ' CLOUDSTACK_ENDPOINT ' ) ) ,
api_http_method = dict ( choices = [ ' get ' , ' post ' ] , default = ' get ' ) ,
api_http_method = dict ( choices = [ ' get ' , ' post ' ] , default = os . environ . get ( ' CLOUDSTACK_METHOD ' ) or ' get ' ) ,
api_timeout = dict ( type = ' int ' , default = 10 ) ,
api_timeout = dict ( type = ' int ' , default = os . environ . get ( ' CLOUDSTACK_TIMEOUT ' ) or 10 ) ,
api_region = dict ( default = ' cloudstack ' ) ,
api_region = dict ( default = os . environ . get ( ' CLOUDSTACK_REGION ' ) or ' cloudstack ' ) ,
)
)
def cs_required_together ( ) :
def cs_required_together ( ) :
return [ [ ' api_key ' , ' api_secret ' , ' api_url ' ] ]
return [ [ ' api_key ' , ' api_secret ' ] ]
class AnsibleCloudStack ( object ) :
class AnsibleCloudStack ( object ) :
def __init__ ( self , module ) :
def __init__ ( self , module ) :
if not has_lib_cs :
if not HAS_LIB_CS :
module . fail_json ( msg = " python library cs required: pip install cs " )
module . fail_json ( msg = " python library cs required: pip install cs " )
self . result = {
self . result = {
' changed ' : False ,
' changed ' : False ,
' diff ' : {
' diff ' : {
' before ' : dict ( ) ,
' before ' : dict ( ) ,
' after ' : dict ( )
' after ' : dict ( )
}
}
@ -123,26 +124,30 @@ class AnsibleCloudStack(object):
self . hypervisor = None
self . hypervisor = None
self . capabilities = None
self . capabilities = None
def _connect ( self ) :
def _connect ( self ) :
api_key = self . module . params . get ( ' api_key ' )
api_region = self . module . params . get ( ' api_region ' ) or os . environ . get ( ' CLOUDSTACK_REGION ' )
api_secret = self . module . params . get ( ' api_secret ' )
try :
api_url = self . module . params . get ( ' api_url ' )
config = read_config ( api_region )
api_http_method = self . module . params . get ( ' api_http_method ' )
except KeyError :
api_timeout = self . module . params . get ( ' api_timeout ' )
config = { }
if api_key and api_secret and api_url :
api_config = {
self . cs = CloudStack (
' endpoint ' : self . module . params . get ( ' api_url ' ) or config . get ( ' endpoint ' ) ,
endpoint = api_url ,
' key ' : self . module . params . get ( ' api_key ' ) or config . get ( ' key ' ) ,
key = api_key ,
' secret ' : self . module . params . get ( ' api_secret ' ) or config . get ( ' secret ' ) ,
secret = api_secret ,
' timeout ' : self . module . params . get ( ' api_timeout ' ) or config . get ( ' timeout ' ) ,
timeout = api_timeout ,
' method ' : self . module . params . get ( ' api_http_method ' ) or config . get ( ' method ' ) ,
method = api_http_method
}
)
self . result . update ( {
else :
' api_region ' : api_region ,
api_region = self . module . params . get ( ' api_region ' , ' cloudstack ' )
' api_url ' : api_config [ ' endpoint ' ] ,
self . cs = CloudStack ( * * read_config ( api_region ) )
' api_key ' : api_config [ ' key ' ] ,
' api_timeout ' : api_config [ ' timeout ' ] ,
' api_http_method ' : api_config [ ' method ' ] ,
} )
if not all ( [ api_config [ ' endpoint ' ] , api_config [ ' key ' ] , api_config [ ' secret ' ] ] ) :
self . module . fail_json ( msg = " Missing api credentials: can not authenticate " , result = self . result )
self . cs = CloudStack ( * * api_config )
def get_or_fallback ( self , key = None , fallback_key = None ) :
def get_or_fallback ( self , key = None , fallback_key = None ) :
value = self . module . params . get ( key )
value = self . module . params . get ( key )
@ -150,12 +155,6 @@ class AnsibleCloudStack(object):
value = self . module . params . get ( fallback_key )
value = self . module . params . get ( fallback_key )
return value
return value
# TODO: for backward compatibility only, remove if not used anymore
def _has_changed ( self , want_dict , current_dict , only_keys = None ) :
return self . has_changed ( want_dict = want_dict , current_dict = current_dict , only_keys = only_keys )
def has_changed ( self , want_dict , current_dict , only_keys = None ) :
def has_changed ( self , want_dict , current_dict , only_keys = None ) :
result = False
result = False
for key , value in want_dict . items ( ) :
for key , value in want_dict . items ( ) :
@ -202,7 +201,6 @@ class AnsibleCloudStack(object):
result = True
result = True
return result
return result
def _get_by_key ( self , key = None , my_dict = None ) :
def _get_by_key ( self , key = None , my_dict = None ) :
if my_dict is None :
if my_dict is None :
my_dict = { }
my_dict = { }
@ -212,7 +210,6 @@ class AnsibleCloudStack(object):
self . module . fail_json ( msg = " Something went wrong: %s not found " % key )
self . module . fail_json ( msg = " Something went wrong: %s not found " % key )
return my_dict
return my_dict
def get_vpc ( self , key = None ) :
def get_vpc ( self , key = None ) :
""" Return a VPC dictionary or the value of given key of. """
""" Return a VPC dictionary or the value of given key of. """
if self . vpc :
if self . vpc :
@ -259,11 +256,10 @@ class AnsibleCloudStack(object):
self . _vpc_networks_ids = [ ]
self . _vpc_networks_ids = [ ]
if vpcs :
if vpcs :
for vpc in vpcs [ ' vpc ' ] :
for vpc in vpcs [ ' vpc ' ] :
for n in vpc . get ( ' network ' , [ ] ) :
for n in vpc . get ( ' network ' , [ ] ) :
self . _vpc_networks_ids . append ( n [ ' id ' ] )
self . _vpc_networks_ids . append ( n [ ' id ' ] )
return network_id in self . _vpc_networks_ids
return network_id in self . _vpc_networks_ids
def get_network ( self , key = None ) :
def get_network ( self , key = None ) :
""" Return a network dictionary or the value of given key of. """
""" Return a network dictionary or the value of given key of. """
if self . network :
if self . network :
@ -296,7 +292,6 @@ class AnsibleCloudStack(object):
return self . _get_by_key ( key , self . network )
return self . _get_by_key ( key , self . network )
self . module . fail_json ( msg = " Network ' %s ' not found " % network )
self . module . fail_json ( msg = " Network ' %s ' not found " % network )
def get_project ( self , key = None ) :
def get_project ( self , key = None ) :
if self . project :
if self . project :
return self . _get_by_key ( key , self . project )
return self . _get_by_key ( key , self . project )
@ -306,18 +301,18 @@ class AnsibleCloudStack(object):
project = os . environ . get ( ' CLOUDSTACK_PROJECT ' )
project = os . environ . get ( ' CLOUDSTACK_PROJECT ' )
if not project :
if not project :
return None
return None
args = { }
args = {
args [ ' account ' ] = self . get_account ( key = ' name ' )
' account ' : self . get_account ( key = ' name ' ) ,
args [ ' domainid ' ] = self . get_domain ( key = ' id ' )
' domainid ' : self . get_domain ( key = ' id ' )
}
projects = self . cs . listProjects ( * * args )
projects = self . cs . listProjects ( * * args )
if projects :
if projects :
for p in projects [ ' project ' ] :
for p in projects [ ' project ' ] :
if project . lower ( ) in [ p [ ' name ' ] . lower ( ) , p [ ' id ' ] ] :
if project . lower ( ) in [ p [ ' name ' ] . lower ( ) , p [ ' id ' ] ] :
self . project = p
self . project = p
return self . _get_by_key ( key , self . project )
return self . _get_by_key ( key , self . project )
self . module . fail_json ( msg = " project ' %s ' not found " % project )
self . module . fail_json ( msg = " project ' %s ' not found " % project )
def get_ip_address ( self , key = None ) :
def get_ip_address ( self , key = None ) :
if self . ip_address :
if self . ip_address :
return self . _get_by_key ( key , self . ip_address )
return self . _get_by_key ( key , self . ip_address )
@ -341,7 +336,6 @@ class AnsibleCloudStack(object):
self . ip_address = ip_addresses [ ' publicipaddress ' ] [ 0 ]
self . ip_address = ip_addresses [ ' publicipaddress ' ] [ 0 ]
return self . _get_by_key ( key , self . ip_address )
return self . _get_by_key ( key , self . ip_address )
def get_vm_guest_ip ( self ) :
def get_vm_guest_ip ( self ) :
vm_guest_ip = self . module . params . get ( ' vm_guest_ip ' )
vm_guest_ip = self . module . params . get ( ' vm_guest_ip ' )
default_nic = self . get_vm_default_nic ( )
default_nic = self . get_vm_default_nic ( )
@ -354,7 +348,6 @@ class AnsibleCloudStack(object):
return vm_guest_ip
return vm_guest_ip
self . module . fail_json ( msg = " Secondary IP ' %s ' not assigned to VM " % vm_guest_ip )
self . module . fail_json ( msg = " Secondary IP ' %s ' not assigned to VM " % vm_guest_ip )
def get_vm_default_nic ( self ) :
def get_vm_default_nic ( self ) :
if self . vm_default_nic :
if self . vm_default_nic :
return self . vm_default_nic
return self . vm_default_nic
@ -367,7 +360,6 @@ class AnsibleCloudStack(object):
return self . vm_default_nic
return self . vm_default_nic
self . module . fail_json ( msg = " No default IP address of VM ' %s ' found " % self . module . params . get ( ' vm ' ) )
self . module . fail_json ( msg = " No default IP address of VM ' %s ' found " % self . module . params . get ( ' vm ' ) )
def get_vm ( self , key = None ) :
def get_vm ( self , key = None ) :
if self . vm :
if self . vm :
return self . _get_by_key ( key , self . vm )
return self . _get_by_key ( key , self . vm )
@ -386,12 +378,11 @@ class AnsibleCloudStack(object):
vms = self . cs . listVirtualMachines ( * * args )
vms = self . cs . listVirtualMachines ( * * args )
if vms :
if vms :
for v in vms [ ' virtualmachine ' ] :
for v in vms [ ' virtualmachine ' ] :
if vm . lower ( ) in [ v [ ' name ' ] . lower ( ) , v [ ' displayname ' ] . lower ( ) , v [ ' id ' ] ] :
if vm . lower ( ) in [ v [ ' name ' ] . lower ( ) , v [ ' displayname ' ] . lower ( ) , v [ ' id ' ] ] :
self . vm = v
self . vm = v
return self . _get_by_key ( key , self . vm )
return self . _get_by_key ( key , self . vm )
self . module . fail_json ( msg = " Virtual machine ' %s ' not found " % vm )
self . module . fail_json ( msg = " Virtual machine ' %s ' not found " % vm )
def get_zone ( self , key = None ) :
def get_zone ( self , key = None ) :
if self . zone :
if self . zone :
return self . _get_by_key ( key , self . zone )
return self . _get_by_key ( key , self . zone )
@ -411,12 +402,11 @@ class AnsibleCloudStack(object):
if zones :
if zones :
for z in zones [ ' zone ' ] :
for z in zones [ ' zone ' ] :
if zone . lower ( ) in [ z [ ' name ' ] . lower ( ) , z [ ' id ' ] ] :
if zone . lower ( ) in [ z [ ' name ' ] . lower ( ) , z [ ' id ' ] ] :
self . zone = z
self . zone = z
return self . _get_by_key ( key , self . zone )
return self . _get_by_key ( key , self . zone )
self . module . fail_json ( msg = " zone ' %s ' not found " % zone )
self . module . fail_json ( msg = " zone ' %s ' not found " % zone )
def get_os_type ( self , key = None ) :
def get_os_type ( self , key = None ) :
if self . os_type :
if self . os_type :
return self . _get_by_key ( key , self . zone )
return self . _get_by_key ( key , self . zone )
@ -428,12 +418,11 @@ class AnsibleCloudStack(object):
os_types = self . cs . listOsTypes ( )
os_types = self . cs . listOsTypes ( )
if os_types :
if os_types :
for o in os_types [ ' ostype ' ] :
for o in os_types [ ' ostype ' ] :
if os_type in [ o [ ' description ' ] , o [ ' id ' ] ] :
if os_type in [ o [ ' description ' ] , o [ ' id ' ] ] :
self . os_type = o
self . os_type = o
return self . _get_by_key ( key , self . os_type )
return self . _get_by_key ( key , self . os_type )
self . module . fail_json ( msg = " OS type ' %s ' not found " % os_type )
self . module . fail_json ( msg = " OS type ' %s ' not found " % os_type )
def get_hypervisor ( self ) :
def get_hypervisor ( self ) :
if self . hypervisor :
if self . hypervisor :
return self . hypervisor
return self . hypervisor
@ -452,7 +441,6 @@ class AnsibleCloudStack(object):
return self . hypervisor
return self . hypervisor
self . module . fail_json ( msg = " Hypervisor ' %s ' not found " % hypervisor )
self . module . fail_json ( msg = " Hypervisor ' %s ' not found " % hypervisor )
def get_account ( self , key = None ) :
def get_account ( self , key = None ) :
if self . account :
if self . account :
return self . _get_by_key ( key , self . account )
return self . _get_by_key ( key , self . account )
@ -467,17 +455,17 @@ class AnsibleCloudStack(object):
if not domain :
if not domain :
self . module . fail_json ( msg = " Account must be specified with Domain " )
self . module . fail_json ( msg = " Account must be specified with Domain " )
args = { }
args = {
args [ ' name ' ] = account
' name ' : account ,
args [ ' domainid ' ] = self . get_domain ( key = ' id ' )
' domainid ' : self . get_domain ( key = ' id ' ) ,
args [ ' listall ' ] = True
' listall ' : True
}
accounts = self . cs . listAccounts ( * * args )
accounts = self . cs . listAccounts ( * * args )
if accounts :
if accounts :
self . account = accounts [ ' account ' ] [ 0 ]
self . account = accounts [ ' account ' ] [ 0 ]
return self . _get_by_key ( key , self . account )
return self . _get_by_key ( key , self . account )
self . module . fail_json ( msg = " Account ' %s ' not found " % account )
self . module . fail_json ( msg = " Account ' %s ' not found " % account )
def get_domain ( self , key = None ) :
def get_domain ( self , key = None ) :
if self . domain :
if self . domain :
return self . _get_by_key ( key , self . domain )
return self . _get_by_key ( key , self . domain )
@ -488,49 +476,46 @@ class AnsibleCloudStack(object):
if not domain :
if not domain :
return None
return None
args = { }
args = {
args [ ' listall ' ] = True
' listall ' : True ,
}
domains = self . cs . listDomains ( * * args )
domains = self . cs . listDomains ( * * args )
if domains :
if domains :
for d in domains [ ' domain ' ] :
for d in domains [ ' domain ' ] :
if d [ ' path ' ] . lower ( ) in [ domain . lower ( ) , " root/ " + domain . lower ( ) , " root " + domain . lower ( ) ] :
if d [ ' path ' ] . lower ( ) in [ domain . lower ( ) , " root/ " + domain . lower ( ) , " root " + domain . lower ( ) ] :
self . domain = d
self . domain = d
return self . _get_by_key ( key , self . domain )
return self . _get_by_key ( key , self . domain )
self . module . fail_json ( msg = " Domain ' %s ' not found " % domain )
self . module . fail_json ( msg = " Domain ' %s ' not found " % domain )
def get_tags ( self , resource = None ) :
def get_tags ( self , resource = None ) :
existing_tags = [ ]
existing_tags = [ ]
for tag in resource . get ( ' tags ' , [ ] ) :
for tag in resource . get ( ' tags ' , [ ] ) :
existing_tags . append ( { ' key ' : tag [ ' key ' ] , ' value ' : tag [ ' value ' ] } )
existing_tags . append ( { ' key ' : tag [ ' key ' ] , ' value ' : tag [ ' value ' ] } )
return existing_tags
return existing_tags
def _process_tags ( self , resource , resource_type , tags , operation = " create " ) :
def _process_tags ( self , resource , resource_type , tags , operation = " create " ) :
if tags :
if tags :
self . result [ ' changed ' ] = True
self . result [ ' changed ' ] = True
if not self . module . check_mode :
if not self . module . check_mode :
args = { }
args = {
args [ ' resourceids ' ] = resource [ ' id ' ]
' resourceids ' : resource [ ' id ' ] ,
args [ ' resourcetype ' ] = resource_type
' resourcetype ' : resource_type ,
args [ ' tags ' ] = tags
' tags ' : tags ,
}
if operation == " create " :
if operation == " create " :
response = self . cs . createTags ( * * args )
response = self . cs . createTags ( * * args )
else :
else :
response = self . cs . deleteTags ( * * args )
response = self . cs . deleteTags ( * * args )
self . poll_job ( response )
self . poll_job ( response )
def _tags_that_should_exist_or_be_updated ( self , resource , tags ) :
def _tags_that_should_exist_or_be_updated ( self , resource , tags ) :
existing_tags = self . get_tags ( resource )
existing_tags = self . get_tags ( resource )
return [ tag for tag in tags if tag not in existing_tags ]
return [ tag for tag in tags if tag not in existing_tags ]
def _tags_that_should_not_exist ( self , resource , tags ) :
def _tags_that_should_not_exist ( self , resource , tags ) :
existing_tags = self . get_tags ( resource )
existing_tags = self . get_tags ( resource )
return [ tag for tag in existing_tags if tag not in tags ]
return [ tag for tag in existing_tags if tag not in tags ]
def ensure_tags ( self , resource , resource_type = None ) :
def ensure_tags ( self , resource , resource_type = None ) :
if not resource_type or not resource :
if not resource_type or not resource :
self . module . fail_json ( msg = " Error: Missing resource or resource_type for tags. " )
self . module . fail_json ( msg = " Error: Missing resource or resource_type for tags. " )
@ -543,7 +528,6 @@ class AnsibleCloudStack(object):
resource [ ' tags ' ] = tags
resource [ ' tags ' ] = tags
return resource
return resource
def get_capabilities ( self , key = None ) :
def get_capabilities ( self , key = None ) :
if self . capabilities :
if self . capabilities :
return self . _get_by_key ( key , self . capabilities )
return self . _get_by_key ( key , self . capabilities )
@ -551,12 +535,6 @@ class AnsibleCloudStack(object):
self . capabilities = capabilities [ ' capability ' ]
self . capabilities = capabilities [ ' capability ' ]
return self . _get_by_key ( key , self . capabilities )
return self . _get_by_key ( key , self . capabilities )
# TODO: for backward compatibility only, remove if not used anymore
def _poll_job ( self , job = None , key = None ) :
return self . poll_job ( job = job , key = key )
def poll_job ( self , job = None , key = None ) :
def poll_job ( self , job = None , key = None ) :
if ' jobid ' in job :
if ' jobid ' in job :
while True :
while True :
@ -570,7 +548,6 @@ class AnsibleCloudStack(object):
time . sleep ( 2 )
time . sleep ( 2 )
return job
return job
def get_result ( self , resource ) :
def get_result ( self , resource ) :
if resource :
if resource :
returns = self . common_returns . copy ( )
returns = self . common_returns . copy ( )
@ -584,12 +561,6 @@ class AnsibleCloudStack(object):
if search_key in resource :
if search_key in resource :
self . result [ return_key ] = int ( resource [ search_key ] )
self . result [ return_key ] = int ( resource [ search_key ] )
# Special handling for tags
if ' tags ' in resource :
if ' tags ' in resource :
self . result [ ' tags ' ] = [ ]
self . result [ ' tags ' ] = resource [ ' tags ' ]
for tag in resource [ ' tags ' ] :
result_tag = { }
result_tag [ ' key ' ] = tag [ ' key ' ]
result_tag [ ' value ' ] = tag [ ' value ' ]
self . result [ ' tags ' ] . append ( result_tag )
return self . result
return self . result