1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
4#
5# This file is part of Ansible
6#
7# Ansible is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# Ansible is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
19
20# PYTHON_ARGCOMPLETE_OK
21
22from __future__ import (absolute_import, division, print_function)
23__metaclass__ = type
24
25__requires__ = ['ansible_base']
26
27
28import errno
29import os
30import shutil
31import sys
32import traceback
33
34from ansible import context
35from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
36from ansible.module_utils._text import to_text
37
38
39# Used for determining if the system is running a new enough python version
40# and should only restrict on our documented minimum versions
41_PY3_MIN = sys.version_info[:2] >= (3, 5)
42_PY2_MIN = (2, 6) <= sys.version_info[:2] < (3,)
43_PY_MIN = _PY3_MIN or _PY2_MIN
44if not _PY_MIN:
45    raise SystemExit('ERROR: Ansible requires a minimum of Python2 version 2.6 or Python3 version 3.5. Current version: %s' % ''.join(sys.version.splitlines()))
46
47
48class LastResort(object):
49    # OUTPUT OF LAST RESORT
50    def display(self, msg, log_only=None):
51        print(msg, file=sys.stderr)
52
53    def error(self, msg, wrap_text=None):
54        print(msg, file=sys.stderr)
55
56
57if __name__ == '__main__':
58
59    display = LastResort()
60
61    try:  # bad ANSIBLE_CONFIG or config options can force ugly stacktrace
62        import ansible.constants as C
63        from ansible.utils.display import Display
64    except AnsibleOptionsError as e:
65        display.error(to_text(e), wrap_text=False)
66        sys.exit(5)
67
68    cli = None
69    me = os.path.basename(sys.argv[0])
70
71    try:
72        display = Display()
73        display.debug("starting run")
74
75        sub = None
76        target = me.split('-')
77        if target[-1][0].isdigit():
78            # Remove any version or python version info as downstreams
79            # sometimes add that
80            target = target[:-1]
81
82        if len(target) > 1:
83            sub = target[1]
84            myclass = "%sCLI" % sub.capitalize()
85        elif target[0] == 'ansible':
86            sub = 'adhoc'
87            myclass = 'AdHocCLI'
88        else:
89            raise AnsibleError("Unknown Ansible alias: %s" % me)
90
91        try:
92            mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
93        except ImportError as e:
94            # ImportError members have changed in py3
95            if 'msg' in dir(e):
96                msg = e.msg
97            else:
98                msg = e.message
99            if msg.endswith(' %s' % sub):
100                raise AnsibleError("Ansible sub-program not implemented: %s" % me)
101            else:
102                raise
103
104        b_ansible_dir = os.path.expanduser(os.path.expandvars(b"~/.ansible"))
105        try:
106            os.mkdir(b_ansible_dir, 0o700)
107        except OSError as exc:
108            if exc.errno != errno.EEXIST:
109                display.warning("Failed to create the directory '%s': %s"
110                                % (to_text(b_ansible_dir, errors='surrogate_or_replace'),
111                                   to_text(exc, errors='surrogate_or_replace')))
112        else:
113            display.debug("Created the '%s' directory" % to_text(b_ansible_dir, errors='surrogate_or_replace'))
114
115        try:
116            args = [to_text(a, errors='surrogate_or_strict') for a in sys.argv]
117        except UnicodeError:
118            display.error('Command line args are not in utf-8, unable to continue.  Ansible currently only understands utf-8')
119            display.display(u"The full traceback was:\n\n%s" % to_text(traceback.format_exc()))
120            exit_code = 6
121        else:
122            cli = mycli(args)
123            exit_code = cli.run()
124
125    except AnsibleOptionsError as e:
126        cli.parser.print_help()
127        display.error(to_text(e), wrap_text=False)
128        exit_code = 5
129    except AnsibleParserError as e:
130        display.error(to_text(e), wrap_text=False)
131        exit_code = 4
132# TQM takes care of these, but leaving comment to reserve the exit codes
133#    except AnsibleHostUnreachable as e:
134#        display.error(str(e))
135#        exit_code = 3
136#    except AnsibleHostFailed as e:
137#        display.error(str(e))
138#        exit_code = 2
139    except AnsibleError as e:
140        display.error(to_text(e), wrap_text=False)
141        exit_code = 1
142    except KeyboardInterrupt:
143        display.error("User interrupted execution")
144        exit_code = 99
145    except Exception as e:
146        if C.DEFAULT_DEBUG:
147            # Show raw stacktraces in debug mode, It also allow pdb to
148            # enter post mortem mode.
149            raise
150        have_cli_options = bool(context.CLIARGS)
151        display.error("Unexpected Exception, this is probably a bug: %s" % to_text(e), wrap_text=False)
152        if not have_cli_options or have_cli_options and context.CLIARGS['verbosity'] > 2:
153            log_only = False
154            if hasattr(e, 'orig_exc'):
155                display.vvv('\nexception type: %s' % to_text(type(e.orig_exc)))
156                why = to_text(e.orig_exc)
157                if to_text(e) != why:
158                    display.vvv('\noriginal msg: %s' % why)
159        else:
160            display.display("to see the full traceback, use -vvv")
161            log_only = True
162        display.display(u"the full traceback was:\n\n%s" % to_text(traceback.format_exc()), log_only=log_only)
163        exit_code = 250
164
165    sys.exit(exit_code)
166