diff --git a/neutron_sec_group b/neutron_sec_group index f397e0a..52e57e3 100644 --- a/neutron_sec_group +++ b/neutron_sec_group @@ -64,7 +64,7 @@ options: description: - "List of security group rules. Available parameters of a rule: direction, port_range_min, port_range_max, ethertype, protocol, - remote_ip_prefix/remote_ip_group" + remote_ip_prefix|remote_group_id|remote_group_name" required: false default: none requirements: ["neutronclient", "keystoneclient"] @@ -93,6 +93,18 @@ neutron_sec_group: ethertype: "IPv4" protocol: "tcp" remote_ip_prefix: "10.0.0.1/24" + - direction: "ingress" + port_range_min: "22" + port_range_max: "22" + ethertype: "IPv4" + protocol: "tcp" + remote_group_id: UUID_OF_GROUP + - direction: "ingress" + port_range_min: "22" + port_range_max: "22" + ethertype: "IPv4" + protocol: "tcp" + remote_group_name: 'default' ''' try: @@ -102,13 +114,14 @@ try: except ImportError: print "failed=True msg='neutronclient and keystoneclient are required'" + def main(): """ Main function - entry point. The magic starts here ;-) """ module = AnsibleModule( argument_spec=dict( - auth_url=dict(default="http://127.0.0.1:5000/v2.0/"), + auth_url=dict(default='http://127.0.0.1:5000/v2.0/'), login_username=dict(required=True), login_password=dict(required=True), login_tenant_name=dict(required=True), @@ -117,7 +130,7 @@ def main(): region_name=dict(default=None), rules=dict(default=None), tenant_name=dict(required=False), - state=dict(default="present", choices=['present', 'absent']) + state=dict(default='present', choices=['present', 'absent']) ), supports_check_mode=True ) @@ -126,35 +139,38 @@ def main(): try: # Get id of security group (as a result check whether it exists) + tenant_id = _get_tenant_id(module, identity_client) + params = { - 'name': module.params['name'], - 'tenant_id': _get_tenant_id(module, identity_client), - 'fields': 'id' + 'name': module.params['name'], + 'tenant_id': tenant_id, + 'fields': 'id', } - sec_groups = network_client.list_security_groups(**params)["security_groups"] + + sec_groups = network_client.list_security_groups(**params)['security_groups'] if len(sec_groups) > 1: - raise exceptions.NeutronClientNoUniqueMatch(resource='security_group',name=name) + raise exceptions.NeutronClientNoUniqueMatch(resource='security_group', name=name) elif len(sec_groups) == 0: - sec_group_exists = False + sec_group_exists = False else: - sec_group = sec_groups[0] - sec_group_exists = True + sec_group = sec_groups[0] + sec_group_exists = True # state=present -> create or update depending on whether sg exists. - if module.params['state'] == "present": + if module.params['state'] == 'present': # UPDATE if sec_group_exists: - changed, sg = _update_sg(module, network_client, sec_group) + changed, sg = _update_sg(module, network_client, sec_group, tenant_id) if changed: module.exit_json(sec_group=sg, updated=True, changed=changed) else: module.exit_json(sec_group=sg, changed=changed) # CREATE else: - sg = _create_sg(module, network_client, identity_client) + sg = _create_sg(module, network_client, tenant_id) module.exit_json(sec_group=sg, created=True, changed=True) # DELETE - elif module.params['state'] == "absent" and sec_group_exists: + elif module.params['state'] == 'absent' and sec_group_exists: _delete_sg(module, network_client, sec_group) module.exit_json(changed=True) @@ -165,6 +181,7 @@ def main(): except Exception as exc: module.fail_json(msg="Error: %s" % str(exc)) + def _delete_sg(module, network_client, sec_group): """ Deletes a security group. @@ -177,7 +194,7 @@ def _delete_sg(module, network_client, sec_group): network_client.delete_security_group(sec_group['id']) -def _create_sg(module, network_client, identity_client): +def _create_sg(module, network_client, tenant_id): """ Creates a security group. :param module: module to get security group params from. @@ -188,26 +205,23 @@ def _create_sg(module, network_client, identity_client): """ if module.check_mode: return None - # NOTE: we don't do explicit rule validation, the API server will take - # care of that for us :-) - rules = module.params['rules'] data = { - "security_group": { - "name": module.params['name'], - "description": module.params['description'], - 'tenant_id': _get_tenant_id(module, identity_client) + 'security_group': { + 'name': module.params['name'], + 'description': module.params['description'], + 'tenant_id': tenant_id } } sg = network_client.create_security_group(data) - sg = sg["security_group"] + sg = sg['security_group'] - changed, sg = _update_sg(module, network_client, sg) + changed, sg = _update_sg(module, network_client, sg, tenant_id) return sg -def _update_sg(module, network_client, sg): +def _update_sg(module, network_client, sg, tenant_id): """ Updates a security group. :param module: module to get updated security group param from. @@ -220,17 +234,17 @@ def _update_sg(module, network_client, sg): sg = sg['security_group'] # We only allow description updating, no name updating - if module.params["description"] \ + if module.params['description'] \ and not module.params['description'] == sg['description'] \ and module.check_mode: changed = True - elif module.params["description"] \ + elif module.params['description'] \ and not module.params['description'] == sg['description'] \ and not module.check_mode: body = { - "security_group": { - "description": module.params["description"] + 'security_group': { + 'description': module.params['description'] } } sg = network_client.update_security_group(sg['id'], body) @@ -287,7 +301,7 @@ def _update_sg_rules(module, network_client, sg, wanted_rules): new_rules = [rule for rule in wanted_rules if 'done' not in rule] if len(new_rules): if not module.check_mode: - sg = _create_sg_rules(network_client, sg, new_rules) + sg = _create_sg_rules(network_client, sg, new_rules, tenant_id) changed = True #then delete not ok @@ -301,7 +315,7 @@ def _update_sg_rules(module, network_client, sg, wanted_rules): return changed -def _create_sg_rules(network_client, sg, rules): +def _create_sg_rules(network_client, sg, rules, tenant_id): """ Creates a set of security group rules in a given security group. :param network_client: network client to use to create rules. @@ -311,10 +325,15 @@ def _create_sg_rules(network_client, sg, rules): """ if rules: for rule in rules: + if 'remote_group_name' in rule: + rule['remote_group_id'] = _get_security_group_id(network_client, + rule['remote_group_name'], + tenant_id) + rule.pop('remote_group_name', None) rule['tenant_id'] = sg['tenant_id'] rule['security_group_id'] = sg['id'] data = { - "security_group_rule": rule + 'security_group_rule': rule } network_client.create_security_group_rule(data) @@ -323,23 +342,43 @@ def _create_sg_rules(network_client, sg, rules): return sg +def _get_security_group_id(network_client, group_name, tenant_id): + """ + Lookup the UUID for a named security group. This provides the ability to + specify a SourceGroup via remote_group_id. + + http://docs.openstack.org/openstack-ops/content/security_groups.html + + This will return the first match to a group name. + :param network_client: network client ot use to lookup group_id + :param group_name: The name of the security group to lookup + """ + + params = { + 'name': group_name, + 'tenant_id': tenant_id, + 'fields': 'id' + } + + return network_client.list_security_groups(**params)['security_groups'][0]['id'] + + def _get_tenant_id(module, identity_client): """ Returns the tenant_id, given tenant_name. - if tenant_name is not specified in the module params uses tenant_id - from Keystone session + if tenant_name is not specified in the module params uses login_tenant_name :param identity_client: identity_client used to get the tenant_id from its name. :param module_params: module parameters. """ if not module.params['tenant_name']: - tenant_id = identity_client.tenant_id + tenant_name = module.params['login_tenant_name'] else: tenant_name = module.params['tenant_name'] - tenant = _get_tenant(identity_client, tenant_name) - tenant_id = tenant.id - return tenant_id + tenant = _get_tenant(identity_client, tenant_name) + + return tenant.id def _get_tenant(identity_client, tenant_name):