diff --git a/lib/ansible/modules/cloud/google/gce_mig.py b/lib/ansible/modules/cloud/google/gce_mig.py index bb44be1f25b..11433b35b20 100644 --- a/lib/ansible/modules/cloud/google/gce_mig.py +++ b/lib/ansible/modules/cloud/google/gce_mig.py @@ -84,6 +84,13 @@ options: on Autoscaling. required: false default: null + named_ports: + version_added: "2.3" + description: + - Define named ports that backend services can forward data to. Format is a a list of + name:port dictionaries. + required: false + default: null ''' EXAMPLES = ''' @@ -104,6 +111,11 @@ EXAMPLES = ''' state: present size: 1 template: my-instance-template-1 + named_ports: + - name: http + port: 80 + - name: foobar + port: 82 - pause: seconds=30 - name: Recreate MIG Instances with Instance Template change. gce_mig: @@ -167,6 +179,12 @@ name: type: string sample: "my-managed-instance-group" +named_ports: + description: list of named ports acted upon + returned: when named_ports are initially set or updated + type: list + sample: [{ "name": "http", "port": 80 }, { "name": "foo", "port": 82 }] + size: description: Number of VMs in Managed Instance Group. returned: changed @@ -222,6 +240,18 @@ deleted_autoscaler: returned: When the delete of an Autoscaler was attempted. type: bool sample: true + +set_named_ports: + description: True if the named_ports have been set + returned: named_ports have been set + type: bool + sample: true + +updated_named_ports: + description: True if the named_ports have been updated + returned: named_ports have been updated + type: bool + sample: true ''' import socket @@ -324,6 +354,38 @@ def _validate_autoscaling_params(params): return (True, '') +def _validate_named_port_params(params): + """ + Validate the named ports parameters + + :param params: Ansible dictionary containing named_ports configuration + It is expected that autoscaling config will be found at the + key 'named_ports'. That key should contain a list of + {name : port} dictionaries. + :type params: ``dict`` + + :return: Tuple containing a boolean and a string. True if params + are valid, False otherwise, plus str for message. + :rtype: ``(``bool``, ``str``)`` + """ + if not params['named_ports']: + # It's optional, so if not set at all, it's valid. + return (True, '') + if not isinstance(params['named_ports'], list): + return (False, 'named_ports: expected list of name:port dictionaries.') + req_fields = [ + {'name': 'name', 'required': True, 'type': str}, + {'name': 'port', 'required': True, 'type': int} + ] # yapf: disable + + for np in params['named_ports']: + (valid_named_ports, np_msg) = _check_params(np, req_fields) + if not valid_named_ports: + return (False, np_msg) + + return (True, '') + + def _get_instance_list(mig, field='name', filter_list=['NONE']): """ Helper to grab field from instances response. @@ -595,6 +657,38 @@ def get_mig(gce, name, zone): return None +def update_named_ports(mig, named_ports): + """ + Set the named ports on a Managed Instance Group. + + Sort the existing named ports and new. If different, update. + This also implicitly allows for the removal of named_por + + :param mig: Managed Instance Group Object from libcloud. + :type mig: :class: `GCEInstanceGroupManager` + + :param named_ports: list of dictionaries in the format of {'name': port} + :type named_ports: ``list`` of ``dict`` + + :return: True if successful + :rtype: ``bool`` + """ + changed = False + existing_ports = [] + new_ports = [] + if hasattr(mig.instance_group, 'named_ports'): + existing_ports = sorted(mig.instance_group.named_ports, + key=lambda x: x['name']) + if named_ports is not None: + new_ports = sorted(named_ports, key=lambda x: x['name']) + + if existing_ports != new_ports: + if mig.instance_group.set_named_ports(named_ports): + changed = True + + return changed + + def main(): module = AnsibleModule(argument_spec=dict( name=dict(required=True), @@ -607,6 +701,7 @@ def main(): state=dict(choices=['absent', 'present'], default='present'), zone=dict(required=True), autoscaling=dict(type='dict', default=None), + named_ports=dict(type='list', default=None), service_account_email=dict(), service_account_permissions=dict(type='list'), pem_file=dict(), @@ -634,11 +729,22 @@ def main(): params['template'] = module.params.get('template') params['recreate_instances'] = module.params.get('recreate_instances') params['autoscaling'] = module.params.get('autoscaling', None) + params['named_ports'] = module.params.get('named_ports', None) (valid_autoscaling, as_msg) = _validate_autoscaling_params(params) if not valid_autoscaling: module.fail_json(msg=as_msg, changed=False) + if params['named_ports'] is not None and not hasattr( + gce, 'ex_instancegroup_set_named_ports'): + module.fail_json( + msg="Apache Libcloud 1.3.0+ is required to use 'named_ports' option", + changed=False) + + (valid_named_ports, np_msg) = _validate_named_port_params(params) + if not valid_named_ports: + module.fail_json(msg=np_msg, changed=False) + changed = False json_output = {'state': params['state'], 'zone': params['zone']} mig = get_mig(gce, params['name'], params['zone']) @@ -681,6 +787,18 @@ def main(): changed=False) json_output['created_autoscaler'] = True + # Add named ports if available + if params['named_ports']: + mig = get_mig(gce, params['name'], params['zone']) + if not mig: + module.fail_json( + msg='Unable to fetch created MIG %s to create \ + autoscaler in zone: %s' % ( + params['name'], params['zone']), changed=False) + json_output['set_named_ports'] = update_named_ports( + mig, params['named_ports']) + if json_output['set_named_ports']: + json_output['named_ports'] = params['named_ports'] elif params['state'] == 'absent': # Delete MIG @@ -704,6 +822,7 @@ def main(): else: # Update MIG + # If we're going to update a MIG, we need a size and template values. # If not specified, we use the values from the existing MIG. if not params['size']: @@ -755,6 +874,11 @@ def main(): changed = update_autoscaler(gce, autoscaler, params['autoscaling']) json_output['updated_autoscaler'] = changed + named_ports = params['named_ports'] or [] + json_output['updated_named_ports'] = update_named_ports(mig, + named_ports) + if json_output['updated_named_ports']: + json_output['named_ports'] = named_ports json_output['changed'] = changed json_output.update(params)