diff --git a/.ci/README.md b/.ci/README.md index 67a3805b..9248ac58 100644 --- a/.ci/README.md +++ b/.ci/README.md @@ -28,7 +28,6 @@ for doing `setup.py install` while pulling a Docker container, for example. ### Environment Variables -* `TARGET_COUNT`: number of targets for `debops_` run. Defaults to 2. * `DISTRO`: the `mitogen_` tests need a target Docker container distro. This name comes from the Docker Hub `mitogen` user, i.e. `mitogen/$DISTRO-test` * `DISTROS`: the `ansible_` tests can run against multiple targets diff --git a/.ci/ansible_install.py b/.ci/ansible_install.py deleted file mode 100755 index 900303f6..00000000 --- a/.ci/ansible_install.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import ci_lib - -batches = [ - [ - 'aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws', - ] -] - -ci_lib.run_batches(batches) diff --git a/.ci/ansible_tests.py b/.ci/ansible_tests.py index 0dd978c4..3ec48dfd 100755 --- a/.ci/ansible_tests.py +++ b/.ci/ansible_tests.py @@ -6,11 +6,13 @@ import glob import os import signal import sys -import textwrap + +import jinja2 import ci_lib +TEMPLATES_DIR = os.path.join(ci_lib.GIT_ROOT, 'tests/ansible/templates') TESTS_DIR = os.path.join(ci_lib.GIT_ROOT, 'tests/ansible') HOSTS_DIR = os.path.join(ci_lib.TMP, 'hosts') @@ -33,7 +35,7 @@ ci_lib.check_stray_processes(interesting) with ci_lib.Fold('docker_setup'): - containers = ci_lib.make_containers() + containers = ci_lib.container_specs(ci_lib.DISTROS) ci_lib.start_containers(containers) @@ -52,37 +54,19 @@ with ci_lib.Fold('job_setup'): distros[container['distro']].append(container['name']) families[container['family']].append(container['name']) + jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(searchpath=TEMPLATES_DIR), + lstrip_blocks=True, # Remove spaces and tabs from before a block + trim_blocks=True, # Remove first newline after a block + ) + inventory_template = jinja_env.get_template('test-targets.j2') inventory_path = os.path.join(HOSTS_DIR, 'target') + with open(inventory_path, 'w') as fp: - fp.write('[test-targets]\n') - fp.writelines( - "%(name)s " - "ansible_host=%(hostname)s " - "ansible_port=%(port)s " - "ansible_python_interpreter=%(python_path)s " - "ansible_user=mitogen__has_sudo_nopw " - "ansible_password=has_sudo_nopw_password" - "\n" - % container - for container in containers - ) - - for distro, hostnames in sorted(distros.items(), key=lambda t: t[0]): - fp.write('\n[%s]\n' % distro) - fp.writelines('%s\n' % name for name in hostnames) - - for family, hostnames in sorted(families.items(), key=lambda t: t[0]): - fp.write('\n[%s]\n' % family) - fp.writelines('%s\n' % name for name in hostnames) - - fp.write(textwrap.dedent( - ''' - [linux:children] - test-targets - - [linux_containers:children] - test-targets - ''' + fp.write(inventory_template.render( + containers=containers, + distros=distros, + families=families, )) ci_lib.dump_file(inventory_path) diff --git a/.ci/azure-pipelines-steps.yml b/.ci/azure-pipelines-steps.yml index 919b992b..791372af 100644 --- a/.ci/azure-pipelines-steps.yml +++ b/.ci/azure-pipelines-steps.yml @@ -14,6 +14,19 @@ steps: versionSpec: '$(python.version)' condition: ne(variables['python.version'], '') +- script: | + set -o errexit + set -o nounset + set -o pipefail + + aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws + displayName: Authenticate to container registry + condition: eq(variables['Agent.OS'], 'Linux') + env: + AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID) + AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY) + AWS_DEFAULT_REGION: $(AWS_DEFAULT_REGION) + - script: | set -o errexit set -o nounset @@ -90,7 +103,3 @@ steps: "$PYTHON" -m tox -e "$(tox.env)" displayName: "Run tests" - env: - AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID) - AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY) - AWS_DEFAULT_REGION: $(AWS_DEFAULT_REGION) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 94919e35..0bf4556b 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -16,37 +16,26 @@ trigger: - docs-master jobs: -- job: mac11 +- job: mac12 # vanilla Ansible is really slow timeoutInMinutes: 120 steps: - template: azure-pipelines-steps.yml pool: - # https://github.com/actions/runner-images/blob/main/images/macos/macos-11-Readme.md - vmImage: macOS-11 + # https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md + vmImage: macOS-12 strategy: matrix: - Mito_27: - tox.env: py27-mode_mitogen Mito_312: - python.version: '3.12' tox.env: py312-mode_mitogen - - Loc_27_210: - tox.env: py27-mode_localhost-ansible2.10 - Loc_312_9: - python.version: '3.12' - tox.env: py312-mode_localhost-ansible9 - - Van_27_210: - tox.env: py27-mode_localhost-ansible2.10-strategy_linear - Van_312_9: - python.version: '3.12' - tox.env: py312-mode_localhost-ansible9-strategy_linear + Loc_312_10: + tox.env: py312-mode_localhost-ansible10 + Van_312_10: + tox.env: py312-mode_localhost-ansible10-strategy_linear - job: Linux pool: - # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md + # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2004-Readme.md vmImage: ubuntu-20.04 steps: - template: azure-pipelines-steps.yml @@ -163,3 +152,6 @@ jobs: Ans_312_9: python.version: '3.12' tox.env: py312-mode_ansible-ansible9 + Ans_312_10: + python.version: '3.12' + tox.env: py312-mode_ansible-ansible10 diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index 3e716385..dfe49b97 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -27,6 +27,13 @@ os.chdir( ) ) + +IMAGE_TEMPLATE = os.environ.get( + 'MITOGEN_TEST_IMAGE_TEMPLATE', + 'public.ecr.aws/n5z0e8q9/%(distro)s-test', +) + + _print = print def print(*args, **kwargs): file = kwargs.get('file', sys.stdout) @@ -193,8 +200,6 @@ GIT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) DISTRO = os.environ.get('DISTRO', 'debian9') # Used only when MODE=ansible DISTROS = os.environ.get('DISTROS', 'centos6 centos8 debian9 debian11 ubuntu1604 ubuntu2004').split() -TARGET_COUNT = int(os.environ.get('TARGET_COUNT', '2')) -BASE_PORT = 2200 TMP = TempDir().path @@ -217,6 +222,7 @@ os.environ['PYTHONPATH'] = '%s:%s' % ( def get_docker_hostname(): """Return the hostname where the docker daemon is running. """ + # Duplicated in testlib url = os.environ.get('DOCKER_HOST') if url in (None, 'http+docker://localunixsocket'): return 'localhost' @@ -225,27 +231,34 @@ def get_docker_hostname(): return parsed.netloc.partition(':')[0] -def make_containers(name_prefix='', port_offset=0): +def container_specs( + distros, + base_port=2200, + image_template=IMAGE_TEMPLATE, + name_template='target-%(distro)s-%(index)d', +): """ >>> import pprint - >>> BASE_PORT=2200; DISTROS=['debian11', 'centos6'] - >>> pprint.pprint(make_containers()) + >>> pprint.pprint(container_specs(['debian11-py3', 'centos6'])) [{'distro': 'debian11', 'family': 'debian', 'hostname': 'localhost', 'image': 'public.ecr.aws/n5z0e8q9/debian11-test', + 'index': 1, 'name': 'target-debian11-1', 'port': 2201, - 'python_path': '/usr/bin/python'}, + 'python_path': '/usr/bin/python3'}, {'distro': 'centos6', 'family': 'centos', 'hostname': 'localhost', 'image': 'public.ecr.aws/n5z0e8q9/centos6-test', + 'index': 2, 'name': 'target-centos6-2', 'port': 2202, 'python_path': '/usr/bin/python'}] """ docker_hostname = get_docker_hostname() + # Code duplicated in testlib.py, both should be updated together distro_pattern = re.compile(r''' (?P(?P[a-z]+)[0-9]+) (?:-(?Ppy3))? @@ -256,30 +269,27 @@ def make_containers(name_prefix='', port_offset=0): i = 1 lst = [] - for distro in DISTROS: + for distro in distros: + # Code duplicated in testlib.py, both should be updated together d = distro_pattern.match(distro).groupdict(default=None) - distro = d['distro'] - family = d['family'] - image = 'public.ecr.aws/n5z0e8q9/%s-test' % (distro,) - if d['py'] == 'py3': + if d.pop('py') == 'py3': python_path = '/usr/bin/python3' else: python_path = '/usr/bin/python' - if d['count']: - count = int(count) - else: - count = 1 + count = int(d.pop('count') or '1', 10) for x in range(count): - lst.append({ - "distro": distro, "family": family, "image": image, - "name": name_prefix + ("target-%s-%s" % (distro, i)), + d['index'] = i + d.update({ + 'image': image_template % d, + 'name': name_template % d, "hostname": docker_hostname, - "port": BASE_PORT + i + port_offset, + 'port': base_port + i, "python_path": python_path, }) + lst.append(d) i += 1 return lst diff --git a/.ci/debops_common_install.py b/.ci/debops_common_install.py index afafe39a..825126c7 100755 --- a/.ci/debops_common_install.py +++ b/.ci/debops_common_install.py @@ -2,16 +2,10 @@ import ci_lib -# Naturally DebOps only supports Debian. -ci_lib.DISTROS = ['debian'] - ci_lib.run_batches([ [ 'python -m pip --no-python-version-warning --disable-pip-version-check "debops[ansible]==2.1.2"', ], - [ - 'aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws', - ], ]) ci_lib.run('ansible-galaxy collection install debops.debops:==2.1.2') diff --git a/.ci/debops_common_tests.py b/.ci/debops_common_tests.py index 7db8a797..b065486f 100755 --- a/.ci/debops_common_tests.py +++ b/.ci/debops_common_tests.py @@ -6,9 +6,6 @@ import sys import ci_lib -# DebOps only supports Debian. -ci_lib.DISTROS = ['debian'] * ci_lib.TARGET_COUNT - project_dir = os.path.join(ci_lib.TMP, 'project') vars_path = 'ansible/inventory/group_vars/debops_all_hosts.yml' inventory_path = 'ansible/inventory/hosts' @@ -16,7 +13,11 @@ docker_hostname = ci_lib.get_docker_hostname() with ci_lib.Fold('docker_setup'): - containers = ci_lib.make_containers(port_offset=500, name_prefix='debops-') + containers = ci_lib.container_specs( + ['debian*2'], + base_port=2700, + name_template='debops-target-%(distro)s-%(index)d', + ) ci_lib.start_containers(containers) diff --git a/.ci/localhost_ansible_install.py b/.ci/localhost_ansible_install.py deleted file mode 100755 index d08ddafc..00000000 --- a/.ci/localhost_ansible_install.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -import ci_lib - -batches = [ -] - -ci_lib.run_batches(batches) diff --git a/.ci/mitogen_install.py b/.ci/mitogen_install.py deleted file mode 100755 index b0b1eb14..00000000 --- a/.ci/mitogen_install.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python - -import ci_lib - -batches = [ -] - -if ci_lib.have_docker(): - batches.append([ - 'aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws', - ]) - - -ci_lib.run_batches(batches) diff --git a/.ci/mitogen_py24_install.py b/.ci/mitogen_py24_install.py index bd6ecb24..85ea013c 100755 --- a/.ci/mitogen_py24_install.py +++ b/.ci/mitogen_py24_install.py @@ -3,9 +3,6 @@ import ci_lib batches = [ - [ - 'aws ecr-public get-login-password | docker login --username AWS --password-stdin public.ecr.aws', - ], [ 'curl https://dw.github.io/mitogen/binaries/ubuntu-python-2.4.6.tar.bz2 | sudo tar -C / -jxv', ] diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..4520c3cf --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,326 @@ +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions + +name: Tests + +on: + pull_request: + push: + branches-ignore: + - docs-master + +env: + #ANSIBLE_VERBOSITY: 3 + #MITOGEN_LOG_LEVEL: DEBUG + MITOGEN_TEST_IMAGE_TEMPLATE: "ghcr.io/mitogen-hq/%(distro)s-test" + +# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners +# https://github.com/actions/runner-images/blob/main/README.md#software-and-image-support +jobs: + linux: + # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2004-Readme.md + runs-on: ubuntu-20.04 + + strategy: + fail-fast: false + matrix: + include: + - name: Ans_27_210 + tox_env: py27-mode_ansible-ansible2.10 + - name: Ans_27_4 + tox_env: py27-mode_ansible-ansible4 + + - name: Ans_36_210 + python_version: '3.6' + tox_env: py36-mode_ansible-ansible2.10 + - name: Ans_36_4 + python_version: '3.6' + tox_env: py36-mode_ansible-ansible4 + + - name: Ans_311_210 + python_version: '3.11' + tox_env: py311-mode_ansible-ansible2.10 + - name: Ans_311_3 + python_version: '3.11' + tox_env: py311-mode_ansible-ansible3 + - name: Ans_311_4 + python_version: '3.11' + tox_env: py311-mode_ansible-ansible4 + - name: Ans_311_5 + python_version: '3.11' + tox_env: py311-mode_ansible-ansible5 + - name: Ans_312_6 + python_version: '3.12' + tox_env: py312-mode_ansible-ansible6 + - name: Ans_312_7 + python_version: '3.12' + tox_env: py312-mode_ansible-ansible7 + - name: Ans_312_8 + python_version: '3.12' + tox_env: py312-mode_ansible-ansible8 + - name: Ans_312_9 + python_version: '3.12' + tox_env: py312-mode_ansible-ansible9 + - name: Ans_312_10 + python_version: '3.12' + tox_env: py312-mode_ansible-ansible10 + - name: Van_312_10 + python_version: '3.12' + tox_env: py312-mode_ansible-ansible10-strategy_linear + + - name: Mito_27_centos6 + tox_env: py27-mode_mitogen-distro_centos6 + - name: Mito_27_centos7 + tox_env: py27-mode_mitogen-distro_centos7 + - name: Mito_27_centos8 + tox_env: py27-mode_mitogen-distro_centos8 + - name: Mito_27_debian9 + tox_env: py27-mode_mitogen-distro_debian9 + - name: Mito_27_debian10 + tox_env: py27-mode_mitogen-distro_debian10 + - name: Mito_27_debian11 + tox_env: py27-mode_mitogen-distro_debian11 + - name: Mito_27_ubuntu1604 + tox_env: py27-mode_mitogen-distro_ubuntu1604 + - name: Mito_27_ubuntu1804 + tox_env: py27-mode_mitogen-distro_ubuntu1804 + - name: Mito_27_ubuntu2004 + tox_env: py27-mode_mitogen-distro_ubuntu2004 + + - name: Mito_36_centos6 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_centos6 + - name: Mito_36_centos7 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_centos7 + - name: Mito_36_centos8 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_centos8 + - name: Mito_36_debian9 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_debian9 + - name: Mito_36_debian10 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_debian10 + - name: Mito_36_debian11 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_debian11 + - name: Mito_36_ubuntu1604 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_ubuntu1604 + - name: Mito_36_ubuntu1804 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_ubuntu1804 + - name: Mito_36_ubuntu2004 + python_version: '3.6' + tox_env: py36-mode_mitogen-distro_ubuntu2004 + + - name: Mito_312_centos6 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_centos6 + - name: Mito_312_centos7 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_centos7 + - name: Mito_312_centos8 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_centos8 + - name: Mito_312_debian9 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_debian9 + - name: Mito_312_debian10 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_debian10 + - name: Mito_312_debian11 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_debian11 + - name: Mito_312_ubuntu1604 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_ubuntu1604 + - name: Mito_312_ubuntu1804 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_ubuntu1804 + - name: Mito_312_ubuntu2004 + python_version: '3.12' + tox_env: py312-mode_mitogen-distro_ubuntu2004 + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python_version }} + if: ${{ matrix.python_version }} + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install build deps + run: | + set -o errexit -o nounset -o pipefail + + sudo apt-get update + sudo apt-get install -y python2-dev python3-pip virtualenv + - name: Show Python versions + run: | + set -o errexit -o nounset -o pipefail + + # macOS builders lack a realpath command + type python && python -c"import os.path;print(os.path.realpath('$(type -p python)'))" && python --version + type python2 && python2 -c"import os.path;print(os.path.realpath('$(type -p python2)'))" && python2 --version + type python3 && python3 -c"import os.path;print(os.path.realpath('$(type -p python3)'))" && python3 --version + echo + + if [ -e /usr/bin/python ]; then + echo "/usr/bin/python: sys.executable: $(/usr/bin/python -c 'import sys; print(sys.executable)')" + fi + + if [ -e /usr/bin/python2 ]; then + echo "/usr/bin/python2: sys.executable: $(/usr/bin/python2 -c 'import sys; print(sys.executable)')" + fi + + if [ -e /usr/bin/python2.7 ]; then + echo "/usr/bin/python2.7: sys.executable: $(/usr/bin/python2.7 -c 'import sys; print(sys.executable)')" + fi + - name: Install tooling + run: | + set -o errexit -o nounset -o pipefail + + # Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12) + PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))') + + if [[ -z $PYTHON ]]; then + echo 1>&2 "Python interpreter could not be determined" + exit 1 + fi + + if [[ $PYTHON == "python2.7" && $(uname) == "Darwin" ]]; then + "$PYTHON" -m ensurepip --user --altinstall --no-default-pip + "$PYTHON" -m pip install --user -r "tests/requirements-tox.txt" + elif [[ $PYTHON == "python2.7" ]]; then + curl "https://bootstrap.pypa.io/pip/2.7/get-pip.py" --output "get-pip.py" + "$PYTHON" get-pip.py --user --no-python-version-warning + # Avoid Python 2.x pip masking system pip + rm -f ~/.local/bin/{easy_install,pip,wheel} + "$PYTHON" -m pip install --user -r "tests/requirements-tox.txt" + else + "$PYTHON" -m pip install -r "tests/requirements-tox.txt" + fi + - name: Run tests + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -o errexit -o nounset -o pipefail + + # Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12) + PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))') + + if [[ -z $PYTHON ]]; then + echo 1>&2 "Python interpreter could not be determined" + exit 1 + fi + + "$PYTHON" -m tox -e "${{ matrix.tox_env }}" + + macos: + # https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md + runs-on: macos-12 + timeout-minutes: 120 + + strategy: + fail-fast: false + matrix: + include: + - name: Mito_27 + tox_env: py27-mode_mitogen + - name: Mito_312 + tox_env: py312-mode_mitogen + + - name: Loc_27_210 + tox_env: py27-mode_localhost-ansible2.10 + - name: Loc_312_10 + tox_env: py312-mode_localhost-ansible10 + + - name: Van_27_210 + tox_env: py27-mode_localhost-ansible2.10-strategy_linear + - name: Van_312_10 + tox_env: py312-mode_localhost-ansible10-strategy_linear + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python_version }} + if: ${{ matrix.python_version }} + - name: Show Python versions + run: | + set -o errexit -o nounset -o pipefail + + # macOS builders lack a realpath command + type python && python -c"import os.path;print(os.path.realpath('$(type -p python)'))" && python --version + type python2 && python2 -c"import os.path;print(os.path.realpath('$(type -p python2)'))" && python2 --version + type python3 && python3 -c"import os.path;print(os.path.realpath('$(type -p python3)'))" && python3 --version + echo + + if [ -e /usr/bin/python ]; then + echo "/usr/bin/python: sys.executable: $(/usr/bin/python -c 'import sys; print(sys.executable)')" + fi + + if [ -e /usr/bin/python2 ]; then + echo "/usr/bin/python2: sys.executable: $(/usr/bin/python2 -c 'import sys; print(sys.executable)')" + fi + + if [ -e /usr/bin/python2.7 ]; then + echo "/usr/bin/python2.7: sys.executable: $(/usr/bin/python2.7 -c 'import sys; print(sys.executable)')" + fi + + if [ -e /Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 ]; then + # GitHub macOS 12 images: python2.7 is installed, but not on $PATH + echo "/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7: sys.executable: $(/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 -c 'import sys; print(sys.executable)')" + fi + - name: Install tooling + run: | + set -o errexit -o nounset -o pipefail + + # Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12) + PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))') + + if [[ -z $PYTHON ]]; then + echo 1>&2 "Python interpreter could not be determined" + exit 1 + fi + + if [[ $PYTHON == "python2.7" && $(uname) == "Darwin" ]]; then + # GitHub macOS 12 images: python2.7 is installed, but not on $PATH + PYTHON="/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7" + "$PYTHON" -m ensurepip --user --altinstall --no-default-pip + "$PYTHON" -m pip install --user -r "tests/requirements-tox.txt" + elif [[ $PYTHON == "python2.7" ]]; then + curl "https://bootstrap.pypa.io/pip/2.7/get-pip.py" --output "get-pip.py" + "$PYTHON" get-pip.py --user --no-python-version-warning + # Avoid Python 2.x pip masking system pip + rm -f ~/.local/bin/{easy_install,pip,wheel} + "$PYTHON" -m pip install --user -r "tests/requirements-tox.txt" + else + "$PYTHON" -m pip install -r "tests/requirements-tox.txt" + fi + - name: Run tests + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -o errexit -o nounset -o pipefail + + # Tox environment name (e.g. py312-mode_mitogen) -> Python executable name (e.g. python3.12) + PYTHON=$(python -c 'import re; print(re.sub(r"^py([23])([0-9]{1,2}).*", r"python\1.\2", "${{ matrix.tox_env }}"))') + + if [[ -z $PYTHON ]]; then + echo 1>&2 "Python interpreter could not be determined" + exit 1 + fi + + if [[ $PYTHON == "python2.7" && $(uname) == "Darwin" ]]; then + # GitHub macOS 12 images: python2.7 is installed, but not on $PATH + PYTHON="/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7" + fi + + "$PYTHON" -m tox -e "${{ matrix.tox_env }}" diff --git a/ansible_mitogen/affinity.py b/ansible_mitogen/affinity.py index 635ee7b9..223794ab 100644 --- a/ansible_mitogen/affinity.py +++ b/ansible_mitogen/affinity.py @@ -83,7 +83,6 @@ import multiprocessing import os import struct -import mitogen.core import mitogen.parent @@ -265,7 +264,7 @@ class LinuxPolicy(FixedPolicy): for x in range(16): chunks.append(struct.pack('>= 64 - return mitogen.core.b('').join(chunks) + return b''.join(chunks) def _get_thread_ids(self): try: diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py index 6bdf11ba..a3f66eac 100644 --- a/ansible_mitogen/connection.py +++ b/ansible_mitogen/connection.py @@ -1129,6 +1129,6 @@ class Connection(ansible.plugins.connection.ConnectionBase): self.get_chain().call( ansible_mitogen.target.transfer_file, context=self.binding.get_child_service_context(), - in_path=in_path, - out_path=out_path + in_path=ansible_mitogen.utils.unsafe.cast(in_path), + out_path=ansible_mitogen.utils.unsafe.cast(out_path) ) diff --git a/ansible_mitogen/loaders.py b/ansible_mitogen/loaders.py index 9729b8a1..9d9876ac 100644 --- a/ansible_mitogen/loaders.py +++ b/ansible_mitogen/loaders.py @@ -49,7 +49,7 @@ __all__ = [ ANSIBLE_VERSION_MIN = (2, 10) -ANSIBLE_VERSION_MAX = (2, 16) +ANSIBLE_VERSION_MAX = (2, 17) NEW_VERSION_MSG = ( "Your Ansible version (%s) is too recent. The most recent version\n" diff --git a/ansible_mitogen/logging.py b/ansible_mitogen/logging.py index 40b2b339..4d5647a4 100644 --- a/ansible_mitogen/logging.py +++ b/ansible_mitogen/logging.py @@ -32,15 +32,13 @@ __metaclass__ = type import logging import os +import ansible.utils.display + import mitogen.core import mitogen.utils -try: - from __main__ import display -except ImportError: - import ansible.utils.display - display = ansible.utils.display.Display() +display = ansible.utils.display.Display() #: The process name set via :func:`set_process_name`. _process_name = None diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index 9cc97a48..38f351ed 100644 --- a/ansible_mitogen/mixins.py +++ b/ansible_mitogen/mixins.py @@ -35,18 +35,16 @@ import pwd import random import traceback -try: - from shlex import quote as shlex_quote -except ImportError: - from pipes import quote as shlex_quote - -from ansible.module_utils._text import to_bytes -from ansible.parsing.utils.jsonify import jsonify - import ansible import ansible.constants import ansible.plugins import ansible.plugins.action +import ansible.utils.unsafe_proxy +import ansible.vars.clean + +from ansible.module_utils.common.text.converters import to_bytes, to_text +from ansible.module_utils.six.moves import shlex_quote +from ansible.parsing.utils.jsonify import jsonify import mitogen.core import mitogen.select @@ -57,24 +55,6 @@ import ansible_mitogen.target import ansible_mitogen.utils import ansible_mitogen.utils.unsafe -from ansible.module_utils._text import to_text - -try: - from ansible.utils.unsafe_proxy import wrap_var -except ImportError: - from ansible.vars.unsafe_proxy import wrap_var - -try: - # ansible 2.8 moved remove_internal_keys to the clean module - from ansible.vars.clean import remove_internal_keys -except ImportError: - try: - from ansible.vars.manager import remove_internal_keys - except ImportError: - # ansible 2.3.3 has remove_internal_keys as a protected func on the action class - # we'll fallback to calling self._remove_internal_keys in this case - remove_internal_keys = lambda a: "Not found" - LOG = logging.getLogger(__name__) @@ -280,7 +260,9 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase): paths, mode, sudoable) return self.fake_shell(lambda: mitogen.select.Select.all( self._connection.get_chain().call_async( - ansible_mitogen.target.set_file_mode, path, mode + ansible_mitogen.target.set_file_mode, + ansible_mitogen.utils.unsafe.cast(path), + mode, ) for path in paths )) @@ -357,7 +339,9 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase): def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, - delete_remote_tmp=True, wrap_async=False): + delete_remote_tmp=True, wrap_async=False, + ignore_unknown_opts=False, + ): """ Collect up a module's execution environment then use it to invoke target.run_module() or helpers.run_module_async() in the target @@ -370,7 +354,13 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase): if task_vars is None: task_vars = {} - self._update_module_args(module_name, module_args, task_vars) + if ansible_mitogen.utils.ansible_version[:2] >= (2, 17): + self._update_module_args( + module_name, module_args, task_vars, + ignore_unknown_opts=ignore_unknown_opts, + ) + else: + self._update_module_args(module_name, module_args, task_vars) env = {} self._compute_environment_string(env) self._set_temp_file_args(module_args, wrap_async) @@ -403,10 +393,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase): self._remove_tmp_path(tmp) # prevents things like discovered_interpreter_* or ansible_discovered_interpreter_* from being set - # handle ansible 2.3.3 that has remove_internal_keys in a different place - check = remove_internal_keys(result) - if check == 'Not found': - self._remove_internal_keys(result) + ansible.vars.clean.remove_internal_keys(result) # taken from _execute_module of ansible 2.8.6 # propagate interpreter discovery results back to the controller @@ -430,7 +417,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase): result['deprecations'] = [] result['deprecations'].extend(self._discovery_deprecation_warnings) - return wrap_var(result) + return ansible.utils.unsafe_proxy.wrap_var(result) def _postprocess_response(self, result): """ diff --git a/ansible_mitogen/planner.py b/ansible_mitogen/planner.py index 0b1b7aab..4cdc0f20 100644 --- a/ansible_mitogen/planner.py +++ b/ansible_mitogen/planner.py @@ -54,6 +54,7 @@ import mitogen.select import ansible_mitogen.loaders import ansible_mitogen.parsing import ansible_mitogen.target +import ansible_mitogen.utils.unsafe LOG = logging.getLogger(__name__) @@ -215,12 +216,15 @@ class ScriptPlanner(BinaryPlanner): """ def _rewrite_interpreter(self, path): """ - Given the original interpreter binary extracted from the script's - interpreter line, look up the associated `ansible_*_interpreter` - variable, render it and return it. + Given the interpreter path (from the script's hashbang line), return + the desired interpreter path. This tries, in order + + 1. Look up & render the `ansible_*_interpreter` variable, if set + 2. Look up the `discovered_interpreter_*` fact, if present + 3. The unmodified path from the hashbang line. :param str path: - Absolute UNIX path to original interpreter. + Absolute path to original interpreter (e.g. '/usr/bin/python'). :returns: Shell fragment prefix used to execute the script via "/bin/sh -c". @@ -228,13 +232,25 @@ class ScriptPlanner(BinaryPlanner): involved here, the vanilla implementation uses it and that use is exploited in common playbooks. """ - key = u'ansible_%s_interpreter' % os.path.basename(path).strip() + interpreter_name = os.path.basename(path).strip() + key = u'ansible_%s_interpreter' % interpreter_name try: template = self._inv.task_vars[key] except KeyError: - return path + pass + else: + configured_interpreter = self._inv.templar.template(template) + return ansible_mitogen.utils.unsafe.cast(configured_interpreter) + + key = u'discovered_interpreter_%s' % interpreter_name + try: + discovered_interpreter = self._inv.task_vars['ansible_facts'][key] + except KeyError: + pass + else: + return ansible_mitogen.utils.unsafe.cast(discovered_interpreter) - return mitogen.utils.cast(self._inv.templar.template(template)) + return path def _get_interpreter(self): path, arg = ansible_mitogen.parsing.parse_hashbang( @@ -249,7 +265,8 @@ class ScriptPlanner(BinaryPlanner): if arg: fragment += ' ' + arg - return fragment, path.startswith('python') + is_python = path.startswith('python') + return fragment, is_python def get_kwargs(self, **kwargs): interpreter_fragment, is_python = self._get_interpreter() @@ -460,7 +477,7 @@ def read_file(path): finally: os.close(fd) - return mitogen.core.b('').join(bits) + return b''.join(bits) def _propagate_deps(invocation, planner, context): diff --git a/ansible_mitogen/plugins/connection/mitogen_local.py b/ansible_mitogen/plugins/connection/mitogen_local.py index 6ff86733..2d1e7052 100644 --- a/ansible_mitogen/plugins/connection/mitogen_local.py +++ b/ansible_mitogen/plugins/connection/mitogen_local.py @@ -42,13 +42,7 @@ except ImportError: import ansible_mitogen.connection import ansible_mitogen.process - -if sys.version_info > (3,): - viewkeys = dict.keys -elif sys.version_info > (2, 7): - viewkeys = dict.viewkeys -else: - viewkeys = lambda dct: set(dct) +viewkeys = getattr(dict, 'viewkeys', dict.keys) def dict_diff(old, new): diff --git a/ansible_mitogen/process.py b/ansible_mitogen/process.py index 3a41a43d..7ec70f2a 100644 --- a/ansible_mitogen/process.py +++ b/ansible_mitogen/process.py @@ -61,10 +61,9 @@ import mitogen.utils import ansible import ansible.constants as C import ansible.errors + import ansible_mitogen.logging import ansible_mitogen.services - -from mitogen.core import b import ansible_mitogen.affinity @@ -639,7 +638,7 @@ class MuxProcess(object): try: # Let the parent know our listening socket is ready. - mitogen.core.io_op(self.model.child_sock.send, b('1')) + mitogen.core.io_op(self.model.child_sock.send, b'1') # Block until the socket is closed, which happens on parent exit. mitogen.core.io_op(self.model.child_sock.recv, 1) finally: diff --git a/ansible_mitogen/runner.py b/ansible_mitogen/runner.py index fbd2eff2..5d048917 100644 --- a/ansible_mitogen/runner.py +++ b/ansible_mitogen/runner.py @@ -40,7 +40,9 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type import atexit +import ctypes import json +import logging import os import re import shlex @@ -50,19 +52,12 @@ import tempfile import traceback import types +from ansible.module_utils.six.moves import shlex_quote + import mitogen.core import ansible_mitogen.target # TODO: circular import -from mitogen.core import b -from mitogen.core import bytes_partition -from mitogen.core import str_rpartition from mitogen.core import to_text -try: - import ctypes -except ImportError: - # Python 2.4 - ctypes = None - try: # Python >= 3.4, PEP 451 ModuleSpec API import importlib.machinery @@ -77,15 +72,6 @@ try: except ImportError: from io import StringIO -try: - from shlex import quote as shlex_quote -except ImportError: - from pipes import quote as shlex_quote - -# Absolute imports for <2.5. -logging = __import__('logging') - - # Prevent accidental import of an Ansible module from hanging on stdin read. import ansible.module_utils.basic ansible.module_utils.basic._ANSIBLE_ARGS = '{}' @@ -95,15 +81,13 @@ ansible.module_utils.basic._ANSIBLE_ARGS = '{}' # explicit call to res_init() on each task invocation. BSD-alikes export it # directly, Linux #defines it as "__res_init". libc__res_init = None -if ctypes: - libc = ctypes.CDLL(None) - for symbol in 'res_init', '__res_init': - try: - libc__res_init = getattr(libc, symbol) - except AttributeError: - pass +libc = ctypes.CDLL(None) +for symbol in 'res_init', '__res_init': + try: + libc__res_init = getattr(libc, symbol) + except AttributeError: + pass -iteritems = getattr(dict, 'iteritems', dict.items) LOG = logging.getLogger(__name__) @@ -217,13 +201,13 @@ class EnvironmentFileWatcher(object): for line in fp: # ' #export foo=some var ' -> ['#export', 'foo=some var '] bits = shlex_split_b(line) - if (not bits) or bits[0].startswith(b('#')): + if (not bits) or bits[0].startswith(b'#'): continue - if bits[0] == b('export'): + if bits[0] == b'export': bits.pop(0) - key, sep, value = bytes_partition(b(' ').join(bits), b('=')) + key, sep, value = b' '.join(bits).partition(b'=') if key and sep: yield key, value @@ -601,7 +585,7 @@ class ModuleUtilsImporter(object): mod.__path__ = [] mod.__package__ = str(fullname) else: - mod.__package__ = str(str_rpartition(to_text(fullname), '.')[0]) + mod.__package__ = str(to_text(fullname).rpartition('.')[0]) exec(code, mod.__dict__) self._loaded.add(fullname) return mod @@ -616,7 +600,7 @@ class TemporaryEnvironment(object): def __init__(self, env=None): self.original = dict(os.environ) self.env = env or {} - for key, value in iteritems(self.env): + for key, value in mitogen.core.iteritems(self.env): key = mitogen.core.to_text(key) value = mitogen.core.to_text(value) if value is None: @@ -824,7 +808,7 @@ class ScriptRunner(ProgramRunner): self.interpreter_fragment = interpreter_fragment self.is_python = is_python - b_ENCODING_STRING = b('# -*- coding: utf-8 -*-') + b_ENCODING_STRING = b'# -*- coding: utf-8 -*-' def _get_program(self): return self._rewrite_source( @@ -857,13 +841,13 @@ class ScriptRunner(ProgramRunner): # While Ansible rewrites the #! using ansible_*_interpreter, it is # never actually used to execute the script, instead it is a shell # fragment consumed by shell/__init__.py::build_module_command(). - new = [b('#!') + utf8(self.interpreter_fragment)] + new = [b'#!' + utf8(self.interpreter_fragment)] if self.is_python: new.append(self.b_ENCODING_STRING) - _, _, rest = bytes_partition(s, b('\n')) + _, _, rest = s.partition(b'\n') new.append(rest) - return b('\n').join(new) + return b'\n'.join(new) class NewStyleRunner(ScriptRunner): @@ -976,8 +960,7 @@ class NewStyleRunner(ScriptRunner): # change the default encoding. This hack was removed from Ansible long ago, # but not before permeating into many third party modules. PREHISTORIC_HACK_RE = re.compile( - b(r'reload\s*\(\s*sys\s*\)\s*' - r'sys\s*\.\s*setdefaultencoding\([^)]+\)') + br'reload\s*\(\s*sys\s*\)\s*sys\s*\.\s*setdefaultencoding\([^)]+\)', ) def _setup_program(self): @@ -985,7 +968,7 @@ class NewStyleRunner(ScriptRunner): context=self.service_context, path=self.path, ) - self.source = self.PREHISTORIC_HACK_RE.sub(b(''), source) + self.source = self.PREHISTORIC_HACK_RE.sub(b'', source) def _get_code(self): try: @@ -1003,7 +986,7 @@ class NewStyleRunner(ScriptRunner): if mitogen.core.PY3: main_module_name = '__main__' else: - main_module_name = b('__main__') + main_module_name = b'__main__' def _handle_magic_exception(self, mod, exc): """ @@ -1035,7 +1018,7 @@ class NewStyleRunner(ScriptRunner): approximation of the original package hierarchy, so that relative imports function correctly. """ - pkg, sep, modname = str_rpartition(self.py_module_name, '.') + pkg, sep, _ = self.py_module_name.rpartition('.') if not sep: return None if mitogen.core.PY3: @@ -1078,7 +1061,7 @@ class NewStyleRunner(ScriptRunner): class JsonArgsRunner(ScriptRunner): - JSON_ARGS = b('<>') + JSON_ARGS = b'<>' def _get_args_contents(self): return json.dumps(self.args).encode() diff --git a/ansible_mitogen/services.py b/ansible_mitogen/services.py index 3e9de652..abc0e379 100644 --- a/ansible_mitogen/services.py +++ b/ansible_mitogen/services.py @@ -50,6 +50,8 @@ import threading import ansible.constants +from ansible.module_utils.six import reraise + import mitogen.core import mitogen.service import ansible_mitogen.loaders @@ -66,20 +68,6 @@ LOG = logging.getLogger(__name__) ansible_mitogen.loaders.shell_loader.get('sh') -if sys.version_info[0] == 3: - def reraise(tp, value, tb): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value -else: - exec( - "def reraise(tp, value, tb=None):\n" - " raise tp, value, tb\n" - ) - - def _get_candidate_temp_dirs(): try: # >=2.5 diff --git a/ansible_mitogen/target.py b/ansible_mitogen/target.py index 7d907d62..ee4cb398 100644 --- a/ansible_mitogen/target.py +++ b/ansible_mitogen/target.py @@ -39,6 +39,7 @@ __metaclass__ = type import errno import grp import json +import logging import operator import os import pwd @@ -51,26 +52,9 @@ import tempfile import traceback import types -# Absolute imports for <2.5. -logging = __import__('logging') - import mitogen.core import mitogen.parent import mitogen.service -from mitogen.core import b - -try: - reduce -except NameError: - # Python 3.x. - from functools import reduce - -try: - BaseException -except NameError: - # Python 2.4 - BaseException = Exception - # Ansible since PR #41749 inserts "import __main__" into # ansible.module_utils.basic. Mitogen's importer will refuse such an import, so @@ -80,6 +64,9 @@ if not sys.modules.get(str('__main__')): sys.modules[str('__main__')] = types.ModuleType(str('__main__')) import ansible.module_utils.json_utils + +from ansible.module_utils.six.moves import reduce + import ansible_mitogen.runner @@ -615,8 +602,8 @@ def exec_args(args, in_data='', chdir=None, shell=None, emulate_tty=False): stdout, stderr = proc.communicate(in_data) if emulate_tty: - stdout = stdout.replace(b('\n'), b('\r\n')) - return proc.returncode, stdout, stderr or b('') + stdout = stdout.replace(b'\n', b'\r\n') + return proc.returncode, stdout, stderr or b'' def exec_command(cmd, in_data='', chdir=None, shell=None, emulate_tty=False): @@ -746,9 +733,7 @@ def set_file_mode(path, spec, fd=None): """ Update the permissions of a file using the same syntax as chmod(1). """ - if isinstance(spec, int): - new_mode = spec - elif not mitogen.core.PY3 and isinstance(spec, long): + if isinstance(spec, mitogen.core.integer_types): new_mode = spec elif spec.isdigit(): new_mode = int(spec, 8) diff --git a/ansible_mitogen/transport_config.py b/ansible_mitogen/transport_config.py index 3ab623f8..39a4b604 100644 --- a/ansible_mitogen/transport_config.py +++ b/ansible_mitogen/transport_config.py @@ -65,21 +65,12 @@ import abc import os import ansible.utils.shlex import ansible.constants as C +import ansible.executor.interpreter_discovery +import ansible.utils.unsafe_proxy from ansible.module_utils.six import with_metaclass from ansible.module_utils.parsing.convert_bool import boolean -# this was added in Ansible >= 2.8.0; fallback to the default interpreter if necessary -try: - from ansible.executor.interpreter_discovery import discover_interpreter -except ImportError: - discover_interpreter = lambda action,interpreter_name,discovery_mode,task_vars: '/usr/bin/python' - -try: - from ansible.utils.unsafe_proxy import AnsibleUnsafeText -except ImportError: - from ansible.vars.unsafe_proxy import AnsibleUnsafeText - import mitogen.core @@ -115,12 +106,13 @@ def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_pyth action._finding_python_interpreter = True # fake pipelining so discover_interpreter can be happy action._connection.has_pipelining = True - s = AnsibleUnsafeText(discover_interpreter( + s = ansible.executor.interpreter_discovery.discover_interpreter( action=action, interpreter_name=interpreter_name, discovery_mode=s, - task_vars=task_vars)) - + task_vars=task_vars, + ) + s = ansible.utils.unsafe_proxy.AnsibleUnsafeText(s) # cache discovered interpreter task_vars['ansible_facts'][discovered_interpreter_config] = s action._connection.has_pipelining = False @@ -498,12 +490,13 @@ class PlayContextSpec(Spec): ) def ssh_args(self): + local_vars = self._task_vars.get("hostvars", {}).get(self._inventory_name, {}) return [ mitogen.core.to_text(term) for s in ( - C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), - C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), - C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=local_vars), + C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=local_vars), + C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=local_vars) ) for term in ansible.utils.shlex.shlex_split(s or '') ] @@ -738,12 +731,13 @@ class MitogenViaSpec(Spec): ) def ssh_args(self): + local_vars = self._task_vars.get("hostvars", {}).get(self._inventory_name, {}) return [ mitogen.core.to_text(term) for s in ( - C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), - C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})), - C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=local_vars), + C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=local_vars), + C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=local_vars) ) for term in ansible.utils.shlex.shlex_split(s) if s diff --git a/docs/_templates/ansible.html b/docs/_templates/ansible.html deleted file mode 100644 index 770e0a45..00000000 --- a/docs/_templates/ansible.html +++ /dev/null @@ -1,14 +0,0 @@ - -Mitogen for Ansible (Redirect) - - - - diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 97771afa..a01b497f 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -14,23 +14,5 @@ {% block footer %} {{ super() }} - - - - - {% endblock %} diff --git a/docs/_templates/piwik-config.js b/docs/_templates/piwik-config.js deleted file mode 100644 index ad24c9f6..00000000 --- a/docs/_templates/piwik-config.js +++ /dev/null @@ -1,5 +0,0 @@ -window._paq = []; -window._paq.push(['trackPageView']); -window._paq.push(['enableLinkTracking']); -window._paq.push(['enableHeartBeatTimer', 30]); -window._paq.push(['setSiteId', 6]); diff --git a/docs/ansible_detailed.rst b/docs/ansible_detailed.rst index e6f7b42c..fed347c8 100644 --- a/docs/ansible_detailed.rst +++ b/docs/ansible_detailed.rst @@ -75,34 +75,6 @@ Installation ``mitogen_host_pinned`` strategies exists to mimic the ``free`` and ``host_pinned`` strategies. -4. - - .. raw:: html - -
- - - Get notified of new releases and important fixes. - -

