mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 13:56:09 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			366 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| #
 | |
| # Scaleway Load-balancer management module
 | |
| #
 | |
| # Copyright (C) 2018 Online SAS.
 | |
| # https://www.scaleway.com
 | |
| #
 | |
| # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function
 | |
| 
 | |
| __metaclass__ = type
 | |
| 
 | |
| DOCUMENTATION = '''
 | |
| ---
 | |
| module: scaleway_lb
 | |
| short_description: Scaleway load-balancer management module
 | |
| author: Remy Leone (@remyleone)
 | |
| description:
 | |
|     - "This module manages load-balancers on Scaleway."
 | |
| extends_documentation_fragment:
 | |
|     - community.general.scaleway
 | |
|     - community.general.attributes
 | |
| 
 | |
| attributes:
 | |
|   check_mode:
 | |
|     support: full
 | |
|   diff_mode:
 | |
|     support: none
 | |
| 
 | |
| options:
 | |
| 
 | |
|   name:
 | |
|     type: str
 | |
|     description:
 | |
|       - Name of the load-balancer.
 | |
|     required: true
 | |
| 
 | |
|   description:
 | |
|     type: str
 | |
|     description:
 | |
|       - Description of the load-balancer.
 | |
|     required: true
 | |
| 
 | |
|   organization_id:
 | |
|     type: str
 | |
|     description:
 | |
|       - Organization identifier.
 | |
|     required: true
 | |
| 
 | |
|   state:
 | |
|     type: str
 | |
|     description:
 | |
|      - Indicate desired state of the instance.
 | |
|     default: present
 | |
|     choices:
 | |
|       - present
 | |
|       - absent
 | |
| 
 | |
|   region:
 | |
|     type: str
 | |
|     description:
 | |
|     - Scaleway zone.
 | |
|     required: true
 | |
|     choices:
 | |
|       - nl-ams
 | |
|       - fr-par
 | |
|       - pl-waw
 | |
| 
 | |
|   tags:
 | |
|     type: list
 | |
|     elements: str
 | |
|     default: []
 | |
|     description:
 | |
|     - List of tags to apply to the load-balancer.
 | |
| 
 | |
|   wait:
 | |
|     description:
 | |
|     - Wait for the load-balancer to reach its desired state before returning.
 | |
|     type: bool
 | |
|     default: false
 | |
| 
 | |
|   wait_timeout:
 | |
|     type: int
 | |
|     description:
 | |
|     - Time to wait for the load-balancer to reach the expected state.
 | |
|     required: false
 | |
|     default: 300
 | |
| 
 | |
|   wait_sleep_time:
 | |
|     type: int
 | |
|     description:
 | |
|     - Time to wait before every attempt to check the state of the load-balancer.
 | |
|     required: false
 | |
|     default: 3
 | |
| '''
 | |
| 
 | |
| EXAMPLES = '''
 | |
| - name: Create a load-balancer
 | |
|   community.general.scaleway_lb:
 | |
|     name: foobar
 | |
|     state: present
 | |
|     organization_id: 951df375-e094-4d26-97c1-ba548eeb9c42
 | |
|     region: fr-par
 | |
|     tags:
 | |
|       - hello
 | |
| 
 | |
| - name: Delete a load-balancer
 | |
|   community.general.scaleway_lb:
 | |
|     name: foobar
 | |
|     state: absent
 | |
|     organization_id: 951df375-e094-4d26-97c1-ba548eeb9c42
 | |
|     region: fr-par
 | |
| '''
 | |
| 
 | |
| RETURNS = '''
 | |
| {
 | |
|    "scaleway_lb": {
 | |
|       "backend_count": 0,
 | |
|       "frontend_count": 0,
 | |
|       "description": "Description of my load-balancer",
 | |
|       "id": "00000000-0000-0000-0000-000000000000",
 | |
|       "instances": [
 | |
|          {
 | |
|             "id": "00000000-0000-0000-0000-000000000000",
 | |
|             "ip_address": "10.0.0.1",
 | |
|             "region": "fr-par",
 | |
|             "status": "ready"
 | |
|          },
 | |
|          {
 | |
|             "id": "00000000-0000-0000-0000-000000000000",
 | |
|             "ip_address": "10.0.0.2",
 | |
|             "region": "fr-par",
 | |
|             "status": "ready"
 | |
|          }
 | |
|       ],
 | |
|       "ip": [
 | |
|          {
 | |
|             "id": "00000000-0000-0000-0000-000000000000",
 | |
|             "ip_address": "192.168.0.1",
 | |
|             "lb_id": "00000000-0000-0000-0000-000000000000",
 | |
|             "region": "fr-par",
 | |
|             "organization_id": "00000000-0000-0000-0000-000000000000",
 | |
|             "reverse": ""
 | |
|          }
 | |
|       ],
 | |
|       "name": "lb_ansible_test",
 | |
|       "organization_id": "00000000-0000-0000-0000-000000000000",
 | |
|       "region": "fr-par",
 | |
|       "status": "ready",
 | |
|       "tags": [
 | |
|         "first_tag",
 | |
|         "second_tag"
 | |
|       ]
 | |
|    }
 | |
| }
 | |
| '''
 | |
