mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 13:56:09 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			1570 lines
		
	
	
	
		
			55 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1570 lines
		
	
	
	
		
			55 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| #
 | |
| # Copyright (c) 2015 CenturyLink
 | |
| # 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: clc_server
 | |
| short_description: Create, Delete, Start and Stop servers in CenturyLink Cloud
 | |
| description:
 | |
|   - An Ansible module to Create, Delete, Start and Stop servers in CenturyLink Cloud.
 | |
| extends_documentation_fragment:
 | |
|   - community.general.attributes
 | |
| attributes:
 | |
|   check_mode:
 | |
|     support: full
 | |
|   diff_mode:
 | |
|     support: none
 | |
| options:
 | |
|   additional_disks:
 | |
|     description:
 | |
|       - The list of additional disks for the server
 | |
|     type: list
 | |
|     elements: dict
 | |
|     default: []
 | |
|   add_public_ip:
 | |
|     description:
 | |
|       - Whether to add a public ip to the server
 | |
|     type: bool
 | |
|     default: false
 | |
|   alias:
 | |
|     description:
 | |
|       - The account alias to provision the servers under.
 | |
|     type: str
 | |
|   anti_affinity_policy_id:
 | |
|     description:
 | |
|       - The anti-affinity policy to assign to the server. This is mutually exclusive with 'anti_affinity_policy_name'.
 | |
|     type: str
 | |
|   anti_affinity_policy_name:
 | |
|     description:
 | |
|       - The anti-affinity policy to assign to the server. This is mutually exclusive with 'anti_affinity_policy_id'.
 | |
|     type: str
 | |
|   alert_policy_id:
 | |
|     description:
 | |
|       - The alert policy to assign to the server. This is mutually exclusive with 'alert_policy_name'.
 | |
|     type: str
 | |
|   alert_policy_name:
 | |
|     description:
 | |
|       - The alert policy to assign to the server. This is mutually exclusive with 'alert_policy_id'.
 | |
|     type: str
 | |
|   count:
 | |
|     description:
 | |
|       - The number of servers to build (mutually exclusive with exact_count)
 | |
|     default: 1
 | |
|     type: int
 | |
|   count_group:
 | |
|     description:
 | |
|       - Required when exact_count is specified.  The Server Group use to determine how many servers to deploy.
 | |
|     type: str
 | |
|   cpu:
 | |
|     description:
 | |
|       - How many CPUs to provision on the server
 | |
|     default: 1
 | |
|     type: int
 | |
|   cpu_autoscale_policy_id:
 | |
|     description:
 | |
|       - The autoscale policy to assign to the server.
 | |
|     type: str
 | |
|   custom_fields:
 | |
|     description:
 | |
|       - The list of custom fields to set on the server.
 | |
|     type: list
 | |
|     default: []
 | |
|     elements: dict
 | |
|   description:
 | |
|     description:
 | |
|       - The description to set for the server.
 | |
|     type: str
 | |
|   exact_count:
 | |
|     description:
 | |
|       - Run in idempotent mode.  Will insure that this exact number of servers are running in the provided group,
 | |
|         creating and deleting them to reach that count.  Requires count_group to be set.
 | |
|     type: int
 | |
|   group:
 | |
|     description:
 | |
|       - The Server Group to create servers under.
 | |
|     type: str
 | |
|     default: 'Default Group'
 | |
|   ip_address:
 | |
|     description:
 | |
|       - The IP Address for the server. One is assigned if not provided.
 | |
|     type: str
 | |
|   location:
 | |
|     description:
 | |
|       - The Datacenter to create servers in.
 | |
|     type: str
 | |
|   managed_os:
 | |
|     description:
 | |
|       - Whether to create the server as 'Managed' or not.
 | |
|     type: bool
 | |
|     default: false
 | |
|     required: false
 | |
|   memory:
 | |
|     description:
 | |
|       - Memory in GB.
 | |
|     type: int
 | |
|     default: 1
 | |
|   name:
 | |
|     description:
 | |
|       - A 1 to 6 character identifier to use for the server. This is required when state is 'present'
 | |
|     type: str
 | |
|   network_id:
 | |
|     description:
 | |
|       - The network UUID on which to create servers.
 | |
|     type: str
 | |
|   packages:
 | |
|     description:
 | |
|       - The list of blue print packages to run on the server after its created.
 | |
|     type: list
 | |
|     elements: dict
 | |
|     default: []
 | |
|   password:
 | |
|     description:
 | |
|       - Password for the administrator / root user
 | |
|     type: str
 | |
|   primary_dns:
 | |
|     description:
 | |
|       - Primary DNS used by the server.
 | |
|     type: str
 | |
|   public_ip_protocol:
 | |
|     description:
 | |
|       - The protocol to use for the public ip if add_public_ip is set to True.
 | |
|     type: str
 | |
|     default: 'TCP'
 | |
|     choices: ['TCP', 'UDP', 'ICMP']
 | |
|   public_ip_ports:
 | |
|     description:
 | |
|       - A list of ports to allow on the firewall to the servers public ip, if add_public_ip is set to True.
 | |
|     type: list
 | |
|     elements: dict
 | |
|     default: []
 | |
|   secondary_dns:
 | |
|     description:
 | |
|       - Secondary DNS used by the server.
 | |
|     type: str
 | |
|   server_ids:
 | |
|     description:
 | |
|       - Required for started, stopped, and absent states.
 | |
|         A list of server Ids to insure are started, stopped, or absent.
 | |
|     type: list
 | |
|     default: []
 | |
|     elements: str
 | |
|   source_server_password:
 | |
|     description:
 | |
|       - The password for the source server if a clone is specified.
 | |
|     type: str
 | |
|   state:
 | |
|     description:
 | |
|       - The state to insure that the provided resources are in.
 | |
|     type: str
 | |
|     default: 'present'
 | |
|     choices: ['present', 'absent', 'started', 'stopped']
 | |
|   storage_type:
 | |
|     description:
 | |
|       - The type of storage to attach to the server.
 | |
|     type: str
 | |
|     default: 'standard'
 | |
|     choices: ['standard', 'hyperscale']
 | |
|   template:
 | |
|     description:
 | |
|       - The template to use for server creation.  Will search for a template if a partial string is provided.
 | |
|         This is required when state is 'present'
 | |
|     type: str
 | |
|   ttl:
 | |
|     description:
 | |
|       - The time to live for the server in seconds.  The server will be deleted when this time expires.
 | |
|     type: str
 | |
|   type:
 | |
|     description:
 | |
|       - The type of server to create.
 | |
|     type: str
 | |
|     default: 'standard'
 | |
|     choices: ['standard', 'hyperscale', 'bareMetal']
 | |
|   configuration_id:
 | |
|     description:
 | |
|       -  Only required for bare metal servers.
 | |
|          Specifies the identifier for the specific configuration type of bare metal server to deploy.
 | |
|     type: str
 | |
|   os_type:
 | |
|     description:
 | |
|       - Only required for bare metal servers.
 | |
|         Specifies the OS to provision with the bare metal server.
 | |
|     type: str
 | |
|     choices: ['redHat6_64Bit', 'centOS6_64Bit', 'windows2012R2Standard_64Bit', 'ubuntu14_64Bit']
 | |
|   wait:
 | |
|     description:
 | |
|       - Whether to wait for the provisioning tasks to finish before returning.
 | |
|     type: bool
 | |
|     default: true
 | |
| requirements:
 | |
|     - python = 2.7
 | |
|     - requests >= 2.5.0
 | |
|     - clc-sdk
 | |
| author: "CLC Runner (@clc-runner)"
 | |
| notes:
 | |
|     - To use this module, it is required to set the below environment variables which enables access to the
 | |
|       Centurylink Cloud
 | |
|           - CLC_V2_API_USERNAME, the account login id for the centurylink cloud
 | |
