diff --git a/library/docker_swarm.py b/library/docker_swarm.py deleted file mode 100644 index 204c605..0000000 --- a/library/docker_swarm.py +++ /dev/null @@ -1,520 +0,0 @@ -#!/usr/bin/python - -# Copyright 2016 Red Hat | Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' ---- -module: docker_swarm -short_description: Manage Swarm cluster -version_added: "2.7" -description: - - Create a new Swarm cluster. - - Add/Remove nodes or managers to an existing cluster. -options: - advertise_addr: - description: - - Externally reachable address advertised to other nodes. - - This can either be an address/port combination - in the form C(192.168.1.1:4567), or an interface followed by a - port number, like C(eth0:4567). - - If the port number is omitted, - the port number from the listen address is used. - - If C(advertise_addr) is not specified, it will be automatically - detected when possible. - listen_addr: - description: - - Listen address used for inter-manager communication. - - This can either be an address/port combination in the form - C(192.168.1.1:4567), or an interface followed by a port number, - like C(eth0:4567). - - If the port number is omitted, the default swarm listening port - is used. - default: 0.0.0.0:2377 - force: - description: - - Use with state C(present) to force creating a new Swarm, even if already part of one. - - Use with state C(absent) to Leave the swarm even if this node is a manager. - type: bool - default: 'no' - state: - description: - - Set to C(present), to create/update a new cluster. - - Set to C(join), to join an existing cluster. - - Set to C(absent), to leave an existing cluster. - - Set to C(remove), to remove an absent node from the cluster. - - Set to C(inspect) to display swarm informations. - required: true - default: present - choices: - - present - - join - - absent - - remove - - inspect - node_id: - description: - - Swarm id of the node to remove. - - Used with I(state=remove). - join_token: - description: - - Swarm token used to join a swarm cluster. - - Used with I(state=join). - remote_addrs: - description: - - Remote address of a manager to connect to. - - Used with I(state=join). - task_history_retention_limit: - description: - - Maximum number of tasks history stored. - - Docker default value is C(5). - snapshot_interval: - description: - - Number of logs entries between snapshot. - - Docker default value is C(10000). - keep_old_snapshots: - description: - - Number of snapshots to keep beyond the current snapshot. - - Docker default value is C(0). - log_entries_for_slow_followers: - description: - - Number of log entries to keep around to sync up slow followers after a snapshot is created. - heartbeat_tick: - description: - - Amount of ticks (in seconds) between each heartbeat. - - Docker default value is C(1s). - election_tick: - description: - - Amount of ticks (in seconds) needed without a leader to trigger a new election. - - Docker default value is C(10s). - dispatcher_heartbeat_period: - description: - - The delay for an agent to send a heartbeat to the dispatcher. - - Docker default value is C(5s). - node_cert_expiry: - description: - - Automatic expiry for nodes certificates. - - Docker default value is C(3months). - name: - description: - - The name of the swarm. - labels: - description: - - User-defined key/value metadata. - signing_ca_cert: - description: - - The desired signing CA certificate for all swarm node TLS leaf certificates, in PEM format. - signing_ca_key: - description: - - The desired signing CA key for all swarm node TLS leaf certificates, in PEM format. - ca_force_rotate: - description: - - An integer whose purpose is to force swarm to generate a new signing CA certificate and key, - if none have been specified. - - Docker default value is C(0). - autolock_managers: - description: - - If set, generate a key and use it to lock data stored on the managers. - - Docker default value is C(no). - type: bool - rotate_worker_token: - description: Rotate the worker join token. - type: bool - default: 'no' - rotate_manager_token: - description: Rotate the manager join token. - type: bool - default: 'no' -extends_documentation_fragment: - - docker -requirements: - - python >= 2.7 - - Docker API >= 1.35 -author: - - Thierry Bouvet (@tbouvet) -''' - -EXAMPLES = ''' - -- name: Init a new swarm with default parameters - docker_swarm: - state: present - advertise_addr: 192.168.1.1 - -- name: Update swarm configuration - docker_swarm: - state: present - election_tick: 5 - -- name: Add nodes - docker_swarm: - state: join - advertise_addr: 192.168.1.2 - join_token: SWMTKN-1--xxxxx - remote_addrs: [ '192.168.1.1:2377' ] - -- name: Leave swarm for a node - docker_swarm: - state: absent - -- name: Remove a swarm manager - docker_swarm: - state: absent - force: true - -- name: Remove node from swarm - docker_swarm: - state: remove - node_id: mynode - -- name: Inspect swarm - docker_swarm: - state: inspect - register: swarm_info -''' - -RETURN = ''' -swarm_facts: - description: Informations about swarm. - returned: success - type: complex - contains: - JoinTokens: - description: Tokens to connect to the Swarm. - returned: success - type: complex - contains: - Worker: - description: Token to create a new I(worker) node - returned: success - type: str - example: SWMTKN-1--xxxxx - Manager: - description: Token to create a new I(manager) node - returned: success - type: str - example: SWMTKN-1--xxxxx -actions: - description: Provides the actions done on the swarm. - returned: when action failed. - type: list - example: "['This cluster is already a swarm cluster']" - -''' - -import json -from time import sleep -try: - from docker.errors import APIError -except ImportError: - # missing docker-py handled in ansible.module_utils.docker - pass - -from ansible.module_utils.docker_common import AnsibleDockerClient, DockerBaseClass -from ansible.module_utils._text import to_native - - -class TaskParameters(DockerBaseClass): - def __init__(self, client): - super(TaskParameters, self).__init__() - - self.state = None - self.advertise_addr = None - self.listen_addr = None - self.force_new_cluster = None - self.remote_addrs = None - self.join_token = None - - # Spec - self.snapshot_interval = None - self.task_history_retention_limit = None - self.keep_old_snapshots = None - self.log_entries_for_slow_followers = None - self.heartbeat_tick = None - self.election_tick = None - self.dispatcher_heartbeat_period = None - self.node_cert_expiry = None - self.external_cas = None - self.name = None - self.labels = None - self.log_driver = None - self.signing_ca_cert = None - self.signing_ca_key = None - self.ca_force_rotate = None - self.autolock_managers = None - self.rotate_worker_token = None - self.rotate_manager_token = None - - for key, value in client.module.params.items(): - setattr(self, key, value) - - self.update_parameters(client) - - def update_parameters(self, client): - self.spec = client.create_swarm_spec( - snapshot_interval=self.snapshot_interval, - task_history_retention_limit=self.task_history_retention_limit, - keep_old_snapshots=self.keep_old_snapshots, - log_entries_for_slow_followers=self.log_entries_for_slow_followers, - heartbeat_tick=self.heartbeat_tick, - election_tick=self.election_tick, - dispatcher_heartbeat_period=self.dispatcher_heartbeat_period, - node_cert_expiry=self.node_cert_expiry, - name=self.name, - labels=self.labels, - signing_ca_cert=self.signing_ca_cert, - signing_ca_key=self.signing_ca_key, - ca_force_rotate=self.ca_force_rotate, - autolock_managers=self.autolock_managers, - log_driver=self.log_driver - ) - - -class SwarmManager(DockerBaseClass): - - def __init__(self, client, results): - - super(SwarmManager, self).__init__() - - self.client = client - self.results = results - self.check_mode = self.client.check_mode - - self.parameters = TaskParameters(client) - - def __call__(self): - choice_map = { - "present": self.init_swarm, - "join": self.join, - "absent": self.leave, - "remove": self.remove, - "inspect": self.inspect_swarm - } - - choice_map.get(self.parameters.state)() - - def __isSwarmManager(self): - try: - data = self.client.inspect_swarm() - json_str = json.dumps(data, ensure_ascii=False) - self.swarm_info = json.loads(json_str) - return True - except APIError: - return False - - def inspect_swarm(self): - try: - data = self.client.inspect_swarm() - json_str = json.dumps(data, ensure_ascii=False) - self.swarm_info = json.loads(json_str) - self.results['changed'] = False - self.results['swarm_facts'] = self.swarm_info - except APIError: - return - - def init_swarm(self): - if self.__isSwarmManager(): - self.__update_swarm() - return - - try: - if self.parameters.advertise_addr is None: - self.client.fail(msg="advertise_addr is required to initialize a swarm cluster.") - - self.client.init_swarm( - advertise_addr=self.parameters.advertise_addr, listen_addr=self.parameters.listen_addr, - force_new_cluster=self.parameters.force_new_cluster, swarm_spec=self.parameters.spec) - except APIError as exc: - self.client.fail(msg="Can not create a new Swarm Cluster: %s" % to_native(exc)) - - self.__isSwarmManager() - self.results['actions'].append("New Swarm cluster created: %s" % (self.swarm_info['ID'])) - self.results['changed'] = True - self.results['swarm_facts'] = {u'JoinTokens': self.swarm_info['JoinTokens']} - - def __update_spec(self, spec): - if (self.parameters.node_cert_expiry is None): - self.parameters.node_cert_expiry = spec['CAConfig']['NodeCertExpiry'] - - if (self.parameters.dispatcher_heartbeat_period is None): - self.parameters.dispatcher_heartbeat_period = spec['Dispatcher']['HeartbeatPeriod'] - - if (self.parameters.snapshot_interval is None): - self.parameters.snapshot_interval = spec['Raft']['SnapshotInterval'] - if (self.parameters.keep_old_snapshots is None): - self.parameters.keep_old_snapshots = spec['Raft']['KeepOldSnapshots'] - if (self.parameters.heartbeat_tick is None): - self.parameters.heartbeat_tick = spec['Raft']['HeartbeatTick'] - if (self.parameters.log_entries_for_slow_followers is None): - self.parameters.log_entries_for_slow_followers = spec['Raft']['LogEntriesForSlowFollowers'] - if (self.parameters.election_tick is None): - self.parameters.election_tick = spec['Raft']['ElectionTick'] - - if (self.parameters.task_history_retention_limit is None): - self.parameters.task_history_retention_limit = spec['Orchestration']['TaskHistoryRetentionLimit'] - - if (self.parameters.autolock_managers is None): - self.parameters.autolock_managers = spec['EncryptionConfig']['AutoLockManagers'] - - if (self.parameters.name is None): - self.parameters.name = spec['Name'] - - if (self.parameters.labels is None): - self.parameters.labels = spec['Labels'] - - if 'LogDriver' in spec['TaskDefaults']: - self.parameters.log_driver = spec['TaskDefaults']['LogDriver'] - - self.parameters.update_parameters(self.client) - - return self.parameters.spec - - def __update_swarm(self): - try: - self.inspect_swarm() - version = self.swarm_info['Version']['Index'] - spec = self.swarm_info['Spec'] - new_spec = self.__update_spec(spec) - del spec['TaskDefaults'] - if spec == new_spec: - self.results['actions'].append("No modification") - self.results['changed'] = False - return - self.client.update_swarm( - version=version, swarm_spec=new_spec, rotate_worker_token=self.parameters.rotate_worker_token, - rotate_manager_token=self.parameters.rotate_manager_token) - except APIError as exc: - self.client.fail(msg="Can not update a Swarm Cluster: %s" % to_native(exc)) - return - - self.inspect_swarm() - self.results['actions'].append("Swarm cluster updated") - self.results['changed'] = True - - def __isSwarmNode(self): - info = self.client.info() - if info: - json_str = json.dumps(info, ensure_ascii=False) - self.swarm_info = json.loads(json_str) - if self.swarm_info['Swarm']['NodeID']: - return True - return False - - def join(self): - if self.__isSwarmNode(): - self.results['actions'].append("This node is already part of a swarm.") - return - try: - self.client.join_swarm( - remote_addrs=self.parameters.remote_addrs, join_token=self.parameters.join_token, listen_addr=self.parameters.listen_addr, - advertise_addr=self.parameters.advertise_addr) - except APIError as exc: - self.client.fail(msg="Can not join the Swarm Cluster: %s" % to_native(exc)) - self.results['actions'].append("New node is added to swarm cluster") - self.results['changed'] = True - - def leave(self): - if not(self.__isSwarmNode()): - self.results['actions'].append("This node is not part of a swarm.") - return - try: - self.client.leave_swarm(force=self.parameters.force) - except APIError as exc: - self.client.fail(msg="This node can not leave the Swarm Cluster: %s" % to_native(exc)) - self.results['actions'].append("Node has leaved the swarm cluster") - self.results['changed'] = True - - def __get_node_info(self): - try: - node_info = self.client.inspect_node(node_id=self.parameters.node_id) - except APIError as exc: - raise exc - json_str = json.dumps(node_info, ensure_ascii=False) - node_info = json.loads(json_str) - return node_info - - def __check_node_is_down(self): - for _x in range(0, 5): - node_info = self.__get_node_info() - if node_info['Status']['State'] == 'down': - return True - sleep(5) - return False - - def remove(self): - if not(self.__isSwarmManager()): - self.client.fail(msg="This node is not a manager.") - - try: - status_down = self.__check_node_is_down() - except APIError: - return - - if not(status_down): - self.client.fail(msg="Can not remove the node. The status node is ready and not down.") - - try: - self.client.remove_node(node_id=self.parameters.node_id, force=self.parameters.force) - except APIError as exc: - self.client.fail(msg="Can not remove the node from the Swarm Cluster: %s" % to_native(exc)) - self.results['actions'].append("Node is removed from swarm cluster.") - self.results['changed'] = True - - -def main(): - argument_spec = dict( - advertise_addr=dict(type='str'), - state=dict(type='str', choices=['present', 'join', 'absent', 'remove', 'inspect'], default='present'), - force=dict(type='bool', default=False), - listen_addr=dict(type='str', default='0.0.0.0:2377'), - remote_addrs=dict(type='list'), - join_token=dict(type='str'), - snapshot_interval=dict(type='int'), - task_history_retention_limit=dict(type='int'), - keep_old_snapshots=dict(type='int'), - log_entries_for_slow_followers=dict(type='int'), - heartbeat_tick=dict(type='int'), - election_tick=dict(type='int'), - dispatcher_heartbeat_period=dict(type='int'), - node_cert_expiry=dict(type='int'), - name=dict(type='str'), - labels=dict(type='dict'), - signing_ca_cert=dict(type='str'), - signing_ca_key=dict(type='str'), - ca_force_rotate=dict(type='int'), - autolock_managers=dict(type='bool'), - node_id=dict(type='str'), - rotate_worker_token=dict(type='bool', default=False), - rotate_manager_token=dict(type='bool', default=False) - ) - - required_if = [ - ('state', 'join', ['advertise_addr', 'remote_addrs', 'join_token']), - ('state', 'remove', ['node_id']) - ] - - client = AnsibleDockerClient( - argument_spec=argument_spec, - supports_check_mode=True, - required_if=required_if - ) - - results = dict( - changed=False, - result='', - actions=[] - ) - - SwarmManager(client, results)() - client.module.exit_json(**results) - - -if __name__ == '__main__': - main()