mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-25 20:01:25 -07:00
Previously, when the attributes of a GCE firewall change, they were ignored. This PR changes that behavior and now updates them. Note that the "update" also removes attributes that are not specified. An overview of the firewall rule behavior is as follows: 1. firewall name in GCP, state=absent in PLAYBOOK: Delete from GCP 2. firewall name in PLAYBOOK, not in GCP: Add to GCP. 3. firewall name in GCP, name not in PLAYBOOK: No change. 4. firewall names exist in both GCP and PLAYBOOK, attributes differ: Update GCP to match attributes from PLAYBOOK.
336 lines
11 KiB
Python
336 lines
11 KiB
Python
#!/usr/bin/python
|
|
# Copyright 2013 Google Inc.
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible 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.
|
|
#
|
|
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: gce_net
|
|
version_added: "1.5"
|
|
short_description: create/destroy GCE networks and firewall rules
|
|
description:
|
|
- This module can create and destroy Google Compue Engine networks and
|
|
firewall rules U(https://developers.google.com/compute/docs/networking).
|
|
The I(name) parameter is reserved for referencing a network while the
|
|
I(fwname) parameter is used to reference firewall rules.
|
|
IPv4 Address ranges must be specified using the CIDR
|
|
U(http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) format.
|
|
Full install/configuration instructions for the gce* modules can
|
|
be found in the comments of ansible/test/gce_tests.py.
|
|
options:
|
|
allowed:
|
|
description:
|
|
- the protocol:ports to allow ('tcp:80' or 'tcp:80,443' or 'tcp:80-800;udp:1-25')
|
|
this parameter is mandatory when creating or updating a firewall rule
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
ipv4_range:
|
|
description:
|
|
- the IPv4 address range in CIDR notation for the network
|
|
this parameter is not mandatory when you specified existing network in name parameter, but when you create new network, this parameter is mandatory
|
|
required: false
|
|
aliases: ['cidr']
|
|
fwname:
|
|
description:
|
|
- name of the firewall rule
|
|
required: false
|
|
default: null
|
|
aliases: ['fwrule']
|
|
name:
|
|
description:
|
|
- name of the network
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
src_range:
|
|
description:
|
|
- the source IPv4 address range in CIDR notation
|
|
required: false
|
|
default: null
|
|
aliases: ['src_cidr']
|
|
src_tags:
|
|
description:
|
|
- the source instance tags for creating a firewall rule
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
target_tags:
|
|
version_added: "1.9"
|
|
description:
|
|
- the target instance tags for creating a firewall rule
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
state:
|
|
description:
|
|
- desired state of the network or firewall
|
|
required: false
|
|
default: "present"
|
|
choices: ["active", "present", "absent", "deleted"]
|
|
aliases: []
|
|
service_account_email:
|
|
version_added: "1.6"
|
|
description:
|
|
- service account email
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
pem_file:
|
|
version_added: "1.6"
|
|
description:
|
|
- path to the pem file associated with the service account email
|
|
This option is deprecated. Use 'credentials_file'.
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
credentials_file:
|
|
version_added: "2.1.0"
|
|
description:
|
|
- path to the JSON file associated with the service account email
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
project_id:
|
|
version_added: "1.6"
|
|
description:
|
|
- your GCE project ID
|
|
required: false
|
|
default: null
|
|
aliases: []
|
|
|
|
requirements:
|
|
- "python >= 2.6"
|
|
- "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials"
|
|
author: "Eric Johnson (@erjohnso) <erjohnso@google.com>"
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Simple example of creating a new network
|
|
- local_action:
|
|
module: gce_net
|
|
name: privatenet
|
|
ipv4_range: '10.240.16.0/24'
|
|
|
|
# Simple example of creating a new firewall rule
|
|
- local_action:
|
|
module: gce_net
|
|
name: privatenet
|
|
fwname: all-web-webproxy
|
|
allowed: tcp:80,8080
|
|
src_tags: ["web", "proxy"]
|
|
|
|
'''
|
|
|
|
try:
|
|
from libcloud.compute.types import Provider
|
|
from libcloud.compute.providers import get_driver
|
|
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
|
ResourceExistsError, ResourceNotFoundError
|
|
_ = Provider.GCE
|
|
HAS_LIBCLOUD = True
|
|
except ImportError:
|
|
HAS_LIBCLOUD = False
|
|
|
|
def format_allowed_section(allowed):
|
|
"""Format each section of the allowed list"""
|
|
if allowed.count(":") == 0:
|
|
protocol = allowed
|
|
ports = []
|
|
elif allowed.count(":") == 1:
|
|
protocol, ports = allowed.split(":")
|
|
else:
|
|
return []
|
|
if ports.count(","):
|
|
ports = ports.split(",")
|
|
else:
|
|
ports = [ports]
|
|
return_val = {"IPProtocol": protocol}
|
|
if ports:
|
|
return_val["ports"] = ports
|
|
return return_val
|
|
|
|
def format_allowed(allowed):
|
|
"""Format the 'allowed' value so that it is GCE compatible."""
|
|
return_value = []
|
|
if allowed.count(";") == 0:
|
|
return [format_allowed_section(allowed)]
|
|
else:
|
|
sections = allowed.split(";")
|
|
for section in sections:
|
|
return_value.append(format_allowed_section(section))
|
|
return return_value
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec = dict(
|
|
allowed = dict(),
|
|
ipv4_range = dict(),
|
|
fwname = dict(),
|
|
name = dict(),
|
|
src_range = dict(type='list'),
|
|
src_tags = dict(type='list'),
|
|
target_tags = dict(type='list'),
|
|
state = dict(default='present'),
|
|
service_account_email = dict(),
|
|
pem_file = dict(),
|
|
credentials_file = dict(),
|
|
project_id = dict(),
|
|
)
|
|
)
|
|
|
|
if not HAS_LIBCLOUD:
|
|
module.exit_json(msg='libcloud with GCE support (0.17.0+) required for this module')
|
|
|
|
gce = gce_connect(module)
|
|
|
|
allowed = module.params.get('allowed')
|
|
ipv4_range = module.params.get('ipv4_range')
|
|
fwname = module.params.get('fwname')
|
|
name = module.params.get('name')
|
|
src_range = module.params.get('src_range')
|
|
src_tags = module.params.get('src_tags')
|
|
target_tags = module.params.get('target_tags')
|
|
state = module.params.get('state')
|
|
|
|
changed = False
|
|
json_output = {'state': state}
|
|
|
|
if state in ['active', 'present']:
|
|
network = None
|
|
try:
|
|
network = gce.ex_get_network(name)
|
|
json_output['name'] = name
|
|
json_output['ipv4_range'] = network.cidr
|
|
except ResourceNotFoundError:
|
|
pass
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
|
|
# user wants to create a new network that doesn't yet exist
|
|
if name and not network:
|
|
if not ipv4_range:
|
|
module.fail_json(msg="Network '" + name + "' is not found. To create network, 'ipv4_range' parameter is required",
|
|
changed=False)
|
|
|
|
try:
|
|
network = gce.ex_create_network(name, ipv4_range)
|
|
json_output['name'] = name
|
|
json_output['ipv4_range'] = ipv4_range
|
|
changed = True
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
|
|
if fwname:
|
|
# user creating a firewall rule
|
|
if not allowed and not src_range and not src_tags:
|
|
if changed and network:
|
|
module.fail_json(
|
|
msg="Network created, but missing required " + \
|
|
"firewall rule parameter(s)", changed=True)
|
|
module.fail_json(
|
|
msg="Missing required firewall rule parameter(s)",
|
|
changed=False)
|
|
|
|
allowed_list = format_allowed(allowed)
|
|
|
|
# Fetch existing rule and if it exists, compare attributes
|
|
# update if attributes changed. Create if doesn't exist.
|
|
try:
|
|
fw_changed = False
|
|
fw = gce.ex_get_firewall(fwname)
|
|
|
|
# If old and new attributes are different, we update the firewall rule.
|
|
# This implicitly let's us clear out attributes as well.
|
|
# allowed_list is required and must not be None for firewall rules.
|
|
if allowed_list and (allowed_list != fw.allowed):
|
|
fw.allowed = allowed_list
|
|
fw_changed = True
|
|
|
|
if src_range != fw.source_ranges:
|
|
fw.source_ranges = src_range
|
|
fw_changed = True
|
|
|
|
if src_tags != fw.source_tags:
|
|
fw.source_tags = src_tags
|
|
fw_changed = True
|
|
|
|
if src_tags != fw.target_tags:
|
|
fw.target_tags = target_tags
|
|
fw_changed = True
|
|
|
|
if fw_changed is True:
|
|
try:
|
|
gce.ex_update_firewall(fw)
|
|
changed = True
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
|
|
# Firewall rule not found so we try to create it.
|
|
except ResourceNotFoundError:
|
|
try:
|
|
gce.ex_create_firewall(fwname, allowed_list, network=name,
|
|
source_ranges=src_range, source_tags=src_tags, target_tags=target_tags)
|
|
changed = True
|
|
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
|
|
json_output['fwname'] = fwname
|
|
json_output['allowed'] = allowed
|
|
json_output['src_range'] = src_range
|
|
json_output['src_tags'] = src_tags
|
|
json_output['target_tags'] = target_tags
|
|
|
|
if state in ['absent', 'deleted']:
|
|
if fwname:
|
|
json_output['fwname'] = fwname
|
|
fw = None
|
|
try:
|
|
fw = gce.ex_get_firewall(fwname)
|
|
except ResourceNotFoundError:
|
|
pass
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
if fw:
|
|
gce.ex_destroy_firewall(fw)
|
|
changed = True
|
|
elif name:
|
|
json_output['name'] = name
|
|
network = None
|
|
try:
|
|
network = gce.ex_get_network(name)
|
|
|
|
except ResourceNotFoundError:
|
|
pass
|
|
except Exception as e:
|
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
|
if network:
|
|
gce.ex_destroy_network(network)
|
|
changed = True
|
|
|
|
json_output['changed'] = changed
|
|
module.exit_json(**json_output)
|
|
|
|
# import module snippets
|
|
from ansible.module_utils.basic import *
|
|
from ansible.module_utils.gce import *
|
|
|
|
if __name__ == '__main__':
|
|
main()
|