|           - CLC_V2_API_PASSWORD, the account password for the centurylink cloud
 | |
|     - Alternatively, the module accepts the API token and account alias. The API token can be generated using the
 | |
|       CLC account login and password via the HTTP api call @ https://api.ctl.io/v2/authentication/login
 | |
|           - CLC_V2_API_TOKEN, the API token generated from https://api.ctl.io/v2/authentication/login
 | |
|           - CLC_ACCT_ALIAS, the account alias associated with the centurylink cloud
 | |
|     - Users can set CLC_V2_API_URL to specify an endpoint for pointing to a different CLC environment.
 | |
| '''
 | |
| 
 | |
| EXAMPLES = '''
 | |
| # Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
 | |
| 
 | |
| - name: Provision a single Ubuntu Server
 | |
|   community.general.clc_server:
 | |
|     name: test
 | |
|     template: ubuntu-14-64
 | |
|     count: 1
 | |
|     group: Default Group
 | |
|     state: present
 | |
| 
 | |
| - name: Ensure 'Default Group' has exactly 5 servers
 | |
|   community.general.clc_server:
 | |
|     name: test
 | |
|     template: ubuntu-14-64
 | |
|     exact_count: 5
 | |
|     count_group: Default Group
 | |
|     group: Default Group
 | |
| 
 | |
| - name: Stop a Server
 | |
|   community.general.clc_server:
 | |
|     server_ids:
 | |
|       - UC1ACCT-TEST01
 | |
|     state: stopped
 | |
| 
 | |
| - name: Start a Server
 | |
|   community.general.clc_server:
 | |
|     server_ids:
 | |
|       - UC1ACCT-TEST01
 | |
|     state: started
 | |
| 
 | |
| - name: Delete a Server
 | |
|   community.general.clc_server:
 | |
|     server_ids:
 | |
|       - UC1ACCT-TEST01
 | |
|     state: absent
 | |
| '''
 | |
| 
 | |
| RETURN = '''
 | |
| server_ids:
 | |
|     description: The list of server ids that are created
 | |
|     returned: success
 | |
|     type: list
 | |
|     sample:
 | |
|         [
 | |
|             "UC1TEST-SVR01",
 | |
|             "UC1TEST-SVR02"
 | |
|         ]
 | |
| partially_created_server_ids:
 | |
|     description: The list of server ids that are partially created
 | |
|     returned: success
 | |
|     type: list
 | |
|     sample:
 | |
|         [
 | |
|             "UC1TEST-SVR01",
 | |
|             "UC1TEST-SVR02"
 | |
|         ]
 | |
| servers:
 | |
|     description: The list of server objects returned from CLC
 | |
|     returned: success
 | |
|     type: list
 | |
|     sample:
 | |
|         [
 | |
|            {
 | |
|               "changeInfo":{
 | |
|                  "createdBy":"service.wfad",
 | |
|                  "createdDate":1438196820,
 | |
|                  "modifiedBy":"service.wfad",
 | |
|                  "modifiedDate":1438196820
 | |
|               },
 | |
|               "description":"test-server",
 | |
|               "details":{
 | |
|                  "alertPolicies":[
 | |
| 
 | |
|                  ],
 | |
|                  "cpu":1,
 | |
|                  "customFields":[
 | |
| 
 | |
|                  ],
 | |
|                  "diskCount":3,
 | |
|                  "disks":[
 | |
|                     {
 | |
|                        "id":"0:0",
 | |
|                        "partitionPaths":[
 | |
| 
 | |
|                        ],
 | |
|                        "sizeGB":1
 | |
|                     },
 | |
|                     {
 | |
|                        "id":"0:1",
 | |
|                        "partitionPaths":[
 | |
| 
 | |
|                        ],
 | |
|                        "sizeGB":2
 | |
|                     },
 | |
|                     {
 | |
|                        "id":"0:2",
 | |
|                        "partitionPaths":[
 | |
| 
 | |
|                        ],
 | |
|                        "sizeGB":14
 | |
|                     }
 | |
|                  ],
 | |
|                  "hostName":"",
 | |
|                  "inMaintenanceMode":false,
 | |
|                  "ipAddresses":[
 | |
|                     {
 | |
|                        "internal":"10.1.1.1"
 | |
|                     }
 | |
|                  ],
 | |
|                  "memoryGB":1,
 | |
|                  "memoryMB":1024,
 | |
|                  "partitions":[
 | |
| 
 | |
|                  ],
 | |
|                  "powerState":"started",
 | |
|                  "snapshots":[
 | |
| 
 | |
|                  ],
 | |
|                  "storageGB":17
 | |
|               },
 | |
|               "groupId":"086ac1dfe0b6411989e8d1b77c4065f0",
 | |
|               "id":"test-server",
 | |
|               "ipaddress":"10.120.45.23",
 | |
|               "isTemplate":false,
 | |
|               "links":[
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server",
 | |
|                     "id":"test-server",
 | |
|                     "rel":"self",
 | |
|                     "verbs":[
 | |
|                        "GET",
 | |
|                        "PATCH",
 | |
|                        "DELETE"
 | |
|                     ]
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/groups/wfad/086ac1dfe0b6411989e8d1b77c4065f0",
 | |
|                     "id":"086ac1dfe0b6411989e8d1b77c4065f0",
 | |
|                     "rel":"group"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/accounts/wfad",
 | |
|                     "id":"wfad",
 | |
|                     "rel":"account"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/billing/wfad/serverPricing/test-server",
 | |
|                     "rel":"billing"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/publicIPAddresses",
 | |
|                     "rel":"publicIPAddresses",
 | |
|                     "verbs":[
 | |
|                        "POST"
 | |
|                     ]
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/credentials",
 | |
|                     "rel":"credentials"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/statistics",
 | |
|                     "rel":"statistics"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/510ec21ae82d4dc89d28479753bf736a/upcomingScheduledActivities",
 | |
|                     "rel":"upcomingScheduledActivities"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/510ec21ae82d4dc89d28479753bf736a/scheduledActivities",
 | |
|                     "rel":"scheduledActivities",
 | |
|                     "verbs":[
 | |
|                        "GET",
 | |
|                        "POST"
 | |
|                     ]
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/capabilities",
 | |
|                     "rel":"capabilities"
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/alertPolicies",
 | |
|                     "rel":"alertPolicyMappings",
 | |
|                     "verbs":[
 | |
|                        "POST"
 | |
|                     ]
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/antiAffinityPolicy",
 | |
|                     "rel":"antiAffinityPolicyMapping",
 | |
|                     "verbs":[
 | |
|                        "PUT",
 | |
|                        "DELETE"
 | |
|                     ]
 | |
|                  },
 | |
|                  {
 | |
|                     "href":"/v2/servers/wfad/test-server/cpuAutoscalePolicy",
 | |
|                     "rel":"cpuAutoscalePolicyMapping",
 | |
|                     "verbs":[
 | |
|                        "PUT",
 | |
|                        "DELETE"
 | |
|                     ]
 | |
|                  }
 | |
|               ],
 | |
|               "locationId":"UC1",
 | |
|               "name":"test-server",
 | |
|               "os":"ubuntu14_64Bit",
 | |
|               "osType":"Ubuntu 14 64-bit",
 | |
|               "status":"active",
 | |
|               "storageType":"standard",
 | |
|               "type":"standard"
 | |
|            }
 | |
|         ]
 | |