| 
 | |
| import datetime
 | |
| import time
 | |
| from ansible.module_utils.basic import AnsibleModule
 | |
| from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_REGIONS, SCALEWAY_ENDPOINT, scaleway_argument_spec, Scaleway
 | |
| 
 | |
| STABLE_STATES = (
 | |
|     "ready",
 | |
|     "absent"
 | |
| )
 | |
| 
 | |
| MUTABLE_ATTRIBUTES = (
 | |
|     "name",
 | |
|     "description"
 | |
| )
 | |
| 
 | |
| 
 | |
| def payload_from_wished_lb(wished_lb):
 | |
|     return {
 | |
|         "organization_id": wished_lb["organization_id"],
 | |
|         "name": wished_lb["name"],
 | |
|         "tags": wished_lb["tags"],
 | |
|         "description": wished_lb["description"]
 | |
|     }
 | |
| 
 | |
| 
 | |
| def fetch_state(api, lb):
 | |
|     api.module.debug("fetch_state of load-balancer: %s" % lb["id"])
 | |
|     response = api.get(path=api.api_path + "/%s" % lb["id"])
 | |
| 
 | |
|     if response.status_code == 404:
 | |
|         return "absent"
 | |
| 
 | |
|     if not response.ok:
 | |
|         msg = 'Error during state fetching: (%s) %s' % (response.status_code, response.json)
 | |
|         api.module.fail_json(msg=msg)
 | |
| 
 | |
|     try:
 | |
|         api.module.debug("Load-balancer %s in state: %s" % (lb["id"], response.json["status"]))
 | |
|         return response.json["status"]
 | |
|     except KeyError:
 | |
|         api.module.fail_json(msg="Could not fetch state in %s" % response.json)
 | |
| 
 | |
| 
 | |
| def wait_to_complete_state_transition(api, lb, force_wait=False):
 | |
|     wait = api.module.params["wait"]
 | |
|     if not (wait or force_wait):
 | |
|         return
 | |
|     wait_timeout = api.module.params["wait_timeout"]
 | |
|     wait_sleep_time = api.module.params["wait_sleep_time"]
 | |
| 
 | |
|     start = datetime.datetime.utcnow()
 | |
|     end = start + datetime.timedelta(seconds=wait_timeout)
 | |
|     while datetime.datetime.utcnow() < end:
 | |
|         api.module.debug("We are going to wait for the load-balancer to finish its transition")
 | |
|         state = fetch_state(api, lb)
 | |
|         if state in STABLE_STATES:
 | |
|             api.module.debug("It seems that the load-balancer is not in transition anymore.")
 | |
|             api.module.debug("load-balancer in state: %s" % fetch_state(api, lb))
 | |
|             break
 | |
|         time.sleep(wait_sleep_time)
 | |
|     else:
 | |
|         api.module.fail_json(msg="Server takes too long to finish its transition")
 | |
| 
 | |
| 
 | |
| def lb_attributes_should_be_changed(target_lb, wished_lb):
 | |
|     diff = dict((attr, wished_lb[attr]) for attr in MUTABLE_ATTRIBUTES if target_lb[attr] != wished_lb[attr])
 | |
| 
 | |
|     if diff:
 | |
|         return dict((attr, wished_lb[attr]) for attr in MUTABLE_ATTRIBUTES)
 | |
|     else:
 | |
|         return diff
 | |
| 
 | |
| 
 | |
| def present_strategy(api, wished_lb):
 | |
|     changed = False
 | |
| 
 | |
|     response = api.get(path=api.api_path)
 | |
|     if not response.ok:
 | |
|         api.module.fail_json(msg='Error getting load-balancers [{0}: {1}]'.format(
 | |
|             response.status_code, response.json['message']))
 | |
| 
 | |
|     lbs_list = response.json["lbs"]
 | |
|     lb_lookup = dict((lb["name"], lb)
 | |
|                      for lb in lbs_list)
 | |
| 
 | |
|     if wished_lb["name"] not in lb_lookup.keys():
 | |
|         changed = True
 | |
|         if api.module.check_mode:
 | |
|             return changed, {"status": "A load-balancer would be created."}
 | |
| 
 | |
|         # Create Load-balancer
 | |
|         api.warn(payload_from_wished_lb(wished_lb))
 | |
|         creation_response = api.post(path=api.api_path,
 | |
|                                      data=payload_from_wished_lb(wished_lb))
 | |
| 
 | |
|         if not creation_response.ok:
 | |
