diff --git a/lib/ansible/modules/cloud/google/gcp_healthcheck.py b/lib/ansible/modules/cloud/google/gcp_healthcheck.py
new file mode 100644
index 00000000000..0b3ae9355df
--- /dev/null
+++ b/lib/ansible/modules/cloud/google/gcp_healthcheck.py
@@ -0,0 +1,469 @@
+#!/usr/bin/python
+# Copyright 2017 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 .
+
+ANSIBLE_METADATA = {'metadata_version': '1.0',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+
+DOCUMENTATION = '''
+---
+module: gcp_healthcheck
+version_added: "2.4"
+short_description: Create, Update or Destroy a Healthcheck.
+description:
+ - Create, Update or Destroy a Healthcheck. Currently only HTTP and
+ HTTPS Healthchecks are supported. Healthchecks are used to monitor
+ individual instances, managed instance groups and/or backend
+ services. Healtchecks are reusable.
+ - Visit
+ U(https://cloud.google.com/compute/docs/load-balancing/health-checks)
+ for an overview of Healthchecks on GCP.
+ - See
+ U(https://cloud.google.com/compute/docs/reference/latest/httpHealthChecks) for
+ API details on HTTP Healthchecks.
+ - See
+ U(https://cloud.google.com/compute/docs/reference/latest/httpsHealthChecks)
+ for more details on the HTTPS Healtcheck API.
+requirements:
+ - "python >= 2.6"
+ - "google-api-python-client >= 1.6.2"
+ - "google-auth >= 0.9.0"
+ - "google-auth-httplib2 >= 0.0.2"
+notes:
+ - Only supports HTTP and HTTPS Healthchecks currently.
+author:
+ - "Tom Melendez (@supertom) "
+options:
+ check_interval:
+ description:
+ - How often (in seconds) to send a health check.
+ required: false
+ default: 5
+ healthcheck_name:
+ description:
+ - Name of the Healthcheck.
+ required: true
+ healthcheck_type:
+ description:
+ - Type of Healthcheck.
+ required: true
+ choices: ["HTTP", "HTTPS"]
+ host_header:
+ description:
+ - The value of the host header in the health check request. If left
+ empty, the public IP on behalf of which this health
+ check is performed will be used.
+ required: true
+ default: ""
+ port:
+ description:
+ - The TCP port number for the health check request. The default value is
+ 443 for HTTPS and 80 for HTTP.
+ required: false
+ request_path:
+ description:
+ - The request path of the HTTPS health check request.
+ required: false
+ default: "/"
+ state:
+ description: State of the Healthcheck.
+ required: true
+ choices: ["present", "absent"]
+ timeout:
+ description:
+ - How long (in seconds) to wait for a response before claiming
+ failure. It is invalid for timeout
+ to have a greater value than check_interval.
+ required: false
+ default: 5
+ unhealthy_threshold:
+ description:
+ - A so-far healthy instance will be marked unhealthy after this
+ many consecutive failures.
+ required: false
+ default: 2
+ healthy_threshold:
+ description:
+ - A so-far unhealthy instance will be marked healthy after this
+ many consecutive successes.
+ required: false
+ default: 2
+ service_account_email:
+ description:
+ - service account email
+ required: false
+ default: null
+ service_account_permissions:
+ version_added: "2.0"
+ description:
+ - service account permissions (see
+ U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create),
+ --scopes section for detailed information)
+ required: false
+ default: null
+ choices: [
+ "bigquery", "cloud-platform", "compute-ro", "compute-rw",
+ "useraccounts-ro", "useraccounts-rw", "datastore", "logging-write",
+ "monitoring", "sql-admin", "storage-full", "storage-ro",
+ "storage-rw", "taskqueue", "userinfo-email"
+ ]
+ credentials_file:
+ description:
+ - Path to the JSON file associated with the service account email
+ default: null
+ required: false
+ project_id:
+ description:
+ - Your GCP project ID
+ required: false
+ default: null
+'''
+
+EXAMPLES = '''
+- name: Create Minimum HealthCheck
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: my-healthcheck
+ healthcheck_type: HTTP
+ state: present
+- name: Create HTTP HealthCheck
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: my-healthcheck
+ healthcheck_type: HTTP
+ host: my-host
+ request_path: /hc
+ check_interval: 10
+ timeout: 30
+ unhealthy_threshhold: 2
+ healthy_threshhold: 1
+ state: present
+- name: Create HTTPS HealthCheck
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ https_healthcheck }}"
+ healthcheck_type: HTTPS
+ host_header: my-host
+ request_path: /hc
+ check_interval: 5
+ timeout: 5
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ state: present
+'''
+
+RETURN = '''
+state:
+ description: state of the Healthcheck
+ returned: Always.
+ type: str
+ sample: present
+healthcheck_name:
+ description: Name of the Healthcheck
+ returned: Always
+ type: str
+ sample: my-url-map
+healthcheck_type:
+ description: Type of the Healthcheck
+ returned: Always
+ type: str
+ sample: HTTP
+healthcheck:
+ description: GCP Healthcheck dictionary
+ returned: Always. Refer to GCP documentation for detailed field descriptions.
+ type: dict
+ sample: { "name": "my-hc", "port": 443, "requestPath": "/foo" }
+'''
+
+
+# import module snippets
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.gcp import check_params, get_google_api_client, GCPUtils
+
+USER_AGENT_PRODUCT = 'ansible-healthcheck'
+USER_AGENT_VERSION = '0.0.1'
+
+
+def _validate_healthcheck_params(params):
+ """
+ Validate healthcheck params.
+
+ Simple validation has already assumed by AnsibleModule.
+
+ :param params: Ansible dictionary containing configuration.
+ :type params: ``dict``
+
+ :return: True or raises ValueError
+ :rtype: ``bool`` or `class:ValueError`
+ """
+ if params['timeout'] > params['check_interval']:
+ raise ValueError("timeout (%s) is greater than check_interval (%s)" % (
+ params['timeout'], params['check_interval']))
+
+ return (True, '')
+
+
+def _build_healthcheck_dict(params):
+ """
+ Reformat services in Ansible Params for GCP.
+
+ :param params: Params from AnsibleModule object
+ :type params: ``dict``
+
+ :param project_id: The GCP project ID.
+ :type project_id: ``str``
+
+ :return: dictionary suitable for submission to GCP
+ HealthCheck (HTTP/HTTPS) API.
+ :rtype ``dict``
+ """
+ gcp_dict = GCPUtils.params_to_gcp_dict(params, 'healthcheck_name')
+ if 'timeout' in gcp_dict:
+ gcp_dict['timeoutSec'] = gcp_dict['timeout']
+ del gcp_dict['timeout']
+
+ if 'checkInterval' in gcp_dict:
+ gcp_dict['checkIntervalSec'] = gcp_dict['checkInterval']
+ del gcp_dict['checkInterval']
+
+ if 'hostHeader' in gcp_dict:
+ gcp_dict['host'] = gcp_dict['hostHeader']
+ del gcp_dict['hostHeader']
+
+ if 'healthcheckType' in gcp_dict:
+ del gcp_dict['healthcheckType']
+ return gcp_dict
+
+
+def _get_req_resource(client, resource_type):
+ if resource_type == 'HTTPS':
+ return (client.httpsHealthChecks(), 'httpsHealthCheck')
+ else:
+ return (client.httpHealthChecks(), 'httpHealthCheck')
+
+
+def get_healthcheck(client, name, project_id=None, resource_type='HTTP'):
+ """
+ Get a Healthcheck from GCP.
+
+ :param client: An initialized GCE Compute Disovery resource.
+ :type client: :class: `googleapiclient.discovery.Resource`
+
+ :param name: Name of the Url Map.
+ :type name: ``str``
+
+ :param project_id: The GCP project ID.
+ :type project_id: ``str``
+
+ :return: A dict resp from the respective GCP 'get' request.
+ :rtype: ``dict``
+ """
+ try:
+ resource, entity_name = _get_req_resource(client, resource_type)
+ args = {'project': project_id, entity_name: name}
+ req = resource.get(**args)
+ return GCPUtils.execute_api_client_req(req, raise_404=False)
+ except:
+ raise
+
+
+def create_healthcheck(client, params, project_id, resource_type='HTTP'):
+ """
+ Create a new Healthcheck.
+
+ :param client: An initialized GCE Compute Disovery resource.
+ :type client: :class: `googleapiclient.discovery.Resource`
+
+ :param params: Dictionary of arguments from AnsibleModule.
+ :type params: ``dict``
+
+ :return: Tuple with changed status and response dict
+ :rtype: ``tuple`` in the format of (bool, dict)
+ """
+ gcp_dict = _build_healthcheck_dict(params)
+ try:
+ resource, _ = _get_req_resource(client, resource_type)
+ args = {'project': project_id, 'body': gcp_dict}
+ req = resource.insert(**args)
+ return_data = GCPUtils.execute_api_client_req(req, client, raw=False)
+ if not return_data:
+ return_data = get_healthcheck(client,
+ name=params['healthcheck_name'],
+ project_id=project_id)
+ return (True, return_data)
+ except:
+ raise
+
+
+def delete_healthcheck(client, name, project_id, resource_type='HTTP'):
+ """
+ Delete a Healthcheck.
+
+ :param client: An initialized GCE Compute Disover resource.
+ :type client: :class: `googleapiclient.discovery.Resource`
+
+ :param name: Name of the Url Map.
+ :type name: ``str``
+
+ :param project_id: The GCP project ID.
+ :type project_id: ``str``
+
+ :return: Tuple with changed status and response dict
+ :rtype: ``tuple`` in the format of (bool, dict)
+ """
+ try:
+ resource, entity_name = _get_req_resource(client, resource_type)
+ args = {'project': project_id, entity_name: name}
+ req = resource.delete(**args)
+ return_data = GCPUtils.execute_api_client_req(req, client)
+ return (True, return_data)
+ except:
+ raise
+
+
+def update_healthcheck(client, healthcheck, params, name, project_id,
+ resource_type='HTTP'):
+ """
+ Update a Healthcheck.
+
+ If the healthcheck has not changed, the update will not occur.
+
+ :param client: An initialized GCE Compute Disovery resource.
+ :type client: :class: `googleapiclient.discovery.Resource`
+
+ :param healthcheck: Name of the Url Map.
+ :type healthcheck: ``dict``
+
+ :param params: Dictionary of arguments from AnsibleModule.
+ :type params: ``dict``
+
+ :param name: Name of the Url Map.
+ :type name: ``str``
+
+ :param project_id: The GCP project ID.
+ :type project_id: ``str``
+
+ :return: Tuple with changed status and response dict
+ :rtype: ``tuple`` in the format of (bool, dict)
+ """
+ gcp_dict = _build_healthcheck_dict(params)
+ ans = GCPUtils.are_params_equal(healthcheck, gcp_dict)
+ if ans:
+ return (False, 'no update necessary')
+
+ try:
+ resource, entity_name = _get_req_resource(client, resource_type)
+ args = {'project': project_id, entity_name: name, 'body': gcp_dict}
+ req = resource.update(**args)
+ return_data = GCPUtils.execute_api_client_req(
+ req, client=client, raw=False)
+ return (True, return_data)
+ except:
+ raise
+
+
+def main():
+ module = AnsibleModule(argument_spec=dict(
+ healthcheck_name=dict(required=True),
+ healthcheck_type=dict(required=True,
+ choices=['HTTP', 'HTTPS']),
+ request_path=dict(required=False, default='/'),
+ check_interval=dict(required=False, type='int', default=5),
+ healthy_threshold=dict(required=False, type='int', default=2),
+ unhealthy_threshold=dict(required=False, type='int', default=2),
+ host_header=dict(required=False, type='str', default=''),
+ timeout=dict(required=False, type='int', default=5),
+ port=dict(required=False, type='int'),
+ state=dict(choices=['absent', 'present'], default='present'),
+ service_account_email=dict(),
+ service_account_permissions=dict(type='list'),
+ credentials_file=dict(),
+ project_id=dict(), ), )
+
+ client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT,
+ user_agent_version=USER_AGENT_VERSION)
+
+ params = {}
+
+ params['healthcheck_name'] = module.params.get('healthcheck_name')
+ params['healthcheck_type'] = module.params.get('healthcheck_type')
+ params['request_path'] = module.params.get('request_path')
+ params['check_interval'] = module.params.get('check_interval')
+ params['healthy_threshold'] = module.params.get('healthy_threshold')
+ params['unhealthy_threshold'] = module.params.get('unhealthy_threshold')
+ params['host_header'] = module.params.get('host_header')
+ params['timeout'] = module.params.get('timeout')
+ params['port'] = module.params.get('port', None)
+ params['state'] = module.params.get('state')
+
+ if not params['port']:
+ params['port'] = 80
+ if params['healthcheck_type'] == 'HTTPS':
+ params['port'] = 443
+ try:
+ _validate_healthcheck_params(params)
+ except Exception as e:
+ module.fail_json(msg=e.message, changed=False)
+
+ changed = False
+ json_output = {'state': params['state']}
+ healthcheck = get_healthcheck(client,
+ name=params['healthcheck_name'],
+ project_id=conn_params['project_id'],
+ resource_type=params['healthcheck_type'])
+
+ if not healthcheck:
+ if params['state'] == 'absent':
+ # Doesn't exist in GCE, and state==absent.
+ changed = False
+ module.fail_json(
+ msg="Cannot delete unknown healthcheck: %s" %
+ (params['healthcheck_name']))
+ else:
+ # Create
+ changed, json_output['healthcheck'] = create_healthcheck(client,
+ params=params,
+ project_id=conn_params['project_id'],
+ resource_type=params['healthcheck_type'])
+ elif params['state'] == 'absent':
+ # Delete
+ changed, json_output['healthcheck'] = delete_healthcheck(client,
+ name=params['healthcheck_name'],
+ project_id=conn_params['project_id'],
+ resource_type=params['healthcheck_type'])
+ else:
+ changed, json_output['healthcheck'] = update_healthcheck(client,
+ healthcheck=healthcheck,
+ params=params,
+ name=params['healthcheck_name'],
+ project_id=conn_params['project_id'],
+ resource_type=params['healthcheck_type'])
+ json_output['changed'] = changed
+ json_output.update(params)
+ module.exit_json(**json_output)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/gce.yml b/test/integration/gce.yml
index 268b0257705..f19201ec138 100644
--- a/test/integration/gce.yml
+++ b/test/integration/gce.yml
@@ -8,4 +8,5 @@
- { role: test_gce_tag, tags: test_gce_tag }
- { role: test_gce_net, tags: test_gce_net }
- { role: test_gcp_url_map, tags: test_gcp_url_map }
+ - { role: test_gcp_healthcheck, tags: test_gcp_healthcheck }
# TODO: tests for gce_lb, gc_storage
diff --git a/test/integration/roles/test_gcp_healthcheck/defaults/main.yml b/test/integration/roles/test_gcp_healthcheck/defaults/main.yml
new file mode 100644
index 00000000000..a993cf43ea3
--- /dev/null
+++ b/test/integration/roles/test_gcp_healthcheck/defaults/main.yml
@@ -0,0 +1,7 @@
+---
+# defaults file for test_gcp_healthcheck
+service_account_email: "{{ gce_service_account_email }}"
+credentials_file: "{{ gce_pem_file }}"
+project_id: "{{ gce_project_id }}"
+http_healthcheck: "ans-int-healthcheck-http-{{ resource_prefix|lower }}"
+https_healthcheck: "ans-int-healthcheck-https-{{ resource_prefix|lower }}"
\ No newline at end of file
diff --git a/test/integration/roles/test_gcp_healthcheck/tasks/main.yml b/test/integration/roles/test_gcp_healthcheck/tasks/main.yml
new file mode 100644
index 00000000000..cb3d4416d08
--- /dev/null
+++ b/test/integration/roles/test_gcp_healthcheck/tasks/main.yml
@@ -0,0 +1,176 @@
+# GCP Healthcheck Integration Tests.
+######
+# ============================================================
+- name: param check
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ http_healthcheck }}"
+ healthcheck_type: HTTP
+ host_header: my-host
+ request_path: /hc
+ check_interval: 10
+ timeout: 30
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ state: present
+ ignore_errors: True
+ register: result
+- name: check interval
+ assert:
+ that:
+ 'result.msg == "timeout (30) is greater than check_interval (10)"'
+# ============================================================
+- name: create "{{ http_healthcheck }}"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ http_healthcheck }}"
+ healthcheck_type: HTTP
+ host_header: my-host
+ request_path: /hc
+ check_interval: 5
+ timeout: 5
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ state: present
+ register: result
+- name: assert create
+ assert:
+ that:
+ - 'result.state == "present"'
+ - 'result.changed'
+# ============================================================
+- name: "update {{ http_healthcheck }}, no change"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ http_healthcheck }}"
+ healthcheck_type: HTTP
+ host_header: my-host
+ request_path: /hc
+ check_interval: 5
+ timeout: 5
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ state: present
+ register: result
+- name: assert update no change
+ assert:
+ that:
+ - 'result.state == "present"'
+ - 'not result.changed'
+ - 'result.port == 80'
+# ============================================================
+- name: create minimum "{{ https_healthcheck }}"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ https_healthcheck }}"
+ healthcheck_type: HTTPS
+ state: present
+ register: result
+- name: assert create
+ assert:
+ that:
+ - 'result.state == "present"'
+ - 'result.changed'
+# ============================================================
+- name: "update {{ https_healthcheck }}, no change"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ https_healthcheck }}"
+ healthcheck_type: HTTPS
+ state: present
+ register: result
+- name: assert not updated
+ assert:
+ that:
+ - 'result.state == "present"'
+ - 'result.port == 443'
+ - 'not result.changed'
+# ============================================================
+- name: update "{{ https_healthcheck }}"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ https_healthcheck }}"
+ healthcheck_type: HTTPS
+ host_header: my-host
+ request_path: /hc
+ check_interval: 5
+ timeout: 5
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ port: 444
+ state: present
+ register: result
+- name: assert update "{{ https_healthcheck }}"
+ assert:
+ that:
+ - 'result.state == "present"'
+ - 'result.changed'
+ - 'result.port == 444'
+# ============================================================
+- pause: seconds=5
+# ============================================================
+- name: delete "{{ http_healthcheck }}"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ http_healthcheck }}"
+ healthcheck_type: HTTP
+ host_header: my-host
+ request_path: /hc
+ check_interval: 5
+ timeout: 5
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ state: absent
+ register: result
+ tags:
+ - delete
+- name: assert absent
+ assert:
+ that:
+ - 'result.state == "absent"'
+ - 'result.changed'
+# ============================================================
+- name: delete "{{ https_healthcheck }}"
+# ============================================================
+ gcp_healthcheck:
+ service_account_email: "{{ service_account_email }}"
+ credentials_file: "{{ credentials_file }}"
+ project_id: "{{ project_id }}"
+ healthcheck_name: "{{ https_healthcheck }}"
+ healthcheck_type: HTTPS
+ host_header: my-host
+ request_path: /hc
+ check_interval: 5
+ timeout: 5
+ unhealthy_threshold: 2
+ healthy_threshold: 1
+ state: absent
+ register: result
+ tags:
+ - delete
+- name: assert absent
+ assert:
+ that:
+ - 'result.state == "absent"'
+ - 'result.changed'
diff --git a/test/units/module_utils/gcp/test_utils.py b/test/units/module_utils/gcp/test_utils.py
index 98425219492..e2b0feb612e 100644
--- a/test/units/module_utils/gcp/test_utils.py
+++ b/test/units/module_utils/gcp/test_utils.py
@@ -342,3 +342,32 @@ class GCPUtilsTestCase(unittest.TestCase):
# params1 has exclude fields, params2 doesn't. Should be equal
actual = GCPUtils.are_params_equal(params1, params2)
self.assertTrue(actual)
+
+ def test_filter_gcp_fields(self):
+ input_data = {
+ u'kind': u'compute#httpsHealthCheck',
+ u'description': u'',
+ u'timeoutSec': 5,
+ u'checkIntervalSec': 5,
+ u'port': 443,
+ u'healthyThreshold': 2,
+ u'host': u'',
+ u'requestPath': u'/',
+ u'unhealthyThreshold': 2,
+ u'creationTimestamp': u'2017-05-16T15:09:36.546-07:00',
+ u'id': u'8727093129334146639',
+ u'selfLink': u'https://www.googleapis.com/compute/v1/projects/myproject/global/httpsHealthChecks/myhealthcheck',
+ u'name': u'myhealthcheck'}
+
+ expected = {
+ 'name': 'myhealthcheck',
+ 'checkIntervalSec': 5,
+ 'port': 443,
+ 'unhealthyThreshold': 2,
+ 'healthyThreshold': 2,
+ 'host': '',
+ 'timeoutSec': 5,
+ 'requestPath': '/'}
+
+ actual = GCPUtils.filter_gcp_fields(input_data)
+ self.assertEquals(expected, actual)