-
- - - - - - -

- - - -

-

- Demo ~~~~ @@ -165,7 +137,9 @@ Noteworthy Differences +-----------------+-----------------+ | 8 | 3.9 - 3.12 | +-----------------+-----------------+ - | 9 | 3.10 - 3.12 | + | 9 | | + +-----------------+ 3.10 - 3.12 | + | 10 | | +-----------------+-----------------+ Verify your installation is running one of these versions by checking @@ -271,15 +245,14 @@ Noteworthy Differences * "Module Replacer" style modules are not supported. These rarely appear in practice, and light web searches failed to reveal many examples of them. -.. - * The ``ansible_python_interpreter`` variable is parsed using a restrictive - :mod:`shell-like ` syntax, permitting values such as ``/usr/bin/env - FOO=bar python`` or ``source /opt/rh/rh-python36/enable && python``, which - occur in practice. Jinja2 templating is also supported for complex task-level - interpreter settings. Ansible `documents this - `_ - as an absolute path, however the implementation passes it unquoted through - the shell, permitting arbitrary code to be injected. +* The ``ansible_python_interpreter`` variable is parsed using a restrictive + :mod:`shell-like ` syntax, permitting values such as ``/usr/bin/env + FOO=bar python`` or ``source /opt/rh/rh-python36/enable && python``. + Jinja2 templating is also supported for complex task-level + interpreter settings. Ansible documents `ansible_python_interpreter + `_ + as an absolute path and releases since June 2024 (e.g. Ansible 10.1) + reflect this. Older Ansible releases passed it to the shell unquoted. .. * Configurations will break that rely on the `hashbang argument splitting @@ -1418,20 +1391,3 @@ Despite the small margin for optimization, Mitogen still manages **6.2x less bandwidth and 1.8x less time**. .. image:: images/ansible/pcaps/costapp-uk-india.svg - - -.. raw:: html - - - diff --git a/docs/changelog.rst b/docs/changelog.rst index 53c1d6c8..883d09d2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,10 +22,49 @@ Unreleased ---------- * :gh:issue:`636` os.chdir fails if the sudo/become user lacks adequate permissions to chdir prior to task +* :gh:issue:`1127` :mod:`mitogen`: Consolidate mitogen backward compatibility + fallbacks and polyfills into :mod:`mitogen.core` +* :gh:issue:`1127` :mod:`ansible_mitogen`: Remove backward compatibility + fallbacks for Python 2.4 & 2.5. +* :gh:issue:`1127` :mod:`ansible_mitogen`: Remove fallback imports for Ansible + releases before 2.10 +* :gh:issue:`1127` :mod:`ansible_mitogen`: Consolidate Python 2 & 3 + compatibility +* :gh:issue:`1128` CI: Start migration from Azure DevOps to GitHub Actions + + +v0.3.10 (2024-09-20) +-------------------- + +* :gh:issue:`950` Fix Solaris/Illumos/SmartOS compatibility with become +* :gh:issue:`1087` Fix :exc:`mitogen.core.StreamError` when Ansible template + module is called with a ``dest:`` filename that has an extension +* :gh:issue:`1110` Fix :exc:`mitogen.core.StreamError` when Ansible copy + module is called with a file larger than 124 kibibytes + (:data:`ansible_mitogen.connection.Connection.SMALL_FILE_LIMIT`) +* :gh:issue:`905` Initial support for templated ``ansible_ssh_args``, + ``ansible_ssh_common_args``, and ``ansible_ssh_extra_args`` variables. + NB: play or task scoped variables will probably still fail. +* :gh:issue:`694` CI: Fixed a race condition and some resource leaks causing + some of intermittent failures when running the test suite. + + +v0.3.9 (2024-08-13) +------------------- + +* :gh:issue:`1097` Respect `ansible_facts.discovered_interpreter_python` when + executing non new-style modules (e.g. JSONARGS style, WANT_JSON style). +* :gh:issue:`1074` Support Ansible 10 (ansible-core 2.17) + + +v0.3.8 (2024-07-30) +------------------- + * :gh:issue:`952` Fix Ansible `--ask-become-pass`, add test coverage * :gh:issue:`957` Fix Ansible exception when executing against 10s of hosts "ValueError: filedescriptor out of range in select()" * :gh:issue:`1066` Support Ansible `ansible_host_key_checking` & `ansible_ssh_host_key_checking` +* :gh:issue:`1090` CI: Migrate macOS integration tests to macOS 12, drop Python 2.7 jobs v0.3.7 (2024-04-08) diff --git a/docs/conf.py b/docs/conf.py index ad9771b4..b7dd1525 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,7 @@ import sys sys.path.append('.') -VERSION = '0.3.7' +VERSION = '0.3.9' author = u'Network Genomics' copyright = u'2021, the Mitogen authors' @@ -16,7 +16,6 @@ html_show_copyright = False html_show_sourcelink = False html_show_sphinx = False html_sidebars = {'**': ['globaltoc.html', 'github.html']} -html_additional_pages = {'ansible': 'ansible.html'} html_static_path = ['_static'] html_theme = 'alabaster' html_theme_options = { diff --git a/docs/contributors.rst b/docs/contributors.rst index ed7fef11..e40607a0 100644 --- a/docs/contributors.rst +++ b/docs/contributors.rst @@ -116,6 +116,7 @@ sponsorship and outstanding future-thinking of its early adopters.