@ -21,7 +21,7 @@ description:
- Create , start , stop and delete servers on the cloudscale . ch IaaS service .
- Create , start , stop and delete servers on the cloudscale . ch IaaS service .
- All operations are performed using the cloudscale . ch public API v1 .
- All operations are performed using the cloudscale . ch public API v1 .
- " For details consult the full API documentation: U(https://www.cloudscale.ch/en/api/v1). "
- " For details consult the full API documentation: U(https://www.cloudscale.ch/en/api/v1). "
- A n valid API token is required for all operations . You can create as many tokens as you like using the cloudscale . ch control panel at
- A valid API token is required for all operations . You can create as many tokens as you like using the cloudscale . ch control panel at
U ( https : / / control . cloudscale . ch ) .
U ( https : / / control . cloudscale . ch ) .
notes :
notes :
- Instead of the api_token parameter the CLOUDSCALE_API_TOKEN environment variable can be used .
- Instead of the api_token parameter the CLOUDSCALE_API_TOKEN environment variable can be used .
@ -86,6 +86,11 @@ options:
description :
description :
- cloudscale . ch API token .
- cloudscale . ch API token .
- This can also be passed in the CLOUDSCALE_API_TOKEN environment variable .
- This can also be passed in the CLOUDSCALE_API_TOKEN environment variable .
api_timeout :
description :
- Timeout in seconds for calls to the cloudscale . ch API .
default : 30
version_added : " 2.5 "
'''
'''
EXAMPLES = '''
EXAMPLES = '''
@ -203,12 +208,10 @@ from datetime import datetime, timedelta
from time import sleep
from time import sleep
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . six . moves . urllib . parse import urlencode
from ansible . module_utils . urls import fetch_url
from ansible . module_utils . urls import fetch_url
API_URL = ' https://api.cloudscale.ch/v1/ '
API_URL = ' https://api.cloudscale.ch/v1/ '
TIMEOUT_WAIT = 30
ALLOWED_STATES = ( ' running ' ,
ALLOWED_STATES = ( ' running ' ,
' stopped ' ,
' stopped ' ,
' absent ' ,
' absent ' ,
@ -245,26 +248,29 @@ class AnsibleCloudscaleServer(object):
self . info = self . _transform_state ( matching_server [ 0 ] )
self . info = self . _transform_state ( matching_server [ 0 ] )
elif len ( matching_server ) > 1 :
elif len ( matching_server ) > 1 :
self . _module . fail_json ( msg = " More than one server with name ' %s ' exists. "
self . _module . fail_json ( msg = " More than one server with name ' %s ' exists. "
" Use the ' uuid ' parameter to identify the server " % name )
" Use the ' uuid ' parameter to identify the server . " % name )
def _get ( self , api_call ) :
def _get ( self , api_call ) :
resp , info = fetch_url ( self . _module , API_URL + api_call , headers = self . _auth_header )
resp , info = fetch_url ( self . _module , API_URL + api_call , headers = self . _auth_header , timeout = self . _module . params [ ' api_timeout ' ] )
if info [ ' status ' ] == 200 :
if info [ ' status ' ] == 200 :
return json . loads ( resp . read ( ) )
return json . loads ( resp . read ( ) )
else :
else :
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with GET for '
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with GET for '
' " %s " : %s ' % ( api_call , info [ ' body ' ] ) )
' " %s " .' % api_call , fetch_url_info = info )
def _post ( self , api_call , data = None ) :
def _post ( self , api_call , data = None ) :
headers = self . _auth_header . copy ( )
if data is not None :
if data is not None :
data = urlencode ( data )
data = self . _module . jsonify ( data )
headers [ ' Content-type ' ] = ' application/json '
resp , info = fetch_url ( self . _module ,
resp , info = fetch_url ( self . _module ,
API_URL + api_call ,
API_URL + api_call ,
headers = self . _auth_ header,
headers = headers ,
method = ' POST ' ,
method = ' POST ' ,
data = data )
data = data ,
timeout = self . _module . params [ ' api_timeout ' ] )
if info [ ' status ' ] == 201 :
if info [ ' status ' ] == 201 :
return json . loads ( resp . read ( ) )
return json . loads ( resp . read ( ) )
@ -272,19 +278,20 @@ class AnsibleCloudscaleServer(object):
return None
return None
else :
else :
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with POST for '
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with POST for '
' " %s " : %s ' % ( api_call , info [ ' body ' ] ) )
' " %s " .' % api_call , fetch_url_info = info )
def _delete ( self , api_call ) :
def _delete ( self , api_call ) :
resp , info = fetch_url ( self . _module ,
resp , info = fetch_url ( self . _module ,
API_URL + api_call ,
API_URL + api_call ,
headers = self . _auth_header ,
headers = self . _auth_header ,
method = ' DELETE ' )
method = ' DELETE ' ,
timeout = self . _module . params [ ' api_timeout ' ] )
if info [ ' status ' ] == 204 :
if info [ ' status ' ] == 204 :
return None
return None
else :
else :
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with DELETE for '
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with DELETE for '
' " %s " : %s ' % ( api_call , info [ ' body ' ] ) )
' " %s " .' % api_call , fetch_url_info = info )
@staticmethod
@staticmethod
def _transform_state ( server ) :
def _transform_state ( server ) :
@ -302,9 +309,11 @@ class AnsibleCloudscaleServer(object):
return
return
# Can't use _get here because we want to handle 404
# Can't use _get here because we want to handle 404
url_path = ' servers/ ' + self . info [ ' uuid ' ]
resp , info = fetch_url ( self . _module ,
resp , info = fetch_url ( self . _module ,
API_URL + ' servers/ ' + self . info [ ' uuid ' ] ,
API_URL + url_path ,
headers = self . _auth_header )
headers = self . _auth_header ,
timeout = self . _module . params [ ' api_timeout ' ] )
if info [ ' status ' ] == 200 :
if info [ ' status ' ] == 200 :
self . info = self . _transform_state ( json . loads ( resp . read ( ) ) )
self . info = self . _transform_state ( json . loads ( resp . read ( ) ) )
elif info [ ' status ' ] == 404 :
elif info [ ' status ' ] == 404 :
@ -312,18 +321,19 @@ class AnsibleCloudscaleServer(object):
' name ' : self . info . get ( ' name ' , None ) ,
' name ' : self . info . get ( ' name ' , None ) ,
' state ' : ' absent ' }
' state ' : ' absent ' }
else :
else :
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API for '
self . _module . fail_json ( msg = ' Failure while calling the cloudscale.ch API with GET for '
' update_info: %s ' % info [ ' body ' ] )
' "%s " . ' % url_path , fetch_url_info = info )
def wait_for_state ( self , states ) :
def wait_for_state ( self , states ) :
start = datetime . now ( )
start = datetime . now ( )
while datetime . now ( ) - start < timedelta ( seconds = TIMEOUT_WAIT ) :
timeout = self . _module . params [ ' api_timeout ' ] * 2
while datetime . now ( ) - start < timedelta ( seconds = timeout ) :
self . update_info ( )
self . update_info ( )
if self . info [ ' state ' ] in states :
if self . info [ ' state ' ] in states :
return True
return True
sleep ( 1 )
sleep ( 1 )
self . _module . fail_json ( msg = ' Timeout while waiting for a state change on server %s to states %s . Current state is %s '
self . _module . fail_json ( msg = ' Timeout while waiting for a state change on server %s to states %s . Current state is %s . '
% ( self . info [ ' name ' ] , states , self . info [ ' state ' ] ) )
% ( self . info [ ' name ' ] , states , self . info [ ' state ' ] ) )
def create_server ( self ) :
def create_server ( self ) :
@ -336,14 +346,14 @@ class AnsibleCloudscaleServer(object):
missing_parameters . append ( p )
missing_parameters . append ( p )
if len ( missing_parameters ) > 0 :
if len ( missing_parameters ) > 0 :
self . _module . fail_json ( msg = ' Missing required parameter(s) to create a new server: %s ' %
self . _module . fail_json ( msg = ' Missing required parameter(s) to create a new server: %s . ' %
' ' . join ( missing_parameters ) )
' ' . join ( missing_parameters ) )
# Sanitize data dictionary
# Sanitize data dictionary
for k , v in data . items ( ) :
for k , v in data . items ( ) :
# Remove items not relevant to the create server call
# Remove items not relevant to the create server call
if k in ( ' api_token ' , ' uuid' , ' state ' ) :
if k in ( ' api_token ' , ' api_timeout' , ' uuid' , ' state ' ) :
del data [ k ]
del data [ k ]
continue
continue
@ -388,6 +398,7 @@ def main():
anti_affinity_with = dict ( ) ,
anti_affinity_with = dict ( ) ,
user_data = dict ( ) ,
user_data = dict ( ) ,
api_token = dict ( no_log = True ) ,
api_token = dict ( no_log = True ) ,
api_timeout = dict ( default = 30 , type = ' int ' ) ,
) ,
) ,
required_one_of = ( ( ' name ' , ' uuid ' ) , ) ,
required_one_of = ( ( ' name ' , ' uuid ' ) , ) ,
mutually_exclusive = ( ( ' name ' , ' uuid ' ) , ) ,
mutually_exclusive = ( ( ' name ' , ' uuid ' ) , ) ,