Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure clc_public_ip #22

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions example-playbooks/example_clc_publicip_delete_playbook.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright 2015 CenturyLink
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -19,8 +19,6 @@
tasks:
- name: Delete All Public IPs For Servers
clc_publicip:
server_ids:
- UC1NJSTTRAIN58
- UC1NJSTTRAIN59
server_id: UC1NJSTTRAIN58
state: absent
register: clc
15 changes: 7 additions & 8 deletions example-playbooks/example_clc_publicip_playbook.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright 2015 CenturyLink
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -19,12 +19,11 @@
tasks:
- name: Create Public IP For Servers
clc_publicip:
protocol: 'TCP'
ports:
- 80
server_ids:
- UC1WFADTEST01
- UC1WFADTEST02
- {protocol: 'UDP', port: 23456}
- {protocol: 'TCP', port: 10000, port_to: 10050}
- {port: 80}
server_id: UC1WFADTEST01
source_restrictions:
- 70.100.60.140/32
- 71.100.60.0/24
Expand Down
222 changes: 128 additions & 94 deletions src/main/python/clc_ansible_module/clc_publicip.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# This file is part of CenturyLink Cloud, and is maintained
# by the Workflow as a Service Team
#
# Copyright 2015 CenturyLink
# Copyright 2015 CenturyLink
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -32,20 +32,24 @@
- An Ansible module to add or delete public ip addresses on an existing server or servers in CenturyLink Cloud.
version_added: "2.0"
options:
protocol:
private_ip:
description:
- The protocol that the public IP will listen for.
default: TCP
choices: ['TCP', 'UDP', 'ICMP']
- The private ip to which the public IP should be NAT'd.
If not provided, a private ip will be provisioned on the server's
primary network
default: None
choices: Any private ip belonging to the server
required: False
ports:
description:
- A list of ports to expose. This is required when state is 'present'
- A list of structures specifying port (required), protocol ['TCP','UDP'] (required), and
port_to (optional; used when specifying a range of ports)
- Example: {protocol: 'TCP', port: 10000, port_to: 10050}
required: False
default: None
server_ids:
server_id:
description:
- A list of servers to create public ips on.
- The server on which to create a public ip
required: True
source_restrictions:
description:
Expand Down Expand Up @@ -93,12 +97,11 @@
tasks:
- name: Create Public IP For Servers
clc_publicip:
protocol: 'TCP'
ports:
- 80
server_ids:
- UC1TEST-SVR01
- UC1TEST-SVR02
- {port: 80, protocol: 'TCP'}
- {port: 10000, port_to: 10050}
- {port: 24601, protocol: 'UDP'}
server_id: UC1TEST-SVR01
state: present
register: clc

Expand All @@ -110,11 +113,9 @@
gather_facts: False
connection: local
tasks:
- name: Create Public IP For Servers
- name: Delete Public IP For Servers
clc_publicip:
server_ids:
- UC1TEST-SVR01
- UC1TEST-SVR02
server_id: UC1TEST-SVR01
state: absent
register: clc

