Modernize install (#76021)

Co-authored-by: Matt Clay <matt@mystile.com>
Co-authored-by: Matt Davis <mrd@redhat.com>
Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
pull/76096/head
Matt Martz 3 years ago committed by GitHub
parent 43d09710c8
commit 66a83314b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,10 @@ include docs/docsite/rst/collections/all_plugins.rst
exclude docs/docsite/rst_warnings exclude docs/docsite/rst_warnings
exclude docs/docsite/rst/conf.py exclude docs/docsite/rst/conf.py
exclude docs/docsite/rst/index.rst exclude docs/docsite/rst/index.rst
exclude docs/docsite/rst/dev_guide/testing/sanity/bin-symlinks.rst
exclude docs/docsite/rst/dev_guide/testing/sanity/botmeta.rst
exclude docs/docsite/rst/dev_guide/testing/sanity/integration-aliases.rst
exclude docs/docsite/rst/dev_guide/testing/sanity/release-names.rst
recursive-exclude docs/docsite/_build * recursive-exclude docs/docsite/_build *
recursive-exclude docs/docsite/_extensions *.pyc *.pyo recursive-exclude docs/docsite/_extensions *.pyc *.pyo
include examples/hosts include examples/hosts
@ -31,6 +35,9 @@ recursive-include test/lib/ansible_test/_util/controller/sanity/validate-modules
recursive-include test/sanity *.json *.py *.txt recursive-include test/sanity *.json *.py *.txt
recursive-include test/support *.py *.ps1 *.psm1 *.cs recursive-include test/support *.py *.ps1 *.psm1 *.cs
exclude test/sanity/code-smell/botmeta.* exclude test/sanity/code-smell/botmeta.*
exclude test/sanity/code-smell/release-names.*
exclude test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py
exclude test/lib/ansible_test/_internal/commands/sanity/integration_aliases.py
recursive-include test/units * recursive-include test/units *
include Makefile include Makefile
include MANIFEST.in include MANIFEST.in
@ -38,3 +45,5 @@ include changelogs/CHANGELOG*.rst
include changelogs/changelog.yaml include changelogs/changelog.yaml
recursive-include hacking/build_library *.py recursive-include hacking/build_library *.py
include hacking/build-ansible.py include hacking/build-ansible.py
include hacking/test-module.py
include bin/*

@ -1 +1 @@
../lib/ansible/cli/scripts/ansible_cli_stub.py ../lib/ansible/cli/adhoc.py

@ -1 +1 @@
ansible ../lib/ansible/cli/config.py

@ -1 +1 @@
ansible ../lib/ansible/cli/console.py

@ -1 +1 @@
ansible ../lib/ansible/cli/doc.py

@ -1 +1 @@
ansible ../lib/ansible/cli/galaxy.py

@ -1 +1 @@
ansible ../lib/ansible/cli/inventory.py

@ -1 +1 @@
ansible ../lib/ansible/cli/playbook.py

@ -1 +1 @@
ansible ../lib/ansible/cli/pull.py

@ -1 +1 @@
ansible ../lib/ansible/cli/vault.py

@ -0,0 +1,3 @@
minor_changes:
- Installation - modernize our python installation, to reduce dynamic code in setup.py, and migrate
what is feasible to setup.cfg. This will enable shipping wheels in the future.

@ -48,10 +48,12 @@ FULL_PATH=$($PYTHON_BIN -c "import os; print(os.path.realpath('$HACKING_DIR'))")
export ANSIBLE_HOME="$(dirname "$FULL_PATH")" export ANSIBLE_HOME="$(dirname "$FULL_PATH")"
PREFIX_PYTHONPATH="$ANSIBLE_HOME/lib" PREFIX_PYTHONPATH="$ANSIBLE_HOME/lib"
ANSIBLE_TEST_PREFIX_PYTHONPATH="$ANSIBLE_HOME/test/lib"
PREFIX_PATH="$ANSIBLE_HOME/bin" PREFIX_PATH="$ANSIBLE_HOME/bin"
PREFIX_MANPATH="$ANSIBLE_HOME/docs/man" PREFIX_MANPATH="$ANSIBLE_HOME/docs/man"
expr "$PYTHONPATH" : "${PREFIX_PYTHONPATH}.*" > /dev/null || prepend_path PYTHONPATH "$PREFIX_PYTHONPATH" expr "$PYTHONPATH" : "${PREFIX_PYTHONPATH}.*" > /dev/null || prepend_path PYTHONPATH "$PREFIX_PYTHONPATH"
expr "$PYTHONPATH" : "${ANSIBLE_TEST_PREFIX_PYTHONPATH}.*" > /dev/null || prepend_path PYTHONPATH "$ANSIBLE_TEST_PREFIX_PYTHONPATH"
expr "$PATH" : "${PREFIX_PATH}.*" > /dev/null || prepend_path PATH "$PREFIX_PATH" expr "$PATH" : "${PREFIX_PATH}.*" > /dev/null || prepend_path PATH "$PREFIX_PATH"
expr "$MANPATH" : "${PREFIX_MANPATH}.*" > /dev/null || prepend_path MANPATH "$PREFIX_MANPATH" expr "$MANPATH" : "${PREFIX_MANPATH}.*" > /dev/null || prepend_path MANPATH "$PREFIX_MANPATH"

@ -5,6 +5,7 @@ set HACKING_DIR (dirname (status -f))
set FULL_PATH (python -c "import os; print(os.path.realpath('$HACKING_DIR'))") set FULL_PATH (python -c "import os; print(os.path.realpath('$HACKING_DIR'))")
set ANSIBLE_HOME (dirname $FULL_PATH) set ANSIBLE_HOME (dirname $FULL_PATH)
set PREFIX_PYTHONPATH $ANSIBLE_HOME/lib set PREFIX_PYTHONPATH $ANSIBLE_HOME/lib
set ANSIBLE_TEST_PREFIX_PYTHONPATH $ANSIBLE_HOME/test/lib
set PREFIX_PATH $ANSIBLE_HOME/bin set PREFIX_PATH $ANSIBLE_HOME/bin
set PREFIX_MANPATH $ANSIBLE_HOME/docs/man set PREFIX_MANPATH $ANSIBLE_HOME/docs/man
@ -31,6 +32,16 @@ else
end end
end end
# Set ansible_test PYTHONPATH
switch PYTHONPATH
case "$ANSIBLE_TEST_PREFIX_PYTHONPATH*"
case "*"
if not [ $QUIET ]
echo "Appending PYTHONPATH"
end
set -gx PYTHONPATH "$ANSIBLE_TEST_PREFIX_PYTHONPATH:$PYTHONPATH"
end
# Set PATH # Set PATH
if not contains $PREFIX_PATH $PATH if not contains $PREFIX_PATH $PATH
set -gx PATH $PREFIX_PATH $PATH set -gx PATH $PREFIX_PATH $PATH

@ -0,0 +1,41 @@
# Copyright: (c) 2021, Matt Martz <matt@sivel.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import argparse
import importlib
import os
import sys
from importlib.metadata import distribution
def _short_name(name):
return name.replace('ansible-', '').replace('ansible', 'adhoc')
def main():
dist = distribution('ansible-core')
ep_map = {_short_name(ep.name): ep for ep in dist.entry_points if ep.group == 'console_scripts'}
parser = argparse.ArgumentParser(prog='python -m ansible', add_help=False)
parser.add_argument('entry_point', choices=list(ep_map) + ['test'])
args, extra = parser.parse_known_args()
if args.entry_point == 'test':
ansible_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
source_root = os.path.join(ansible_root, 'test', 'lib')
if os.path.exists(os.path.join(source_root, 'ansible_test', '_internal', '__init__.py')):
# running from source, use that version of ansible-test instead of any version that may already be installed
sys.path.insert(0, source_root)
module = importlib.import_module('ansible_test._util.target.cli.ansible_test_cli_stub')
main = module.main
else:
main = ep_map[args.entry_point].load()
main([args.entry_point] + extra)
if __name__ == '__main__':
main()

@ -7,17 +7,36 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import sys
# Used for determining if the system is running a new enough python version
# and should only restrict on our documented minimum versions
if sys.version_info < (3, 8):
raise SystemExit(
'ERROR: Ansible requires Python 3.8 or newer on the controller. '
'Current version: %s' % ''.join(sys.version.splitlines())
)
import errno
import getpass import getpass
import os import os
import subprocess import subprocess
import sys import traceback
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from pathlib import Path
try:
from ansible import constants as C
from ansible.utils.display import Display, initialize_locale
initialize_locale()
display = Display()
except Exception as e:
print('ERROR: %s' % e, file=sys.stderr)
sys.exit(5)
from ansible.cli.arguments import option_helpers as opt_help
from ansible import constants as C
from ansible import context from ansible import context
from ansible.errors import AnsibleError from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
from ansible.inventory.manager import InventoryManager from ansible.inventory.manager import InventoryManager
from ansible.module_utils.six import with_metaclass, string_types, PY3 from ansible.module_utils.six import with_metaclass, string_types, PY3
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text
@ -27,7 +46,6 @@ from ansible.plugins.loader import add_all_plugin_dirs
from ansible.release import __version__ from ansible.release import __version__
from ansible.utils.collection_loader import AnsibleCollectionConfig from ansible.utils.collection_loader import AnsibleCollectionConfig
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
from ansible.utils.display import Display
from ansible.utils.path import unfrackpath from ansible.utils.path import unfrackpath
from ansible.utils.unsafe_proxy import to_unsafe_text from ansible.utils.unsafe_proxy import to_unsafe_text
from ansible.vars.manager import VariableManager from ansible.vars.manager import VariableManager
@ -39,9 +57,6 @@ except ImportError:
HAS_ARGCOMPLETE = False HAS_ARGCOMPLETE = False
display = Display()
class CLI(with_metaclass(ABCMeta, object)): class CLI(with_metaclass(ABCMeta, object)):
''' code behind bin/ansible* programs ''' ''' code behind bin/ansible* programs '''
@ -292,7 +307,7 @@ class CLI(with_metaclass(ABCMeta, object)):
ansible.arguments.option_helpers.add_runas_options(self.parser) ansible.arguments.option_helpers.add_runas_options(self.parser)
self.parser.add_option('--my-option', dest='my_option', action='store') self.parser.add_option('--my-option', dest='my_option', action='store')
""" """
self.parser = opt_help.create_base_parser(os.path.basename(self.args[0]), usage=usage, desc=desc, epilog=epilog, ) self.parser = opt_help.create_base_parser(self.name, usage=usage, desc=desc, epilog=epilog)
@abstractmethod @abstractmethod
def post_process_args(self, options): def post_process_args(self, options):
@ -532,3 +547,74 @@ class CLI(with_metaclass(ABCMeta, object)):
raise AnsibleError('Empty password was provided from file (%s)' % pwd_file) raise AnsibleError('Empty password was provided from file (%s)' % pwd_file)
return to_unsafe_text(secret) return to_unsafe_text(secret)
@classmethod
def cli_executor(cls, args=None):
if args is None:
args = sys.argv
try:
display.debug("starting run")
ansible_dir = Path("~/.ansible").expanduser()
try:
ansible_dir.mkdir(mode=0o700)
except OSError as exc:
if exc.errno != errno.EEXIST:
display.warning(
"Failed to create the directory '%s': %s" % (ansible_dir, to_text(exc, errors='surrogate_or_replace'))
)
else:
display.debug("Created the '%s' directory" % ansible_dir)
try:
args = [to_text(a, errors='surrogate_or_strict') for a in args]
except UnicodeError:
display.error('Command line args are not in utf-8, unable to continue. Ansible currently only understands utf-8')
display.display(u"The full traceback was:\n\n%s" % to_text(traceback.format_exc()))
exit_code = 6
else:
cli = cls(args)
exit_code = cli.run()
except AnsibleOptionsError as e:
cli.parser.print_help()
display.error(to_text(e), wrap_text=False)
exit_code = 5
except AnsibleParserError as e:
display.error(to_text(e), wrap_text=False)
exit_code = 4
# TQM takes care of these, but leaving comment to reserve the exit codes
# except AnsibleHostUnreachable as e:
# display.error(str(e))
# exit_code = 3
# except AnsibleHostFailed as e:
# display.error(str(e))
# exit_code = 2
except AnsibleError as e:
display.error(to_text(e), wrap_text=False)
exit_code = 1
except KeyboardInterrupt:
display.error("User interrupted execution")
exit_code = 99
except Exception as e:
if C.DEFAULT_DEBUG:
# Show raw stacktraces in debug mode, It also allow pdb to
# enter post mortem mode.
raise
have_cli_options = bool(context.CLIARGS)
display.error("Unexpected Exception, this is probably a bug: %s" % to_text(e), wrap_text=False)
if not have_cli_options or have_cli_options and context.CLIARGS['verbosity'] > 2:
log_only = False
if hasattr(e, 'orig_exc'):
display.vvv('\nexception type: %s' % to_text(type(e.orig_exc)))
why = to_text(e.orig_exc)
if to_text(e) != why:
display.vvv('\noriginal msg: %s' % why)
else:
display.display("to see the full traceback, use -vvv")
log_only = True
display.display(u"the full traceback was:\n\n%s" % to_text(traceback.format_exc()), log_only=log_only)
exit_code = 250
sys.exit(exit_code)

@ -1,13 +1,16 @@
#!/usr/bin/env python
# Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
@ -25,6 +28,8 @@ class AdHocCLI(CLI):
this command allows you to define and run a single task 'playbook' against a set of hosts this command allows you to define and run a single task 'playbook' against a set of hosts
''' '''
name = 'ansible'
def init_parser(self): def init_parser(self):
''' create an options parser for bin/ansible ''' ''' create an options parser for bin/ansible '''
super(AdHocCLI, self).init_parser(usage='%prog <host-pattern> [options]', super(AdHocCLI, self).init_parser(usage='%prog <host-pattern> [options]',
@ -179,3 +184,11 @@ class AdHocCLI(CLI):
loader.cleanup_all_tmp_files() loader.cleanup_all_tmp_files()
return result return result
def main(args=None):
AdHocCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,9 +1,14 @@
#!/usr/bin/env python
# Copyright: (c) 2017, Ansible Project # Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import os import os
import shlex import shlex
import subprocess import subprocess
@ -13,7 +18,6 @@ from ansible import context
import ansible.plugins.loader as plugin_loader import ansible.plugins.loader as plugin_loader
from ansible import constants as C from ansible import constants as C
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.config.manager import ConfigManager, Setting from ansible.config.manager import ConfigManager, Setting
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
@ -33,6 +37,8 @@ display = Display()
class ConfigCLI(CLI): class ConfigCLI(CLI):
""" Config command line class """ """ Config command line class """
name = 'ansible-config'
def __init__(self, args, callback=None): def __init__(self, args, callback=None):
self.config_file = None self.config_file = None
@ -469,3 +475,11 @@ class ConfigCLI(CLI):
text = self._get_plugin_configs(context.CLIARGS['type'], context.CLIARGS['args']) text = self._get_plugin_configs(context.CLIARGS['type'], context.CLIARGS['args'])
self.pager(to_text('\n'.join(text), errors='surrogate_or_strict')) self.pager(to_text('\n'.join(text), errors='surrogate_or_strict'))
def main(args=None):
ConfigCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,11 +1,16 @@
#!/usr/bin/env python
# Copyright: (c) 2014, Nandor Sivok <dominis@haxor.hu> # Copyright: (c) 2014, Nandor Sivok <dominis@haxor.hu>
# Copyright: (c) 2016, Redhat Inc # Copyright: (c) 2016, Redhat Inc
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import atexit import atexit
import cmd import cmd
import getpass import getpass
@ -15,7 +20,6 @@ import sys
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.executor.task_queue_manager import TaskQueueManager from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.module_utils._text import to_native, to_text from ansible.module_utils._text import to_native, to_text
@ -56,6 +60,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
- `exit`: exit ansible-console - `exit`: exit ansible-console
''' '''
name = 'ansible-console'
modules = [] modules = []
ARGUMENTS = {'host-pattern': 'A name of a group in the inventory, a shell-like glob ' ARGUMENTS = {'host-pattern': 'A name of a group in the inventory, a shell-like glob '
'selecting hosts in inventory or any combination of the two separated by commas.'} 'selecting hosts in inventory or any combination of the two separated by commas.'}
@ -406,7 +411,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
if module_name in self.modules: if module_name in self.modules:
in_path = module_loader.find_plugin(module_name) in_path = module_loader.find_plugin(module_name)
if in_path: if in_path:
oc, a, _, _ = plugin_docs.get_docstring(in_path, fragment_loader) oc, a, _dummy1, _dummy2 = plugin_docs.get_docstring(in_path, fragment_loader)
if oc: if oc:
display.display(oc['short_description']) display.display(oc['short_description'])
display.display('Parameters:') display.display('Parameters:')
@ -438,7 +443,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
def module_args(self, module_name): def module_args(self, module_name):
in_path = module_loader.find_plugin(module_name) in_path = module_loader.find_plugin(module_name)
oc, a, _, _ = plugin_docs.get_docstring(in_path, fragment_loader, is_module=True) oc, a, _dummy1, _dummy2 = plugin_docs.get_docstring(in_path, fragment_loader, is_module=True)
return list(oc['options'].keys()) return list(oc['options'].keys())
def run(self): def run(self):
@ -494,3 +499,11 @@ class ConsoleCLI(CLI, cmd.Cmd):
atexit.register(readline.write_history_file, histfile) atexit.register(readline.write_history_file, histfile)
self.set_prompt() self.set_prompt()
self.cmdloop() self.cmdloop()
def main(args=None):
ConsoleCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,10 +1,15 @@
#!/usr/bin/env python
# Copyright: (c) 2014, James Tanner <tanner.jc@gmail.com> # Copyright: (c) 2014, James Tanner <tanner.jc@gmail.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import datetime import datetime
import json import json
import pkgutil import pkgutil
@ -19,7 +24,6 @@ import ansible.plugins.loader as plugin_loader
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.collections.list import list_collection_dirs from ansible.collections.list import list_collection_dirs
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
@ -335,6 +339,8 @@ class DocCLI(CLI, RoleMixin):
provides a printout of their DOCUMENTATION strings, provides a printout of their DOCUMENTATION strings,
and it can create a short "snippet" which can be pasted into a playbook. ''' and it can create a short "snippet" which can be pasted into a playbook. '''
name = 'ansible-doc'
# default ignore list for detailed views # default ignore list for detailed views
IGNORE = ('module', 'docuri', 'version_added', 'short_description', 'now_date', 'plainexamples', 'returndocs', 'collection') IGNORE = ('module', 'docuri', 'version_added', 'short_description', 'now_date', 'plainexamples', 'returndocs', 'collection')
@ -1383,3 +1389,11 @@ def _do_lookup_snippet(doc):
text.append(snippet) text.append(snippet)
return text return text
def main(args=None):
DocCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,10 +1,15 @@
#!/usr/bin/env python
# Copyright: (c) 2013, James Cammarata <jcammarata@ansible.com> # Copyright: (c) 2013, James Cammarata <jcammarata@ansible.com>
# Copyright: (c) 2018-2021, Ansible Project # Copyright: (c) 2018-2021, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import json import json
import os.path import os.path
import re import re
@ -17,7 +22,6 @@ from yaml.error import YAMLError
import ansible.constants as C import ansible.constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.galaxy import Galaxy, get_collections_galaxy_meta_info from ansible.galaxy import Galaxy, get_collections_galaxy_meta_info
@ -134,6 +138,8 @@ def _get_collection_widths(collections):
class GalaxyCLI(CLI): class GalaxyCLI(CLI):
'''command to manage Ansible roles in shared repositories, the default of which is Ansible Galaxy *https://galaxy.ansible.com*.''' '''command to manage Ansible roles in shared repositories, the default of which is Ansible Galaxy *https://galaxy.ansible.com*.'''
name = 'ansible-galaxy'
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url") SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url")
def __init__(self, args): def __init__(self, args):
@ -1679,3 +1685,11 @@ class GalaxyCLI(CLI):
display.display(resp['status']) display.display(resp['status'])
return True return True
def main(args=None):
GalaxyCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,10 +1,15 @@
#!/usr/bin/env python
# Copyright: (c) 2017, Brian Coca <bcoca@ansible.com> # Copyright: (c) 2017, Brian Coca <bcoca@ansible.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import sys import sys
import argparse import argparse
@ -12,7 +17,6 @@ from operator import attrgetter
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.module_utils._text import to_bytes, to_native, to_text
@ -46,6 +50,8 @@ INTERNAL_VARS = frozenset(['ansible_diff_mode',
class InventoryCLI(CLI): class InventoryCLI(CLI):
''' used to display or dump the configured inventory as Ansible sees it ''' ''' used to display or dump the configured inventory as Ansible sees it '''
name = 'ansible-inventory'
ARGUMENTS = {'host': 'The name of a host to match in the inventory, relevant when using --list', ARGUMENTS = {'host': 'The name of a host to match in the inventory, relevant when using --list',
'group': 'The name of a group in the inventory, relevant when using --graph', } 'group': 'The name of a group in the inventory, relevant when using --graph', }
@ -402,3 +408,11 @@ class InventoryCLI(CLI):
results = format_group(top) results = format_group(top)
return results return results
def main(args=None):
InventoryCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,16 +1,20 @@
#!/usr/bin/env python
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import os import os
import stat import stat
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.executor.playbook_executor import PlaybookExecutor from ansible.executor.playbook_executor import PlaybookExecutor
@ -29,6 +33,8 @@ class PlaybookCLI(CLI):
''' the tool to run *Ansible playbooks*, which are a configuration and multinode deployment system. ''' the tool to run *Ansible playbooks*, which are a configuration and multinode deployment system.
See the project home page (https://docs.ansible.com) for more information. ''' See the project home page (https://docs.ansible.com) for more information. '''
name = 'ansible-playbook'
def init_parser(self): def init_parser(self):
# create parser for CLI options # create parser for CLI options
@ -215,3 +221,11 @@ class PlaybookCLI(CLI):
for host in inventory.list_hosts(): for host in inventory.list_hosts():
hostname = host.get_name() hostname = host.get_name()
variable_manager.clear_facts(hostname) variable_manager.clear_facts(hostname)
def main(args=None):
PlaybookCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,10 +1,15 @@
#!/usr/bin/env python
# Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import datetime import datetime
import os import os
import platform import platform
@ -16,7 +21,6 @@ import time
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleOptionsError from ansible.errors import AnsibleOptionsError
from ansible.module_utils._text import to_native, to_text from ansible.module_utils._text import to_native, to_text
@ -40,6 +44,8 @@ class PullCLI(CLI):
excellent way to gather and analyze remote logs from ansible-pull. excellent way to gather and analyze remote logs from ansible-pull.
''' '''
name = 'ansible-pull'
DEFAULT_REPO_TYPE = 'git' DEFAULT_REPO_TYPE = 'git'
DEFAULT_PLAYBOOK = 'local.yml' DEFAULT_PLAYBOOK = 'local.yml'
REPO_CHOICES = ('git', 'subversion', 'hg', 'bzr') REPO_CHOICES = ('git', 'subversion', 'hg', 'bzr')
@ -341,3 +347,11 @@ class PullCLI(CLI):
if playbook is None: if playbook is None:
display.warning("\n".join(errors)) display.warning("\n".join(errors))
return playbook return playbook
def main(args=None):
PullCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -1,170 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import errno
import os
import shutil
import sys
import traceback
# Used for determining if the system is running a new enough python version
# and should only restrict on our documented minimum versions
_PY38_MIN = sys.version_info[:2] >= (3, 8)
if not _PY38_MIN:
raise SystemExit(
'ERROR: Ansible requires Python 3.8 or newer on the controller. '
'Current version: %s' % ''.join(sys.version.splitlines())
)
# These lines appear after the PY38 check, to ensure the "friendly" error happens before
# any invalid syntax appears in other files that may get imported
from ansible import context
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
from ansible.module_utils._text import to_text
from pathlib import Path
class LastResort(object):
# OUTPUT OF LAST RESORT
def display(self, msg, log_only=None):
print(msg, file=sys.stderr)
def error(self, msg, wrap_text=None):
print(msg, file=sys.stderr)
if __name__ == '__main__':
display = LastResort()
try: # bad ANSIBLE_CONFIG or config options can force ugly stacktrace
import ansible.constants as C
from ansible.utils.display import Display, initialize_locale
except AnsibleOptionsError as e:
display.error(to_text(e), wrap_text=False)
sys.exit(5)
initialize_locale()
cli = None
me = Path(sys.argv[0]).name
try:
display = Display()
display.debug("starting run")
sub = None
target = me.split('-')
if target[-1][0].isdigit():
# Remove any version or python version info as downstreams
# sometimes add that
target = target[:-1]
if len(target) > 1:
sub = target[1]
myclass = "%sCLI" % sub.capitalize()
elif target[0] == 'ansible':
sub = 'adhoc'
myclass = 'AdHocCLI'
else:
raise AnsibleError("Unknown Ansible alias: %s" % me)
try:
mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
except ImportError as e:
# ImportError members have changed in py3
if 'msg' in dir(e):
msg = e.msg
else:
msg = e.message
if msg.endswith(' %s' % sub):
raise AnsibleError("Ansible sub-program not implemented: %s" % me)
else:
raise
ansible_dir = Path("~/.ansible").expanduser()
try:
ansible_dir.mkdir(mode=0o700)
except OSError as exc:
if exc.errno != errno.EEXIST:
display.warning(
"Failed to create the directory '%s': %s" % (ansible_dir, to_text(exc, errors='surrogate_or_replace'))
)
else:
display.debug("Created the '%s' directory" % ansible_dir)
try:
args = [to_text(a, errors='surrogate_or_strict') for a in sys.argv]
except UnicodeError:
display.error('Command line args are not in utf-8, unable to continue. Ansible currently only understands utf-8')
display.display(u"The full traceback was:\n\n%s" % to_text(traceback.format_exc()))
exit_code = 6
else:
cli = mycli(args)
exit_code = cli.run()
except AnsibleOptionsError as e:
cli.parser.print_help()
display.error(to_text(e), wrap_text=False)
exit_code = 5
except AnsibleParserError as e:
display.error(to_text(e), wrap_text=False)
exit_code = 4
# TQM takes care of these, but leaving comment to reserve the exit codes
# except AnsibleHostUnreachable as e:
# display.error(str(e))
# exit_code = 3
# except AnsibleHostFailed as e:
# display.error(str(e))
# exit_code = 2
except AnsibleError as e:
display.error(to_text(e), wrap_text=False)
exit_code = 1
except KeyboardInterrupt:
display.error("User interrupted execution")
exit_code = 99
except Exception as e:
if C.DEFAULT_DEBUG:
# Show raw stacktraces in debug mode, It also allow pdb to
# enter post mortem mode.
raise
have_cli_options = bool(context.CLIARGS)
display.error("Unexpected Exception, this is probably a bug: %s" % to_text(e), wrap_text=False)
if not have_cli_options or have_cli_options and context.CLIARGS['verbosity'] > 2:
log_only = False
if hasattr(e, 'orig_exc'):
display.vvv('\nexception type: %s' % to_text(type(e.orig_exc)))
why = to_text(e.orig_exc)
if to_text(e) != why:
display.vvv('\noriginal msg: %s' % why)
else:
display.display("to see the full traceback, use -vvv")
log_only = True
display.display(u"the full traceback was:\n\n%s" % to_text(traceback.format_exc()), log_only=log_only)
exit_code = 250
sys.exit(exit_code)

@ -6,6 +6,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import argparse
import fcntl import fcntl
import hashlib import hashlib
import os import os
@ -20,6 +21,7 @@ import json
from contextlib import contextmanager from contextlib import contextmanager
from ansible import constants as C from ansible import constants as C
from ansible.cli.arguments.option_helpers import AnsibleVersion
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.six import PY3 from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves import cPickle, StringIO from ansible.module_utils.six.moves import cPickle, StringIO
@ -32,6 +34,8 @@ from ansible.utils.path import unfrackpath, makedirs_safe
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.jsonrpc import JsonRpcServer from ansible.utils.jsonrpc import JsonRpcServer
display = Display()
def read_stream(byte_stream): def read_stream(byte_stream):
size = int(byte_stream.readline().strip()) size = int(byte_stream.readline().strip())
@ -217,9 +221,15 @@ class ConnectionProcess(object):
display.display('shutdown complete', log_only=True) display.display('shutdown complete', log_only=True)
def main(): def main(args=None):
""" Called to initiate the connect to the remote device """ Called to initiate the connect to the remote device
""" """
parser = argparse.ArgumentParser(prog='ansible-connection', add_help=False)
parser.add_argument('--version', action=AnsibleVersion, nargs=0)
parser.add_argument('playbook_pid')
parser.add_argument('task_uuid')
args = parser.parse_args(args[1:] if args is not None else args)
rc = 0 rc = 0
result = {} result = {}
messages = list() messages = list()
@ -260,8 +270,8 @@ def main():
if rc == 0: if rc == 0:
ssh = connection_loader.get('ssh', class_only=True) ssh = connection_loader.get('ssh', class_only=True)
ansible_playbook_pid = sys.argv[1] ansible_playbook_pid = args.playbook_pid
task_uuid = sys.argv[2] task_uuid = args.task_uuid
cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user, play_context.connection, ansible_playbook_pid) cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user, play_context.connection, ansible_playbook_pid)
# create the persistent connection dir if need be and create the paths # create the persistent connection dir if need be and create the paths
# which we will be using later # which we will be using later
@ -345,5 +355,4 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
display = Display()
main() main()

@ -1,16 +1,20 @@
#!/usr/bin/env python
# (c) 2014, James Tanner <tanner.jc@gmail.com> # (c) 2014, James Tanner <tanner.jc@gmail.com>
# Copyright: (c) 2018, Ansible Project # Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# PYTHON_ARGCOMPLETE_OK
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import os import os
import sys import sys
from ansible import constants as C from ansible import constants as C
from ansible import context from ansible import context
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help from ansible.cli.arguments import option_helpers as opt_help
from ansible.errors import AnsibleOptionsError from ansible.errors import AnsibleOptionsError
from ansible.module_utils._text import to_text, to_bytes from ansible.module_utils._text import to_text, to_bytes
@ -32,6 +36,8 @@ class VaultCLI(CLI):
If you'd like to not expose what variables you are using, you can keep an individual task file entirely encrypted. If you'd like to not expose what variables you are using, you can keep an individual task file entirely encrypted.
''' '''
name = 'ansible-vault'
FROM_STDIN = "stdin" FROM_STDIN = "stdin"
FROM_ARGS = "the command line args" FROM_ARGS = "the command line args"
FROM_PROMPT = "the interactive prompt" FROM_PROMPT = "the interactive prompt"
@ -462,3 +468,11 @@ class VaultCLI(CLI):
self.new_encrypt_vault_id) self.new_encrypt_vault_id)
display.display("Rekey successful", stderr=True) display.display("Rekey successful", stderr=True)
def main(args=None):
VaultCLI.cli_executor(args)
if __name__ == '__main__':
main()

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools >= 39.2.0", "wheel"]
build-backend = "setuptools.build_meta"

@ -0,0 +1,60 @@
# Minimum target setuptools 39.2.0
[metadata]
name = ansible-core
version = attr: ansible.release.__version__
description = Radically simple IT automation
long_description = file: README.rst
author = Ansible, Inc.
author_email = info@ansible.com
url = https://ansible.com/
project_urls =
Bug Tracker=https://github.com/ansible/ansible/issues
CI: Azure Pipelines=https://dev.azure.com/ansible/ansible/
Code of Conduct=https://docs.ansible.com/ansible/latest/community/code_of_conduct.html
Documentation=https://docs.ansible.com/ansible-core/
Mailing lists=https://docs.ansible.com/ansible/latest/community/communication.html#mailing-list-information
Source Code=https://github.com/ansible/ansible
license = GPLv3+
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Intended Audience :: Developers
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Natural Language :: English
Operating System :: POSIX
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3 :: Only
Topic :: System :: Installation/Setup
Topic :: System :: Systems Administration
Topic :: Utilities
[options]
zip_safe = False
python_requires = >=3.8
include_package_data = True
# keep ansible-test as a verbatim script to work with editable installs, since it needs to do its
# own package redirection magic that's beyond the scope of the normal `ansible` path redirection
# done by setuptools `develop`
scripts =
bin/ansible-test
# setuptools 51.0.0
# [options.entry_points]
# console_scripts =
# ansible = ansible.cli.adhoc:main
# ansible-config = ansible.cli.config:main
# ansible-console = ansible.cli.console:main
# ansible-doc = ansible.cli.doc:main
# ansible-galaxy = ansible.cli.galaxy:main
# ansible-inventory = ansible.cli.inventory:main
# ansible-playbook = ansible.cli.playbook:main
# ansible-pull = ansible.cli.pull:main
# ansible-vault = ansible.cli.vault:main
# ansible-connection = ansible.cli.scripts.ansible_connection_cli_stub:main
# ansible-test = ansible_test._util.target.cli.ansible_test_cli_stub:main

@ -1,394 +1,31 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import json import pathlib
import os
import os.path
import re
import sys
import warnings
from collections import defaultdict from setuptools import find_packages, setup
try: here = pathlib.Path(__file__).parent.resolve()
from setuptools import setup, find_packages
from setuptools.command.build_py import build_py as BuildPy
from setuptools.command.install_lib import install_lib as InstallLib
from setuptools.command.install_scripts import install_scripts as InstallScripts
except ImportError:
print("Ansible now needs setuptools in order to build. Install it using"
" your package manager (usually python-setuptools) or via pip (pip"
" install setuptools).", file=sys.stderr)
sys.exit(1)
# `distutils` must be imported after `setuptools` or it will cause explosions install_requires = (here / 'requirements.txt').read_text(encoding='utf-8').splitlines()
# with `setuptools >=48.0.0, <49.1`.
# Refs:
# * https://github.com/ansible/ansible/issues/70456
# * https://github.com/pypa/setuptools/issues/2230
# * https://github.com/pypa/setuptools/commit/bd110264
from distutils.command.build_scripts import build_scripts as BuildScripts
from distutils.command.sdist import sdist as SDist
setup(
def find_package_info(*file_paths): install_requires=install_requires,
try:
with open(os.path.join(*file_paths), 'r') as f:
info_file = f.read()
except Exception:
raise RuntimeError("Unable to find package info.")
# The version line must have the form
# __version__ = 'ver'
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
info_file, re.M)
author_match = re.search(r"^__author__ = ['\"]([^'\"]*)['\"]",
info_file, re.M)
if version_match and author_match:
return version_match.group(1), author_match.group(1)
raise RuntimeError("Unable to find package info.")
def _validate_install_ansible_core():
"""Validate that we can install ansible-core. This checks if
ansible<=2.9 or ansible-base>=2.10 are installed.
"""
# Skip common commands we can ignore
# Do NOT add bdist_wheel here, we don't ship wheels
# and bdist_wheel is the only place we can prevent pip
# from installing, as pip creates a wheel, and installs the wheel
# and we have no influence over installation within a wheel
if set(('sdist', 'egg_info')).intersection(sys.argv):
return
if os.getenv('ANSIBLE_SKIP_CONFLICT_CHECK', '') not in ('', '0'):
return
# Save these for later restoring things to pre invocation
sys_modules = sys.modules.copy()
sys_modules_keys = set(sys_modules)
# Make sure `lib` isn't in `sys.path` that could confuse this
sys_path = sys.path[:]
abspath = os.path.abspath
sys.path[:] = [p for p in sys.path if abspath(p) != abspath('lib')]
try:
from ansible.release import __version__
except ImportError:
pass
else:
version_tuple = tuple(int(v) for v in __version__.split('.')[:2])
if version_tuple >= (2, 11):
return
elif version_tuple == (2, 10):
ansible_name = 'ansible-base'
else:
ansible_name = 'ansible'
stars = '*' * 76
raise RuntimeError(
'''
%s
Cannot install ansible-core with a pre-existing %s==%s
installation.
Installing ansible-core with ansible-2.9 or older, or ansible-base-2.10
currently installed with pip is known to cause problems. Please uninstall
%s and install the new version:
pip uninstall %s
pip install ansible-core
If you want to skip the conflict checks and manually resolve any issues
afterwards, set the ANSIBLE_SKIP_CONFLICT_CHECK environment variable:
ANSIBLE_SKIP_CONFLICT_CHECK=1 pip install ansible-core
%s
''' % (stars, ansible_name, __version__, ansible_name, ansible_name, stars))
finally:
sys.path[:] = sys_path
for key in sys_modules_keys.symmetric_difference(sys.modules):
sys.modules.pop(key, None)
sys.modules.update(sys_modules)
_validate_install_ansible_core()
SYMLINK_CACHE = 'SYMLINK_CACHE.json'
def _find_symlinks(topdir, extension=''):
"""Find symlinks that should be maintained
Maintained symlinks exist in the bin dir or are modules which have
aliases. Our heuristic is that they are a link in a certain path which
point to a file in the same directory.
.. warn::
We want the symlinks in :file:`bin/` that link into :file:`lib/ansible/*` (currently,
:command:`ansible`, :command:`ansible-test`, and :command:`ansible-connection`) to become
real files on install. Updates to the heuristic here *must not* add them to the symlink
cache.
"""
symlinks = defaultdict(list)
for base_path, dirs, files in os.walk(topdir):
for filename in files:
filepath = os.path.join(base_path, filename)
if os.path.islink(filepath) and filename.endswith(extension):
target = os.readlink(filepath)
if target.startswith('/'):
# We do not support absolute symlinks at all
continue
if os.path.dirname(target) == '':
link = filepath[len(topdir):]
if link.startswith('/'):
link = link[1:]
symlinks[os.path.basename(target)].append(link)
else:
# Count how many directory levels from the topdir we are
levels_deep = os.path.dirname(filepath).count('/')
# Count the number of directory levels higher we walk up the tree in target
target_depth = 0
for path_component in target.split('/'):
if path_component == '..':
target_depth += 1
# If we walk past the topdir, then don't store
if target_depth >= levels_deep:
break
else:
target_depth -= 1
else:
# If we managed to stay within the tree, store the symlink
link = filepath[len(topdir):]
if link.startswith('/'):
link = link[1:]
symlinks[target].append(link)
return symlinks
def _cache_symlinks(symlink_data):
with open(SYMLINK_CACHE, 'w') as f:
json.dump(symlink_data, f)
def _maintain_symlinks(symlink_type, base_path):
"""Switch a real file into a symlink"""
try:
# Try the cache first because going from git checkout to sdist is the
# only time we know that we're going to cache correctly
with open(SYMLINK_CACHE, 'r') as f:
symlink_data = json.load(f)
except (IOError, OSError) as e:
# IOError on py2, OSError on py3. Both have errno
if e.errno == 2:
# SYMLINKS_CACHE doesn't exist. Fallback to trying to create the
# cache now. Will work if we're running directly from a git
# checkout or from an sdist created earlier.
library_symlinks = _find_symlinks('lib', '.py')
library_symlinks.update(_find_symlinks('test/lib'))
symlink_data = {'script': _find_symlinks('bin'),
'library': library_symlinks,
}
# Sanity check that something we know should be a symlink was
# found. We'll take that to mean that the current directory
# structure properly reflects symlinks in the git repo
if 'ansible-playbook' in symlink_data['script']['ansible']:
_cache_symlinks(symlink_data)
else:
raise RuntimeError(
"Pregenerated symlink list was not present and expected "
"symlinks in ./bin were missing or broken. "
"Perhaps this isn't a git checkout?"
)
else:
raise
symlinks = symlink_data[symlink_type]
for source in symlinks:
for dest in symlinks[source]:
dest_path = os.path.join(base_path, dest)
if not os.path.islink(dest_path):
try:
os.unlink(dest_path)
except OSError as e:
if e.errno == 2:
# File does not exist which is all we wanted
pass
os.symlink(source, dest_path)
class BuildPyCommand(BuildPy):
def run(self):
BuildPy.run(self)
_maintain_symlinks('library', self.build_lib)
class BuildScriptsCommand(BuildScripts):
def run(self):
BuildScripts.run(self)
_maintain_symlinks('script', self.build_dir)
class InstallLibCommand(InstallLib):
def run(self):
InstallLib.run(self)
_maintain_symlinks('library', self.install_dir)
class InstallScriptsCommand(InstallScripts):
def run(self):
InstallScripts.run(self)
_maintain_symlinks('script', self.install_dir)
class SDistCommand(SDist):
def run(self):
# have to generate the cache of symlinks for release as sdist is the
# only command that has access to symlinks from the git repo
library_symlinks = _find_symlinks('lib', '.py')
library_symlinks.update(_find_symlinks('test/lib'))
symlinks = {'script': _find_symlinks('bin'),
'library': library_symlinks,
}
_cache_symlinks(symlinks)
SDist.run(self)
# Print warnings at the end because no one will see warnings before all the normal status
# output
if os.environ.get('_ANSIBLE_SDIST_FROM_MAKEFILE', False) != '1':
warnings.warn('When setup.py sdist is run from outside of the Makefile,'
' the generated tarball may be incomplete. Use `make snapshot`'
' to create a tarball from an arbitrary checkout or use'
' `cd packaging/release && make release version=[..]` for official builds.',
RuntimeWarning)
def read_file(file_name):
"""Read file and return its contents."""
with open(file_name, 'r') as f:
return f.read()
def read_requirements(file_name):
"""Read requirements file as a list."""
reqs = read_file(file_name).splitlines()
if not reqs:
raise RuntimeError(
"Unable to read requirements from the %s file"
"That indicates this copy of the source code is incomplete."
% file_name
)
return reqs
def get_dynamic_setup_params():
"""Add dynamically calculated setup params to static ones."""
return {
# Retrieve the long description from the README
'long_description': read_file('README.rst'),
'install_requires': read_requirements('requirements.txt'),
}
here = os.path.abspath(os.path.dirname(__file__))
__version__, __author__ = find_package_info(here, 'lib', 'ansible', 'release.py')
static_setup_params = dict(
# Use the distutils SDist so that symlinks are not expanded
# Use a custom Build for the same reason
cmdclass={
'build_py': BuildPyCommand,
'build_scripts': BuildScriptsCommand,
'install_lib': InstallLibCommand,
'install_scripts': InstallScriptsCommand,
'sdist': SDistCommand,
},
name='ansible-core',
version=__version__,
description='Radically simple IT automation',
author=__author__,
author_email='info@ansible.com',
url='https://ansible.com/',
project_urls={
'Bug Tracker': 'https://github.com/ansible/ansible/issues',
'CI: Azure Pipelines': 'https://dev.azure.com/ansible/ansible/',
'Code of Conduct': 'https://docs.ansible.com/ansible/latest/community/code_of_conduct.html',
'Documentation': 'https://docs.ansible.com/ansible/',
'Mailing lists': 'https://docs.ansible.com/ansible/latest/community/communication.html#mailing-list-information',
'Source Code': 'https://github.com/ansible/ansible',
},
license='GPLv3+',
# Ansible will also make use of a system copy of python-six and
# python-selectors2 if installed but use a Bundled copy if it's not.
python_requires='>=3.8',
package_dir={'': 'lib', package_dir={'': 'lib',
'ansible_test': 'test/lib/ansible_test'}, 'ansible_test': 'test/lib/ansible_test'},
packages=find_packages('lib') + find_packages('test/lib'), packages=find_packages('lib') + find_packages('test/lib'),
include_package_data=True, entry_points={
classifiers=[ 'console_scripts': [
'Development Status :: 5 - Production/Stable', 'ansible=ansible.cli.adhoc:main',
'Environment :: Console', 'ansible-config=ansible.cli.config:main',
'Intended Audience :: Developers', 'ansible-console=ansible.cli.console:main',
'Intended Audience :: Information Technology', 'ansible-doc=ansible.cli.doc:main',
'Intended Audience :: System Administrators', 'ansible-galaxy=ansible.cli.galaxy:main',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'ansible-inventory=ansible.cli.inventory:main',
'Natural Language :: English', 'ansible-playbook=ansible.cli.playbook:main',
'Operating System :: POSIX', 'ansible-pull=ansible.cli.pull:main',
'Programming Language :: Python :: 3', 'ansible-vault=ansible.cli.vault:main',
'Programming Language :: Python :: 3.8', 'ansible-connection=ansible.cli.scripts.ansible_connection_cli_stub:main',
'Programming Language :: Python :: 3.9', ],
'Programming Language :: Python :: 3.10', },
'Topic :: System :: Installation/Setup',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
],
scripts=[
'bin/ansible',
'bin/ansible-playbook',
'bin/ansible-pull',
'bin/ansible-doc',
'bin/ansible-galaxy',
'bin/ansible-console',
'bin/ansible-connection',
'bin/ansible-vault',
'bin/ansible-config',
'bin/ansible-inventory',
'bin/ansible-test',
],
data_files=[],
# Installing as zip files would break due to references to __file__
zip_safe=False
) )
def main():
"""Invoke installation process using setuptools."""
setup_params = dict(static_setup_params, **get_dynamic_setup_params())
ignore_warning_regex = (
r"Unknown distribution option: '(project_urls|python_requires)'"
)
warnings.filterwarnings(
'ignore',
message=ignore_warning_regex,
category=UserWarning,
module='distutils.dist',
)
setup(**setup_params)
warnings.resetwarnings()
if __name__ == '__main__':
main()

@ -1 +1,2 @@
shippable/posix/group3 shippable/posix/group3
needs/target/connection

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -eux
group=local
cd ../connection
INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \
-e target_hosts="${group}" \
-e action_prefix= \
-e local_tmp=/tmp/ansible-local \
-e remote_tmp=/tmp/ansible-remote \
"$@"

@ -1,4 +1,5 @@
needs/ssh needs/ssh
shippable/posix/group3 shippable/posix/group3
needs/target/setup_paramiko needs/target/setup_paramiko
needs/target/connection
destructive # potentially installs/uninstalls OS packages via setup_paramiko destructive # potentially installs/uninstalls OS packages via setup_paramiko

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -eux
group=paramiko_ssh
cd ../connection
INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \
-e target_hosts="${group}" \
-e action_prefix= \
-e local_tmp=/tmp/ansible-local \
-e remote_tmp=/tmp/ansible-remote \
"$@"

@ -1,2 +0,0 @@
needs/target/connection
hidden

@ -1,18 +0,0 @@
#!/usr/bin/env bash
set -eux
# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir.
# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix.
group=$(python -c \
"from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('connection_', ''))")
cd ../connection
INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \
-e target_hosts="${group}" \
-e action_prefix= \
-e local_tmp=/tmp/ansible-local \
-e remote_tmp=/tmp/ansible-remote \
"$@"

@ -1,2 +1,3 @@
needs/ssh needs/ssh
shippable/posix/group1 shippable/posix/group1
needs/target/connection

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -eux
group=ssh
cd ../connection
INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \
-e target_hosts="${group}" \
-e action_prefix= \
-e local_tmp=/tmp/ansible-local \
-e remote_tmp=/tmp/ansible-remote \
"$@"

@ -16,6 +16,7 @@
invalid2: ../invalid invalid2: ../invalid
out_of_tree_circle: /tmp/ansible-test-link-dir/out_of_tree_circle out_of_tree_circle: /tmp/ansible-test-link-dir/out_of_tree_circle
subdir3: ../subdir2/subdir3 subdir3: ../subdir2/subdir3
bar.txt: ../bar.txt
- file: path={{local_temp_dir}} state=directory - file: path={{local_temp_dir}} state=directory
name: ensure temp dir exists name: ensure temp dir exists

@ -0,0 +1,2 @@
context/controller
shippable/posix/group5

@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -eu
source virtualenv.sh
set +x
unset PYTHONPATH
base_dir="$(dirname "$(dirname "$(dirname "$(dirname "${OUTPUT_DIR}")")")")"
bin_dir="$(dirname "$(command -v pip)")"
# deps are already installed, using --no-deps to avoid re-installing them
pip install "${base_dir}" --disable-pip-version-check --no-deps
# --use-feature=in-tree-build not available on all platforms
for bin in "${bin_dir}/ansible"*; do
name="$(basename "${bin}")"
entry_point="${name//ansible-/}"
entry_point="${entry_point//ansible/adhoc}"
echo "=== ${name} (${entry_point})=${bin} ==="
if [ "${name}" == "ansible-test" ]; then
"${bin}" --help | tee /dev/stderr | grep -Eo "^usage:\ ansible-test\ .*"
python -m ansible "${entry_point}" --help | tee /dev/stderr | grep -Eo "^usage:\ ansible-test\ .*"
else
"${bin}" --version | tee /dev/stderr | grep -Eo "(^${name}\ \[core\ .*|executable location = ${bin}$)"
python -m ansible "${entry_point}" --version | tee /dev/stderr | grep -Eo "(^${name}\ \[core\ .*|executable location = ${bin}$)"
fi
done

@ -2,6 +2,15 @@
set -ux set -ux
cleanup() {
unlink normal/library/_symlink.py
}
pushd normal/library
ln -s _underscore.py _symlink.py
popd
trap 'cleanup' EXIT
# check normal execution # check normal execution
for myplay in normal/*.yml for myplay in normal/*.yml

@ -47,11 +47,11 @@ from .provisioning import (
) )
def main(): def main(cli_args=None): # type: (t.Optional[t.List[str]]) -> None
"""Main program function.""" """Main program function."""
try: try:
os.chdir(data_context().content.root) os.chdir(data_context().content.root)
args = parse_args() args = parse_args(cli_args)
config = args.config(args) # type: CommonConfig config = args.config(args) # type: CommonConfig
display.verbosity = config.verbosity display.verbosity = config.verbosity
display.truncate = config.truncate display.truncate = config.truncate

@ -20,7 +20,7 @@ from .compat import (
) )
def parse_args(): # type: () -> argparse.Namespace def parse_args(argv=None): # type: (t.Optional[t.List[str]]) -> argparse.Namespace
"""Parse command line arguments.""" """Parse command line arguments."""
completer = CompositeActionCompletionFinder() completer = CompositeActionCompletionFinder()
@ -29,7 +29,7 @@ def parse_args(): # type: () -> argparse.Namespace
else: else:
epilog = 'Install the "argcomplete" python package to enable tab completion.' epilog = 'Install the "argcomplete" python package to enable tab completion.'
parser = argparse.ArgumentParser(epilog=epilog) parser = argparse.ArgumentParser(prog='ansible-test', epilog=epilog)
do_commands(parser, completer) do_commands(parser, completer)
@ -38,7 +38,10 @@ def parse_args(): # type: () -> argparse.Namespace
always_complete_options=False, always_complete_options=False,
) )
argv = sys.argv[1:] if argv is None:
argv = sys.argv[1:]
else:
argv = argv[1:]
args = parser.parse_args(argv) args = parser.parse_args(argv)
if args.explain and not args.verbosity: if args.explain and not args.verbosity:

@ -35,15 +35,15 @@ SECCOMP_CHOICES = [
# It is necessary for payload creation to reconstruct the bin directory when running ansible-test from an installed version of ansible. # It is necessary for payload creation to reconstruct the bin directory when running ansible-test from an installed version of ansible.
# It is also used to construct the injector directory at runtime. # It is also used to construct the injector directory at runtime.
ANSIBLE_BIN_SYMLINK_MAP = { ANSIBLE_BIN_SYMLINK_MAP = {
'ansible': '../lib/ansible/cli/scripts/ansible_cli_stub.py', 'ansible': '../lib/ansible/cli/adhoc.py',
'ansible-config': 'ansible', 'ansible-config': '../lib/ansible/cli/config.py',
'ansible-connection': '../lib/ansible/cli/scripts/ansible_connection_cli_stub.py', 'ansible-connection': '../lib/ansible/cli/scripts/ansible_connection_cli_stub.py',
'ansible-console': 'ansible', 'ansible-console': '../lib/ansible/cli/console.py',
'ansible-doc': 'ansible', 'ansible-doc': '../lib/ansible/cli/doc.py',
'ansible-galaxy': 'ansible', 'ansible-galaxy': '../lib/ansible/cli/galaxy.py',
'ansible-inventory': 'ansible', 'ansible-inventory': '../lib/ansible/cli/inventory.py',
'ansible-playbook': 'ansible', 'ansible-playbook': '../lib/ansible/cli/playbook.py',
'ansible-pull': 'ansible', 'ansible-pull': '../lib/ansible/cli/pull.py',
'ansible-test': '../test/lib/ansible_test/_util/target/cli/ansible_test_cli_stub.py', 'ansible-test': '../test/lib/ansible_test/_util/target/cli/ansible_test_cli_stub.py',
'ansible-vault': 'ansible', 'ansible-vault': '../lib/ansible/cli/vault.py',
} }

@ -70,6 +70,10 @@ def main():
is_module = True is_module = True
elif path == 'test/lib/ansible_test/_util/target/cli/ansible_test_cli_stub.py': elif path == 'test/lib/ansible_test/_util/target/cli/ansible_test_cli_stub.py':
pass # ansible-test entry point must be executable and have a shebang pass # ansible-test entry point must be executable and have a shebang
elif re.search(r'^lib/ansible/cli/[^/]+\.py', path):
pass # cli entry points must be executable and have a shebang
elif path.startswith('examples/'):
continue # examples trigger some false positives due to location
elif path.startswith('lib/') or path.startswith('test/lib/'): elif path.startswith('lib/') or path.startswith('test/lib/'):
if executable: if executable:
print('%s:%d:%d: should not be executable' % (path, 0, 0)) print('%s:%d:%d: should not be executable' % (path, 0, 0))

@ -11,7 +11,7 @@ import os
import sys import sys
def main(): def main(args=None): # type: (t.Optional[t.List[str]]) -> None
"""Main program entry point.""" """Main program entry point."""
ansible_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ansible_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
source_root = os.path.join(ansible_root, 'test', 'lib') source_root = os.path.join(ansible_root, 'test', 'lib')
@ -30,7 +30,7 @@ def main():
# noinspection PyProtectedMember # noinspection PyProtectedMember
from ansible_test._internal import main as cli_main from ansible_test._internal import main as cli_main
cli_main() cli_main(args)
def version_to_str(version): def version_to_str(version):

@ -29,6 +29,7 @@ def assemble_files_to_ship(complete_file_list):
'hacking/tests/*', 'hacking/tests/*',
'hacking/ticket_stubs/*', 'hacking/ticket_stubs/*',
'test/sanity/code-smell/botmeta.*', 'test/sanity/code-smell/botmeta.*',
'test/sanity/code-smell/release-names.*',
'test/utils/*', 'test/utils/*',
'test/utils/*/*', 'test/utils/*/*',
'test/utils/*/*/*', 'test/utils/*/*/*',
@ -53,8 +54,9 @@ def assemble_files_to_ship(complete_file_list):
'hacking/report.py', 'hacking/report.py',
'hacking/return_skeleton_generator.py', 'hacking/return_skeleton_generator.py',
'hacking/test-module', 'hacking/test-module',
'hacking/test-module.py',
'test/support/README.md', 'test/support/README.md',
'test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py',
'test/lib/ansible_test/_internal/commands/sanity/integration_aliases.py',
'.cherry_picker.toml', '.cherry_picker.toml',
'.mailmap', '.mailmap',
# Generated as part of a build step # Generated as part of a build step
@ -74,22 +76,27 @@ def assemble_files_to_ship(complete_file_list):
'hacking/env-setup', 'hacking/env-setup',
'hacking/env-setup.fish', 'hacking/env-setup.fish',
'MANIFEST', 'MANIFEST',
'setup.cfg',
# docs for test files not included in sdist
'docs/docsite/rst/dev_guide/testing/sanity/bin-symlinks.rst',
'docs/docsite/rst/dev_guide/testing/sanity/botmeta.rst',
'docs/docsite/rst/dev_guide/testing/sanity/integration-aliases.rst',
'docs/docsite/rst/dev_guide/testing/sanity/release-names.rst',
)) ))
# These files are generated and then intentionally added to the sdist # These files are generated and then intentionally added to the sdist
# Manpages # Manpages
ignore_script = ('ansible-connection', 'ansible-test')
manpages = ['docs/man/man1/ansible.1'] manpages = ['docs/man/man1/ansible.1']
for dirname, dummy, files in os.walk('bin'): for dirname, dummy, files in os.walk('bin'):
for filename in files: for filename in files:
path = os.path.join(dirname, filename) if filename in ignore_script:
if os.path.islink(path): continue
if os.readlink(path) == 'ansible': manpages.append('docs/man/man1/%s.1' % filename)
manpages.append('docs/man/man1/%s.1' % filename)
# Misc # Misc
misc_generated_files = [ misc_generated_files = [
'SYMLINK_CACHE.json',
'PKG-INFO', 'PKG-INFO',
] ]
@ -110,7 +117,11 @@ def assemble_files_to_install(complete_file_list):
""" """
This looks for all of the files which should show up in an installation of ansible This looks for all of the files which should show up in an installation of ansible
""" """
ignore_patterns = tuple() ignore_patterns = (
# Tests excluded from sdist
'test/lib/ansible_test/_internal/commands/sanity/bin_symlinks.py',
'test/lib/ansible_test/_internal/commands/sanity/integration_aliases.py',
)
pkg_data_files = [] pkg_data_files = []
for path in complete_file_list: for path in complete_file_list:
@ -256,12 +267,19 @@ def check_sdist_files_are_wanted(sdist_dir, to_ship_files):
dirname = '' dirname = ''
for filename in files: for filename in files:
if filename == 'setup.cfg':
continue
path = os.path.join(dirname, filename) path = os.path.join(dirname, filename)
if path not in to_ship_files: if path not in to_ship_files:
if fnmatch.fnmatch(path, 'changelogs/CHANGELOG-v2.[0-9]*.rst'): if fnmatch.fnmatch(path, 'changelogs/CHANGELOG-v2.[0-9]*.rst'):
# changelog files are expected # changelog files are expected
continue continue
if fnmatch.fnmatch(path, 'lib/ansible_core.egg-info/*'):
continue
# FIXME: ansible-test doesn't pass the paths of symlinks to us so we aren't # FIXME: ansible-test doesn't pass the paths of symlinks to us so we aren't
# checking those # checking those
if not os.path.islink(os.path.join(sdist_dir, path)): if not os.path.islink(os.path.join(sdist_dir, path)):
@ -282,7 +300,7 @@ def check_installed_contains_expected(install_dir, to_install_files):
EGG_RE = re.compile('ansible[^/]+\\.egg-info/(PKG-INFO|SOURCES.txt|' EGG_RE = re.compile('ansible[^/]+\\.egg-info/(PKG-INFO|SOURCES.txt|'
'dependency_links.txt|not-zip-safe|requires.txt|top_level.txt)$') 'dependency_links.txt|not-zip-safe|requires.txt|top_level.txt|entry_points.txt)$')
def check_installed_files_are_wanted(install_dir, to_install_files): def check_installed_files_are_wanted(install_dir, to_install_files):

@ -1,14 +1,8 @@
docs/docsite/rst/dev_guide/testing/sanity/no-smart-quotes.rst no-smart-quotes docs/docsite/rst/dev_guide/testing/sanity/no-smart-quotes.rst no-smart-quotes
docs/docsite/rst/locales/ja/LC_MESSAGES/dev_guide.po no-smart-quotes # Translation of the no-smart-quotes rule docs/docsite/rst/locales/ja/LC_MESSAGES/dev_guide.po no-smart-quotes # Translation of the no-smart-quotes rule
examples/play.yml shebang
examples/scripts/ConfigureRemotingForAnsible.ps1 pslint:PSCustomUseLiteralPath examples/scripts/ConfigureRemotingForAnsible.ps1 pslint:PSCustomUseLiteralPath
examples/scripts/my_test_facts.py shebang # example module but not in a normal module location
examples/scripts/my_test_info.py shebang # example module but not in a normal module location
examples/scripts/my_test.py shebang # example module but not in a normal module location
examples/scripts/upgrade_to_ps3.ps1 pslint:PSCustomUseLiteralPath examples/scripts/upgrade_to_ps3.ps1 pslint:PSCustomUseLiteralPath
examples/scripts/upgrade_to_ps3.ps1 pslint:PSUseApprovedVerbs examples/scripts/upgrade_to_ps3.ps1 pslint:PSUseApprovedVerbs
lib/ansible/cli/console.py pylint:disallowed-name
lib/ansible/cli/scripts/ansible_cli_stub.py shebang
lib/ansible/cli/scripts/ansible_connection_cli_stub.py shebang lib/ansible/cli/scripts/ansible_connection_cli_stub.py shebang
lib/ansible/config/base.yml no-unwanted-files lib/ansible/config/base.yml no-unwanted-files
lib/ansible/executor/playbook_executor.py pylint:disallowed-name lib/ansible/executor/playbook_executor.py pylint:disallowed-name

Loading…
Cancel
Save