1# -*- coding: utf-8 -*-
2# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
3# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com>
4# Copyright (c) 2013, Michael Scherer <misc@zarb.org>
5# Copyright (c) 2017 Ansible Project
6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7
8from __future__ import (absolute_import, division, print_function)
9__metaclass__ = type
10
11DOCUMENTATION = '''
12    author: Michael Scherer (@mscherer) <misc@zarb.org>
13    name: funcd
14    short_description: Use funcd to connect to target
15    description:
16        - This transport permits you to use Ansible over Func.
17        - For people who have already setup func and that wish to play with ansible,
18          this permit to move gradually to ansible without having to redo completely the setup of the network.
19    options:
20      remote_addr:
21        description:
22            - The path of the chroot you want to access.
23        default: inventory_hostname
24        vars:
25            - name: ansible_host
26            - name: ansible_func_host
27'''
28
29HAVE_FUNC = False
30try:
31    import func.overlord.client as fc
32    HAVE_FUNC = True
33except ImportError:
34    pass
35
36import os
37import tempfile
38import shutil
39
40from ansible.errors import AnsibleError
41from ansible.plugins.connection import ConnectionBase
42from ansible.utils.display import Display
43
44display = Display()
45
46
47class Connection(ConnectionBase):
48    """ Func-based connections """
49
50    has_pipelining = False
51
52    def __init__(self, runner, host, port, *args, **kwargs):
53        self.runner = runner
54        self.host = host
55        # port is unused, this go on func
56        self.port = port
57        self.client = None
58
59    def connect(self, port=None):
60        if not HAVE_FUNC:
61            raise AnsibleError("func is not installed")
62
63        self.client = fc.Client(self.host)
64        return self
65
66    def exec_command(self, cmd, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
67        """ run a command on the remote minion """
68
69        if in_data:
70            raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
71
72        # totally ignores privlege escalation
73        display.vvv("EXEC %s" % cmd, host=self.host)
74        p = self.client.command.run(cmd)[self.host]
75        return p[0], p[1], p[2]
76
77    @staticmethod
78    def _normalize_path(path, prefix):
79        if not path.startswith(os.path.sep):
80            path = os.path.join(os.path.sep, path)
81        normpath = os.path.normpath(path)
82        return os.path.join(prefix, normpath[1:])
83
84    def put_file(self, in_path, out_path):
85        """ transfer a file from local to remote """
86
87        out_path = self._normalize_path(out_path, '/')
88        display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
89        self.client.local.copyfile.send(in_path, out_path)
90
91    def fetch_file(self, in_path, out_path):
92        """ fetch a file from remote to local """
93
94        in_path = self._normalize_path(in_path, '/')
95        display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
96        # need to use a tmp dir due to difference of semantic for getfile
97        # ( who take a # directory as destination) and fetch_file, who
98        # take a file directly
99        tmpdir = tempfile.mkdtemp(prefix="func_ansible")
100        self.client.local.getfile.get(in_path, tmpdir)
101        shutil.move(os.path.join(tmpdir, self.host, os.path.basename(in_path)), out_path)
102        shutil.rmtree(tmpdir)
103
104    def close(self):
105        """ terminate the connection; nothing to do here """
106        pass
107