From 1e14e51150c88495a6b30f99e6b09e426a49827c Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 31 Mar 2015 16:37:07 -0400 Subject: [PATCH 1/5] Add OpenStack Security Group support Two modules - one for security groups and one to manage rules in a security group. --- cloud/openstack/os_security_group.py | 113 ++++++++++++++++ cloud/openstack/os_security_group_rule.py | 156 ++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 cloud/openstack/os_security_group.py create mode 100644 cloud/openstack/os_security_group_rule.py diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py new file mode 100644 index 00000000000..193a156251a --- /dev/null +++ b/cloud/openstack/os_security_group.py @@ -0,0 +1,113 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade +except ImportError: + print("failed=True msg='shade is required for this module'") + + +DOCUMENTATION = ''' +--- +module: os_security_group +short_description: Add/Delete security groups from an OpenStack cloud. +extends_documentation_fragment: openstack +version_added: "2.0" +description: + - Add or Remove security groups from an OpenStack cloud. +options: + name: + description: + - Name that has to be given to the security group + required: true + description: + description: + - Long description of the purpose of the security group + required: false + default: None + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + +requirements: ["shade"] +''' + +EXAMPLES = ''' +# Create a security group +- os_security_group: cloud=mordred name=foo + description=security group for foo servers +''' + + +def _security_group(module, nova_client, action='create', **kwargs): + f = getattr(nova_client.security_groups, action) + try: + secgroup = f(**kwargs) + except Exception, e: + module.fail_json(msg='Failed to %s security group %s: %s' % + (action, module.params['name'], e.message)) + + +def main(): + + argument_spec = openstack_full_argument_spec( + name = dict(required=True), + description = dict(default=None), + state = dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + try: + cloud = shade.openstack_cloud(**module.params) + nova_client = cloud.nova_client + changed = False + secgroup = cloud.get_security_group(module.params['name']) + + if module.params['state'] == 'present': + secgroup = cloud.get_security_group(module.params['name']) + if not secgroup: + _security_group(module, nova_client, action='create', + name=module.params['name'], + description=module.params['description']) + changed = True + + if secgroup and secgroup.description != module.params['description']: + _security_group(module, nova_client, action='update', + group=secgroup.id, + name=module.params['name'], + description=module.params['description']) + changed = True + + if module.params['state'] == 'absent': + if secgroup: + _security_group(module, nova_client, action='delete', + group=secgroup.id) + changed = True + + module.exit_json(changed=changed, id=module.params['name'], result="success") + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +main() diff --git a/cloud/openstack/os_security_group_rule.py b/cloud/openstack/os_security_group_rule.py new file mode 100644 index 00000000000..849919e6394 --- /dev/null +++ b/cloud/openstack/os_security_group_rule.py @@ -0,0 +1,156 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade +except ImportError: + print("failed=True msg='shade is required for this module'") + + +DOCUMENTATION = ''' +--- +module: os_security_group_rule +short_description: Add/Delete rule from an existing security group +extends_documentation_fragment: openstack +version_added: "2.0" +description: + - Add or Remove rule from an existing security group +options: + security_group: + description: + - Name of the security group + required: true + protocol: + description: + - IP protocol + choices: ['tcp', 'udp', 'icmp'] + default: tcp + port_range_min: + description: + - Starting port + required: true + port_range_max: + description: + - Ending port + required: true + remote_ip_prefix: + description: + - Source IP address(es) in CIDR notation (exclusive with remote_group) + required: false + remote_group: + description: + - ID of Security group to link (exclusive with remote_ip_prefix) + required: false + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + default: present + +requirements: ["shade"] +''' +# TODO(mordred): add ethertype and direction + +EXAMPLES = ''' +# Create a security group rule +- os_security_group_rule: + cloud: mordred + security_group: foo + protocol: tcp + port_range_min: 80 + port_range_max: 80 + remote_ip_prefix: 0.0.0.0/0 +''' + + +def _security_group_rule(module, nova_client, action='create', **kwargs): + f = getattr(nova_client.security_group_rules, action) + try: + secgroup = f(**kwargs) + except Exception, e: + module.fail_json(msg='Failed to %s security group rule: %s' % + (action, e.message)) + + +def _get_rule_from_group(module, secgroup): + for rule in secgroup.rules: + if (rule['ip_protocol'] == module.params['protocol'] and + rule['from_port'] == module.params['port_range_min'] and + rule['to_port'] == module.params['port_range_max'] and + rule['ip_range']['cidr'] == module.params['remote_ip_prefix']): + return rule + return None + +def main(): + + argument_spec = openstack_full_argument_spec( + security_group = dict(required=True), + protocol = dict(default='tcp', choices=['tcp', 'udp', 'icmp']), + port_range_min = dict(required=True), + port_range_max = dict(required=True), + remote_ip_prefix = dict(required=False, default=None), + # TODO(mordred): Make remote_group handle name and id + remote_group = dict(required=False, default=None), + state = dict(default='present', choices=['absent', 'present']), + ) + module_kwargs = openstack_module_kwargs( + mutually_exclusive=[ + ['remote_ip_prefix', 'remote_group'], + ], + ) + module = AnsibleModule(argument_spec, **module_kwargs) + + try: + cloud = shade.openstack_cloud(**module.params) + nova_client = cloud.nova_client + changed = False + + secgroup = cloud.get_security_group(module.params['security_group']) + + if module.params['state'] == 'present': + if not secgroup: + module.fail_json(msg='Could not find security group %s' % + module.params['security_group']) + + if not _get_rule_from_group(module, secgroup): + _security_group_rule(module, nova_client, 'create', + parent_group_id=secgroup.id, + ip_protocol=module.params['protocol'], + from_port=module.params['port_range_min'], + to_port=module.params['port_range_max'], + cidr=module.params['remote_ip'], + group_id=module.params['remote_group'], + changed = True + + + if module.params['state'] == 'absent' and secgroup: + rule = _get_rule_from_group(module, secgroup) + if secgroup and rule: + _security_group_rule(module, nova_client, 'delete', + rule=rule['id']) + changed = True + + module.exit_json(changed=changed, result="success") + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +main() From c298741aa64bda04be2d317f1e0fd0fa5abb21f6 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Tue, 9 Jun 2015 15:24:38 -0400 Subject: [PATCH 2/5] Update for latest shade API Shade version 0.7.0 introduces new API methods for creating, deleting, and updating security groups. Let's use those and clean up the module. --- cloud/openstack/os_security_group.py | 99 +++++++++++++++++----------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py index 193a156251a..55422ac20a3 100644 --- a/cloud/openstack/os_security_group.py +++ b/cloud/openstack/os_security_group.py @@ -18,8 +18,9 @@ try: import shade + HAS_SHADE = True except ImportError: - print("failed=True msg='shade is required for this module'") + HAS_SHADE = False DOCUMENTATION = ''' @@ -51,58 +52,80 @@ requirements: ["shade"] EXAMPLES = ''' # Create a security group -- os_security_group: cloud=mordred name=foo - description=security group for foo servers +- os_security_group: + cloud=mordred + name=foo + description=security group for foo servers ''' -def _security_group(module, nova_client, action='create', **kwargs): - f = getattr(nova_client.security_groups, action) - try: - secgroup = f(**kwargs) - except Exception, e: - module.fail_json(msg='Failed to %s security group %s: %s' % - (action, module.params['name'], e.message)) +def _needs_update(module, secgroup): + """Check for differences in the updatable values. + + NOTE: We don't currently allow name updates. + """ + if secgroup['description'] != module.params['description']: + return True + return False + + +def _system_state_change(module, secgroup): + state = module.params['state'] + if state == 'present': + if not secgroup: + return True + return _needs_update(module, secgroup) + if state == 'absent' and secgroup: + return True + return False def main(): - argument_spec = openstack_full_argument_spec( - name = dict(required=True), - description = dict(default=None), - state = dict(default='present', choices=['absent', 'present']), + name=dict(required=True), + description=dict(default=None), + state=dict(default='present', choices=['absent', 'present']), ) + module_kwargs = openstack_module_kwargs() - module = AnsibleModule(argument_spec, **module_kwargs) + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + name = module.params['name'] + state = module.params['state'] + description = module.params['description'] try: cloud = shade.openstack_cloud(**module.params) - nova_client = cloud.nova_client - changed = False - secgroup = cloud.get_security_group(module.params['name']) + secgroup = cloud.get_security_group(name) - if module.params['state'] == 'present': - secgroup = cloud.get_security_group(module.params['name']) + if module.check_mode: + module.exit_json(changed=_system_state_change(module, secgroup)) + + if state == 'present': if not secgroup: - _security_group(module, nova_client, action='create', - name=module.params['name'], - description=module.params['description']) - changed = True + secgroup = cloud.create_security_group(name, description) + module.exit_json(changed=True, result='created', + id=secgroup['id']) + else: + if _needs_update(module, secgroup): + secgroup = cloud.update_security_group( + secgroup['id'], description=description) + module.exit_json(changed=True, result='updated', + id=secgroup['id']) + else: + module.exit_json(changed=False, result='success') - if secgroup and secgroup.description != module.params['description']: - _security_group(module, nova_client, action='update', - group=secgroup.id, - name=module.params['name'], - description=module.params['description']) - changed = True - - if module.params['state'] == 'absent': - if secgroup: - _security_group(module, nova_client, action='delete', - group=secgroup.id) - changed = True - - module.exit_json(changed=changed, id=module.params['name'], result="success") + if state == 'absent': + if not secgroup: + module.exit_json(changed=False, result='success') + else: + cloud.delete_security_group(secgroup['id']) + module.exit_json(changed=True, result='deleted') except shade.OpenStackCloudException as e: module.fail_json(msg=e.message) From 5be1b64b85b58b5c2777485394205ce0190bb1e7 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Tue, 9 Jun 2015 16:18:38 -0400 Subject: [PATCH 3/5] Update the docstring for os_security_group Indicate that idempotence is on security group names, and give an example for updating a security group description. --- cloud/openstack/os_security_group.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py index 55422ac20a3..bf316962a39 100644 --- a/cloud/openstack/os_security_group.py +++ b/cloud/openstack/os_security_group.py @@ -34,7 +34,8 @@ description: options: name: description: - - Name that has to be given to the security group + - Name that has to be given to the security group. This module + requires that security group names be unique. required: true description: description: @@ -54,8 +55,16 @@ EXAMPLES = ''' # Create a security group - os_security_group: cloud=mordred + state=present name=foo description=security group for foo servers + +# Update the existing 'foo' security group description +- os_security_group: + cloud=mordred + state=present + name=foo + description=updated description for the foo security group ''' From e5cedc617a0eb3c75773887a01f361607cc7d171 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Wed, 10 Jun 2015 14:02:37 -0400 Subject: [PATCH 4/5] Remove 'result' value This value is pretty much useless, and a holdover from the old module code. Let's remove it. --- cloud/openstack/os_security_group.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cloud/openstack/os_security_group.py b/cloud/openstack/os_security_group.py index bf316962a39..4aaff2470d6 100644 --- a/cloud/openstack/os_security_group.py +++ b/cloud/openstack/os_security_group.py @@ -118,23 +118,21 @@ def main(): if state == 'present': if not secgroup: secgroup = cloud.create_security_group(name, description) - module.exit_json(changed=True, result='created', - id=secgroup['id']) + module.exit_json(changed=True, id=secgroup['id']) else: if _needs_update(module, secgroup): secgroup = cloud.update_security_group( secgroup['id'], description=description) - module.exit_json(changed=True, result='updated', - id=secgroup['id']) + module.exit_json(changed=True, id=secgroup['id']) else: - module.exit_json(changed=False, result='success') + module.exit_json(changed=False) if state == 'absent': if not secgroup: - module.exit_json(changed=False, result='success') + module.exit_json(changed=False) else: cloud.delete_security_group(secgroup['id']) - module.exit_json(changed=True, result='deleted') + module.exit_json(changed=True) except shade.OpenStackCloudException as e: module.fail_json(msg=e.message) From bf699e55f6e4ac934b404504b16fc9ef643b4efb Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Wed, 10 Jun 2015 14:14:01 -0400 Subject: [PATCH 5/5] Remove os_security_group_rule module The rules module will have it's own branch. --- cloud/openstack/os_security_group_rule.py | 156 ---------------------- 1 file changed, 156 deletions(-) delete mode 100644 cloud/openstack/os_security_group_rule.py diff --git a/cloud/openstack/os_security_group_rule.py b/cloud/openstack/os_security_group_rule.py deleted file mode 100644 index 849919e6394..00000000000 --- a/cloud/openstack/os_security_group_rule.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/python - -# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. -# Copyright (c) 2013, Benno Joy -# -# This module is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This software is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this software. If not, see . - -try: - import shade -except ImportError: - print("failed=True msg='shade is required for this module'") - - -DOCUMENTATION = ''' ---- -module: os_security_group_rule -short_description: Add/Delete rule from an existing security group -extends_documentation_fragment: openstack -version_added: "2.0" -description: - - Add or Remove rule from an existing security group -options: - security_group: - description: - - Name of the security group - required: true - protocol: - description: - - IP protocol - choices: ['tcp', 'udp', 'icmp'] - default: tcp - port_range_min: - description: - - Starting port - required: true - port_range_max: - description: - - Ending port - required: true - remote_ip_prefix: - description: - - Source IP address(es) in CIDR notation (exclusive with remote_group) - required: false - remote_group: - description: - - ID of Security group to link (exclusive with remote_ip_prefix) - required: false - state: - description: - - Should the resource be present or absent. - choices: [present, absent] - default: present - -requirements: ["shade"] -''' -# TODO(mordred): add ethertype and direction - -EXAMPLES = ''' -# Create a security group rule -- os_security_group_rule: - cloud: mordred - security_group: foo - protocol: tcp - port_range_min: 80 - port_range_max: 80 - remote_ip_prefix: 0.0.0.0/0 -''' - - -def _security_group_rule(module, nova_client, action='create', **kwargs): - f = getattr(nova_client.security_group_rules, action) - try: - secgroup = f(**kwargs) - except Exception, e: - module.fail_json(msg='Failed to %s security group rule: %s' % - (action, e.message)) - - -def _get_rule_from_group(module, secgroup): - for rule in secgroup.rules: - if (rule['ip_protocol'] == module.params['protocol'] and - rule['from_port'] == module.params['port_range_min'] and - rule['to_port'] == module.params['port_range_max'] and - rule['ip_range']['cidr'] == module.params['remote_ip_prefix']): - return rule - return None - -def main(): - - argument_spec = openstack_full_argument_spec( - security_group = dict(required=True), - protocol = dict(default='tcp', choices=['tcp', 'udp', 'icmp']), - port_range_min = dict(required=True), - port_range_max = dict(required=True), - remote_ip_prefix = dict(required=False, default=None), - # TODO(mordred): Make remote_group handle name and id - remote_group = dict(required=False, default=None), - state = dict(default='present', choices=['absent', 'present']), - ) - module_kwargs = openstack_module_kwargs( - mutually_exclusive=[ - ['remote_ip_prefix', 'remote_group'], - ], - ) - module = AnsibleModule(argument_spec, **module_kwargs) - - try: - cloud = shade.openstack_cloud(**module.params) - nova_client = cloud.nova_client - changed = False - - secgroup = cloud.get_security_group(module.params['security_group']) - - if module.params['state'] == 'present': - if not secgroup: - module.fail_json(msg='Could not find security group %s' % - module.params['security_group']) - - if not _get_rule_from_group(module, secgroup): - _security_group_rule(module, nova_client, 'create', - parent_group_id=secgroup.id, - ip_protocol=module.params['protocol'], - from_port=module.params['port_range_min'], - to_port=module.params['port_range_max'], - cidr=module.params['remote_ip'], - group_id=module.params['remote_group'], - changed = True - - - if module.params['state'] == 'absent' and secgroup: - rule = _get_rule_from_group(module, secgroup) - if secgroup and rule: - _security_group_rule(module, nova_client, 'delete', - rule=rule['id']) - changed = True - - module.exit_json(changed=changed, result="success") - - except shade.OpenStackCloudException as e: - module.fail_json(msg=e.message) - -# this is magic, see lib/ansible/module_common.py -from ansible.module_utils.basic import * -from ansible.module_utils.openstack import * -main()