From 8d3aa464b2c703760fd1fa53ff9cb0520b869d17 Mon Sep 17 00:00:00 2001 From: John Dewey Date: Wed, 27 Nov 2013 00:30:50 -0800 Subject: [PATCH 1/3] Added module to handle nova security groups This module is loosely based on ec2_group module. However, rules are handled slightly differently. Specific rules are able to be removed vs removing all "rogue" [1] rules. [1] Rogue rules are existing security group rules, which are not included in the `rules` dict. --- cloud/nova_group | 333 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 cloud/nova_group diff --git a/cloud/nova_group b/cloud/nova_group new file mode 100644 index 00000000000..18e00c9c7ba --- /dev/null +++ b/cloud/nova_group @@ -0,0 +1,333 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2013, John Dewey +# +# 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 . + +import locale +import os +import six + +try: + from novaclient.openstack.common import uuidutils + from novaclient.openstack.common import strutils + from novaclient.v1_1 import client + from novaclient.v1_1 import security_groups + from novaclient.v1_1 import security_group_rules + from novaclient import exceptions +except ImportError: + print("failed=True msg='novaclient is required for this module to work'") + +DOCUMENTATION = ''' +--- +module: security_group +version_added: "1.5" +short_description: Maintain nova security groups. +description: + - Manage nova security groups using the python-novaclient library. +options: + + login_username: + description: + - Login username to authenticate to keystone. If not set then the value of the OS_USERNAME environment variable is used. + required: false + default: None + login_password: + description: + - Password of login user. If not set then the value of the OS_PASSWORD environment variable is used. + required: false + default: None + login_tenant_name: + description: + - The tenant name of the login user. If not set then the value of the OS_TENANT_NAME environment variable is used. + required: false + default: None + auth_url: + description: + - The keystone url for authentication. If not set then the value of the OS_AUTH_URL environment variable is used. + required: false + default: None + region_name: + description: + - Name of the region. + required: false + default: None + name: + description: + - Name of the security group. + required: true + description: + description: + - Description of the security group. + required: true + rules: + description: + - List of firewall rules to enforce in this group (see example). + Must specify either an IPv4 'cidr' address or 'group' UUID. + required: true + state: + description: + - Indicate desired state of the resource. + choices: ['present', 'absent'] + required: false + default: 'present' + +requirements: ["novaclient"] +''' + +EXAMPLES = ''' +- name: create example group and rules + local_action: + module: security_group + name: example + description: an example nova group + rules: + - ip_protocol: tcp + from_port: 80 + to_port: 80 + cidr: 0.0.0.0/0 + - ip_protocol: tcp + from_port: 3306 + to_port: 3306 + group: "{{ group_uuid }}" + - ip_protocol: icmp + from_port: -1 + to_port: -1 + cidr: 0.0.0.0/0 + +- name: delete rule from example group + local_action: + module: security_group + name: example + description: an example nova group + rules: + - ip_protocol: tcp + from_port: 80 + to_port: 80 + cidr: 0.0.0.0/0 + - ip_protocol: icmp + from_port: -1 + to_port: -1 + cidr: 0.0.0.0/0 + state: absent +''' + +class NovaGroup(object): + def __init__(self, client): + self._sg = security_groups.SecurityGroupManager(client) + + # Taken from novaclient/v1_1/shell.py. + def _get_secgroup(self, secgroup): + # Check secgroup is an UUID + if uuidutils.is_uuid_like(strutils.safe_encode(secgroup)): + try: + sg = self._sg.get(secgroup) + return sg + except exceptions.NotFound: + return False + + # Check secgroup as a name + for s in self._sg.list(): + encoding = (locale.getpreferredencoding() or + sys.stdin.encoding or + 'UTF-8') + if not six.PY3: + s.name = s.name.encode(encoding) + if secgroup == s.name: + return s + return False + + +class SecurityGroup(NovaGroup): + def __init__(self, client, module): + super(SecurityGroup, self).__init__(client) + self._module = module + self._name = module.params.get('name') + self._description = module.params.get('description') + + def exists(self): + return self._get_secgroup(self._name) + + def create(self): + self._sg.create(self._name, self._description) + + def delete(self): + self._sg.delete(self._name) + + +class SecurityGroupRule(NovaGroup): + def __init__(self, client, module): + super(SecurityGroupRule, self).__init__(client) + self._module = module + self._name = module.params.get('name') + self._rules = module.params.get('rules') + self._validate_rules() + self._sgr = security_group_rules.SecurityGroupRuleManager(client) + self._secgroup = self._get_secgroup(self._name) + self._current_rules = self._lookup_dict(self._secgroup.rules) + + def _concat_security_group_rule(self, rule): + """ + Normalize the given rule into a string in the format of: + protocol-from_port-to_port-group + The `group` needs a bit of massaging. + 1. If an empty dict -- return None. + 2. If a dict -- lookup group UUID (novaclient only returns the name). + 3. Return `group` from rules dict. + + :param rule: A novaclient SecurityGroupRule object. + """ + group = rule.get('group') + # Oddly novaclient occasionaly returns None as {}. + if group is not None and not any(group): + group = None + elif type(group) == dict: + g = group.get('name') + group = self._get_secgroup(g) + r = "%s-%s-%s-%s" % (rule.get('ip_protocol'), + rule.get('from_port'), + rule.get('to_port'), + group) + return r + + def _lookup_dict(self, rules): + """ + Populate a dict with current rules. + + :param rule: A novaclient SecurityGroupRule object. + """ + return {self._concat_security_group_rule(rule): rule for rule in rules} + + def _get_rule(self, rule): + """ + Return rule when found and False when not. + + :param rule: A novaclient SecurityGroupRule object. + """ + r = self._concat_security_group_rule(rule) + if r in self._current_rules: + return self._current_rules[r] + else: + return False + + def _validate_rules(self): + for rule in self._rules: + if 'group' in rule and 'cidr' in rule: + self._module.fail_json(msg="Specify group OR cidr") + + def create(self): + changed = False + filtered = [rule for rule in self._rules + if rule.get('state') != 'absent'] + for rule in filtered: + if not self._get_rule(rule): + if 'cidr' in rule: + self._sgr.create(self._secgroup.id, + rule.get('ip_protocol'), + rule.get('from_port'), + rule.get('to_port'), + cidr=rule.get('cidr')) + changed = True + if 'group' in rule: + self._sgr.create(self._secgroup.id, + rule.get('ip_protocol'), + rule.get('from_port'), + rule.get('to_port'), + group_id=rule.get('group')) + changed = True + return changed + + def delete(self): + changed = False + filtered = [rule for rule in self._rules + if rule.get('state') == 'absent'] + for rule in filtered: + r = self._get_rule(rule) + if r: + self._sgr.delete(r.get('id')) + changed = True + return changed + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + description=dict(required=True), + rules=dict(), + login_username=dict(), + login_password=dict(no_log=True), + login_tenant_name=dict(), + auth_url= dict(), + region_name=dict(default=None), + state = dict(default='present', choices=['present', 'absent']), + ), + supports_check_mode=True, + ) + login_username = module.params.get('login_username') + login_password = module.params.get('login_password') + login_tenant_name = module.params.get('login_tenant_name') + auth_url = module.params.get('auth_url') + + # allow stackrc environment variables to be used if ansible vars aren't set + if not login_username and 'OS_USERNAME' in os.environ: + login_username = os.environ['OS_USERNAME'] + + if not login_password and 'OS_PASSWORD' in os.environ: + login_password = os.environ['OS_PASSWORD'] + + if not login_tenant_name and 'OS_TENANT_NAME' in os.environ: + login_tenant_name = os.environ['OS_TENANT_NAME'] + + if not auth_url and 'OS_AUTH_URL' in os.environ: + auth_url = os.environ['OS_AUTH_URL'] + + nova = client.Client(login_username, + login_password, + login_tenant_name, + auth_url, + service_type='compute') + try: + nova.authenticate() + except exceptions.Unauthorized as e: + module.fail_json(msg="Invalid OpenStack Nova credentials.: %s" % e.message) + except exceptions.AuthorizationFailure as e: + module.fail_json(msg="Unable to authorize user: %s" % e.message) + + rules = module.params.get('rules') + state = module.params.get('state') + security_group = SecurityGroup(nova, module) + security_group_rules = SecurityGroupRule(nova, module) + + changed = False + if security_group.exists(): + if state == 'absent': + security_group.delete() + changed = True + elif state == 'present': + security_group.create() + changed = True + + if rules: + if security_group_rules.create(): + changed = True + if security_group_rules.delete(): + changed = True + + module.exit_json(changed=changed, group_id=None) + +# this is magic, see lib/ansible/module_common.py +#<> +main() From ed3b65b5326b00e47e1d7695b4d50df35b2340c2 Mon Sep 17 00:00:00 2001 From: John Dewey Date: Sat, 7 Dec 2013 10:19:28 -0800 Subject: [PATCH 2/3] Made a few canges as I learn more about modules * Set check_mode to False, am not supporting this ATM. * Cleaned up delete/create() into single update(). * Return the group_id if created or found so tasks can be chained. --- cloud/nova_group | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/cloud/nova_group b/cloud/nova_group index 18e00c9c7ba..c96fb725ed5 100644 --- a/cloud/nova_group +++ b/cloud/nova_group @@ -157,14 +157,14 @@ class SecurityGroup(NovaGroup): self._name = module.params.get('name') self._description = module.params.get('description') - def exists(self): + def get(self): return self._get_secgroup(self._name) def create(self): - self._sg.create(self._name, self._description) + return self._sg.create(self._name, self._description) def delete(self): - self._sg.delete(self._name) + return self._sg.delete(self._name) class SecurityGroupRule(NovaGroup): @@ -219,8 +219,6 @@ class SecurityGroupRule(NovaGroup): r = self._concat_security_group_rule(rule) if r in self._current_rules: return self._current_rules[r] - else: - return False def _validate_rules(self): for rule in self._rules: @@ -260,6 +258,14 @@ class SecurityGroupRule(NovaGroup): changed = True return changed + def update(self): + changed = False + if self.create(): + changed = True + if self.delete(): + changed = True + return changed + def main(): module = AnsibleModule( @@ -274,7 +280,7 @@ def main(): region_name=dict(default=None), state = dict(default='present', choices=['present', 'absent']), ), - supports_check_mode=True, + supports_check_mode=False, ) login_username = module.params.get('login_username') login_password = module.params.get('login_password') @@ -309,24 +315,26 @@ def main(): rules = module.params.get('rules') state = module.params.get('state') security_group = SecurityGroup(nova, module) - security_group_rules = SecurityGroupRule(nova, module) changed = False - if security_group.exists(): + group_id = None + group = security_group.get() + if group: + group_id = group.id if state == 'absent': security_group.delete() changed = True elif state == 'present': - security_group.create() + group = security_group.create() changed = True + group_id = group.id - if rules: - if security_group_rules.create(): - changed = True - if security_group_rules.delete(): + if rules is not None: + security_group_rules = SecurityGroupRule(nova, module) + if security_group_rules.update(): changed = True - module.exit_json(changed=changed, group_id=None) + module.exit_json(changed=changed, group_id=group_id) # this is magic, see lib/ansible/module_common.py #<> From db3a6ce14c0b9c424dd129d1a326bb08d9b7944a Mon Sep 17 00:00:00 2001 From: Richard C Isaacson Date: Tue, 11 Mar 2014 13:33:18 -0500 Subject: [PATCH 3/3] Bump relased in version and update module snippets. Closes GH-5069 --- cloud/nova_group | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cloud/nova_group b/cloud/nova_group index c96fb725ed5..21393a79afe 100644 --- a/cloud/nova_group +++ b/cloud/nova_group @@ -33,7 +33,7 @@ except ImportError: DOCUMENTATION = ''' --- module: security_group -version_added: "1.5" +version_added: "1.6" short_description: Maintain nova security groups. description: - Manage nova security groups using the python-novaclient library. @@ -336,6 +336,8 @@ def main(): module.exit_json(changed=changed, group_id=group_id) -# this is magic, see lib/ansible/module_common.py -#<> -main() + +# import module snippets +from ansible.module_utils.basic import * + +main() \ No newline at end of file