Skip to content

Commit edbd715

Browse files
committed
Merge pull request ansible#698 from CenturylinkTechnology/clc_publicip
clc_publicip : the ansible module to manage public ip on a centurylink cloud
2 parents 2dee57b + 8a41108 commit edbd715

File tree

2 files changed

+354
-0
lines changed

2 files changed

+354
-0
lines changed

cloud/centurylink/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

cloud/centurylink/clc_publicip.py

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
#!/usr/bin/python
2+
3+
#
4+
# Copyright (c) 2015 CenturyLink
5+
#
6+
# This file is part of Ansible.
7+
#
8+
# Ansible is free software: you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation, either version 3 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# Ansible is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License
19+
# along with Ansible. If not, see <http://www.gnu.org/licenses/>
20+
#
21+
22+
DOCUMENTATION = '''
23+
module: clc_publicip
24+
short_description: Add and Delete public ips on servers in CenturyLink Cloud.
25+
description:
26+
- An Ansible module to add or delete public ip addresses on an existing server or servers in CenturyLink Cloud.
27+
version_added: 1.0
28+
options:
29+
protocol:
30+
descirption:
31+
- The protocol that the public IP will listen for.
32+
default: TCP
33+
choices: ['TCP', 'UDP', 'ICMP']
34+
required: False
35+
ports:
36+
description:
37+
- A list of ports to expose.
38+
required: True
39+
server_ids:
40+
description:
41+
- A list of servers to create public ips on.
42+
required: True
43+
state:
44+
description:
45+
- Determine wheteher to create or delete public IPs. If present module will not create a second public ip if one
46+
already exists.
47+
default: present
48+
choices: ['present', 'absent']
49+
required: False
50+
wait:
51+
description:
52+
- Whether to wait for the tasks to finish before returning.
53+
choices: [ True, False ]
54+
default: True
55+
required: False
56+
requirements:
57+
- python = 2.7
58+
- requests >= 2.5.0
59+
- clc-sdk
60+
notes:
61+
- To use this module, it is required to set the below environment variables which enables access to the
62+
Centurylink Cloud
63+
- CLC_V2_API_USERNAME: the account login id for the centurylink cloud
64+
- CLC_V2_API_PASSWORD: the account passwod for the centurylink cloud
65+
- Alternatively, the module accepts the API token and account alias. The API token can be generated using the
66+
CLC account login and password via the HTTP api call @ https://api.ctl.io/v2/authentication/login
67+
- CLC_V2_API_TOKEN: the API token generated from https://api.ctl.io/v2/authentication/login
68+
- CLC_ACCT_ALIAS: the account alias associated with the centurylink cloud
69+
- Users can set CLC_V2_API_URL to specify an endpoint for pointing to a different CLC environment.
70+
'''
71+
72+
EXAMPLES = '''
73+
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
74+
75+
- name: Add Public IP to Server
76+
hosts: localhost
77+
gather_facts: False
78+
connection: local
79+
tasks:
80+
- name: Create Public IP For Servers
81+
clc_publicip:
82+
protocol: 'TCP'
83+
ports:
84+
- 80
85+
server_ids:
86+
- UC1ACCTSRVR01
87+
- UC1ACCTSRVR02
88+
state: present
89+
register: clc
90+
91+
- name: debug
92+
debug: var=clc
93+
94+
- name: Delete Public IP from Server
95+
hosts: localhost
96+
gather_facts: False
97+
connection: local
98+
tasks:
99+
- name: Create Public IP For Servers
100+
clc_publicip:
101+
server_ids:
102+
- UC1ACCTSRVR01
103+
- UC1ACCTSRVR02
104+
state: absent
105+
register: clc
106+
107+
- name: debug
108+
debug: var=clc
109+
'''
110+
111+
__version__ = '${version}'
112+
113+
from distutils.version import LooseVersion
114+
115+
try:
116+
import requests
117+
except ImportError:
118+
REQUESTS_FOUND = False
119+
else:
120+
REQUESTS_FOUND = True
121+
122+
#
123+
# Requires the clc-python-sdk.
124+
# sudo pip install clc-sdk
125+
#
126+
try:
127+
import clc as clc_sdk
128+
from clc import CLCException
129+
except ImportError:
130+
CLC_FOUND = False
131+
clc_sdk = None
132+
else:
133+
CLC_FOUND = True
134+
135+
136+
class ClcPublicIp(object):
137+
clc = clc_sdk
138+
module = None
139+
group_dict = {}
140+
141+
def __init__(self, module):
142+
"""
143+
Construct module
144+
"""
145+
self.module = module
146+
if not CLC_FOUND:
147+
self.module.fail_json(
148+
msg='clc-python-sdk required for this module')
149+
if not REQUESTS_FOUND:
150+
self.module.fail_json(
151+
msg='requests library is required for this module')
152+
if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
153+
self.module.fail_json(
154+
msg='requests library version should be >= 2.5.0')
155+
156+
self._set_user_agent(self.clc)
157+
158+
def process_request(self):
159+
"""
160+
Process the request - Main Code Path
161+
:param params: dictionary of module parameters
162+
:return: Returns with either an exit_json or fail_json
163+
"""
164+
self._set_clc_credentials_from_env()
165+
params = self.module.params
166+
server_ids = params['server_ids']
167+
ports = params['ports']
168+
protocol = params['protocol']
169+
state = params['state']
170+
requests = []
171+
chagned_server_ids = []
172+
changed = False
173+
174+
if state == 'present':
175+
changed, chagned_server_ids, requests = self.ensure_public_ip_present(
176+
server_ids=server_ids, protocol=protocol, ports=ports)
177+
elif state == 'absent':
178+
changed, chagned_server_ids, requests = self.ensure_public_ip_absent(
179+
server_ids=server_ids)
180+
else:
181+
return self.module.fail_json(msg="Unknown State: " + state)
182+
self._wait_for_requests_to_complete(requests)
183+
return self.module.exit_json(changed=changed,
184+
server_ids=chagned_server_ids)
185+
186+
@staticmethod
187+
def _define_module_argument_spec():
188+
"""
189+
Define the argument spec for the ansible module
190+
:return: argument spec dictionary
191+
"""
192+
argument_spec = dict(
193+
server_ids=dict(type='list', required=True),
194+
protocol=dict(default='TCP', choices=['TCP', 'UDP', 'ICMP']),
195+
ports=dict(type='list', required=True),
196+
wait=dict(type='bool', default=True),
197+
state=dict(default='present', choices=['present', 'absent']),
198+
)
199+
return argument_spec
200+
201+
def ensure_public_ip_present(self, server_ids, protocol, ports):
202+
"""
203+
Ensures the given server ids having the public ip available
204+
:param server_ids: the list of server ids
205+
:param protocol: the ip protocol
206+
:param ports: the list of ports to expose
207+
:return: (changed, changed_server_ids, results)
208+
changed: A flag indicating if there is any change
209+
changed_server_ids : the list of server ids that are changed
210+
results: The result list from clc public ip call
211+
"""
212+
changed = False
213+
results = []
214+
changed_server_ids = []
215+
servers = self._get_servers_from_clc(
216+
server_ids,
217+
'Failed to obtain server list from the CLC API')
218+
servers_to_change = [
219+
server for server in servers if len(
220+
server.PublicIPs().public_ips) == 0]
221+
ports_to_expose = [{'protocol': protocol, 'port': port}
222+
for port in ports]
223+
for server in servers_to_change:
224+
if not self.module.check_mode:
225+
result = self._add_publicip_to_server(server, ports_to_expose)
226+
results.append(result)
227+
changed_server_ids.append(server.id)
228+
changed = True
229+
return changed, changed_server_ids, results
230+
231+
def _add_publicip_to_server(self, server, ports_to_expose):
232+
result = None
233+
try:
234+
result = server.PublicIPs().Add(ports_to_expose)
235+
except CLCException, ex:
236+
self.module.fail_json(msg='Failed to add public ip to the server : {0}. {1}'.format(
237+
server.id, ex.response_text
238+
))
239+
return result
240+
241+
def ensure_public_ip_absent(self, server_ids):
242+
"""
243+
Ensures the given server ids having the public ip removed if there is any
244+
:param server_ids: the list of server ids
245+
:return: (changed, changed_server_ids, results)
246+
changed: A flag indicating if there is any change
247+
changed_server_ids : the list of server ids that are changed
248+
results: The result list from clc public ip call
249+
"""
250+
changed = False
251+
results = []
252+
changed_server_ids = []
253+
servers = self._get_servers_from_clc(
254+
server_ids,
255+
'Failed to obtain server list from the CLC API')
256+
servers_to_change = [
257+
server for server in servers if len(
258+
server.PublicIPs().public_ips) > 0]
259+
for server in servers_to_change:
260+
if not self.module.check_mode:
261+
result = self._remove_publicip_from_server(server)
262+
results.append(result)
263+
changed_server_ids.append(server.id)
264+
changed = True
265+
return changed, changed_server_ids, results
266+
267+
def _remove_publicip_from_server(self, server):
268+
try:
269+
for ip_address in server.PublicIPs().public_ips:
270+
result = ip_address.Delete()
271+
except CLCException, ex:
272+
self.module.fail_json(msg='Failed to remove public ip from the server : {0}. {1}'.format(
273+
server.id, ex.response_text
274+
))
275+
return result
276+
277+
def _wait_for_requests_to_complete(self, requests_lst):
278+
"""
279+
Waits until the CLC requests are complete if the wait argument is True
280+
:param requests_lst: The list of CLC request objects
281+
:return: none
282+
"""
283+
if not self.module.params['wait']:
284+
return
285+
for request in requests_lst:
286+
request.WaitUntilComplete()
287+
for request_details in request.requests:
288+
if request_details.Status() != 'succeeded':
289+
self.module.fail_json(
290+
msg='Unable to process public ip request')
291+
292+
def _set_clc_credentials_from_env(self):
293+
"""
294+
Set the CLC Credentials on the sdk by reading environment variables
295+
:return: none
296+
"""
297+
env = os.environ
298+
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
299+
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
300+
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
301+
clc_alias = env.get('CLC_ACCT_ALIAS', False)
302+
api_url = env.get('CLC_V2_API_URL', False)
303+
304+
if api_url:
305+
self.clc.defaults.ENDPOINT_URL_V2 = api_url
306+
307+
if v2_api_token and clc_alias:
308+
self.clc._LOGIN_TOKEN_V2 = v2_api_token
309+
self.clc._V2_ENABLED = True
310+
self.clc.ALIAS = clc_alias
311+
elif v2_api_username and v2_api_passwd:
312+
self.clc.v2.SetCredentials(
313+
api_username=v2_api_username,
314+
api_passwd=v2_api_passwd)
315+
else:
316+
return self.module.fail_json(
317+
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
318+
"environment variables")
319+
320+
def _get_servers_from_clc(self, server_ids, message):
321+
"""
322+
Gets list of servers form CLC api
323+
"""
324+
try:
325+
return self.clc.v2.Servers(server_ids).servers
326+
except CLCException as exception:
327+
self.module.fail_json(msg=message + ': %s' % exception)
328+
329+
@staticmethod
330+
def _set_user_agent(clc):
331+
if hasattr(clc, 'SetRequestsSession'):
332+
agent_string = "ClcAnsibleModule/" + __version__
333+
ses = requests.Session()
334+
ses.headers.update({"Api-Client": agent_string})
335+
ses.headers['User-Agent'] += " " + agent_string
336+
clc.SetRequestsSession(ses)
337+
338+
339+
def main():
340+
"""
341+
The main function. Instantiates the module and calls process_request.
342+
:return: none
343+
"""
344+
module = AnsibleModule(
345+
argument_spec=ClcPublicIp._define_module_argument_spec(),
346+
supports_check_mode=True
347+
)
348+
clc_public_ip = ClcPublicIp(module)
349+
clc_public_ip.process_request()
350+
351+
from ansible.module_utils.basic import * # pylint: disable=W0614
352+
if __name__ == '__main__':
353+
main()

0 commit comments

Comments
 (0)