Expand All @@ -128,15 +129,12 @@
returned: success
type: boolean
sample: True
server_ids:
description: The list of server ids that are changed
server_id:
description: The server id that is changed
returned: success
type: list
sample:
[
"UC1TEST-SVR01",
"UC1TEST-SVR02"
]
"UC1TEST-SVR01"
'''

__version__ = '${version}'
Expand Down Expand Up @@ -192,23 +190,55 @@ def process_request(self):
"""
self._set_clc_credentials_from_env()
params = self.module.params
server_ids = params['server_ids']
ports = params['ports']
protocol = params['protocol']

server_id = params.get('server_id')
private_ip = params.get('private_ip')
ports = self._validate_ports(params.get('ports'))
restrictions = params.get('source_restrictions')
state = params['state']
state = params.get('state')

if state == 'present':
changed, changed_server_ids, requests = self.ensure_public_ip_present(
server_ids=server_ids, protocol=protocol, ports=ports, source_restrictions=restrictions)
changed, changed_server_id, request = self.ensure_public_ip_present(
server_id=server_id,
private_ip=private_ip,
ports=ports,
source_restrictions=restrictions
)
elif state == 'absent':
changed, changed_server_ids, requests = self.ensure_public_ip_absent(
server_ids=server_ids)
changed, changed_server_id, request = self.ensure_public_ip_absent(
server_id=server_id)
else:
return self.module.fail_json(msg="Unknown State: " + state)
self._wait_for_requests_to_complete(requests)
self._wait_for_request_to_complete(request)
return self.module.exit_json(changed=changed,
server_ids=changed_server_ids)
server_id=changed_server_id)

def _validate_ports(self, ports):
"""
Validates the provided list of port structures
Note that this is required because Ansible does not seem to validate argument specs
beyond the first level of the dictionary
:param ports: list of dictionaries specifying port, protocol, and an optional port_to
:return: list of validated ports
"""
validated_ports = []

for entry in ports:
if 'port' not in entry and 'protocol' not in entry:
continue
elif 'protocol' not in entry:
entry['protocol'] = 'TCP'
elif entry['protocol'] not in ['TCP', 'UDP']:
self.module.fail_json(
msg="Valid protocols for this module are: [TCP, UDP]: You specified '{0}'".format(entry['protocol'])
)
elif 'port' not in entry:
self.module.fail_json(msg="You must provide a port")

validated_ports.append(entry)

return validated_ports


@staticmethod
def _define_module_argument_spec():
Expand All @@ -217,84 +247,89 @@ def _define_module_argument_spec():
:return: argument spec dictionary
"""
argument_spec = dict(
server_ids=dict(type='list', required=True),
protocol=dict(default='TCP', choices=['TCP', 'UDP', 'ICMP']),
ports=dict(type='list'),
server_id=dict(required=True),
private_ip=dict(default=None),
ports=dict(
type='list',
default=[],
protocol=dict(default='TCP', choices=['TCP', 'UDP']),
port=dict(required=True),
port_to=dict()
),
source_restrictions=dict(type='list'),
wait=dict(type='bool', default=True),
state=dict(default='present', choices=['present', 'absent']),
)
return argument_spec

def ensure_public_ip_present(self, server_ids, protocol, ports, source_restrictions=None):
def ensure_public_ip_present(self, server_id, ports=[], private_ip=None, source_restrictions=None):
"""
Ensures the given server ids having the public ip available
:param server_ids: the list of server ids
:param protocol: the ip protocol
:param ports: the list of ports to expose
:param server_id: the server id
:param private_ip: optional private ip to which public ip should be NAT'ed
:param ports: list of dictionaries specifying ports and protocols to expose
:param source_restrictions: The list of IP range allowed to access the public IP, specified using CIDR notation.
:return: (changed, changed_server_ids, results)
:return: (changed, changed_server_id, result)
changed: A flag indicating if there is any change
changed_server_ids : the list of server ids that are changed
results: The result list from clc public ip call
changed_server_id : the server id that is changed
result: The result from clc public ip call
"""
changed = False
results = []
changed_server_ids = []
result = ""
changed_server_id = ""
restrictions_list = []
servers = self._get_servers_from_clc(
server_ids,
'Failed to obtain server list from the CLC API')
servers_to_change = [
server for server in servers if len(
server.PublicIPs().public_ips) == 0]
ports_to_expose = [{'protocol': protocol, 'port': port}
for port in ports]
server = self._get_server_from_clc(
server_id,
'Failed to obtain server from the CLC API')

if source_restrictions:
restrictions_list = [{'cidr': cidr} for cidr in source_restrictions]
for server in servers_to_change:
if not self.module.check_mode:
result = self._add_publicip_to_server(server, ports_to_expose, source_restrictions=restrictions_list)
results.append(result)
changed_server_ids.append(server.id)
changed = True
return changed, changed_server_ids, results

def _add_publicip_to_server(self, server, ports_to_expose, source_restrictions=None):

if not self.module.check_mode:
result = self._add_publicip_to_server(server, ports, private_ip=private_ip, source_restrictions=restrictions_list)
changed_server_id = server.id
changed = True
return changed, changed_server_id, result

def _add_publicip_to_server(self, server, ports_to_expose, private_ip=None, source_restrictions=None):
result = None

# We are mimicing Control here and auto-enabling ping
# Port is set to 0 because all 'ports' must have a port
ports_to_expose.insert(0, {'protocol': 'ICMP', 'port': 0})

try:
result = server.PublicIPs().Add(ports=ports_to_expose, source_restrictions=source_restrictions)
result = server.PublicIPs().Add(
ports=ports_to_expose
, source_restrictions=source_restrictions
, private_ip=private_ip
)
except CLCException, ex:
self.module.fail_json(msg='Failed to add public ip to the server : {0}. {1}'.format(
server.id, ex.response_text
))
return result

def ensure_public_ip_absent(self, server_ids):
def ensure_public_ip_absent(self, server_id):
"""
Ensures the given server ids having the public ip removed if there is any
:param server_ids: the list of server ids
:return: (changed, changed_server_ids, results)
:param server_id: the server id
:return: (changed, changed_server_id, result)
changed: A flag indicating if there is any change
changed_server_ids : the list of server ids that are changed
results: The result list from clc public ip call
changed_server_id : the changed server id
result: The result from clc public ip call
"""
changed = False
results = []
changed_server_ids = []
servers = self._get_servers_from_clc(
server_ids,
'Failed to obtain server list from the CLC API')
servers_to_change = [
server for server in servers if len(
server.PublicIPs().public_ips) > 0]
for server in servers_to_change:
if not self.module.check_mode:
result = self._remove_publicip_from_server(server)
results.append(result)
changed_server_ids.append(server.id)
changed = True
return changed, changed_server_ids, results
result = ""
changed_server_id = ""
server = self._get_server_from_clc(
server_id,
'Failed to obtain server from the CLC API')
if not self.module.check_mode:
result = self._remove_publicip_from_server(server)
changed_server_id = server.id
changed = True
return changed, changed_server_id, result

def _remove_publicip_from_server(self, server):
result = None
Expand All @@ -307,20 +342,19 @@ def _remove_publicip_from_server(self, server):
))
return result

def _wait_for_requests_to_complete(self, requests_lst):
def _wait_for_request_to_complete(self, request):
"""
Waits until the CLC requests are complete if the wait argument is True
:param requests_lst: The list of CLC request objects
Waits until the CLC request is complete if the wait argument is True
:param request: The CLC request objects
:return: none
"""
if not self.module.params['wait']:
return
for request in requests_lst:
request.WaitUntilComplete()
for request_details in request.requests:
if request_details.Status() != 'succeeded':
self.module.fail_json(
msg='Unable to process public ip request')
request.WaitUntilComplete()
for request_details in request.requests:
if request_details.Status() != 'succeeded':
self.module.fail_json(
msg='Unable to process public ip request')

def _set_clc_credentials_from_env(self):
"""
Expand Down Expand Up @@ -350,12 +384,12 @@ def _set_clc_credentials_from_env(self):
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
"environment variables")

def _get_servers_from_clc(self, server_ids, message):
def _get_server_from_clc(self, server_id, message):
"""
Gets list of servers form CLC api
"""
try:
return self.clc.v2.Servers(server_ids).servers
return self.clc.v2.Server(server_id)
except CLCException as exception:
self.module.fail_json(msg=message + ': %s' % exception)

Expand Down
Loading