| '''
 | |
| 
 | |
| __version__ = '${version}'
 | |
| 
 | |
| import json
 | |
| import os
 | |
| import time
 | |
| import traceback
 | |
| 
 | |
| from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
 | |
| 
 | |
| REQUESTS_IMP_ERR = None
 | |
| try:
 | |
|     import requests
 | |
| except ImportError:
 | |
|     REQUESTS_IMP_ERR = traceback.format_exc()
 | |
|     REQUESTS_FOUND = False
 | |
| else:
 | |
|     REQUESTS_FOUND = True
 | |
| 
 | |
| #
 | |
| #  Requires the clc-python-sdk.
 | |
| #  sudo pip install clc-sdk
 | |
| #
 | |
| CLC_IMP_ERR = None
 | |
| try:
 | |
|     import clc as clc_sdk
 | |
|     from clc import CLCException
 | |
|     from clc import APIFailedResponse
 | |
| except ImportError:
 | |
|     CLC_IMP_ERR = traceback.format_exc()
 | |
|     CLC_FOUND = False
 | |
|     clc_sdk = None
 | |
| else:
 | |
|     CLC_FOUND = True
 | |
| 
 | |
| from ansible.module_utils.basic import AnsibleModule, missing_required_lib
 | |
| 
 | |
| 
 | |
| class ClcServer:
 | |
|     clc = clc_sdk
 | |
| 
 | |
|     def __init__(self, module):
 | |
|         """
 | |
|         Construct module
 | |
|         """
 | |
|         self.clc = clc_sdk
 | |
|         self.module = module
 | |
|         self.group_dict = {}
 | |
| 
 | |
|         if not CLC_FOUND:
 | |
|             self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
 | |
|         if not REQUESTS_FOUND:
 | |
|             self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
 | |
|         if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
 | |
|             self.module.fail_json(
 | |
|                 msg='requests library  version should be >= 2.5.0')
 | |
| 
 | |
|         self._set_user_agent(self.clc)
 | |
| 
 | |
|     def process_request(self):
 | |
|         """
 | |
|         Process the request - Main Code Path
 | |
|         :return: Returns with either an exit_json or fail_json
 | |
|         """
 | |
|         changed = False
 | |
|         new_server_ids = []
 | |
|         server_dict_array = []
 | |
| 
 | |
|         self._set_clc_credentials_from_env()
 | |
|         self.module.params = self._validate_module_params(
 | |
|             self.clc,
 | |
|             self.module)
 | |
|         p = self.module.params
 | |
|         state = p.get('state')
 | |
| 
 | |
|         #
 | |
|         #  Handle each state
 | |
|         #
 | |
|         partial_servers_ids = []
 | |
|         if state == 'absent':
 | |
|             server_ids = p['server_ids']
 | |
|             if not isinstance(server_ids, list):
 | |
|                 return self.module.fail_json(
 | |
|                     msg='server_ids needs to be a list of instances to delete: %s' %
 | |
|                     server_ids)
 | |
| 
 | |
|             (changed,
 | |
|              server_dict_array,
 | |
|              new_server_ids) = self._delete_servers(module=self.module,
 | |
|                                                     clc=self.clc,
 | |
|                                                     server_ids=server_ids)
 | |
| 
 | |
|         elif state in ('started', 'stopped'):
 | |
|             server_ids = p.get('server_ids')
 | |
|             if not isinstance(server_ids, list):
 | |
|                 return self.module.fail_json(
 | |
|                     msg='server_ids needs to be a list of servers to run: %s' %
 | |
|                     server_ids)
 | |
| 
 | |
|             (changed,
 | |
|              server_dict_array,
 | |
|              new_server_ids) = self._start_stop_servers(self.module,
 | |
|                                                         self.clc,
 | |
|                                                         server_ids)
 | |
| 
 | |
|         elif state == 'present':
 | |
|             # Changed is always set to true when provisioning new instances
 | |
|             if not p.get('template') and p.get('type') != 'bareMetal':
 | |
|                 return self.module.fail_json(
 | |
|                     msg='template parameter is required for new instance')
 | |
| 
 | |
|             if p.get('exact_count') is None:
 | |
|                 (server_dict_array,
 | |
|                  new_server_ids,
 | |
|                  partial_servers_ids,
 | |
|                  changed) = self._create_servers(self.module,
 | |
|                                                  self.clc)
 | |
|             else:
 | |
|                 (server_dict_array,
 | |
|                  new_server_ids,
 | |
|                  partial_servers_ids,
 | |
|                  changed) = self._enforce_count(self.module,
 | |
|                                                 self.clc)
 | |
| 
 | |
|         self.module.exit_json(
 | |
|             changed=changed,
 | |
|             server_ids=new_server_ids,
 | |
|             partially_created_server_ids=partial_servers_ids,
 | |
|             servers=server_dict_array)
 | |
| 
 | |
|     @staticmethod
 | |
|     def _define_module_argument_spec():
 | |
|         """
 | |
|         Define the argument spec for the ansible module
 | |
|         :return: argument spec dictionary
 | |
|         """
 | |
|         argument_spec = dict(
 | |
|             name=dict(),
 | |
|             template=dict(),
 | |
|             group=dict(default='Default Group'),
 | |
|             network_id=dict(),
 | |
|             location=dict(),
 | |
|             cpu=dict(default=1, type='int'),
 | |
|             memory=dict(default=1, type='int'),
 | |
|             alias=dict(),
 | |
|             password=dict(no_log=True),
 | |
|             ip_address=dict(),
 | |
|             storage_type=dict(
 | |
|                 default='standard',
 | |
|                 choices=[
 | |
|                     'standard',
 | |
|                     'hyperscale']),
 | |
|             type=dict(default='standard', choices=['standard', 'hyperscale', 'bareMetal']),
 | |
|             primary_dns=dict(),
 | |
|             secondary_dns=dict(),
 | |
|             additional_disks=dict(type='list', default=[], elements='dict'),
 | |
|             custom_fields=dict(type='list', default=[], elements='dict'),
 | |
|             ttl=dict(),
 | |
|             managed_os=dict(type='bool', default=False),
 | |
|             description=dict(),
 | |
|             source_server_password=dict(no_log=True),
 | |
|             cpu_autoscale_policy_id=dict(),
 | |
|             anti_affinity_policy_id=dict(),
 | |
|             anti_affinity_policy_name=dict(),
 | |
|             alert_policy_id=dict(),
 | |
|             alert_policy_name=dict(),
 | |
|             packages=dict(type='list', default=[], elements='dict'),
 | |
|             state=dict(
 | |
|                 default='present',
 | |
|                 choices=[
 | |
|                     'present',
 | |
|                     'absent',
 | |
|                     'started',
 | |
|                     'stopped']),
 | |
|             count=dict(type='int', default=1),
 | |
|             exact_count=dict(type='int', ),
 | |
|             count_group=dict(),
 | |
|             server_ids=dict(type='list', default=[], elements='str'),
 | |
|             add_public_ip=dict(type='bool', default=False),
 | |
|             public_ip_protocol=dict(
 | |
|                 default='TCP',
 | |
|                 choices=[
 | |
|                     'TCP',
 | |
|                     'UDP',
 | |
|                     'ICMP']),
 | |
|             public_ip_ports=dict(type='list', default=[], elements='dict'),
 | |
|             configuration_id=dict(),
 | |
|             os_type=dict(choices=[
 | |
|                 'redHat6_64Bit',
 | |
|                 'centOS6_64Bit',
 | |
|                 'windows2012R2Standard_64Bit',
 | |
|                 'ubuntu14_64Bit'
 | |
|             ]),
 | |
|             wait=dict(type='bool', default=True))
 | |
| 
 | |
|         mutually_exclusive = [
 | |
|             ['exact_count', 'count'],
 | |
|             ['exact_count', 'state'],
 | |
|             ['anti_affinity_policy_id', 'anti_affinity_policy_name'],
 | |
|             ['alert_policy_id', 'alert_policy_name'],
 | |
|         ]
 | |
|         return {"argument_spec": argument_spec,
 | |
|                 "mutually_exclusive": mutually_exclusive}
 | |
| 
 | |
|     def _set_clc_credentials_from_env(self):
 | |
|         """
 | |
|         Set the CLC Credentials on the sdk by reading environment variables
 | |
|         :return: none
 | |
|         """
 | |
|         env = os.environ
 | |
|         v2_api_token = env.get('CLC_V2_API_TOKEN', False)
 | |
|         v2_api_username = env.get('CLC_V2_API_USERNAME', False)
 | |
|         v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
 | |
|         clc_alias = env.get('CLC_ACCT_ALIAS', False)
 | |
|         api_url = env.get('CLC_V2_API_URL', False)
 | |
|         if api_url:
 | |
|             self.clc.defaults.ENDPOINT_URL_V2 = api_url
 | |
| 
 | |
|         if v2_api_token and clc_alias:
 | |
|             self.clc._LOGIN_TOKEN_V2 = v2_api_token
 | |
|             self.clc._V2_ENABLED = True
 | |
|             self.clc.ALIAS = clc_alias
 | |
|         elif v2_api_username and v2_api_passwd:
 | |
|             self.clc.v2.SetCredentials(
 | |
|                 api_username=v2_api_username,
 | |
|                 api_passwd=v2_api_passwd)
 | |
|         else:
 | |
|             return self.module.fail_json(
 | |
|                 msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
 | |
|                     "environment variables")
 | |
| 
 | |
|     @staticmethod
 | |
|     def _validate_module_params(clc, module):
 | |
|         """
 | |
|         Validate the module params, and lookup default values.
 | |
|         :param clc: clc-sdk instance to use
 | |
|         :param module: module to validate
 | |
|         :return: dictionary of validated params
 | |
|         """
 | |
|         params = module.params
 | |
|         datacenter = ClcServer._find_datacenter(clc, module)
 | |
| 
 | |
|         ClcServer._validate_types(module)
 | |
|         ClcServer._validate_name(module)
 | |
| 
 | |
|         params['alias'] = ClcServer._find_alias(clc, module)
 | |
|         params['cpu'] = ClcServer._find_cpu(clc, module)
 | |
|         params['memory'] = ClcServer._find_memory(clc, module)
 | |
|         params['description'] = ClcServer._find_description(module)
 | |
|         params['ttl'] = ClcServer._find_ttl(clc, module)
 | |
|         params['template'] = ClcServer._find_template_id(module, datacenter)
 | |
|         params['group'] = ClcServer._find_group(module, datacenter).id
 | |
|         params['network_id'] = ClcServer._find_network_id(module, datacenter)
 | |
|         params['anti_affinity_policy_id'] = ClcServer._find_aa_policy_id(
 | |
|             clc,
 | |
|             module)
 | |
|         params['alert_policy_id'] = ClcServer._find_alert_policy_id(
 | |
|             clc,
 | |
|             module)
 | |
| 
 | |
|         return params
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_datacenter(clc, module):
 | |
|         """
 | |
|         Find the datacenter by calling the CLC API.
 | |
|         :param clc: clc-sdk instance to use
 | |
|         :param module: module to validate
 | |
|         :return: clc-sdk.Datacenter instance
 | |
|         """
 | |
|         location = module.params.get('location')
 | |
|         try:
 | |
|             if not location:
 | |
|                 account = clc.v2.Account()
 | |
|                 location = account.data.get('primaryDataCenter')
 | |
|             data_center = clc.v2.Datacenter(location)
 | |
|             return data_center
 | |
|         except CLCException:
 | |
|             module.fail_json(msg="Unable to find location: {0}".format(location))
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_alias(clc, module):
 | |
|         """
 | |
|         Find or Validate the Account Alias by calling the CLC API
 | |
|         :param clc: clc-sdk instance to use
 | |
|         :param module: module to validate
 | |
|         :return: clc-sdk.Account instance
 | |
|         """
 | |
|         alias = module.params.get('alias')
 | |
|         if not alias:
 | |
|             try:
 | |
|                 alias = clc.v2.Account.GetAlias()
 | |
|             except CLCException as ex:
 | |
|                 module.fail_json(msg='Unable to find account alias. {0}'.format(
 | |
|                     ex.message
 | |
|                 ))
 | |
|         return alias
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_cpu(clc, module):
 | |
|         """
 | |
|         Find or validate the CPU value by calling the CLC API
 | |
|         :param clc: clc-sdk instance to use
 | |
|         :param module: module to validate
 | |
|         :return: Int value for CPU
 | |
|         """
 | |
|         cpu = module.params.get('cpu')
 | |
|         group_id = module.params.get('group_id')
 | |
|         alias = module.params.get('alias')
 | |
|         state = module.params.get('state')
 | |
| 
 | |
|         if not cpu and state == 'present':
 | |
|             group = clc.v2.Group(id=group_id,
 | |
|                                  alias=alias)
 | |
|             if group.Defaults("cpu"):
 | |
|                 cpu = group.Defaults("cpu")
 | |
|             else:
 | |
|                 module.fail_json(
 | |
|                     msg=str("Can\'t determine a default cpu value. Please provide a value for cpu."))
 | |
|         return cpu
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_memory(clc, module):
 | |
|         """
 | |
|         Find or validate the Memory value by calling the CLC API
 | |
|         :param clc: clc-sdk instance to use
 | |
|         :param module: module to validate
 | |
|         :return: Int value for Memory
 | |
|         """
 | |
|         memory = module.params.get('memory')
 | |
|         group_id = module.params.get('group_id')
 | |
|         alias = module.params.get('alias')
 | |
|         state = module.params.get('state')
 | |
| 
 | |
|         if not memory and state == 'present':
 | |
|             group = clc.v2.Group(id=group_id,
 | |
|                                  alias=alias)
 | |
|             if group.Defaults("memory"):
 | |
|                 memory = group.Defaults("memory")
 | |
|             else:
 | |
|                 module.fail_json(msg=str(
 | |
|                     "Can\'t determine a default memory value. Please provide a value for memory."))
 | |
|         return memory
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_description(module):
 | |
|         """
 | |
|         Set the description module param to name if description is blank
 | |
|         :param module: the module to validate
 | |
|         :return: string description
 | |
|         """
 | |
|         description = module.params.get('description')
 | |
|         if not description:
 | |
|             description = module.params.get('name')
 | |
|         return description
 | |
| 
 | |
|     @staticmethod
 | |
|     def _validate_types(module):
 | |
|         """
 | |
|         Validate that type and storage_type are set appropriately, and fail if not
 | |
|         :param module: the module to validate
 | |
|         :return: none
 | |
|         """
 | |
|         state = module.params.get('state')
 | |
|         server_type = module.params.get(
 | |
|             'type').lower() if module.params.get('type') else None
 | |
|         storage_type = module.params.get(
 | |
|             'storage_type').lower() if module.params.get('storage_type') else None
 | |
| 
 | |
|         if state == "present":
 | |
|             if server_type == "standard" and storage_type not in (
 | |
|                     "standard", "premium"):
 | |
|                 module.fail_json(
 | |
|                     msg=str("Standard VMs must have storage_type = 'standard' or 'premium'"))
 | |
| 
 | |
|             if server_type == "hyperscale" and storage_type != "hyperscale":
 | |
|                 module.fail_json(
 | |
|                     msg=str("Hyperscale VMs must have storage_type = 'hyperscale'"))
 | |
| 
 | |
|     @staticmethod
 | |
|     def _validate_name(module):
 | |
|         """
 | |
|         Validate that name is the correct length if provided, fail if it's not
 | |
|         :param module: the module to validate
 | |
|         :return: none
 | |
|         """
 | |
|         server_name = module.params.get('name')
 | |
|         state = module.params.get('state')
 | |
| 
 | |
|         if state == 'present' and (
 | |
|                 len(server_name) < 1 or len(server_name) > 6):
 | |
|             module.fail_json(msg=str(
 | |
|                 "When state = 'present', name must be a string with a minimum length of 1 and a maximum length of 6"))
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_ttl(clc, module):
 | |
|         """
 | |
|         Validate that TTL is > 3600 if set, and fail if not
 | |
|         :param clc: clc-sdk instance to use
 | |
|         :param module: module to validate
 | |
|         :return: validated ttl
 | |
|         """
 | |
|         ttl = module.params.get('ttl')
 | |
| 
 | |
|         if ttl:
 | |
|             if ttl <= 3600:
 | |
|                 return module.fail_json(msg=str("Ttl cannot be <= 3600"))
 | |
|             else:
 | |
|                 ttl = clc.v2.time_utils.SecondsToZuluTS(int(time.time()) + ttl)
 | |
|         return ttl
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_template_id(module, datacenter):
 | |
|         """
 | |
|         Find the template id by calling the CLC API.
 | |
|         :param module: the module to validate
 | |
|         :param datacenter: the datacenter to search for the template
 | |
|         :return: a valid clc template id
 | |
|         """
 | |
|         lookup_template = module.params.get('template')
 | |
|         state = module.params.get('state')
 | |
|         type = module.params.get('type')
 | |
|         result = None
 | |
| 
 | |
|         if state == 'present' and type != 'bareMetal':
 | |
|             try:
 | |
|                 result = datacenter.Templates().Search(lookup_template)[0].id
 | |
|             except CLCException:
 | |
|                 module.fail_json(
 | |
|                     msg=str(
 | |
|                         "Unable to find a template: " +
 | |
|                         lookup_template +
 | |
|                         " in location: " +
 | |
|                         datacenter.id))
 | |
|         return result
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_network_id(module, datacenter):
 | |
|         """
 | |
|         Validate the provided network id or return a default.
 | |
|         :param module: the module to validate
 | |
|         :param datacenter: the datacenter to search for a network id
 | |
|         :return: a valid network id
 | |
|         """
 | |
|         network_id = module.params.get('network_id')
 | |
| 
 | |
|         if not network_id:
 | |
|             try:
 | |
|                 network_id = datacenter.Networks().networks[0].id
 | |
|                 # -- added for clc-sdk 2.23 compatibility
 | |
|                 # datacenter_networks = clc_sdk.v2.Networks(
 | |
|                 #   networks_lst=datacenter._DeploymentCapabilities()['deployableNetworks'])
 | |
|                 # network_id = datacenter_networks.networks[0].id
 | |
|                 # -- end
 | |
|             except CLCException:
 | |
|                 module.fail_json(
 | |
|                     msg=str(
 | |
|                         "Unable to find a network in location: " +
 | |
|                         datacenter.id))
 | |
| 
 | |
|         return network_id
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_aa_policy_id(clc, module):
 | |
|         """
 | |
|         Validate if the anti affinity policy exist for the given name and throw error if not
 | |
|         :param clc: the clc-sdk instance
 | |
|         :param module: the module to validate
 | |
|         :return: aa_policy_id: the anti affinity policy id of the given name.
 | |
|         """
 | |
|         aa_policy_id = module.params.get('anti_affinity_policy_id')
 | |
|         aa_policy_name = module.params.get('anti_affinity_policy_name')
 | |
|         if not aa_policy_id and aa_policy_name:
 | |
|             alias = module.params.get('alias')
 | |
|             aa_policy_id = ClcServer._get_anti_affinity_policy_id(
 | |
|                 clc,
 | |
|                 module,
 | |
|                 alias,
 | |
|                 aa_policy_name)
 | |
|             if not aa_policy_id:
 | |
|                 module.fail_json(
 | |
|                     msg='No anti affinity policy was found with policy name : %s' % aa_policy_name)
 | |
|         return aa_policy_id
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_alert_policy_id(clc, module):
 | |
|         """
 | |
|         Validate if the alert policy exist for the given name and throw error if not
 | |
|         :param clc: the clc-sdk instance
 | |
|         :param module: the module to validate
 | |
|         :return: alert_policy_id: the alert policy id of the given name.
 | |
|         """
 | |
|         alert_policy_id = module.params.get('alert_policy_id')
 | |
|         alert_policy_name = module.params.get('alert_policy_name')
 | |
|         if not alert_policy_id and alert_policy_name:
 | |
|             alias = module.params.get('alias')
 | |
|             alert_policy_id = ClcServer._get_alert_policy_id_by_name(
 | |
|                 clc=clc,
 | |
|                 module=module,
 | |
|                 alias=alias,
 | |
|                 alert_policy_name=alert_policy_name
 | |
|             )
 | |
|             if not alert_policy_id:
 | |
|                 module.fail_json(
 | |
|                     msg='No alert policy exist with name : %s' % alert_policy_name)
 | |
|         return alert_policy_id
 | |
| 
 | |
|     def _create_servers(self, module, clc, override_count=None):
 | |
|         """
 | |
|         Create New Servers in CLC cloud
 | |
|         :param module: the AnsibleModule object
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :return: a list of dictionaries with server information about the servers that were created
 | |
|         """
 | |
|         p = module.params
 | |
|         request_list = []
 | |
|         servers = []
 | |
|         server_dict_array = []
 | |
|         created_server_ids = []
 | |
|         partial_created_servers_ids = []
 | |
| 
 | |
|         add_public_ip = p.get('add_public_ip')
 | |
|         public_ip_protocol = p.get('public_ip_protocol')
 | |
|         public_ip_ports = p.get('public_ip_ports')
 | |
| 
 | |
|         params = {
 | |
|             'name': p.get('name'),
 | |
|             'template': p.get('template'),
 | |
|             'group_id': p.get('group'),
 | |
|             'network_id': p.get('network_id'),
 | |
|             'cpu': p.get('cpu'),
 | |
|             'memory': p.get('memory'),
 | |
|             'alias': p.get('alias'),
 | |
|             'password': p.get('password'),
 | |
|             'ip_address': p.get('ip_address'),
 | |
|             'storage_type': p.get('storage_type'),
 | |
|             'type': p.get('type'),
 | |
|             'primary_dns': p.get('primary_dns'),
 | |
|             'secondary_dns': p.get('secondary_dns'),
 | |
|             'additional_disks': p.get('additional_disks'),
 | |
|             'custom_fields': p.get('custom_fields'),
 | |
|             'ttl': p.get('ttl'),
 | |
|             'managed_os': p.get('managed_os'),
 | |
|             'description': p.get('description'),
 | |
|             'source_server_password': p.get('source_server_password'),
 | |
|             'cpu_autoscale_policy_id': p.get('cpu_autoscale_policy_id'),
 | |
|             'anti_affinity_policy_id': p.get('anti_affinity_policy_id'),
 | |
|             'packages': p.get('packages'),
 | |
|             'configuration_id': p.get('configuration_id'),
 | |
|             'os_type': p.get('os_type')
 | |
|         }
 | |
| 
 | |
|         count = override_count if override_count else p.get('count')
 | |
| 
 | |
|         changed = False if count == 0 else True
 | |
| 
 | |
|         if not changed:
 | |
|             return server_dict_array, created_server_ids, partial_created_servers_ids, changed
 | |
|         for i in range(0, count):
 | |
|             if not module.check_mode:
 | |
|                 req = self._create_clc_server(clc=clc,
 | |
|                                               module=module,
 | |
|                                               server_params=params)
 | |
|                 server = req.requests[0].Server()
 | |
|                 request_list.append(req)
 | |
|                 servers.append(server)
 | |
| 
 | |
|         self._wait_for_requests(module, request_list)
 | |
|         self._refresh_servers(module, servers)
 | |
| 
 | |
|         ip_failed_servers = self._add_public_ip_to_servers(
 | |
|             module=module,
 | |
|             should_add_public_ip=add_public_ip,
 | |
|             servers=servers,
 | |
|             public_ip_protocol=public_ip_protocol,
 | |
|             public_ip_ports=public_ip_ports)
 | |
|         ap_failed_servers = self._add_alert_policy_to_servers(clc=clc,
 | |
|                                                               module=module,
 | |
|                                                               servers=servers)
 | |
| 
 | |
|         for server in servers:
 | |
|             if server in ip_failed_servers or server in ap_failed_servers:
 | |
|                 partial_created_servers_ids.append(server.id)
 | |
|             else:
 | |
|                 # reload server details
 | |
|                 server = clc.v2.Server(server.id)
 | |
|                 server.data['ipaddress'] = server.details[
 | |
|                     'ipAddresses'][0]['internal']
 | |
| 
 | |
|                 if add_public_ip and len(server.PublicIPs().public_ips) > 0:
 | |
|                     server.data['publicip'] = str(
 | |
|                         server.PublicIPs().public_ips[0])
 | |
|                 created_server_ids.append(server.id)
 | |
|             server_dict_array.append(server.data)
 | |
| 
 | |
|         return server_dict_array, created_server_ids, partial_created_servers_ids, changed
 | |
| 
 | |
|     def _enforce_count(self, module, clc):
 | |
|         """
 | |
|         Enforce that there is the right number of servers in the provided group.
 | |
|         Starts or stops servers as necessary.
 | |
|         :param module: the AnsibleModule object
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :return: a list of dictionaries with server information about the servers that were created or deleted
 | |
|         """
 | |
|         p = module.params
 | |
|         changed = False
 | |
|         count_group = p.get('count_group')
 | |
|         datacenter = ClcServer._find_datacenter(clc, module)
 | |
|         exact_count = p.get('exact_count')
 | |
|         server_dict_array = []
 | |
|         partial_servers_ids = []
 | |
|         changed_server_ids = []
 | |
| 
 | |
|         # fail here if the exact count was specified without filtering
 | |
|         # on a group, as this may lead to a undesired removal of instances
 | |
|         if exact_count and count_group is None:
 | |
|             return module.fail_json(
 | |
|                 msg="you must use the 'count_group' option with exact_count")
 | |
| 
 | |
|         servers, running_servers = ClcServer._find_running_servers_by_group(
 | |
|             module, datacenter, count_group)
 | |
| 
 | |
|         if len(running_servers) == exact_count:
 | |
|             changed = False
 | |
| 
 | |
|         elif len(running_servers) < exact_count:
 | |
|             to_create = exact_count - len(running_servers)
 | |
|             server_dict_array, changed_server_ids, partial_servers_ids, changed \
 | |
|                 = self._create_servers(module, clc, override_count=to_create)
 | |
| 
 | |
|             for server in server_dict_array:
 | |
|                 running_servers.append(server)
 | |
| 
 | |
|         elif len(running_servers) > exact_count:
 | |
|             to_remove = len(running_servers) - exact_count
 | |
|             all_server_ids = sorted([x.id for x in running_servers])
 | |
|             remove_ids = all_server_ids[0:to_remove]
 | |
| 
 | |
|             (changed, server_dict_array, changed_server_ids) \
 | |
|                 = ClcServer._delete_servers(module, clc, remove_ids)
 | |
| 
 | |
|         return server_dict_array, changed_server_ids, partial_servers_ids, changed
 | |
| 
 | |
|     @staticmethod
 | |
|     def _wait_for_requests(module, request_list):
 | |
|         """
 | |
|         Block until server provisioning requests are completed.
 | |
|         :param module: the AnsibleModule object
 | |
|         :param request_list: a list of clc-sdk.Request instances
 | |
|         :return: none
 | |
|         """
 | |
|         wait = module.params.get('wait')
 | |
|         if wait:
 | |
|             # Requests.WaitUntilComplete() returns the count of failed requests
 | |
|             failed_requests_count = sum(
 | |
|                 [request.WaitUntilComplete() for request in request_list])
 | |
| 
 | |
|             if failed_requests_count > 0:
 | |
|                 module.fail_json(
 | |
|                     msg='Unable to process server request')
 | |
| 
 | |
|     @staticmethod
 | |
|     def _refresh_servers(module, servers):
 | |
|         """
 | |
|         Loop through a list of servers and refresh them.
 | |
|         :param module: the AnsibleModule object
 | |
|         :param servers: list of clc-sdk.Server instances to refresh
 | |
|         :return: none
 | |
|         """
 | |
|         for server in servers:
 | |
|             try:
 | |
|                 server.Refresh()
 | |
|             except CLCException as ex:
 | |
|                 module.fail_json(msg='Unable to refresh the server {0}. {1}'.format(
 | |
|                     server.id, ex.message
 | |
|                 ))
 | |
| 
 | |
|     @staticmethod
 | |
|     def _add_public_ip_to_servers(
 | |
|             module,
 | |
|             should_add_public_ip,
 | |
|             servers,
 | |
|             public_ip_protocol,
 | |
|             public_ip_ports):
 | |
|         """
 | |
|         Create a public IP for servers
 | |
|         :param module: the AnsibleModule object
 | |
|         :param should_add_public_ip: boolean - whether or not to provision a public ip for servers.  Skipped if False
 | |
|         :param servers: List of servers to add public ips to
 | |
|         :param public_ip_protocol: a protocol to allow for the public ips
 | |
|         :param public_ip_ports: list of ports to allow for the public ips
 | |
|         :return: none
 | |
|         """
 | |
|         failed_servers = []
 | |
|         if not should_add_public_ip:
 | |
|             return failed_servers
 | |
| 
 | |
|         ports_lst = []
 | |
|         request_list = []
 | |
|         server = None
 | |
| 
 | |
|         for port in public_ip_ports:
 | |
|             ports_lst.append(
 | |
|                 {'protocol': public_ip_protocol, 'port': port})
 | |
|         try:
 | |
|             if not module.check_mode:
 | |
|                 for server in servers:
 | |
|                     request = server.PublicIPs().Add(ports_lst)
 | |
|                     request_list.append(request)
 | |
|         except APIFailedResponse:
 | |
|             failed_servers.append(server)
 | |
|         ClcServer._wait_for_requests(module, request_list)
 | |
|         return failed_servers
 | |
| 
 | |
|     @staticmethod
 | |
|     def _add_alert_policy_to_servers(clc, module, servers):
 | |
|         """
 | |
|         Associate the alert policy to servers
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param module: the AnsibleModule object
 | |
|         :param servers: List of servers to add alert policy to
 | |
|         :return: failed_servers: the list of servers which failed while associating alert policy
 | |
|         """
 | |
|         failed_servers = []
 | |
|         p = module.params
 | |
|         alert_policy_id = p.get('alert_policy_id')
 | |
|         alias = p.get('alias')
 | |
| 
 | |
|         if alert_policy_id and not module.check_mode:
 | |
|             for server in servers:
 | |
|                 try:
 | |
|                     ClcServer._add_alert_policy_to_server(
 | |
|                         clc=clc,
 | |
|                         alias=alias,
 | |
|                         server_id=server.id,
 | |
|                         alert_policy_id=alert_policy_id)
 | |
|                 except CLCException:
 | |
|                     failed_servers.append(server)
 | |
|         return failed_servers
 | |
| 
 | |
|     @staticmethod
 | |
|     def _add_alert_policy_to_server(
 | |
|             clc, alias, server_id, alert_policy_id):
 | |
|         """
 | |
|         Associate an alert policy to a clc server
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param alias: the clc account alias
 | |
|         :param server_id: The clc server id
 | |
|         :param alert_policy_id: the alert policy id to be associated to the server
 | |
|         :return: none
 | |
|         """
 | |
|         try:
 | |
|             clc.v2.API.Call(
 | |
|                 method='POST',
 | |
|                 url='servers/%s/%s/alertPolicies' % (alias, server_id),
 | |
|                 payload=json.dumps(
 | |
|                     {
 | |
|                         'id': alert_policy_id
 | |
|                     }))
 | |
|         except APIFailedResponse as e:
 | |
|             raise CLCException(
 | |
|                 'Failed to associate alert policy to the server : {0} with Error {1}'.format(
 | |
|                     server_id, str(e.response_text)))
 | |
| 
 | |
|     @staticmethod
 | |
|     def _get_alert_policy_id_by_name(clc, module, alias, alert_policy_name):
 | |
|         """
 | |
|         Returns the alert policy id for the given alert policy name
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param module: the AnsibleModule object
 | |
|         :param alias: the clc account alias
 | |
|         :param alert_policy_name: the name of the alert policy
 | |
|         :return: alert_policy_id: the alert policy id
 | |
|         """
 | |
|         alert_policy_id = None
 | |
|         policies = clc.v2.API.Call('GET', '/v2/alertPolicies/%s' % alias)
 | |
|         if not policies:
 | |
|             return alert_policy_id
 | |
|         for policy in policies.get('items'):
 | |
|             if policy.get('name') == alert_policy_name:
 | |
|                 if not alert_policy_id:
 | |
|                     alert_policy_id = policy.get('id')
 | |
|                 else:
 | |
|                     return module.fail_json(
 | |
|                         msg='multiple alert policies were found with policy name : %s' % alert_policy_name)
 | |
|         return alert_policy_id
 | |
| 
 | |
|     @staticmethod
 | |
|     def _delete_servers(module, clc, server_ids):
 | |
|         """
 | |
|         Delete the servers on the provided list
 | |
|         :param module: the AnsibleModule object
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param server_ids: list of servers to delete
 | |
|         :return: a list of dictionaries with server information about the servers that were deleted
 | |
|         """
 | |
|         terminated_server_ids = []
 | |
|         server_dict_array = []
 | |
|         request_list = []
 | |
| 
 | |
|         if not isinstance(server_ids, list) or len(server_ids) < 1:
 | |
|             return module.fail_json(
 | |
|                 msg='server_ids should be a list of servers, aborting')
 | |
| 
 | |
|         servers = clc.v2.Servers(server_ids).Servers()
 | |
|         for server in servers:
 | |
|             if not module.check_mode:
 | |
|                 request_list.append(server.Delete())
 | |
|         ClcServer._wait_for_requests(module, request_list)
 | |
| 
 | |
|         for server in servers:
 | |
|             terminated_server_ids.append(server.id)
 | |
| 
 | |
|         return True, server_dict_array, terminated_server_ids
 | |
| 
 | |
|     @staticmethod
 | |
|     def _start_stop_servers(module, clc, server_ids):
 | |
|         """
 | |
|         Start or Stop the servers on the provided list
 | |
|         :param module: the AnsibleModule object
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param server_ids: list of servers to start or stop
 | |
|         :return: a list of dictionaries with server information about the servers that were started or stopped
 | |
|         """
 | |
|         p = module.params
 | |
|         state = p.get('state')
 | |
|         changed = False
 | |
|         changed_servers = []
 | |
|         server_dict_array = []
 | |
|         result_server_ids = []
 | |
|         request_list = []
 | |
| 
 | |
|         if not isinstance(server_ids, list) or len(server_ids) < 1:
 | |
|             return module.fail_json(
 | |
|                 msg='server_ids should be a list of servers, aborting')
 | |
| 
 | |
|         servers = clc.v2.Servers(server_ids).Servers()
 | |
|         for server in servers:
 | |
|             if server.powerState != state:
 | |
|                 changed_servers.append(server)
 | |
|                 if not module.check_mode:
 | |
|                     request_list.append(
 | |
|                         ClcServer._change_server_power_state(
 | |
|                             module,
 | |
|                             server,
 | |
|                             state))
 | |
|                 changed = True
 | |
| 
 | |
|         ClcServer._wait_for_requests(module, request_list)
 | |
|         ClcServer._refresh_servers(module, changed_servers)
 | |
| 
 | |
|         for server in set(changed_servers + servers):
 | |
|             try:
 | |
|                 server.data['ipaddress'] = server.details[
 | |
|                     'ipAddresses'][0]['internal']
 | |
|                 server.data['publicip'] = str(
 | |
|                     server.PublicIPs().public_ips[0])
 | |
|             except (KeyError, IndexError):
 | |
|                 pass
 | |
| 
 | |
|             server_dict_array.append(server.data)
 | |
|             result_server_ids.append(server.id)
 | |
| 
 | |
|         return changed, server_dict_array, result_server_ids
 | |
| 
 | |
|     @staticmethod
 | |
|     def _change_server_power_state(module, server, state):
 | |
|         """
 | |
|         Change the server powerState
 | |
|         :param module: the module to check for intended state
 | |
|         :param server: the server to start or stop
 | |
|         :param state: the intended powerState for the server
 | |
|         :return: the request object from clc-sdk call
 | |
|         """
 | |
|         result = None
 | |
|         try:
 | |
|             if state == 'started':
 | |
|                 result = server.PowerOn()
 | |
|             else:
 | |
|                 # Try to shut down the server and fall back to power off when unable to shut down.
 | |
|                 result = server.ShutDown()
 | |
|                 if result and hasattr(result, 'requests') and result.requests[0]:
 | |
|                     return result
 | |
|                 else:
 | |
|                     result = server.PowerOff()
 | |
|         except CLCException:
 | |
|             module.fail_json(
 | |
|                 msg='Unable to change power state for server {0}'.format(
 | |
|                     server.id))
 | |
|         return result
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_running_servers_by_group(module, datacenter, count_group):
 | |
|         """
 | |
|         Find a list of running servers in the provided group
 | |
|         :param module: the AnsibleModule object
 | |
|         :param datacenter: the clc-sdk.Datacenter instance to use to lookup the group
 | |
|         :param count_group: the group to count the servers
 | |
|         :return: list of servers, and list of running servers
 | |
|         """
 | |
|         group = ClcServer._find_group(
 | |
|             module=module,
 | |
|             datacenter=datacenter,
 | |
|             lookup_group=count_group)
 | |
| 
 | |
|         servers = group.Servers().Servers()
 | |
|         running_servers = []
 | |
| 
 | |
|         for server in servers:
 | |
|             if server.status == 'active' and server.powerState == 'started':
 | |
|                 running_servers.append(server)
 | |
| 
 | |
|         return servers, running_servers
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_group(module, datacenter, lookup_group=None):
 | |
|         """
 | |
|         Find a server group in a datacenter by calling the CLC API
 | |
|         :param module: the AnsibleModule instance
 | |
|         :param datacenter: clc-sdk.Datacenter instance to search for the group
 | |
|         :param lookup_group: string name of the group to search for
 | |
|         :return: clc-sdk.Group instance
 | |
|         """
 | |
|         if not lookup_group:
 | |
|             lookup_group = module.params.get('group')
 | |
|         try:
 | |
|             return datacenter.Groups().Get(lookup_group)
 | |
|         except CLCException:
 | |
|             pass
 | |
| 
 | |
|         # The search above only acts on the main
 | |
|         result = ClcServer._find_group_recursive(
 | |
|             module,
 | |
|             datacenter.Groups(),
 | |
|             lookup_group)
 | |
| 
 | |
|         if result is None:
 | |
|             module.fail_json(
 | |
|                 msg=str(
 | |
|                     "Unable to find group: " +
 | |
|                     lookup_group +
 | |
|                     " in location: " +
 | |
|                     datacenter.id))
 | |
| 
 | |
|         return result
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_group_recursive(module, group_list, lookup_group):
 | |
|         """
 | |
|         Find a server group by recursively walking the tree
 | |
|         :param module: the AnsibleModule instance to use
 | |
|         :param group_list: a list of groups to search
 | |
|         :param lookup_group: the group to look for
 | |
|         :return: list of groups
 | |
|         """
 | |
|         result = None
 | |
|         for group in group_list.groups:
 | |
|             subgroups = group.Subgroups()
 | |
|             try:
 | |
|                 return subgroups.Get(lookup_group)
 | |
|             except CLCException:
 | |
|                 result = ClcServer._find_group_recursive(
 | |
|                     module,
 | |
|                     subgroups,
 | |
|                     lookup_group)
 | |
| 
 | |
|             if result is not None:
 | |
|                 break
 | |
| 
 | |
|         return result
 | |
| 
 | |
|     @staticmethod
 | |
|     def _create_clc_server(
 | |
|             clc,
 | |
|             module,
 | |
|             server_params):
 | |
|         """
 | |
|         Call the CLC Rest API to Create a Server
 | |
|         :param clc: the clc-python-sdk instance to use
 | |
|         :param module: the AnsibleModule instance to use
 | |
|         :param server_params: a dictionary of params to use to create the servers
 | |
|         :return: clc-sdk.Request object linked to the queued server request
 | |
|         """
 | |
| 
 | |
|         try:
 | |
|             res = clc.v2.API.Call(
 | |
|                 method='POST',
 | |
|                 url='servers/%s' %
 | |
|                 (server_params.get('alias')),
 | |
|                 payload=json.dumps(
 | |
|                     {
 | |
|                         'name': server_params.get('name'),
 | |
|                         'description': server_params.get('description'),
 | |
|                         'groupId': server_params.get('group_id'),
 | |
|                         'sourceServerId': server_params.get('template'),
 | |
|                         'isManagedOS': server_params.get('managed_os'),
 | |
|                         'primaryDNS': server_params.get('primary_dns'),
 | |
|                         'secondaryDNS': server_params.get('secondary_dns'),
 | |
|                         'networkId': server_params.get('network_id'),
 | |
|                         'ipAddress': server_params.get('ip_address'),
 | |
|                         'password': server_params.get('password'),
 | |
|                         'sourceServerPassword': server_params.get('source_server_password'),
 | |
|                         'cpu': server_params.get('cpu'),
 | |
|                         'cpuAutoscalePolicyId': server_params.get('cpu_autoscale_policy_id'),
 | |
|                         'memoryGB': server_params.get('memory'),
 | |
|                         'type': server_params.get('type'),
 | |
|                         'storageType': server_params.get('storage_type'),
 | |
|                         'antiAffinityPolicyId': server_params.get('anti_affinity_policy_id'),
 | |
|                         'customFields': server_params.get('custom_fields'),
 | |
|                         'additionalDisks': server_params.get('additional_disks'),
 | |
|                         'ttl': server_params.get('ttl'),
 | |
|                         'packages': server_params.get('packages'),
 | |
|                         'configurationId': server_params.get('configuration_id'),
 | |
|                         'osType': server_params.get('os_type')}))
 | |
| 
 | |
|             result = clc.v2.Requests(res)
 | |
|         except APIFailedResponse as ex:
 | |
|             return module.fail_json(msg='Unable to create the server: {0}. {1}'.format(
 | |
|                 server_params.get('name'),
 | |
|                 ex.response_text
 | |
|             ))
 | |
| 
 | |
|         #
 | |
|         # Patch the Request object so that it returns a valid server
 | |
| 
 | |
|         # Find the server's UUID from the API response
 | |
|         server_uuid = [obj['id']
 | |
|                        for obj in res['links'] if obj['rel'] == 'self'][0]
 | |
| 
 | |
|         # Change the request server method to a _find_server_by_uuid closure so
 | |
|         # that it will work
 | |
|         result.requests[0].Server = lambda: ClcServer._find_server_by_uuid_w_retry(
 | |
|             clc,
 | |
|             module,
 | |
|             server_uuid,
 | |
|             server_params.get('alias'))
 | |
| 
 | |
|         return result
 | |
| 
 | |
|     @staticmethod
 | |
|     def _get_anti_affinity_policy_id(clc, module, alias, aa_policy_name):
 | |
|         """
 | |
|         retrieves the anti affinity policy id of the server based on the name of the policy
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param module: the AnsibleModule object
 | |
|         :param alias: the CLC account alias
 | |
|         :param aa_policy_name: the anti affinity policy name
 | |
|         :return: aa_policy_id: The anti affinity policy id
 | |
|         """
 | |
|         aa_policy_id = None
 | |
|         try:
 | |
|             aa_policies = clc.v2.API.Call(method='GET',
 | |
|                                           url='antiAffinityPolicies/%s' % alias)
 | |
|         except APIFailedResponse as ex:
 | |
|             return module.fail_json(msg='Unable to fetch anti affinity policies for account: {0}. {1}'.format(
 | |
|                 alias, ex.response_text))
 | |
|         for aa_policy in aa_policies.get('items'):
 | |
|             if aa_policy.get('name') == aa_policy_name:
 | |
|                 if not aa_policy_id:
 | |
|                     aa_policy_id = aa_policy.get('id')
 | |
|                 else:
 | |
|                     return module.fail_json(
 | |
|                         msg='multiple anti affinity policies were found with policy name : %s' % aa_policy_name)
 | |
|         return aa_policy_id
 | |
| 
 | |
|     #
 | |
|     #  This is the function that gets patched to the Request.server object using a lambda closure
 | |
|     #
 | |
| 
 | |
|     @staticmethod
 | |
|     def _find_server_by_uuid_w_retry(
 | |
|             clc, module, svr_uuid, alias=None, retries=5, back_out=2):
 | |
|         """
 | |
|         Find the clc server by the UUID returned from the provisioning request.  Retry the request if a 404 is returned.
 | |
|         :param clc: the clc-sdk instance to use
 | |
|         :param module: the AnsibleModule object
 | |
|         :param svr_uuid: UUID of the server
 | |
|         :param retries: the number of retry attempts to make prior to fail. default is 5
 | |
|         :param alias: the Account Alias to search
 | |
|         :return: a clc-sdk.Server instance
 | |
|         """
 | |
|         if not alias:
 | |
|             alias = clc.v2.Account.GetAlias()
 | |
| 
 | |
|         # Wait and retry if the api returns a 404
 | |
|         while True:
 | |
|             retries -= 1
 | |
|             try:
 | |
|                 server_obj = clc.v2.API.Call(
 | |
|                     method='GET', url='servers/%s/%s?uuid=true' %
 | |
|                     (alias, svr_uuid))
 | |
|                 server_id = server_obj['id']
 | |
|                 server = clc.v2.Server(
 | |
|                     id=server_id,
 | |
|                     alias=alias,
 | |
|                     server_obj=server_obj)
 | |
|                 return server
 | |
| 
 | |
|             except APIFailedResponse as e:
 | |
|                 if e.response_status_code != 404:
 | |
|                     return module.fail_json(
 | |
|                         msg='A failure response was received from CLC API when '
 | |
|                         'attempting to get details for a server:  UUID=%s, Code=%i, Message=%s' %
 | |
|                         (svr_uuid, e.response_status_code, e.message))
 | |
|                 if retries == 0:
 | |
|                     return module.fail_json(
 | |
|                         msg='Unable to reach the CLC API after 5 attempts')
 | |
|                 time.sleep(back_out)
 | |
|                 back_out *= 2
 | |
| 
 | |
|     @staticmethod
 | |
|     def _set_user_agent(clc):
 | |
|         if hasattr(clc, 'SetRequestsSession'):
 | |
|             agent_string = "ClcAnsibleModule/" + __version__
 | |
|             ses = requests.Session()
 | |
|             ses.headers.update({"Api-Client": agent_string})
 | |
|             ses.headers['User-Agent'] += " " + agent_string
 | |
|             clc.SetRequestsSession(ses)
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     """
 | |
|     The main function.  Instantiates the module and calls process_request.
 | |
|     :return: none
 | |
|     """
 | |
|     argument_dict = ClcServer._define_module_argument_spec()
 | |
|     module = AnsibleModule(supports_check_mode=True, **argument_dict)
 | |
|     clc_server = ClcServer(module)
 | |
|     clc_server.process_request()
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |