Merge remote-tracking branch 'origin/master' into issue1086

pull/1094/head
timansky 2 days ago
commit 88eb64745e

@ -2,7 +2,7 @@
# `.ci`
This directory contains scripts for Continuous Integration platforms. Currently
Azure Pipelines, but they will also happily run on any Debian-like machine.
GitHub Actions, but ideally they will also run on any Debian-like machine.
The scripts are usually split into `_install` and `_test` steps. The `_install`
step will damage your machine, the `_test` step will just run the tests the way
@ -28,14 +28,15 @@ for doing `setup.py install` while pulling a Docker container, for example.
### Environment Variables
* `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
simultaneously, which speeds things up. This is a space-separated list of
DISTRO names, but additionally, supports:
* `MITOGEN_TEST_DISTRO_SPECS`: a space delimited list of distro specs to run
the tests against. (e.g. `centos6 ubuntu2004-py3*4`). Each spec determines
the Linux distribution, target Python interepreter & number of instances.
Only distributions with a pre-built Linux container image can be used.
* `debian-py3`: when generating Ansible inventory file, set
`ansible_python_interpreter` to `python3`, i.e. run a test where the
target interpreter is Python 3.
* `debian*16`: generate 16 Docker containers running Debian. Also works
with -py3.
* `MITOGEN_TEST_IMAGE_TEMPLATE`: specifies the Linux container image name,
and hence the container registry used for test targets.

@ -35,13 +35,13 @@ ci_lib.check_stray_processes(interesting)
with ci_lib.Fold('docker_setup'):
containers = ci_lib.container_specs(ci_lib.DISTROS)
containers = ci_lib.container_specs(ci_lib.DISTRO_SPECS.split())
ci_lib.start_containers(containers)
with ci_lib.Fold('job_setup'):
os.chdir(TESTS_DIR)
os.chmod('../data/docker/mitogen__has_sudo_pubkey.key', int('0600', 7))
os.chmod('../data/docker/mitogen__has_sudo_pubkey.key', int('0600', 8))
ci_lib.run("mkdir %s", HOSTS_DIR)
for path in glob.glob(TESTS_DIR + '/hosts/*'):

@ -1,105 +0,0 @@
# Each step entry runs a task (Azure Pipelines analog of an Ansible module).
# https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/?view=azure-pipelines&viewFallbackFrom=azure-devops#tool
# `{script: ...}` is shorthand for `{task: CmdLine@<mumble>, inputs: {script: ...}}`.
# The shell is bash.
# https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps-script?view=azure-pipelines
# https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/cmd-line-v2?view=azure-pipelines
steps:
- task: UsePythonVersion@0
displayName: Install python
inputs:
githubToken: '$(GITHUB_PYVER_TOKEN)'
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
set -o pipefail
sudo apt-get update
sudo apt-get install -y python2-dev python3-pip virtualenv
displayName: Install build deps
condition: and(eq(variables['python.version'], ''), eq(variables['Agent.OS'], 'Linux'))
- script: |
set -o errexit
set -o nounset
set -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
displayName: Show python versions
- script: |
set -o errexit
set -o nounset
set -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", "$(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
displayName: Install tooling
- script: |
set -o errexit
set -o nounset
set -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", "$(tox.env)"))')
if [[ -z $PYTHON ]]; then
echo 1>&2 "Python interpreter could not be determined"
exit 1
fi
"$PYTHON" -m tox -e "$(tox.env)"
displayName: "Run tests"

@ -1,157 +0,0 @@
# Python package
# Create and test a Python package on multiple Python versions.
# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
# User defined variables are also injected as environment variables
# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables#environment-variables
#variables:
#ANSIBLE_VERBOSITY: 3
trigger:
branches:
include:
- "*"
exclude:
- docs-master
jobs:
- 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-12-Readme.md
vmImage: macOS-12
strategy:
matrix:
Mito_312:
tox.env: py312-mode_mitogen
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/ubuntu/Ubuntu2004-Readme.md
vmImage: ubuntu-20.04
steps:
- template: azure-pipelines-steps.yml
strategy:
matrix:
Mito_27_centos6:
tox.env: py27-mode_mitogen-distro_centos6
Mito_27_centos7:
tox.env: py27-mode_mitogen-distro_centos7
Mito_27_centos8:
tox.env: py27-mode_mitogen-distro_centos8
Mito_27_debian9:
tox.env: py27-mode_mitogen-distro_debian9
Mito_27_debian10:
tox.env: py27-mode_mitogen-distro_debian10
Mito_27_debian11:
tox.env: py27-mode_mitogen-distro_debian11
Mito_27_ubuntu1604:
tox.env: py27-mode_mitogen-distro_ubuntu1604
Mito_27_ubuntu1804:
tox.env: py27-mode_mitogen-distro_ubuntu1804
Mito_27_ubuntu2004:
tox.env: py27-mode_mitogen-distro_ubuntu2004
Mito_36_centos6:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_centos6
Mito_36_centos7:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_centos7
Mito_36_centos8:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_centos8
Mito_36_debian9:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_debian9
Mito_36_debian10:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_debian10
Mito_36_debian11:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_debian11
Mito_36_ubuntu1604:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_ubuntu1604
Mito_36_ubuntu1804:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_ubuntu1804
Mito_36_ubuntu2004:
python.version: '3.6'
tox.env: py36-mode_mitogen-distro_ubuntu2004
Mito_312_centos6:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_centos6
Mito_312_centos7:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_centos7
Mito_312_centos8:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_centos8
Mito_312_debian9:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_debian9
Mito_312_debian10:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_debian10
Mito_312_debian11:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_debian11
Mito_312_ubuntu1604:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_ubuntu1604
Mito_312_ubuntu1804:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_ubuntu1804
Mito_312_ubuntu2004:
python.version: '3.12'
tox.env: py312-mode_mitogen-distro_ubuntu2004
Ans_27_210:
tox.env: py27-mode_ansible-ansible2.10
Ans_27_4:
tox.env: py27-mode_ansible-ansible4
Ans_36_210:
python.version: '3.6'
tox.env: py36-mode_ansible-ansible2.10
Ans_36_4:
python.version: '3.6'
tox.env: py36-mode_ansible-ansible4
Ans_311_210:
python.version: '3.11'
tox.env: py311-mode_ansible-ansible2.10
Ans_311_3:
python.version: '3.11'
tox.env: py311-mode_ansible-ansible3
Ans_311_4:
python.version: '3.11'
tox.env: py311-mode_ansible-ansible4
Ans_311_5:
python.version: '3.11'
tox.env: py311-mode_ansible-ansible5
Ans_312_6:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible6
Ans_312_7:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible7
Ans_312_8:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible8
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

@ -28,6 +28,10 @@ os.chdir(
)
DISTRO_SPECS = os.environ.get(
'MITOGEN_TEST_DISTRO_SPECS',
'centos6 centos8 debian9 debian11 ubuntu1604 ubuntu2004',
)
IMAGE_TEMPLATE = os.environ.get(
'MITOGEN_TEST_IMAGE_TEMPLATE',
'public.ecr.aws/n5z0e8q9/%(distro)s-test',
@ -196,10 +200,6 @@ class Fold(object):
GIT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
# Used only when MODE=mitogen
DISTRO = os.environ.get('DISTRO', 'debian9')
# Used only when MODE=ansible
DISTROS = os.environ.get('DISTROS', 'centos6 centos8 debian9 debian11 ubuntu1604 ubuntu2004').split()
TMP = TempDir().path

@ -3,8 +3,6 @@
from __future__ import print_function
import getpass
import io
import os
import subprocess
import sys
@ -53,22 +51,13 @@ with ci_lib.Fold('machine_prep'):
subprocess.check_call('sudo chmod 700 ~root/.ssh', shell=True)
subprocess.check_call('sudo chmod 600 ~root/.ssh/authorized_keys', shell=True)
os.chdir(IMAGE_PREP_DIR)
ci_lib.run("ansible-playbook -c local -i localhost, macos_localhost.yml")
if os.path.expanduser('~mitogen__user1') == '~mitogen__user1':
os.chdir(IMAGE_PREP_DIR)
ci_lib.run("ansible-playbook -c local -i localhost, _user_accounts.yml")
# FIXME Don't hardcode https://github.com/mitogen-hq/mitogen/issues/1022
# and os.environ['USER'] is not populated on Azure macOS runners.
os.chdir(HOSTS_DIR)
with io.open('default.hosts', 'r+', encoding='utf-8') as f:
user = getpass.getuser()
content = f.read()
content = content.replace("{{ lookup('pipe', 'whoami') }}", user)
f.seek(0)
f.write(content)
f.truncate()
ci_lib.dump_file('default.hosts')
cmd = ';'.join([
'from __future__ import print_function',
'import os, sys',

@ -8,8 +8,6 @@ import ci_lib
os.environ.update({
'NOCOVERAGE': '1',
'UNIT2': '/usr/local/python2.4.6/bin/unit2',
'MITOGEN_TEST_DISTRO': ci_lib.DISTRO,
'MITOGEN_LOG_LEVEL': 'debug',
'SKIP_ANSIBLE': '1',
})

@ -6,7 +6,6 @@ import os
import ci_lib
os.environ.update({
'MITOGEN_TEST_DISTRO': ci_lib.DISTRO,
'MITOGEN_LOG_LEVEL': 'debug',
'SKIP_ANSIBLE': '1',
})

@ -48,99 +48,33 @@ jobs:
- 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
- name: Ans_313_6
python_version: '3.13'
tox_env: py313-mode_ansible-ansible6
- name: Ans_313_7
python_version: '3.13'
tox_env: py313-mode_ansible-ansible7
- name: Ans_313_8
python_version: '3.13'
tox_env: py313-mode_ansible-ansible8
- name: Ans_313_9
python_version: '3.13'
tox_env: py313-mode_ansible-ansible9
- name: Ans_313_10
python_version: '3.13'
tox_env: py313-mode_ansible-ansible10
- name: Van_313_10
python_version: '3.13'
tox_env: py313-mode_ansible-ansible10-strategy_linear
- name: Mito_27
tox_env: py27-mode_mitogen
- name: Mito_36
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
tox_env: py36-mode_mitogen
- name: Mito_313
python_version: '3.13'
tox_env: py313-mode_mitogen
steps:
- uses: actions/checkout@v4
@ -222,28 +156,22 @@ jobs:
"$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
# https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md
runs-on: macos-13
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: Mito_313
tox_env: py313-mode_mitogen
- name: Loc_27_210
tox_env: py27-mode_localhost-ansible2.10
- name: Loc_312_10
tox_env: py312-mode_localhost-ansible10
- name: Loc_313_10
tox_env: py313-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
- name: Van_313_10
tox_env: py313-mode_localhost-ansible10-strategy_linear
steps:
- uses: actions/checkout@v4
@ -324,3 +252,15 @@ jobs:
fi
"$PYTHON" -m tox -e "${{ matrix.tox_env }}"
# https://github.com/marketplace/actions/alls-green
check:
if: always()
needs:
- linux
- macos
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

@ -1,9 +1,11 @@
# Mitogen
[![PyPI - Version](https://img.shields.io/pypi/v/mitogen)](https://pypi.org/project/mitogen/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mitogen)](https://pypi.org/project/mitogen/)
[![Build Status](https://img.shields.io/github/actions/workflow/status/mitogen-hq/mitogen/tests.yml?branch=master)](https://github.com/mitogen-hq/mitogen/actions?query=branch%3Amaster)
<a href="https://mitogen.networkgenomics.com/">Please see the documentation</a>.
![](https://i.imgur.com/eBM6LhJ.gif)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/mitogen-hq/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mitogen-hq/mitogen/alerts/)
[![Build Status](https://dev.azure.com/mitogen-hq/mitogen/_apis/build/status/mitogen-hq.mitogen?branchName=master)](https://dev.azure.com/mitogen-hq/mitogen/_build/latest?definitionId=1&branchName=master)

@ -159,6 +159,7 @@ def _connect_ssh(spec):
}
}
def _connect_buildah(spec):
"""
Return ContextService arguments for a Buildah connection.
@ -174,6 +175,7 @@ def _connect_buildah(spec):
}
}
def _connect_docker(spec):
"""
Return ContextService arguments for a Docker connection.
@ -277,6 +279,7 @@ def _connect_podman(spec):
}
}
def _connect_setns(spec, kind=None):
"""
Return ContextService arguments for a mitogen_setns connection.
@ -812,7 +815,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
self.context = dct['context']
self.chain = CallChain(self, self.context, pipelined=True)
if self._play_context.become:
if self.become:
self.login_context = dct['via']
else:
self.login_context = self.context
@ -888,6 +891,29 @@ class Connection(ansible.plugins.connection.ConnectionBase):
self.binding.close()
self.binding = None
def _mitogen_var_options(self, templar):
# Workaround for https://github.com/ansible/ansible/issues/84238
var_names = C.config.get_plugin_vars('connection', self._load_name)
variables = templar.available_variables
var_options = {
var_name: templar.template(variables[var_name])
for var_name in var_names
if var_name in variables
}
if self.allow_extras:
extras_var_prefix = 'ansible_%s_' % self.extras_prefix
var_options['_extras'] = {
var_name: templar.template(variables[var_name])
for var_name in variables
if var_name not in var_options
and var_name.startswith(extras_var_prefix)
}
else:
var_options['_extras'] = {}
return var_options
reset_compat_msg = (
'Mitogen only supports "reset_connection" on Ansible 2.5.6 or later'
)
@ -920,11 +946,24 @@ class Connection(ansible.plugins.connection.ConnectionBase):
shared_loader_obj=0
)
# Workaround for https://github.com/ansible/ansible/issues/84238
try:
task, templar = self._play_context.vars.pop(
'_mitogen.smuggled.reset_connection',
)
except KeyError:
pass
else:
self.set_options(
task_keys=task.dump_attrs(),
var_options=self._mitogen_var_options(templar),
)
# Clear out state in case we were ever connected.
self.close()
inventory_name, stack = self._build_stack()
if self._play_context.become:
if self.become:
stack = stack[:-1]
worker_model = ansible_mitogen.process.get_worker_model()

@ -36,8 +36,6 @@ import random
import traceback
import ansible
import ansible.constants
import ansible.plugins
import ansible.plugins.action
import ansible.utils.unsafe_proxy
import ansible.vars.clean
@ -296,7 +294,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
if not path.startswith('~'):
# /home/foo -> /home/foo
return path
if sudoable or not self._play_context.become:
if sudoable or not self._connection.become:
if path == '~':
# ~ -> /home/dmw
return self._connection.homedir

@ -32,35 +32,20 @@ __metaclass__ = type
import os.path
import sys
from ansible.plugins.connection.ssh import (
DOCUMENTATION as _ansible_ssh_DOCUMENTATION,
)
DOCUMENTATION = """
name: mitogen_ssh
author: David Wilson <dw@botanicus.net>
connection: mitogen_ssh
short_description: Connect over SSH via Mitogen
description:
- This connects using an OpenSSH client controlled by the Mitogen for
Ansible extension. It accepts every option the vanilla ssh plugin
accepts.
version_added: "2.5"
options:
ssh_args:
type: str
vars:
- name: ssh_args
- name: ansible_ssh_args
- name: ansible_mitogen_ssh_args
ssh_common_args:
type: str
vars:
- name: ssh_args
- name: ansible_ssh_common_args
- name: ansible_mitogen_ssh_common_args
ssh_extra_args:
type: str
vars:
- name: ssh_args
- name: ansible_ssh_extra_args
- name: ansible_mitogen_ssh_extra_args
"""
""" + _ansible_ssh_DOCUMENTATION.partition('options:\n')[2]
try:
import ansible_mitogen

@ -45,6 +45,7 @@ import ansible_mitogen.mixins
import ansible_mitogen.process
import ansible.executor.process.worker
import ansible.template
import ansible.utils.sentinel
@ -326,3 +327,24 @@ class StrategyMixin(object):
self._worker_model.on_strategy_complete()
finally:
ansible_mitogen.process.set_worker_model(None)
def _smuggle_to_connction_reset(self, task, play_context, iterator, target_host):
# Workaround for https://github.com/ansible/ansible/issues/84238
variables = self._variable_manager.get_vars(
play=iterator._play, host=target_host, task=task,
_hosts=self._hosts_cache, _hosts_all=self._hosts_cache_all,
)
templar = ansible.template.Templar(
loader=self._loader, variables=variables,
)
play_context.vars.update({
'_mitogen.smuggled.reset_connection': (task, templar),
})
def _execute_meta(self, task, play_context, iterator, target_host):
if task.args['_raw_params'] == 'reset_connection':
self._smuggle_to_connction_reset(task, play_context, iterator, target_host)
return super(StrategyMixin, self)._execute_meta(
task, play_context, iterator, target_host,
)

@ -62,7 +62,9 @@ from __future__ import unicode_literals
__metaclass__ = type
import abc
import logging
import os
import ansible.utils.shlex
import ansible.constants as C
import ansible.executor.interpreter_discovery
@ -74,6 +76,9 @@ from ansible.module_utils.parsing.convert_bool import boolean
import mitogen.core
LOG = logging.getLogger(__name__)
def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_python):
"""
Triggers ansible python interpreter discovery if requested.
@ -84,12 +89,12 @@ def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_pyth
# keep trying different interpreters until we don't error
if action._finding_python_interpreter:
return action._possible_python_interpreter
if s in ['auto', 'auto_legacy', 'auto_silent', 'auto_legacy_silent']:
# python is the only supported interpreter_name as of Ansible 2.8.8
interpreter_name = 'python'
discovered_interpreter_config = u'discovered_interpreter_%s' % interpreter_name
if task_vars.get('ansible_facts') is None:
task_vars['ansible_facts'] = {}
@ -130,7 +135,7 @@ def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_pyth
def parse_python_path(s, task_vars, action, rediscover_python):
"""
Given the string set for ansible_python_interpeter, parse it using shell
syntax and return an appropriate argument vector. If the value detected is
syntax and return an appropriate argument vector. If the value detected is
one of interpreter discovery then run that first. Caches python interpreter
discovery value in `facts_from_task_vars` like how Ansible handles this.
"""
@ -208,6 +213,12 @@ class Spec(with_metaclass(abc.ABCMeta, object)):
:data:`True` if privilege escalation should be active.
"""
@abc.abstractmethod
def become_flags(self):
"""
The command line arguments passed to the become executable.
"""
@abc.abstractmethod
def become_method(self):
"""
@ -285,10 +296,9 @@ class Spec(with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod
def sudo_args(self):
"""
The list of additional arguments that should be included in a become
The list of additional arguments that should be included in a sudo
invocation.
"""
# TODO: split out into sudo_args/become_args.
@abc.abstractmethod
def mitogen_via(self):
@ -412,6 +422,43 @@ class PlayContextSpec(Spec):
# used to run interpreter discovery
self._action = connection._action
def _become_option(self, name):
plugin = self._connection.become
try:
return plugin.get_option(name, self._task_vars, self._play_context)
except AttributeError:
# A few ansible_mitogen connection plugins look more like become
# plugins. They don't quite fit Ansible's plugin.get_option() API.
# https://github.com/mitogen-hq/mitogen/issues/1173
fallback_plugins = {'mitogen_doas', 'mitogen_sudo', 'mitogen_su'}
if self._connection.transport not in fallback_plugins:
raise
fallback_options = {
'become_exe',
'become_flags',
}
if name not in fallback_options:
raise
LOG.info(
'Used fallback=PlayContext.%s for plugin=%r, option=%r',
name, self._connection, name,
)
return getattr(self._play_context, name)
def _connection_option(self, name, fallback_attr=None):
try:
return self._connection.get_option(name, hostvars=self._task_vars)
except KeyError:
if fallback_attr is None:
fallback_attr = name
LOG.info(
'Used fallback=PlayContext.%s for plugin=%r, option=%r',
fallback_attr, self._connection, name,
)
return getattr(self._play_context, fallback_attr)
def transport(self):
return self._transport
@ -419,40 +466,31 @@ class PlayContextSpec(Spec):
return self._inventory_name
def remote_addr(self):
return self._play_context.remote_addr
return self._connection_option('host', fallback_attr='remote_addr')
def remote_user(self):
return self._play_context.remote_user
return self._connection_option('remote_user')
def become(self):
return self._play_context.become
return self._connection.become
def become_flags(self):
return self._become_option('become_flags')
def become_method(self):
return self._play_context.become_method
return self._connection.become.name
def become_user(self):
return self._play_context.become_user
return self._become_option('become_user')
def become_pass(self):
# become_pass is owned/provided by the active become plugin. However
# PlayContext is intertwined with it. Known complications
# - ansible_become_password is higher priority than ansible_become_pass,
# `play_context.become_pass` doesn't obey this (atleast with Mitgeon).
# - `meta: reset_connection` runs `connection.reset()` but
# `ansible_mitogen.connection.Connection.reset()` recreates the
# connection object, setting `connection.become = None`.
become_plugin = self._connection.become
try:
become_pass = become_plugin.get_option('become_pass', playcontext=self._play_context)
except AttributeError:
become_pass = self._play_context.become_pass
return optional_secret(become_pass)
return optional_secret(self._become_option('become_pass'))
def password(self):
return optional_secret(self._play_context.password)
return optional_secret(self._connection_option('password'))
def port(self):
return self._play_context.port
return self._connection_option('port')
def python_path(self, rediscover_python=False):
s = self._connection.get_task_var('ansible_python_interpreter')
@ -466,18 +504,13 @@ class PlayContextSpec(Spec):
rediscover_python=rediscover_python)
def host_key_checking(self):
def candidates():
yield self._connection.get_task_var('ansible_ssh_host_key_checking')
yield self._connection.get_task_var('ansible_host_key_checking')
yield C.HOST_KEY_CHECKING
val = next((v for v in candidates() if v is not None), True)
return boolean(val)
return self._connection_option('host_key_checking')
def private_key_file(self):
return self._play_context.private_key_file
return self._connection_option('private_key_file')
def ssh_executable(self):
return C.config.get_config_value("ssh_executable", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {}))
return self._connection_option('ssh_executable')
def timeout(self):
return self._play_context.timeout
@ -490,42 +523,21 @@ 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=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)
self._connection_option('ssh_args'),
self._connection_option('ssh_common_args'),
self._connection_option('ssh_extra_args'),
)
for term in ansible.utils.shlex.shlex_split(s or '')
]
def become_exe(self):
# In Ansible 2.8, PlayContext.become_exe always has a default value due
# to the new options mechanism. Previously it was only set if a value
# ("somewhere") had been specified for the task.
# For consistency in the tests, here we make older Ansibles behave like
# newer Ansibles.
exe = self._play_context.become_exe
if exe is None and self._play_context.become_method == 'sudo':
exe = 'sudo'
return exe
return self._become_option('become_exe')
def sudo_args(self):
return [
mitogen.core.to_text(term)
for term in ansible.utils.shlex.shlex_split(
first_true((
self._play_context.become_flags,
# Ansible <=2.7.
getattr(self._play_context, 'sudo_flags', ''),
# Ansible <=2.3.
getattr(C, 'DEFAULT_BECOME_FLAGS', ''),
getattr(C, 'DEFAULT_SUDO_FLAGS', '')
), default='')
)
]
return ansible.utils.shlex.shlex_split(self.become_flags() or '')
def mitogen_via(self):
return self._connection.get_task_var('mitogen_via')
@ -660,6 +672,9 @@ class MitogenViaSpec(Spec):
def become(self):
return bool(self._become_user)
def become_flags(self):
return self._host_vars.get('ansible_become_flags')
def become_method(self):
return (
self._become_method or
@ -678,6 +693,7 @@ class MitogenViaSpec(Spec):
def password(self):
return optional_secret(
self._host_vars.get('ansible_ssh_password') or
self._host_vars.get('ansible_ssh_pass') or
self._host_vars.get('ansible_password')
)
@ -754,7 +770,7 @@ class MitogenViaSpec(Spec):
mitogen.core.to_text(term)
for s in (
self._host_vars.get('ansible_sudo_flags') or '',
self._host_vars.get('ansible_become_flags') or '',
self.become_flags() or '',
)
for term in ansible.utils.shlex.shlex_split(s)
]

@ -132,13 +132,13 @@ Noteworthy Differences
| 5 | 3.8 - 3.11 |
+-----------------+-----------------+
| 6 | |
+-----------------+ 3.8 - 3.12 |
+-----------------+ 3.8 - 3.13 |
| 7 | |
+-----------------+-----------------+
| 8 | 3.9 - 3.12 |
| 8 | 3.9 - 3.13 |
+-----------------+-----------------+
| 9 | |
+-----------------+ 3.10 - 3.12 |
+-----------------+ 3.10 - 3.13 |
| 10 | |
+-----------------+-----------------+
@ -306,7 +306,8 @@ container.
* Intermediary machines cannot use login and become passwords that were
supplied to Ansible interactively. If an intermediary requires a
password, it must be supplied via ``ansible_ssh_pass``,
``ansible_password``, or ``ansible_become_pass`` inventory variables.
``ansible_ssh_password``, ``ansible_password``, or
``ansible_become_pass`` inventory variables.
* Automatic tunnelling of SSH-dependent actions, such as the
``synchronize`` module, is not yet supported. This will be addressed in a
@ -1011,7 +1012,8 @@ Like the :ans:conn:`ssh` except connection delegation is supported.
* ``ansible_port``, ``ssh_port``
* ``ansible_ssh_executable``, ``ssh_executable``
* ``ansible_ssh_private_key_file``
* ``ansible_ssh_pass``, ``ansible_password`` (default: assume passwordless)
* ``ansible_ssh_pass``, ``ansible_ssh_password``, ``ansible_password``
(default: assume passwordless)
* ``ssh_args``, ``ssh_common_args``, ``ssh_extra_args``
* ``mitogen_mask_remote_name``: if :data:`True`, mask the identity of the
Ansible controller process on remote machines. To simplify diagnostics,
@ -1273,7 +1275,7 @@ on each process whose name begins with ``mitogen:``::
[pid 29858] futex(0x55ea9be52f60, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, NULL, 0xffffffff
^C
$
$
This shows one thread waiting on IO (``poll``) and two more waiting on the same
lock. It is taken from a real example of a deadlock due to a forking bug.

@ -18,7 +18,89 @@ To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub <https://github.com/mitogen-hq/mitogen/>`_.
v0.3.11 (2024-10-30)
In progress (unreleased)
------------------------
v0.3.18 (2024-11-07)
--------------------
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated become method
(e.g. ``ansible_become_method``).
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated become flag
(e.g. ``ansible_become_method``, ``become`` keyword).
v0.3.17 (2024-11-07)
--------------------
* :gh:issue:`1182` CI: Fix incorrect world readable/writable file permissions
on SSH key ``mitogen__has_sudo_pubkey.key`` during Ansible tests.
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated SSH private key file
(e.g. ``ansible_private_key_file``).
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated SSH host key checking
(e.g. ``ansible_host_key_checking``, ``ansible_ssh_host_key_checking``).
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated host address
(e.g. ``ansible_host``, ``ansible_ssh_host``)
* :gh:issue:`1184` Test templated SSH host key checking in task vars
v0.3.16 (2024-11-05)
--------------------
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated become executable
(e.g. ``become_exe``).
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated become executable
arguments (e.g. ``become_flags``).
* :gh:issue:`1083` :mod:`ansible_mitogen`: Templated ssh executable
(``ansible_ssh_executable``).
* :gh:issue:`1083` :mod:`ansible_mitogen`: Fixed templated connection options
during a ``meta: reset_connection`` task.
* :gh:issue:`1129` CI: Migrated macOS 12 runners to macOS 13, due to EOL.
v0.3.15 (2024-10-28)
--------------------
* :gh:issue:`905` :mod:`ansible_mitogen`: Support templated SSH command
arguments (e.g. ``ansible_ssh_args``, ``ansible_ssh_extra_args``).
* :gh:issue:`692` tests: Fix and re-enable several sudo tests
* :gh:issue:`1083` :mod:`ansible_mitogen`: Support templated become password
(e.g. ``ansible_become_pass``, ``ansible_sudo_pass``)
v0.3.14 (2024-10-16)
--------------------
* :gh:issue:`1159` CI: Reduce number of Jobs by parameterizing Mitogen Docker
SSH tests
* :gh:issue:`1083` :mod:`ansible_mitogen`: Support templated become username.
v0.3.13 (2024-10-09)
--------------------
* :gh:issue:`1138` CI: Complete migration from Azure DevOps Pipelines to
GitHub Actions
* :gh:issue:`1116` :mod:`ansible_mitogen`: Support for templated variable
`ansible_ssh_user`.
* :gh:issue:`978` :mod:`ansible_mitogen`: Support templated Ansible SSH port.
* :gh:issue:`1073` Python 3.13 support
v0.3.12 (2024-10-07)
--------------------
* :gh:issue:`1106` :mod:`ansible_mitogen`: Support for `ansible_ssh_password`
connection variable, and templated SSH connection password.
* :gh:issue:`1136` tests: Improve Ansible fail_msg formatting.
* :gh:issue:`1137` tests: Ignore inventory files of inactive tests & benchmarks
* :gh:issue:`1138` CI: Add re-actors/alls-green GitHub Actions job to simplify
branch protections configuration.
v0.3.11 (2024-09-30)
--------------------
* :gh:issue:`1127` :mod:`mitogen`: Consolidate mitogen backward compatibility
@ -101,7 +183,7 @@ v0.3.4 (2023-07-02)
* :gh:issue:`929` Support Ansible 6 and ansible-core 2.13
* :gh:issue:`832` Fix runtime error when using the ansible.builtin.dnf module multiple times
* :gh:issue:`925` :class:`ansible_mitogen.connection.Connection` no longer tries to close the
* :gh:issue:`925` :class:`ansible_mitogen.connection.Connection` no longer tries to close the
connection on destruction. This is expected to reduce cases of `mitogen.core.Error: An attempt
was made to enqueue a message with a Broker that has already exitted`. However it may result in
resource leaks.

@ -127,11 +127,14 @@ sponsorship and outstanding future-thinking of its early adopters.
<li>jgadling</li>
<li>John F Wall &mdash; <em>Making Ansible Great with Massive Parallelism</em></li>
<li><a href="https://github.com/jrosser">Jonathan Rosser</a></li>
<li><a href="https://github.com/jmkeyes">Joshua M. Keyes</a></li>
<li>KennethC</li>
<li><a href="https://github.com/lberruti">Luca Berruti</li>
<li>Lewis Bellwood &mdash; <em>Happy to be apart of a great project.</em></li>
<li>luto</li>
<li><a href="https://mayeu.me/">Mayeu a.k.a Matthieu Maury</a></li>
<li><a href="https://github.com/madsi1m">Michael D'Silva</a></li>
<li><a href="https://github.com/mordekasg">mordek</a></li>
<li><a href="https://twitter.com/nathanhruby">@nathanhruby</a></li>
<li><a href="https://github.com/opoplawski">Orion Poplawski</a></li>
<li><a href="https://github.com/philfry">Philippe Kueck</a></li>

@ -1038,7 +1038,7 @@ receive items in the order they are requested, as they become available.
Mitogen enables SSH compression by default, there are circumstances where
disabling SSH compression is desirable, and many scenarios for future
connection methods where transport-layer compression is not supported at
all.
all.
.. [#f2] Compression may seem redundant, however it is basically free and reducing IO
is always a good idea. The 33% / 200 byte saving may mean the presence or

@ -119,7 +119,7 @@ def _chroot(path):
os.chroot(path)
class Operations(fuse.Operations): # fuse.LoggingMixIn,
class Operations(fuse.Operations): # fuse.LoggingMixIn,
def __init__(self, host, path='.'):
self.host = host
self.root = path

@ -61,7 +61,7 @@ def child_main(sender, delay):
Executed on the main thread of the Python interpreter running on each
target machine, Context.call() from the master. It simply sends the output
of the UNIX 'ps' command at regular intervals toward a Receiver on master.
:param mitogen.core.Sender sender:
The Sender to use for delivering our result. This could target
anywhere, but the sender supplied by the master simply causes results

@ -10,7 +10,6 @@ from __future__ import print_function
import hashlib
import io
import os
import spwd
import mitogen.core
import mitogen.master
@ -57,21 +56,6 @@ def streamy_download_file(context, path):
}
def get_password_hash(username):
"""
Fetch a user's password hash.
"""
try:
h = spwd.getspnam(username)
except KeyError:
return None
# mitogen.core.Secret() is a Unicode subclass with a repr() that hides the
# secret data. This keeps secret stuff out of logs. Like blobs, secrets can
# also be serialized.
return mitogen.core.Secret(h)
def md5sum(path):
"""
Return the MD5 checksum for a file.

@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup.
#: Library version as a tuple.
__version__ = (0, 3, 11)
__version__ = (0, 3, 19, 'dev')
#: This is :data:`False` in slave contexts. Previously it was used to prevent

@ -3290,7 +3290,7 @@ class Router(object):
This can be used from any thread, but its output is only meaningful
from the context of the :class:`Broker` thread, as disconnection or
replacement could happen in parallel on the broker thread at any
moment.
moment.
"""
return (
self._stream_by_id.get(dst_id) or

@ -652,7 +652,7 @@ class ParentImpEnumerationMethod(FinderMethod):
insane) parent package, and if no insane parents exist, simply use
:mod:`sys.path` to search for it from scratch on the filesystem using the
normal Python lookup mechanism.
This is required for older versions of :mod:`ansible.compat.six`,
:mod:`plumbum.colors`, Ansible 2.8 :mod:`ansible.module_utils.distro` and
its submodule :mod:`ansible.module_utils.distro._distro`.

@ -631,7 +631,7 @@ class TimerList(object):
def get_timeout(self):
"""
Return the floating point seconds until the next event is due.
:returns:
Floating point delay, or 0.0, or :data:`None` if no events are
scheduled.

@ -79,6 +79,7 @@ setup(
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: System :: Distributed Computing',
'Topic :: System :: Systems Administration',

@ -7,7 +7,7 @@ started in September 2017. Pull requests in this area are very welcome!
## Running The Tests
[![Build Status](https://dev.azure.com/mitogen-hq/mitogen/_apis/build/status/mitogen-hq.mitogen?branchName=master)](https://dev.azure.com/mitogen-hq/mitogen/_build/latest?definitionId=1&branchName=master)
[![Build Status](https://img.shields.io/github/actions/workflow/status/mitogen-hq/mitogen/tests.yml?branch=master)](https://github.com/mitogen-hq/mitogen/actions?query=branch%3Amaster)
Your computer should have an Internet connection, and the ``docker`` command
line tool should be able to connect to a working Docker daemon (localhost or
@ -30,11 +30,19 @@ and run the tests there.
1. Run ``test``
# Selecting a target distribution
# Selecting target distributions
Docker target images exist for testing against CentOS and Debian, with the
default being Debian. To select CentOS, specify `MITOGEN_TEST_DISTRO=centos` in
the environment.
Linux container images for testing are available at
- https://github.com/orgs/mitogen-hq/packages
- https://public.ecr.aws/n5z0e8q9
The images used are determined by two environment variables
- `MITOGEN_TEST_DISTRO_SPECS`
- `MITOGEN_TEST_IMAGE_TEMPLATE`
Defaults for these can be found in `.ci/ci_lib.py` & `tests/testlib.py`
# User Accounts

@ -48,6 +48,7 @@ host_key_checking = False
[inventory]
any_unparsed_is_failed = true
host_pattern_mismatch = error
ignore_extensions = ~, .bak, .disabled
[callback_profile_tasks]
task_output_limit = 10

@ -4,8 +4,7 @@
# When running the tests outside CI, make a single 'target' host which is the
# local machine. The ansible_user override is necessary since some tests want a
# fixed ansible.cfg remote_user setting to test against.
# FIXME Hardcoded by replacement in some CI runs https://github.com/mitogen-hq/mitogen/issues/1022
# and os.environ['USER'] is not populated on Azure macOS runners.
# os.environ['USER'] is an empty string on GitHub Actions macOS runners.
target ansible_host=localhost ansible_user="{{ lookup('pipe', 'whoami') }}"
[test-targets]
@ -18,4 +17,35 @@ ssh-common-args ansible_host=localhost ansible_user="{{ lookup('pipe', 'whoami')
[issue905:vars]
ansible_ssh_common_args=-o PermitLocalCommand=yes -o LocalCommand="touch {{ ssh_args_canary_file }}"
ssh_args_canary_file=/tmp/ssh_args_{{ inventory_hostname }}
ssh_args_canary_file=/tmp/ssh_args_by_inv_{{ inventory_hostname }}
[tt_targets_bare]
tt-bare
[tt_become_bare]
tt-become-bare
[tt_become_bare:vars]
ansible_host=localhost
ansible_user="{{ lookup('pipe', 'whoami') }}"
[tt_become_by_inv]
tt-become ansible_become="{{ 'true' | trim }}" ansible_become_user=root
tt-become-exe ansible_become=true ansible_become_exe="{{ 'sudo' | trim }}" ansible_become_user=root
tt-become-flags ansible_become=true ansible_become_flags="{{ '--set-home --stdin --non-interactive' | trim }}" ansible_become_user=root
tt-become-method ansible_become=true ansible_become_method="{{ 'sudo' | trim }}" ansible_become_user=root
tt-become-pass ansible_become=true ansible_become_pass="{{ 'pw_required_password' | trim }}" ansible_become_user=mitogen__pw_required
tt-become-user ansible_become=true ansible_become_user="{{ 'root' | trim }}"
[tt_become_by_inv:vars]
ansible_host=localhost
ansible_user="{{ lookup('pipe', 'whoami') }}"
[tt_targets_inventory]
tt-host ansible_host="{{ 'localhost' | trim }}" ansible_password=has_sudo_nopw_password ansible_user=mitogen__has_sudo_nopw
tt-host-key-checking ansible_host=localhost ansible_host_key_checking="{{ 'false' | trim }}" ansible_password=has_sudo_nopw_password ansible_user=mitogen__has_sudo_nopw
tt-password ansible_host=localhost ansible_password="{{ 'has_sudo_nopw_password' | trim }}" ansible_user=mitogen__has_sudo_nopw
tt-port ansible_host=localhost ansible_password=has_sudo_nopw_password ansible_port="{{ 22 | int }}" ansible_user=mitogen__has_sudo_nopw
tt-private-key-file ansible_host=localhost ansible_private_key_file="{{ git_basedir }}/tests/data/docker/mitogen__has_sudo_pubkey.key" ansible_user=mitogen__has_sudo_pubkey
tt-remote-user ansible_host=localhost ansible_password=has_sudo_nopw_password ansible_user="{{ 'mitogen__has_sudo_nopw' | trim }}"
tt-ssh-executable ansible_host=localhost ansible_password=has_sudo_nopw_password ansible_ssh_executable="{{ 'ssh' | trim }}" ansible_user=mitogen__has_sudo_nopw

@ -0,0 +1,18 @@
# Ansible removed its default SSH port in May 2021, defering to the SSH
# implementation.
# https://github.com/ansible/ansible/commit/45618a6f3856f7332df8afe4adc40d85649a70da
# Careful templating is needed to preseve the type(s) of expected_ssh_port,
# particularly in combination with the assert_equal action plugin.
# Do: {{ expected_ssh_port }}
# Don't: {{ expected_ssh_port | any_filter }}
# Don't: {% if ...%}{{ expected_ssh_port }}{% else %}...{% endif %}
# https://stackoverflow.com/questions/66102524/ansible-set-fact-type-cast/66104814#66104814
- set_fact:
expected_ssh_port: null
when: ansible_version.full is version('2.11.1', '>=', strict=True)
- set_fact:
expected_ssh_port: 22
when: ansible_version.full is version('2.11.1', '<', strict=True)

@ -77,7 +77,8 @@
that:
- item.stat.checksum == item.item.expected_checksum
quiet: true # Avoid spamming stdout with 400 kB of item.item.content
fail_msg: item={{ item }}
fail_msg: |
item={{ item }}
with_items: "{{ stat.results }}"
loop_control:
label: "{{ item.stat.path }}"

@ -16,7 +16,8 @@
- assert:
that:
- out.stat.mode in ("0644", "0664")
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: "Copy files from content: arg"
copy:
@ -29,7 +30,8 @@
- assert:
that:
- out.stat.mode == "0400"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Cleanup local weird mode file
file:
@ -55,7 +57,8 @@
- assert:
that:
- out.stat.mode in ("0644", "0664")
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Copy file with weird mode, preserving mode
copy:
@ -69,7 +72,8 @@
- assert:
that:
- out.stat.mode == "1462"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Copy file with weird mode, explicit mode
copy:
@ -84,7 +88,8 @@
- assert:
that:
- out.stat.mode == "1461"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Cleanup
file:

@ -15,7 +15,8 @@
- 'raw.rc == 0'
- 'raw.stdout_lines[-1]|to_text == "2"'
- 'raw.stdout[-1]|to_text == "2"'
fail_msg: raw={{raw}}
fail_msg: |
raw={{ raw }}
- name: Run raw module with sudo
become: true
@ -39,6 +40,7 @@
["root\r\n"],
["root"],
)
fail_msg: raw={{raw}}
fail_msg: |
raw={{ raw }}
tags:
- low_level_execute_command

@ -42,13 +42,17 @@
assert:
that:
- good_temp_path == good_temp_path2
fail_msg: good_temp_path={{good_temp_path}} good_temp_path2={{good_temp_path2}}
fail_msg: |
good_temp_path={{ good_temp_path }}
good_temp_path2={{ good_temp_path2 }}
- name: "Verify different subdir for both tasks"
assert:
that:
- tmp_path.path != tmp_path2.path
fail_msg: tmp_path={{tmp_path}} tmp_path2={{tmp_path2}}
fail_msg: |
tmp_path={{ tmp_path }}
tmp_path2={{ tmp_path2 }}
#
# Verify subdirectory removal.
@ -69,7 +73,9 @@
that:
- not stat1.stat.exists
- not stat2.stat.exists
fail_msg: stat1={{stat1}} stat2={{stat2}}
fail_msg: |
stat1={{ stat1 }}
stat2={{ stat2 }}
#
# Verify good directory persistence.
@ -84,7 +90,8 @@
assert:
that:
- stat.stat.exists
fail_msg: stat={{stat}}
fail_msg: |
stat={{ stat }}
#
# Write some junk into the temp path.
@ -107,7 +114,8 @@
- assert:
that:
- not out.stat.exists
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
#
# root
@ -126,23 +134,24 @@
that:
- tmp_path2.path != tmp_path_root.path
- tmp_path2.path|dirname != tmp_path_root.path|dirname
fail_msg: tmp_path_root={{tmp_path_root}} tmp_path2={{tmp_path2}}
fail_msg: |
tmp_path_root={{ tmp_path_root }}
tmp_path2={{ tmp_path2 }}
#
# readonly homedir
#
# TODO: https://github.com/dw/mitogen/issues/692
# - name: "Try writing to temp directory for the readonly_homedir user"
# become: true
# become_user: mitogen__readonly_homedir
# custom_python_run_script:
# script: |
# from ansible.module_utils.basic import get_module_path
# path = get_module_path() + '/foo.txt'
# result['path'] = path
# open(path, 'w').write("bar")
# register: tmp_path
- name: Try writing to temp directory for the readonly_homedir user
become: true
become_user: mitogen__readonly_homedir
custom_python_run_script:
script: |
from ansible.module_utils.basic import get_module_path
path = get_module_path() + '/foo.txt'
result['path'] = path
open(path, 'w').write("bar")
register: tmp_path
#
# modules get the same base dir
@ -157,7 +166,8 @@
that:
- out.module_path.startswith(good_temp_path2)
- out.module_tmpdir.startswith(good_temp_path2)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- make_tmp_path
- mitogen_only

@ -26,7 +26,8 @@
register: out
- assert:
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: "Expand ~/foo with become active. ~ is become_user's home."
action_passthrough:
@ -49,7 +50,8 @@
register: out
- assert:
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: "Expanding $HOME/foo has no effect."
action_passthrough:
@ -60,7 +62,8 @@
register: out
- assert:
that: out.result == '$HOME/foo'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
# ------------------------
@ -73,7 +76,8 @@
register: out
- assert:
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: "sudoable; Expand ~/foo with become active. ~ is become_user's home."
action_passthrough:
@ -97,7 +101,8 @@
register: out
- assert:
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: "sudoable; Expanding $HOME/foo has no effect."
action_passthrough:
@ -108,6 +113,7 @@
register: out
- assert:
that: out.result == '$HOME/foo'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- remote_expand_user

@ -12,7 +12,8 @@
register: out
- assert:
that: out.result == False
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Ensure does-exist does
copy:
@ -24,7 +25,8 @@
register: out
- assert:
that: out.result == True
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Cleanup
file:

@ -23,7 +23,8 @@
- assert:
that:
- not out2.stat.exists
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- stat:
path: "{{out.src|dirname}}"
@ -32,7 +33,8 @@
- assert:
that:
- not out2.stat.exists
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- file:
path: /tmp/remove_tmp_path_test

@ -40,11 +40,11 @@
delegate_to: localhost
run_once: true
# TODO: https://github.com/dw/mitogen/issues/692
# - file:
# path: /tmp/sync-test.out
# state: absent
# become: true
- name: Ensure clean slate
become: true
file:
path: /tmp/sync-test.out
state: absent
# exception: File "/tmp/venv/lib/python2.7/site-packages/ansible/plugins/action/__init__.py", line 129, in cleanup
# exception: self._remove_tmp_path(self._connection._shell.tmpdir)
@ -66,17 +66,18 @@
- assert:
that: outout == "item!"
fail_msg: outout={{outout}}
fail_msg: |
outout={{ outout }}
when: False
# TODO: https://github.com/dw/mitogen/issues/692
# - file:
# path: "{{item}}"
# state: absent
# become: true
# with_items:
# - /tmp/synchronize-action-key
# - /tmp/sync-test
# - /tmp/sync-test.out
- name: Cleanup
become: true
file:
path: "{{ item }}"
state: absent
with_items:
- /tmp/synchronize-action-key
- /tmp/sync-test
- /tmp/sync-test.out
tags:
- synchronize

@ -22,7 +22,8 @@
- assert:
that: |
out.content|b64decode == '{"I am JSON": true}'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Create text transfer data
action_passthrough:
@ -37,7 +38,8 @@
- assert:
that:
out.content|b64decode == 'I am text.'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Cleanup transfer data
file:

@ -33,6 +33,7 @@
- out.results[1].stdout == 'hi-from-job-2'
- out.results[1].rc == 0
- out.results[1].delta > '0:00:05'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- multiple_items_loop

@ -27,7 +27,8 @@
(job.started == 1) and
(job.changed == True) and
(job.finished == 0)
fail_msg: job={{job}}
fail_msg: |
job={{ job }}
- name: busy-poll up to 100000 times
async_status:
@ -52,7 +53,8 @@
- async_out.failed == False
- async_out.msg == "Hello, world."
- 'async_out.stderr == "binary_producing_json: oh noes\n"'
fail_msg: async_out={{async_out}}
fail_msg: |
async_out={{ async_out }}
vars:
async_out: "{{result.content|b64decode|from_json}}"
tags:

@ -38,7 +38,8 @@
- async_out.msg.startswith("Traceback")
- '"ValueError: No start of json char found\n" in async_out.msg'
- 'async_out.stderr == "binary_producing_junk: oh noes\n"'
fail_msg: async_out={{async_out}}
fail_msg: |
async_out={{ async_out }}
vars:
async_out: "{{result.content|b64decode|from_json}}"
tags:

@ -42,14 +42,16 @@
- async_out.start.startswith("20")
- async_out.stderr == "there"
- async_out.stdout == "hi"
fail_msg: async_out={{async_out}}
fail_msg: |
async_out={{ async_out }}
vars:
async_out: "{{result.content|b64decode|from_json}}"
- assert:
that:
- async_out.invocation.module_args.stdin == None
fail_msg: async_out={{async_out}}
fail_msg: |
async_out={{ async_out }}
when:
- ansible_version.full is version('2.4', '>=', strict=True)
vars:

@ -15,7 +15,9 @@
- assert:
that:
- sync_proc1.pid == sync_proc2.pid
fail_msg: sync_proc1={{sync_proc1}} sync_proc2={{sync_proc2}}
fail_msg: |
sync_proc1={{ sync_proc1 }}
sync_proc2={{ sync_proc2 }}
when: is_mitogen
- name: get async process ID.
@ -52,7 +54,9 @@
- sync_proc1.pid == sync_proc2.pid
- async_result1.pid != sync_proc1.pid
- async_result1.pid != async_result2.pid
fail_msg: async_result1={{async_result1}} async_result2={{async_result2}}
fail_msg: |
async_result1={{ async_result1 }}
async_result2={{ async_result2 }}
when: is_mitogen
tags:
- runner_new_process

@ -23,7 +23,8 @@
(job1.started == 1) and
(job1.changed == True) and
(job1.finished == 0)
fail_msg: job1={{job1}}
fail_msg: |
job1={{ job1 }}
- name: busy-poll up to 100000 times
async_status:
@ -48,7 +49,8 @@
- result1.start|length == 26
- result1.finished == 1
- result1.rc == 0
fail_msg: result1={{result1}}
fail_msg: |
result1={{ result1 }}
- assert:
that:
@ -56,14 +58,16 @@
- result1.stderr_lines == []
- result1.stdout == "alldone"
- result1.stdout_lines == ["alldone"]
fail_msg: result1={{result1}}
fail_msg: |
result1={{ result1 }}
when:
- ansible_version.full is version('2.8', '>', strict=True) # ansible#51393
- assert:
that:
- result1.failed == False
fail_msg: result1={{result1}}
fail_msg: |
result1={{ result1 }}
when:
- ansible_version.full is version('2.4', '>', strict=True)
tags:

@ -29,7 +29,8 @@
- result.failed == 1
- result.finished == 1
- result.msg == "Job reached maximum time limit of 1 seconds."
fail_msg: result={{result}}
fail_msg: |
result={{ result }}
tags:
- mitogen_only
- runner_timeout_then_polling

@ -56,12 +56,15 @@
that:
- result1.rc == 0
- result2.rc == 0
fail_msg: result1={{result1}} result2={{result2}}
fail_msg: |
result1={{ result1 }}
result2={{ result2 }}
- assert:
that:
- result2.stdout == 'im_alive'
fail_msg: result2={{result2}}
fail_msg: |
result2={{ result2 }}
when:
- ansible_version.full is version('2.8', '>=', strict=True) # ansible#51393
tags:

@ -21,6 +21,7 @@
job1.msg == "async task did not complete within the requested time" or
job1.msg == "async task did not complete within the requested time - 1s" or
job1.msg == "Job reached maximum time limit of 1 seconds."
fail_msg: job1={{job1}}
fail_msg: |
job1={{ job1 }}
tags:
- runner_with_polling_and_timeout

@ -5,3 +5,7 @@
- import_playbook: sudo_nopassword.yml
- import_playbook: sudo_password.yml
- import_playbook: sudo_requiretty.yml
- import_playbook: templated_by_inv.yml
- import_playbook: templated_by_play_keywords.yml
- import_playbook: templated_by_play_vars.yml
- import_playbook: templated_by_task_keywords.yml

@ -20,7 +20,8 @@
('password is required' in out.msg) or
('password is required' in out.module_stderr)
)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when: is_mitogen
@ -40,7 +41,8 @@
('Incorrect su password' in out.msg) or
('su password is incorrect' in out.msg)
)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when: is_mitogen
- name: Ensure password su with chdir succeeds
@ -62,7 +64,8 @@
- assert:
that:
- out.stdout == 'mitogen__user1'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
# CI containers lack `setfacl` for unpriv -> unpriv
# https://github.com/mitogen-hq/mitogen/issues/1118
@ -87,7 +90,8 @@
- assert:
that:
- out.stdout == 'mitogen__user1'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
# CI containers lack `setfacl` for unpriv -> unpriv
# https://github.com/mitogen-hq/mitogen/issues/1118

@ -20,7 +20,8 @@
or out.module_stderr is match("sudo: invalid option -- '-'")
or out.module_stdout is match("sudo: unrecognized option [`']--derps'")
or out.module_stderr is match("sudo: unrecognized option [`']--derps'")
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- sudo
- sudo_flags_failure

@ -23,7 +23,8 @@
- >-
(out.module_stderr | default(out.module_stdout, true) | default(out.msg, true)) is search('sudo: unknown user:? slartibartfast')
or (ansible_facts.os_family == 'RedHat' and ansible_facts.distribution_version == '6.10')
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]

@ -11,7 +11,8 @@
- assert:
that:
- out.stdout != 'root'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Ensure passwordless sudo to root succeeds.
shell: whoami
@ -22,7 +23,8 @@
- assert:
that:
- out.stdout == 'root'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- sudo
- sudo_nopassword

@ -5,10 +5,12 @@
tasks:
- name: Ensure sudo password absent but required.
shell: whoami
become: true
become_user: mitogen__pw_required
command:
cmd: whoami
register: out
changed_when: false
ignore_errors: true
when:
# https://github.com/ansible/ansible/pull/70785
@ -23,7 +25,8 @@
('Missing sudo password' in out.msg) or
('password is required' in out.module_stderr)
)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
@ -31,10 +34,12 @@
or is_mitogen
- name: Ensure password sudo incorrect.
shell: whoami
become: true
become_user: mitogen__pw_required
command:
cmd: whoami
register: out
changed_when: false
vars:
ansible_become_pass: nopes
ignore_errors: true
@ -50,25 +55,35 @@
('Incorrect sudo password' in out.msg) or
('sudo password is incorrect' in out.msg)
)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen
# TODO: https://github.com/dw/mitogen/issues/692
# - name: Ensure password sudo succeeds.
# shell: whoami
# become: true
# become_user: mitogen__pw_required
# register: out
# vars:
# ansible_become_pass: pw_required_password
- block:
- name: Ensure password sudo succeeds
become: true
become_user: mitogen__pw_required
vars:
ansible_become_pass: pw_required_password
command:
cmd: whoami
register: sudo_password_success_whoami
changed_when: false
# - assert:
# that:
# - out.stdout == 'mitogen__pw_required'
- assert:
that:
- sudo_password_success_whoami.stdout == 'mitogen__pw_required'
fail_msg: |
sudo_password_success_whoami={{ sudo_password_success_whoami }}
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen
tags:
- sudo
- sudo_password

@ -3,34 +3,38 @@
- name: integration/become/sudo_requiretty.yml
hosts: test-targets
tasks:
# - include_tasks: ../_mitogen_only.yml
# AIUI Vanilla Ansible cannot do sudo when requiretty configured
- include_tasks: ../_mitogen_only.yml
# TODO: https://github.com/dw/mitogen/issues/692
# - name: Verify we can login to a non-passworded requiretty account
# shell: whoami
# become: true
# become_user: mitogen__require_tty
# register: out
- name: Verify we can login to a non-passworded requiretty account
become: true
become_user: mitogen__require_tty
command:
cmd: whoami
changed_when: false
register: sudo_require_tty_whoami
# - assert:
# that:
# - out.stdout == 'mitogen__require_tty'
- assert:
that:
- sudo_require_tty_whoami.stdout == 'mitogen__require_tty'
fail_msg: |
sudo_require_tty_whoami={{ sudo_require_tty_whoami }}
- name: Verify we can login to a passworded requiretty account
become: true
become_user: mitogen__require_tty_pw_required
vars:
ansible_become_pass: require_tty_pw_required_password
command:
cmd: whoami
changed_when: false
register: sudo_require_tty_password_whoami
# ---------------
# TODO: https://github.com/dw/mitogen/issues/692
# - name: Verify we can login to a passworded requiretty account
# shell: whoami
# become: true
# become_user: mitogen__require_tty_pw_required
# vars:
# ansible_become_pass: require_tty_pw_required_password
# register: out
# - assert:
# that:
# - out.stdout == 'mitogen__require_tty_pw_required'
- assert:
that:
- sudo_require_tty_password_whoami.stdout == 'mitogen__require_tty_pw_required'
fail_msg: |
sudo_require_tty_password_whoami={{ sudo_require_tty_password_whoami }}
tags:
- mitogen_only
- sudo

@ -0,0 +1,34 @@
- name: integration/become/templated_by_inv.yml
hosts: tt_become_by_inv
gather_facts: false
tasks:
- name: Gather facts (avoiding any unprivileged become)
vars:
ansible_become: false
setup:
- meta: reset_connection
- name: Templated become in inventory
vars:
expected_become_users:
tt-become: root
tt-become-exe: root
tt-become-flags: root
tt-become-method: root
tt-become-pass: mitogen__pw_required
tt-become-user: root
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_inv_whoami
failed_when:
- become_templated_by_inv_whoami is failed
or become_templated_by_inv_whoami.stdout != expected_become_users[inventory_hostname]
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_become_user in ['root']
or ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen

@ -0,0 +1,53 @@
- name: integration/become/templated_by_play_keywords.yml
hosts: tt_become_bare
gather_facts: false
become: "{{ 'true' | trim }}"
become_exe: "{{ 'sudo' | trim }}"
become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
become_method: "{{ 'sudo' | trim }}"
become_user: "{{ 'root' | trim }}"
tasks:
- meta: reset_connection
- name: Templated become by play keywords, no password
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_play_keywords_whoami
failed_when:
- become_templated_by_play_keywords_whoami is failed
or become_templated_by_play_keywords_whoami.stdout != 'root'
- name: integration/become/templated_by_play_keywords.yml
hosts: tt_become_bare
gather_facts: false
become: "{{ 'true' | trim }}"
become_exe: "{{ 'sudo' | trim }}"
become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
become_method: "{{ 'sudo' | trim }}"
become_user: "{{ 'mitogen__pw_required' | trim }}"
vars:
ansible_become_pass: "{{ 'pw_required_password' | trim }}"
tasks:
- name: Gather facts (avoiding any unprivileged become)
vars:
ansible_become: false
setup:
- meta: reset_connection
- name: Templated become by play keywords, password
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_play_keywords_password_whoami
failed_when:
- become_templated_by_play_keywords_password_whoami is failed
or become_templated_by_play_keywords_password_whoami.stdout != 'mitogen__pw_required'
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen

@ -0,0 +1,52 @@
- name: integration/become/templated_by_play_vars.yml
hosts: tt_become_bare
gather_facts: false
vars:
ansible_become: true
ansible_become_exe: "{{ 'sudo' | trim }}"
ansible_become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
ansible_become_method: "{{ 'sudo' | trim }}"
ansible_become_user: "{{ 'root' | trim }}"
tasks:
- name: Templated become by play vars, no password
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_play_vars_whoami
failed_when:
- become_templated_by_play_vars_whoami is failed
or become_templated_by_play_vars_whoami.stdout != 'root'
- name: integration/become/templated_by_play_vars.yml
hosts: tt_become_bare
gather_facts: false
vars:
ansible_become: true
ansible_become_exe: "{{ 'sudo' | trim }}"
ansible_become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
ansible_become_method: "{{ 'sudo' | trim }}"
ansible_become_pass: "{{ 'pw_required_password' | trim }}"
ansible_become_user: "{{ 'mitogen__pw_required' | trim }}"
tasks:
- name: Gather facts (avoiding any unprivileged become)
vars:
ansible_become: false
setup:
- meta: reset_connection
- name: Templated become by play vars, password
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_play_vars_password_whoami
failed_when:
- become_templated_by_play_vars_password_whoami is failed
or become_templated_by_play_vars_password_whoami.stdout != 'mitogen__pw_required'
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen

@ -0,0 +1,83 @@
- name: integration/become/templated_by_task_keywords.yml
hosts: tt_become_bare
gather_facts: false
# FIXME Resetting the connection shouldn't require credentials
# https://github.com/mitogen-hq/mitogen/issues/1132
become: "{{ 'true' | trim }}"
become_exe: "{{ 'sudo' | trim }}"
become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
become_method: "{{ 'sudo' | trim }}"
become_user: "{{ 'root' | trim }}"
tasks:
- name: Reset connection to target that will be delegate_to
meta: reset_connection
- name: Test connection template by task keywords, with delegate_to
hosts: test-targets[0]
gather_facts: false
tasks:
- name: Templated become by task keywords, with delegate_to
become: "{{ 'true' | trim }}"
become_exe: "{{ 'sudo' | trim }}"
become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
become_method: "{{ 'sudo' | trim }}"
become_user: "{{ 'root' | trim }}"
delegate_to: "{{ groups.tt_become_bare[0] }}"
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_task_with_delegate_to_whoami
failed_when:
- become_templated_by_task_with_delegate_to_whoami is failed
or become_templated_by_task_with_delegate_to_whoami.stdout != 'root'
- name: integration/become/templated_by_task_keywords.yml
hosts: tt_become_bare
gather_facts: false
# FIXME Resetting the connection shouldn't require credentials
# https://github.com/mitogen-hq/mitogen/issues/1132
become: "{{ 'true' | trim }}"
become_exe: "{{ 'sudo' | trim }}"
become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
become_method: "{{ 'sudo' | trim }}"
become_user: "{{ 'mitogen__pw_required' | trim }}"
vars:
ansible_become_pass: "{{ 'pw_required_password' | trim }}"
tasks:
- name: Reset connection to target that will be delegate_to
meta: reset_connection
- name: Test connection template by task keywords, with delegate_to
hosts: test-targets[0]
gather_facts: false
tasks:
- name: Gather facts (avoiding any unprivileged become)
delegate_to: "{{ groups.tt_become_bare[0] }}"
vars:
ansible_become: false
setup:
- name: Templated become by task keywords, with delegate_to
become: "{{ 'true' | trim }}"
become_exe: "{{ 'sudo' | trim }}"
become_flags: "{{ '--set-home --stdin --non-interactive' | trim }}"
become_method: "{{ 'sudo' | trim }}"
become_user: "{{ 'mitogen__pw_required' | trim }}"
delegate_to: "{{ groups.tt_become_bare[0] }}"
vars:
ansible_become_pass: "{{ 'pw_required_password' | trim }}"
command:
cmd: whoami
changed_when: false
check_mode: false
register: become_templated_by_task_with_delegate_to_password_whoami
failed_when:
- become_templated_by_task_with_delegate_to_password_whoami is failed
or become_templated_by_task_with_delegate_to_password_whoami.stdout != 'mitogen__pw_required'
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen

@ -26,4 +26,6 @@
- original.stat.checksum == copied.stat.checksum
# Upstream does not preserve timestamps at al.
#- (not is_mitogen) or (original.stat.mtime|int == copied.stat.mtime|int)
fail_msg: original={{original}} copied={{copied}}
fail_msg: |
original={{ original }}
copied={{ copied }}

@ -18,7 +18,8 @@
- out.result[0].method == "ssh"
- out.result[0].kwargs.username == "joe"
- out.result|length == 1 # no sudo
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
# Now try with a different account.
@ -34,7 +35,8 @@
- out.result[1].method == "sudo"
- out.result[1].kwargs.username == "james"
- out.result|length == 2 # no sudo
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- become_same_user
- mitogen_only

@ -29,7 +29,8 @@
that:
- out.rc == 4
- "'Mitogen was disconnected from the remote environment while a call was in-progress.' in out.stdout"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- disconnect
- disconnect_during_module

@ -16,6 +16,7 @@
- out.result[0] == 0
- out.result[1].decode() == "hello, world\r\n"
- out.result[2].decode().startswith("Shared connection to ")
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- exec_command

@ -44,7 +44,11 @@
# sudo PID has changed.
- out_become.ppid != out_become2.ppid
fail_msg: out={{out}} out2={{out2}} out_become={{out_become}} out_become2={{out_become2}}
fail_msg: |
out={{ out }}
out2={{ out2 }}
out_become={{ out_become }}
out_become2={{ out_become2 }}
tags:
- mitogen_only
- reset

@ -27,7 +27,9 @@
assert:
that:
- become_acct.pid != login_acct.pid
fail_msg: become_acct={{become_acct}} login_acct={{login_acct}}
fail_msg: |
become_acct={{ become_acct }}
login_acct={{ login_acct }}
- name: reset the connection
meta: reset_connection
@ -40,7 +42,9 @@
assert:
that:
- become_acct.pid != new_become_acct.pid
fail_msg: become_acct={{become_acct}} new_become_acct={{new_become_acct}}
fail_msg: |
become_acct={{ become_acct }}
new_become_acct={{ new_become_acct }}
- name: save new pid of login acct
become: false
@ -51,6 +55,8 @@
assert:
that:
- login_acct.pid != new_login_acct.pid
fail_msg: login_acct={{login_acct}} new_login_acct={{new_login_acct}}
fail_msg: |
login_acct={{ login_acct }}
new_login_acct={{ new_login_acct }}
tags:
- reset_become

@ -11,11 +11,11 @@
- name: integration/connection_delegation/delegate_to_template.yml
vars:
physical_host: "cd-normal-alias"
physical_hosts: ["cd-normal-alias", "cd-normal-normal"]
hosts: test-targets
gather_facts: no
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- meta: end_play
when:
@ -67,7 +67,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
'python_path': ['python3000'],
'remote_name': null,
'ssh_args': [

@ -55,6 +55,7 @@
- hosts: cd-normal
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- mitogen_get_stack:
delegate_to: cd-alias
register: out
@ -72,7 +73,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
"python_path": ["python3000"],
'remote_name': null,
'ssh_args': [
@ -98,6 +99,7 @@
- hosts: cd-alias
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -114,7 +116,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
"python_path": ["python3000"],
'remote_name': null,
'ssh_args': [
@ -140,6 +142,7 @@
- hosts: cd-normal-normal
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -167,7 +170,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
"python_path": ["python3000"],
'remote_name': null,
'ssh_args': [
@ -193,6 +196,7 @@
- hosts: cd-normal-alias
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -237,7 +241,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
"python_path": ["python3000"],
'remote_name': null,
'ssh_args': [
@ -262,6 +266,7 @@
- hosts: cd-newuser-normal-normal
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- mitogen_get_stack:
register: out
- assert_equal:
@ -289,7 +294,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
"python_path": ["python3000"],
'remote_name': null,
'ssh_args': [
@ -315,6 +320,7 @@
- hosts: cd-newuser-normal-normal
tasks:
- include_tasks: ../_mitogen_only.yml
- include_tasks: ../_expected_ssh_port.yml
- mitogen_get_stack:
delegate_to: cd-alias
register: out
@ -332,7 +338,7 @@
'keepalive_interval': 30,
'keepalive_count': 10,
'password': null,
'port': null,
'port': '{{ expected_ssh_port }}',
"python_path": ["python3000"],
'remote_name': null,
'ssh_args': [

@ -11,7 +11,8 @@
- assert:
that: (not not out.mitogen_loaded) == (not not is_mitogen)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- local
- local_blemished

@ -14,7 +14,8 @@
- assert:
that: not out.mitogen_loaded
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when: False
tags:
- paramiko

@ -11,7 +11,8 @@
- assert:
that: (not not out.mitogen_loaded) == (not not is_mitogen)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- ssh
- ssh_blemished

@ -31,6 +31,8 @@
- assert:
that:
- old_become_env.pid != new_become_env.pid
fail_msg: old_become_env={{old_become_env}} new_become_env={{new_become_env}}
fail_msg: |
old_become_env={{ old_become_env }}
new_become_env={{ new_become_env }}
tags:
- reconnection

@ -17,7 +17,8 @@
- assert:
that:
- out.stdout is match('.*python([0-9.]+)?\(mitogen:[a-z]+@[^:]+:[0-9]+\)')
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- name: Get cmdline, with mitogen_mask_remote_name
shell: 'cat /proc/$PPID/cmdline | tr \\0 \\n'
@ -29,7 +30,8 @@
- assert:
that:
- out.stdout is match('.*python([0-9.]+)?\(mitogen:ansible\)')
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- mitogen_only
- remote_name

@ -46,7 +46,8 @@
- out.failed
- '"Name or service not known" in out.msg or
"Temporary failure in name resolution" in out.msg'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
- ansible_facts.virtualization_type == "docker"
- ansible_facts.python.version_info[:2] >= [2, 5]

@ -135,7 +135,8 @@
assert:
that:
- legacy.deprecations | default([]) | length > 0
fail_msg: legacy={{legacy}}
fail_msg: |
legacy={{ legacy }}
# only check for a dep warning if legacy returned /usr/bin/python and auto didn't
when:
- legacy.ansible_facts.discovered_interpreter_python == '/usr/bin/python'
@ -146,7 +147,8 @@
assert:
that:
- legacy.warnings | default([]) | length > 0
fail_msg: legacy={{legacy}}
fail_msg: |
legacy={{ legacy }}
# only check for a warning if legacy returned /usr/bin/python and auto didn't
when:
- legacy.ansible_facts.discovered_interpreter_python == '/usr/bin/python'
@ -168,7 +170,8 @@
that:
- auto_silent_out.warnings is not defined
- auto_silent_out.ansible_facts.discovered_interpreter_python == auto_out.ansible_facts.discovered_interpreter_python
fail_msg: auto_silent_out={{auto_silent_out}}
fail_msg: |
auto_silent_out={{ auto_silent_out }}
- name: test that auto_legacy_silent never warns and got the same answer as auto_legacy
@ -186,7 +189,8 @@
that:
- legacy_silent.warnings is not defined
- legacy_silent.ansible_facts.discovered_interpreter_python == legacy.ansible_facts.discovered_interpreter_python
fail_msg: legacy_silent={{legacy_silent}}
fail_msg: |
legacy_silent={{ legacy_silent }}
- name: ensure modules can't set discovered_interpreter_X or ansible_X_interpreter
block:
@ -212,7 +216,7 @@
assert:
that:
- auto_out.ansible_facts.discovered_interpreter_python == discovered_interpreter_expected
fail_msg: >-
fail_msg: |
distro={{ distro }}
distro_major= {{ distro_major }}
system={{ system }}

@ -52,7 +52,7 @@
{% if "1" == "1" %}
{{ special_python }}
{% else %}
python
python
{% endif %}
tags:
- complex_args

@ -19,6 +19,7 @@
- assert:
that: stat.stat.exists
fail_msg: stat={{stat}}
fail_msg: |
stat={{ stat }}
tags:
- cwd_prseserved

@ -12,6 +12,7 @@
that:
- out.external1_path == "ansible/integration/module_utils/module_utils/external1.py"
- out.external2_path == "ansible/lib/module_utils/external2.py"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- adjacent_to_playbook

@ -11,6 +11,7 @@
that:
- out.external1_path == "ansible/lib/module_utils/external1.py"
- out.external2_path == "ansible/lib/module_utils/external2.py"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- from_config_path

@ -10,6 +10,7 @@
- assert:
that:
- out.extmod_path == "ansible/lib/module_utils/externalpkg/extmod.py"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- from_config_path

@ -7,4 +7,5 @@
that:
- out.external3_path == "integration/module_utils/roles/modrole/module_utils/external3.py"
- out.external2_path == "integration/module_utils/roles/modrole/module_utils/external2.py"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}

@ -6,4 +6,5 @@
- assert:
that:
- out.path == "ansible/integration/module_utils/roles/override_modrole/module_utils/known_hosts.py"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}

@ -13,7 +13,8 @@
- assert:
that: "out.stdout == ''"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- become_flags
@ -29,6 +30,7 @@
- assert:
that: "out2.stdout == '2'"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- become_flags

@ -11,6 +11,7 @@
- assert:
that: "result.stdout == '123'"
fail_msg: result={{result}}
fail_msg: |
result={{ result }}
tags:
- environment

@ -3,30 +3,39 @@
- name: integration/playbook_semantics/with_items.yml
hosts: test-targets
gather_facts: true
tasks:
- block:
- name: Spin up a few interpreters
become: true
vars:
ansible_become_user: "mitogen__user{{ item }}"
command:
cmd: whoami
with_sequence: start=1 end=3
register: first_run
changed_when: false
# TODO: https://github.com/dw/mitogen/issues/692
# - name: Spin up a few interpreters
# shell: whoami
# become: true
# vars:
# ansible_become_user: "mitogen__user{{item}}"
# with_sequence: start=1 end=3
# register: first_run
- name: Reuse them
become: true
vars:
ansible_become_user: "mitogen__user{{ item }}"
command:
cmd: whoami
with_sequence: start=1 end=3
register: second_run
changed_when: false
# - name: Reuse them
# shell: whoami
# become: true
# vars:
# ansible_become_user: "mitogen__user{{item}}"
# with_sequence: start=1 end=3
# register: second_run
# - name: Verify first and second run matches expected username.
# assert:
# that:
# - first_run.results[item|int].stdout == ("mitogen__user%d" % (item|int + 1))
# - first_run.results[item|int].stdout == second_run.results[item|int].stdout
# with_sequence: start=0 end=2
tags:
- custom_python_new_style_module
- name: Verify first and second run matches expected username.
vars:
user_expected: "mitogen__user{{ item | int + 1 }}"
assert:
that:
- first_run.results[item | int].stdout == user_expected
- second_run.results[item | int].stdout == user_expected
with_sequence: start=0 end=2
when:
# https://github.com/ansible/ansible/pull/70785
- ansible_facts.distribution not in ["MacOSX"]
or ansible_version.full is version("2.11", ">=", strict=True)
or is_mitogen

@ -12,7 +12,8 @@
- assert:
that: echo.stdout == ""
fail_msg: echo={{echo}}
fail_msg: |
echo={{ echo }}
- name: Create /etc/environment
copy:
@ -32,7 +33,8 @@
- assert:
that: echo.stdout == "555"
fail_msg: echo={{echo}}
fail_msg: |
echo={{ echo }}
- name: Cleanup /etc/environment
file:
@ -51,4 +53,5 @@
- assert:
that: echo.stdout == ""
fail_msg: echo={{echo}}
fail_msg: |
echo={{ echo }}

@ -10,7 +10,8 @@
- assert:
that: echo.stdout == ""
fail_msg: echo={{echo}}
fail_msg: |
echo={{ echo }}
- name: Copy pam_environment
copy:
@ -23,7 +24,8 @@
- assert:
that: echo.stdout == "321"
fail_msg: echo={{echo}}
fail_msg: |
echo={{ echo }}
- name: Cleanup pam_environment
file:
@ -35,4 +37,5 @@
- assert:
that: echo.stdout == ""
fail_msg: echo={{echo}}
fail_msg: |
echo={{ echo }}

@ -25,6 +25,7 @@
- assert:
that:
- not out.stat.exists
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- atexit

@ -16,6 +16,7 @@
out.results[0].item == '1' and
out.results[0].rc == 0 and
(out.results[0].stdout == ansible_nodename)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- builtin_command_module

@ -22,6 +22,7 @@
- (out.module_stdout == "" and out.module_stderr is search(tb_pattern))
or
(out.module_stdout is search(tb_pattern) and out.module_stderr is match("Shared connection to localhost closed."))
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- crashy_new_style_module

@ -22,6 +22,7 @@
(not out.results[0].changed) and
out.results[0].msg == 'Here is my input' and
out.results[0].run_via_env == "yes"
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- custom_bash_hashbang_argument

@ -12,6 +12,7 @@
(not out.changed) and
(not out.results[0].changed) and
out.results[0].msg == 'Here is my input'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- custom_bash_old_style_module

@ -11,6 +11,7 @@
(not out.changed) and
(not out.results[0].changed) and
out.results[0].msg == 'Here is my input'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- custom_bash_want_json_module

@ -23,6 +23,7 @@
out.changed and
out.results[0].changed and
out.results[0].msg == 'Hello, world.'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- custom_binary_producing_json

@ -31,6 +31,7 @@
- out.results[0].failed
- out.results[0].msg.startswith('MODULE FAILURE')
- out.results[0].rc == 0
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- custom_binary_producing_junk

@ -28,7 +28,8 @@
'custom_binary_single_null: cannot execute binary file: Exec format error\r\n',
))
or (ansible_facts.distribution == 'Ubuntu' and ansible_facts.distribution_version == '16.04')
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
tags:
- custom_binary_single_null

@ -10,13 +10,15 @@
that:
- out.results[0].input.foo
- out.results[0].message == 'I am a perl script! Here is my input.'
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
- assert:
that:
- (not out.changed)
- (not out.results[0].changed)
fail_msg: out={{out}}
fail_msg: |
out={{ out }}
when:
- ansible_version.full is version('2.4', '>=', strict=True)
tags:

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save