|             msg = "Error during lb creation: %s: '%s' (%s)" % (creation_response.info['msg'],
 | |
|                                                                creation_response.json['message'],
 | |
|                                                                creation_response.json)
 | |
|             api.module.fail_json(msg=msg)
 | |
| 
 | |
|         wait_to_complete_state_transition(api=api, lb=creation_response.json)
 | |
|         response = api.get(path=api.api_path + "/%s" % creation_response.json["id"])
 | |
|         return changed, response.json
 | |
| 
 | |
|     target_lb = lb_lookup[wished_lb["name"]]
 | |
|     patch_payload = lb_attributes_should_be_changed(target_lb=target_lb,
 | |
|                                                     wished_lb=wished_lb)
 | |
| 
 | |
|     if not patch_payload:
 | |
|         return changed, target_lb
 | |
| 
 | |
|     changed = True
 | |
|     if api.module.check_mode:
 | |
|         return changed, {"status": "Load-balancer attributes would be changed."}
 | |
| 
 | |
|     lb_patch_response = api.put(path=api.api_path + "/%s" % target_lb["id"],
 | |
|                                 data=patch_payload)
 | |
| 
 | |
|     if not lb_patch_response.ok:
 | |
|         api.module.fail_json(msg='Error during load-balancer attributes update: [{0}: {1}]'.format(
 | |
|             lb_patch_response.status_code, lb_patch_response.json['message']))
 | |
| 
 | |
|     wait_to_complete_state_transition(api=api, lb=target_lb)
 | |
|     return changed, lb_patch_response.json
 | |
| 
 | |
| 
 | |
| def absent_strategy(api, wished_lb):
 | |
|     response = api.get(path=api.api_path)
 | |
|     changed = False
 | |
| 
 | |
|     status_code = response.status_code
 | |
|     lbs_json = response.json
 | |
|     lbs_list = lbs_json["lbs"]
 | |
| 
 | |
|     if not response.ok:
 | |
|         api.module.fail_json(msg='Error getting load-balancers [{0}: {1}]'.format(
 | |
|             status_code, response.json['message']))
 | |
| 
 | |
|     lb_lookup = dict((lb["name"], lb)
 | |
|                      for lb in lbs_list)
 | |
|     if wished_lb["name"] not in lb_lookup.keys():
 | |
|         return changed, {}
 | |
| 
 | |
|     target_lb = lb_lookup[wished_lb["name"]]
 | |
|     changed = True
 | |
|     if api.module.check_mode:
 | |
|         return changed, {"status": "Load-balancer would be destroyed"}
 | |
| 
 | |
|     wait_to_complete_state_transition(api=api, lb=target_lb, force_wait=True)
 | |
|     response = api.delete(path=api.api_path + "/%s" % target_lb["id"])
 | |
|     if not response.ok:
 | |
|         api.module.fail_json(msg='Error deleting load-balancer [{0}: {1}]'.format(
 | |
|             response.status_code, response.json))
 | |
| 
 | |
|     wait_to_complete_state_transition(api=api, lb=target_lb)
 | |
|     return changed, response.json
 | |
| 
 | |
| 
 | |
| state_strategy = {
 | |
|     "present": present_strategy,
 | |
|     "absent": absent_strategy
 | |
| }
 | |
| 
 | |
| 
 | |
| def core(module):
 | |
|     region = module.params["region"]
 | |
|     wished_load_balancer = {
 | |
|         "state": module.params["state"],
 | |
|         "name": module.params["name"],
 | |
|         "description": module.params["description"],
 | |
|         "tags": module.params["tags"],
 | |
|         "organization_id": module.params["organization_id"]
 | |
|     }
 | |
|     module.params['api_url'] = SCALEWAY_ENDPOINT
 | |
|     api = Scaleway(module=module)
 | |
|     api.api_path = "lb/v1/regions/%s/lbs" % region
 | |
| 
 | |
|     changed, summary = state_strategy[wished_load_balancer["state"]](api=api,
 | |
|                                                                      wished_lb=wished_load_balancer)
 | |
|     module.exit_json(changed=changed, scaleway_lb=summary)
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     argument_spec = scaleway_argument_spec()
 | |
|     argument_spec.update(dict(
 | |
|         name=dict(required=True),
 | |
|         description=dict(required=True),
 | |
|         region=dict(required=True, choices=SCALEWAY_REGIONS),
 | |
|         state=dict(choices=list(state_strategy.keys()), default='present'),
 | |
|         tags=dict(type="list", elements="str", default=[]),
 | |
|         organization_id=dict(required=True),
 | |
|         wait=dict(type="bool", default=False),
 | |
|         wait_timeout=dict(type="int", default=300),
 | |
|         wait_sleep_time=dict(type="int", default=3),
 | |
|     ))
 | |
|     module = AnsibleModule(
 | |
|         argument_spec=argument_spec,
 | |
|         supports_check_mode=True,
 | |
|     )
 | |
| 
 | |
|     core(module)
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |