summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorin Hochstein <lorinh@gmail.com>2014-07-25 15:29:24 -0400
committerLorin Hochstein <lorinh@gmail.com>2014-07-25 15:29:24 -0400
commit01ab665fbf8bb0b861672e910de2b74eb0a7fc06 (patch)
tree7acd6bb071bd6f436fbacacdb94d9467e072d722
parent1b34a7a2f777340f07e9ff19f2d39e9f4d1c26bd (diff)
parentcf1bf7c259458fba76f0d441e04e657ea73b14c8 (diff)
downloadopenstack-ansible-modules-01ab665fbf8bb0b861672e910de2b74eb0a7fc06.tar.gz
Merge pull request #12 from jorisroovers/neutron_sec_group
Openstack security groups module
-rw-r--r--neutron_sec_group323
1 files changed, 323 insertions, 0 deletions
diff --git a/neutron_sec_group b/neutron_sec_group
new file mode 100644
index 0000000..9e273ea
--- /dev/null
+++ b/neutron_sec_group
@@ -0,0 +1,323 @@
+#!/usr/bin/python
+#
+# (c) Cisco Systems, 2014
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+try:
+ import neutronclient.v2_0.client
+ import keystoneclient.v2_0.client
+except ImportError:
+ print "failed=True msg='neutronclient and keystoneclient are required'"
+
+DOCUMENTATION = '''
+---
+module: neutron_sec_group
+short_description: Create, Remove or Update Openstack security groups
+description:
+ - Create, Remove or Update Openstack security groups
+options:
+ login_username:
+ description:
+ - login username to authenticate to keystone
+ required: true
+ login_password:
+ description:
+ - Password of login user
+ required: true
+ login_tenant_name:
+ description:
+ - The tenant name of the login user
+ required: true
+ auth_url:
+ description:
+ - The keystone url for authentication
+ required: false
+ default: 'http://127.0.0.1:5000/v2.0/'
+ region_name:
+ description:
+ - Name of the region
+ required: false
+ default: None
+ state:
+ description:
+ - Indicate desired state of the security group
+ choices: ['present', 'absent']
+ default: present
+ name:
+ description:
+ - Name to be given to the security group
+ required: true
+ default: None
+ tenant_name:
+ description:
+ - Name of the tenant for which the security group has to be created,
+ if none, the security group would be created for the login tenant.
+ required: false
+ default: None
+ rules:
+ 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"
+ required: false
+ default: none
+requirements: ["neutronclient", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Creates a security group with a number of rules
+neutron_sec_group:
+ login_username: "demo"
+ login_password: "password"
+ login_tenant_name: "demo"
+ auth_url: "http://127.0.0.1:5000/v2.0"
+ name: "sg-test"
+ description: "Description of the security group"
+ state: "present"
+ rules:
+ - { direction: "ingress",
+ port_range_min: "80",
+ port_range_max: "80",
+ 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_ip_prefix: "10.0.0.1/24"
+ }
+'''
+
+
+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/"),
+ login_username=dict(required=True),
+ login_password=dict(required=True),
+ login_tenant_name=dict(required=True),
+ name=dict(required=True),
+ description=dict(default=None),
+ region_name=dict(default=None),
+ rules=dict(default=None),
+ tenant_name=dict(required=False),
+ state=dict(default="present", choices=['present', 'absent'])
+ )
+ )
+ network_client = _get_network_client(module.params)
+ identity_client = _get_identity_client(module.params)
+ try:
+ # Get id of security group (as a result check whether it exists)
+ sec_groups = network_client.list_security_groups()["security_groups"]
+ sec_group = next((sg for sg in sec_groups
+ if sg["name"] == module.params['name']), None)
+ sec_group_exists = True if sec_group else False
+
+ # state=present -> create or update depending on whether sg exists.
+ if module.params['state'] == "present":
+ # UPDATE
+ if sec_group_exists:
+ sg = _update_sg(module, network_client, sec_group)
+ module.exit_json(sec_group=sg, updated=True, changed=True)
+ # CREATE
+ else:
+ sg = _create_sg(module, network_client, identity_client)
+ module.exit_json(sec_group=sg, created=True, changed=True)
+ # DELETE
+ elif module.params['state'] == "absent" and sec_group_exists:
+ _delete_sg(module, network_client, sec_group)
+ module.exit_json(changed=True)
+
+ module.exit_json(changed=False)
+
+ except Exception, e:
+ _handle_exception(module, e)
+
+
+def _delete_sg(module, network_client, sec_group):
+ """
+ Deletes a security group.
+ :param module: module to get security group params from.
+ :param network_client: network client to use.
+ :param sec_group: security group to delete.
+ """
+ network_client.delete_security_group(sec_group['id'])
+
+
+def _create_sg(module, network_client, identity_client):
+ """
+ Creates a security group.
+ :param module: module to get security group params from.
+ :param network_client: network client to use.
+ :param: identity_client: identity_client used if an admin performs the
+ operation for a different tenant.
+ :return: newly created security group.
+ """
+ # 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'],
+ }
+ }
+ _add_tenant_id(identity_client, module.params, data['security_group'])
+
+ sg = network_client.create_security_group(data)
+ sg = sg["security_group"]
+
+ sg = _create_sg_rules(network_client, sg, rules)
+ return sg
+
+
+def _update_sg(module, network_client, sg):
+ """
+ Updates a security group.
+ :param module: module to get updated security group param from.
+ :param network_client: network client to use.
+ :param sg: security group that needs to be updated.
+ :return: the updated security group.
+ """
+ # We only allow description updating, no name updating
+ if module.params["description"]:
+ body = {
+ "security_group": {
+ "description": module.params["description"]
+ }
+ }
+ sg = network_client.update_security_group(sg['id'], body)
+ sg = sg['security_group']
+
+ # Security rules group update
+ # We keep things simple: first remove all rules, then insert the new
+ # rules. Not terribly efficient, but easy to implement.
+ existing_rules = network_client.list_security_group_rules(sg['id'])
+ existing_rules = existing_rules['security_group_rules']
+
+ for rule in existing_rules:
+ network_client.delete_security_group_rule(rule['id'])
+
+ sg = _create_sg_rules(network_client, sg, module.params['rules'])
+
+ return sg
+
+
+def _create_sg_rules(network_client, sg, rules):
+ """
+ Creates a set of security group rules in a given security group.
+ :param network_client: network client to use to create rules.
+ :param sg: security group to create rules in.
+ :param rules: rules to create.
+ :return: the updated security group.
+ """
+ if rules:
+ for rule in rules:
+ rule['security_group_id'] = sg['id']
+ data = {
+ "security_group_rule": rule
+ }
+ network_client.create_security_group_rule(data)
+
+ # fetch security group again to show end result
+ return network_client.show_security_group(sg['id'])['security_group']
+ return sg
+
+
+def _handle_exception(module, e):
+ """
+ Convenience method to deal with exceptions.
+ :param module: module object
+ :param e: exception to deal with
+ """
+ if type(e) is neutronclient.common.exceptions.Unauthorized:
+ module.fail_json(msg="Authenticated error: %s" % str(e))
+ else:
+ module.fail_json(msg="An error occured: %s" % str(e))
+
+
+def _add_tenant_id(identity_client, module_params, data):
+ """
+ Adds the tenant_id to the given data dictionary if tenant_name
+ is specified in the module params.
+ :param: identity_client: identity_client used to get the tenant_id from its
+ name.
+ :param module_params: module parameters.
+ :param data: data dictionary to add tenant id to.
+ """
+ tenant_name = module_params.get('tenant_name')
+ if tenant_name:
+ tenant = _get_tenant(identity_client, tenant_name)
+ data['tenant_id'] = tenant.id
+
+
+def _get_tenant(identity_client, tenant_name):
+ """
+ Returns the tenant, given the tenant_name.
+ :param: identity_client: identity client to use to do the required requests.
+ :param: tenant_name: name of the tenant.
+ :return: tenant for which the name was given.
+ """
+ tenants = identity_client.tenants.list()
+ tenant = next((t for t in tenants if t.name == tenant_name), None)
+ if not tenant:
+ raise Exception("Tenant with name '%s' not found." % tenant_name)
+
+ return tenant
+
+
+def _get_network_client(module_params):
+ """
+ :param module_params: module params containing the openstack credentials
+ used to authenticate.
+ :return: a neutron client.
+ """
+ client = neutronclient.v2_0.client.Client(
+ username=module_params.get('login_username'),
+ password=module_params.get('login_password'),
+ tenant_name=module_params.get('login_tenant_name'),
+ auth_url=module_params.get('auth_url'),
+ region_name=module_params.get('region_name'))
+
+ return client
+
+
+def _get_identity_client(module_params):
+ """
+ :param module_params: module params containing the openstack credentials
+ used to authenticate.
+ :return: a keystone client.
+ """
+ client = keystoneclient.v2_0.client.Client(
+ username=module_params.get('login_username'),
+ password=module_params.get('login_password'),
+ tenant_name=module_params.get('login_tenant_name'),
+ auth_url=module_params.get('auth_url'),
+ region_name=module_params.get('region_name'))
+
+ return client
+
+
+# Let's get the party started!
+from ansible.module_utils.basic import *
+
+main() \ No newline at end of file