.\" Man page generated from reStructuredText. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "TESTINFRA" "1" "Apr 11, 2024" "10.1.0" "testinfra" .SH NAME testinfra \- testinfra Documentation .sp Latest documentation: \fI\%https://testinfra.readthedocs.io/en/latest\fP .SH ABOUT .sp With Testinfra you can write unit tests in Python to test \fIactual state\fP of your servers configured by management tools like \fI\%Salt\fP, \fI\%Ansible\fP, \fI\%Puppet\fP, \fI\%Chef\fP and so on. .sp Testinfra aims to be a \fI\%Serverspec\fP equivalent in python and is written as a plugin to the powerful \fI\%Pytest\fP test engine .SH LICENSE .sp \fI\%Apache License 2.0\fP .sp The logo is licensed under the \fI\%Creative Commons NoDerivatives 4.0 License\fP If you have some other use in mind, contact us. .SH QUICK START .sp Install testinfra using pip: .INDENT 0.0 .INDENT 3.5 .sp .EX $ pip install pytest\-testinfra # or install the devel version $ pip install \(aqgit+https://github.com/pytest\-dev/pytest\-testinfra@main#egg=pytest\-testinfra\(aq .EE .UNINDENT .UNINDENT .sp Write your first tests file to \fItest_myinfra.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX def test_passwd_file(host): passwd = host.file(\(dq/etc/passwd\(dq) assert passwd.contains(\(dqroot\(dq) assert passwd.user == \(dqroot\(dq assert passwd.group == \(dqroot\(dq assert passwd.mode == 0o644 def test_nginx_is_installed(host): nginx = host.package(\(dqnginx\(dq) assert nginx.is_installed assert nginx.version.startswith(\(dq1.2\(dq) def test_nginx_running_and_enabled(host): nginx = host.service(\(dqnginx\(dq) assert nginx.is_running assert nginx.is_enabled .EE .UNINDENT .UNINDENT .sp And run it: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-v test_myinfra.py ====================== test session starts ====================== platform linux \-\- Python 2.7.3 \-\- py\-1.4.26 \-\- pytest\-2.6.4 plugins: testinfra collected 3 items test_myinfra.py::test_passwd_file[local] PASSED test_myinfra.py::test_nginx_is_installed[local] PASSED test_myinfra.py::test_nginx_running_and_enabled[local] PASSED =================== 3 passed in 0.66 seconds ==================== .EE .UNINDENT .UNINDENT .SH DOCUMENTATION .SS Changelog .SS 10.1.0 .INDENT 0.0 .IP \(bu 2 [NEW] Add Interface.link property .IP \(bu 2 [FIX] Make file properties follow symlinks .IP \(bu 2 [FIX] Require pytest>=6 and use future annotations for pytest<7 compatibility .UNINDENT .SS 10.0.0 .INDENT 0.0 .IP \(bu 2 [FIX] Ansible: Fix for missing group names in get_variables() .IP \(bu 2 [FIX] testinfra/modules/blockdevice: Don\(aqt fail on stderr .IP \(bu 2 [DOC] Extend and show the documentation of CommandResult .IP \(bu 2 [FIX] Extend list of valid suffixes for systemd units .IP \(bu 2 [DOC] Add missing Environment doc section .IP \(bu 2 [MISC] Define types for plugin.py .IP \(bu 2 [FIX] Missing RHEL distribution in package module .IP \(bu 2 [NEW] Add brew support in package module .IP \(bu 2 [NEW] Add Service.exists .IP \(bu 2 [MISC] Make CommandResult a dataclass .UNINDENT .SS 9.0.0 .INDENT 0.0 .IP \(bu 2 [BREAKING] pytest\-testinfra now require python >= 3.9 .IP \(bu 2 [BREAKING] Drop deprecated module PipPackage .IP \(bu 2 [NEW] Add support for the SSH ControlPath connection sharing option (#713) .IP \(bu 2 [FIX] Retry SSH on ConnectionResetError (#708) .IP \(bu 2 [FIX] List openSUSE Leap and Tumbleweed explicitly as rpm based distributions .IP \(bu 2 [FIX] Make group name mandatory in group module .UNINDENT .SS 8.1.0 .INDENT 0.0 .IP \(bu 2 [NEW] Add Windows support for File and Service modules .IP \(bu 2 [NEW] Add File.is_executable property .UNINDENT .SS 8.0.0 .INDENT 0.0 .IP \(bu 2 [NEW] Add Group.members attribute .IP \(bu 2 [NEW] Add File.inode attribute .IP \(bu 2 [NEW] Add Interface.routes() method .IP \(bu 2 [NEW] Add Docker.is_restarting attribute .IP \(bu 2 [FIX] Fix possible error in Interface.default() .IP \(bu 2 [FIX] Fix busybox detection in Process module .IP \(bu 2 [FIX] Fix possible KeyError in SysInfo module .IP \(bu 2 [BREAKING] Drop support for python 3.7 .UNINDENT .SS 7.0.1 .INDENT 0.0 .IP \(bu 2 [FIX] Fix \fIcommand \-v\fP compatibility with dash shell .UNINDENT .SS 7.0.0 .INDENT 0.0 .IP \(bu 2 [NEW] Improved ssh config support in Paramiko backend .IP \(bu 2 [NEW] Add chroot backend .IP \(bu 2 [NEW] Add support for Manjaro\-Linux .IP \(bu 2 [NEW] Add support for Cloudlinux .IP \(bu 2 [BREAKING] Drop support for python 3.6 which is EOL .UNINDENT .SS 6.8.0 .INDENT 0.0 .IP \(bu 2 [NEW] Add support for AlmaLinux and RockyLinux .UNINDENT .SS 6.7.0 .INDENT 0.0 .IP \(bu 2 [NEW] Handle ansible_connection type community.docker.docker .IP \(bu 2 [NEW] add ssh_extra_args option .UNINDENT .SS 6.6.0 .INDENT 0.0 .IP \(bu 2 [NEW] Allow to test for user password expiration .IP \(bu 2 [NEW] Handle ANSIBLE_SSH_COMMON_ARGS and ANSIBLE_SSH_EXTRA_ARGS environment variables for ansible connections .IP \(bu 2 [FIX] Fix encoding issue in salt connections .IP \(bu 2 [FIX] Fix AttributeError when \(dqcommand\(dq is not available and fallback to \(dqwhich\(dq .UNINDENT .SS 6.5.0 .INDENT 0.0 .IP \(bu 2 Fallback to which when \(dqcommand \-v\(dq fails .IP \(bu 2 Use realpath by default to resolve symlinks instead of \(dqreadlink \-f\(dq .IP \(bu 2 ansible: Support environment variables .IP \(bu 2 Force package module to resolve to RpmPackage on Fedora .IP \(bu 2 Fix new versions of supervisor may exit with status != 0 .IP \(bu 2 Eventually decode ansible output when it\(aqs not ascii .IP \(bu 2 Either use python3 or python to get remote encoding .UNINDENT .SS 6.4.0 .INDENT 0.0 .IP \(bu 2 Implement Interface names and default (#615) .IP \(bu 2 Implement Service.systemd_properties (#612) .UNINDENT .SS 6.3.0 .INDENT 0.0 .IP \(bu 2 Fix #451 for use with pytest \-p no:terminal .IP \(bu 2 Add client_version() and server_version() and version() to docker module. .UNINDENT .SS 6.2.0 .INDENT 0.0 .IP \(bu 2 Fix #590: Systeminfo doesn\(aqt resolve Windows correctly (#592) .IP \(bu 2 First implementation of network namespaces in addr module (#596) .IP \(bu 2 pip check support in PipPackage module (#605) .IP \(bu 2 pip refactoring: implementation of installed and version (#606) .IP \(bu 2 Allow to specify supervisorctl and supervisord.conf paths (#536) .UNINDENT .SS 6.1.0 .INDENT 0.0 .IP \(bu 2 Fix wrong package module on CentOS having dpkg tools installed #570 (#575) .IP \(bu 2 Deduplicate hosts returned by get_backends() (#572) .IP \(bu 2 Use /run/systemd/system/ to detect systemd (fixes #546) .IP \(bu 2 Use ssh_args from ansible.cfg .IP \(bu 2 Require python >= 3.6 .IP \(bu 2 Fix ValueError with python 3.8+ when using \-\-nagios option. .UNINDENT .SS 6.0.0 .INDENT 0.0 .IP \(bu 2 Breaking change: testinfra has moved to the \fI\%https://github.com/pytest\-dev/\fP organization. Project on PyPi is renamed as pytest\-testinfra. A dummy testinfra will make the transition, but you should rename to pytest\-testinfra in your requirements files. .UNINDENT .SS 5.3.1 .INDENT 0.0 .IP \(bu 2 Fix newly introduced is_masked property on systemd service \fI\%https://github.com/philpep/testinfra/pull/569\fP .UNINDENT .SS 5.3.0 .INDENT 0.0 .IP \(bu 2 Add is_masked property on systemd service .UNINDENT .SS 5.2.2 .INDENT 0.0 .IP \(bu 2 iptables: use \-w option to wait for iptables lock when running in parallel with pytest\-xdist. .UNINDENT .SS 5.2.1 .INDENT 0.0 .IP \(bu 2 Fix documentation build .UNINDENT .SS 5.2.0 .INDENT 0.0 .IP \(bu 2 Allow kubeconfig context to be supplied in kubernetes backend .IP \(bu 2 Drop file.__ne__ implementation and require python >= 3.5 .UNINDENT .SS 5.1.0 .INDENT 0.0 .IP \(bu 2 Use remote_user and remote_port in ansible.cfg .IP \(bu 2 Add \fIarch\fP (architecture) attribute to system_info module .UNINDENT .SS 5.0.0 .INDENT 0.0 .IP \(bu 2 Breaking change: host.file().listdir() is now a method .UNINDENT .SS 4.1.0 .INDENT 0.0 .IP \(bu 2 Pass extra arguments to ansible CLI via host.ansible() .IP \(bu 2 New method host.file.listdir() to list items in a directory. .UNINDENT .SS 4.0.0 .INDENT 0.0 .IP \(bu 2 Drop python2 support .UNINDENT .SS 3.4.0 .INDENT 0.0 .IP \(bu 2 Add podman backend and module .IP \(bu 2 WARNING: this will be the latest testinfra version supporting python2, please upgrade to python3. .UNINDENT .SS 3.3.0 .INDENT 0.0 .IP \(bu 2 Add extras for backend dependencies (#454) .IP \(bu 2 Various enhancements of kitchen integration documentation .IP \(bu 2 ansible backend now support \(dqpassword\(dq field from ansible inventory .IP \(bu 2 New backend \(dqopenshift\(dq .UNINDENT .SS 3.2.1 .INDENT 0.0 .IP \(bu 2 Fix Process module when working with long strings (username, ...) #505 .UNINDENT .SS 3.2.0 .INDENT 0.0 .IP \(bu 2 New module \(dqenvironment\(dq for getting remote environment variables .IP \(bu 2 New module \(dqblock_device\(dq exposing block device information .IP \(bu 2 Add a global flag \-\-force\-ansible to the command line .IP \(bu 2 Raise an error in case of missing ansible inventory file .IP \(bu 2 Fix an escape issue with ansible ssh args set inventory or configuration file .UNINDENT .SS 3.1.0 .INDENT 0.0 .IP \(bu 2 ssh connections uses persistent connections by default. You can disable this by passing controlpersist=0 to the connections options. .IP \(bu 2 ansible ssh connections now use ssh backend instead of paramiko. ansible_ssh_common_args and ansible_ssh_extra_args are now taking in account. .IP \(bu 2 Add a new ansible connection options \(dqforce_ansible\(dq, when set to True, testinfra will always call ansible for all commands he need to run. .IP \(bu 2 Handle all ansible connections types by setting force_ansible=True for connections which doesn\(aqt have a testinfra equivalent connection (for example \(dqnetwork_cli\(dq). .UNINDENT .SS 3.0.6 .INDENT 0.0 .IP \(bu 2 Issue full command logging using DEBUG log level to avoid logging sensible data when log level is INFO. .IP \(bu 2 Fix possible crash when parsing ansible inventories #470 .IP \(bu 2 Support using alternative kubeconfig file in kubectl connections #460 .IP \(bu 2 Support parsing ProxyCommand from ssh_config for paramiko connections .UNINDENT .SS 3.0.5 .INDENT 0.0 .IP \(bu 2 Set default timeout to 10s on ssh/paramiko connections .IP \(bu 2 Add support for ansible inventory parameter ansible_private_key_file .UNINDENT .SS 3.0.4 .INDENT 0.0 .IP \(bu 2 Add support for ansible lxc and lxd connections .UNINDENT .SS 3.0.3 .INDENT 0.0 .IP \(bu 2 Fix paramiko parsing RequestTTY from ssh configs .IP \(bu 2 Re\-add \(dqgroups\(dq key from ansible.get_variables() to be backward compatible with testinfra 2.X .UNINDENT .SS 3.0.2 .INDENT 0.0 .IP \(bu 2 Fix ansible with no inventory resolving to \(dqlocalhost\(dq .IP \(bu 2 Fix support for ansible 2.8 with no inventory .IP \(bu 2 Fix ansible/paramiko which wasn\(aqt reading hosts config from ~/.ssh/config .IP \(bu 2 Allow to pass \-\-ssh\-config and \-\-ssh\-identity\-file to ansible connection .UNINDENT .SS 3.0.1 .INDENT 0.0 .IP \(bu 2 Fix parsing of ipv6 addresses for paramiko, ssh and ansible backends. .IP \(bu 2 Fix \-\-connection=ansible invocation when no hosts are provided .UNINDENT .SS 3.0.0 .INDENT 0.0 .IP \(bu 2 New ansible backend fixing support for ansible 2.8 and license issue. See \fI\%https://github.com/philpep/testinfra/issues/431\fP for details. This make ansible using testinfra native backends and only works for local, ssh or docker connections. I you have others connection types or issues, please open a bug on \fI\%https://github.com/philpep/testinfra/issues/new\fP .IP \(bu 2 Windows support is improved. \(dqpackage\(dq module is handled with Chocolatey and there\(aqs support for the \(dquser\(dq module. .UNINDENT .SS 2.1.0 .INDENT 0.0 .IP \(bu 2 docker: new get_containers() classmethod .IP \(bu 2 socket: fix parsing of ipv6 addresses with new versions of ss .IP \(bu 2 service: systemd fallback to sysv when \(dqsystemctl is\-active\(dq is not working .UNINDENT .SS 2.0.0 .INDENT 0.0 .IP \(bu 2 Add addr module, used to test network connectivity .IP \(bu 2 Drop deprecated \(dqtestinfra\(dq command, you should use \(dqpy.test\(dq instead .IP \(bu 2 Drop deprecated top level fixtures, access them through the fixture \(dqhost\(dq instead. .IP \(bu 2 Drop support for ansible <= 2.4 .UNINDENT .SS 1.19.0 .INDENT 0.0 .IP \(bu 2 Add docker module .IP \(bu 2 Fix pytest 4 compatibility .UNINDENT .SS 1.18.0 .INDENT 0.0 .IP \(bu 2 Allow to urlencode character in host specification \(dquser:pass@host\(dq (#387) .IP \(bu 2 Fix double logging from both pytest and testinfra .IP \(bu 2 Drop support for python 2.6 .IP \(bu 2 Allow to configure timeouts for winrm backend .UNINDENT .SS 1.17.0 .INDENT 0.0 .IP \(bu 2 Add support for ansible \(dqbecome\(dq user in ansible module .IP \(bu 2 Add failed/succeeded property on run() output .UNINDENT .SS 1.16.0 .INDENT 0.0 .IP \(bu 2 packaging: Use setuptools_scm instead of pbr .IP \(bu 2 iptables: add ip6tables support .IP \(bu 2 sysctl: find sysctl outside of PATH (/sbin) .UNINDENT .SS 1.15.0 .INDENT 0.0 .IP \(bu 2 Fix finding ss and netstat command in \(dqsbin\(dq paths for Centos (359) .IP \(bu 2 Add a workaround for \fI\%https://github.com/pytest\-dev/pytest/issues/3542\fP .IP \(bu 2 Handle \(dqstarting\(dq status for Service module on Alpine linux .IP \(bu 2 Fix no_ssl and no_verify_ssl options for WinRM backend .UNINDENT .SS 1.14.1 .INDENT 0.0 .IP \(bu 2 Fix multi\-host test ordering (#347), regression introduced in 1.13.1 .IP \(bu 2 Fix Socket on OpenBSD hosts (#338) .UNINDENT .SS 1.14.0 .INDENT 0.0 .IP \(bu 2 Add a new lxc backend .IP \(bu 2 Socket: fix is_listening for unix sockets .IP \(bu 2 Add namespace and container support for kubernetes backend .IP \(bu 2 Add a cache of parsed ansible inventories for ansible backend .IP \(bu 2 Service: fix service detection on Centos 6 hosts .IP \(bu 2 File: implement file comparison with string paths .UNINDENT .SS 1.13.1 .INDENT 0.0 .IP \(bu 2 package: fix is_installed and version behavior for uninstalled packages (#321 and #326) .IP \(bu 2 ansible: Use predictibles test ordering when using pytest\-xdist to fix random test collections errors (#316) .UNINDENT .SS 1.13.0 .INDENT 0.0 .IP \(bu 2 socket: fix detection of udp listening sockets (#311) .IP \(bu 2 ssh backend: Add support for GSSAPI .UNINDENT .SS 1.12.0 .INDENT 0.0 .IP \(bu 2 ansible: fix compatibility with ansible 2.5 .IP \(bu 2 pip: fix compatibility with pip 10 (#299) .UNINDENT .SS 1.11.1 .INDENT 0.0 .IP \(bu 2 Socket: fix error with old versions of ss without the \-\-no\-header option (#293) .UNINDENT .SS 1.11.0 .INDENT 0.0 .IP \(bu 2 Fix bad error reporting when using ansible module without ansible backend (#288) .IP \(bu 2 Socket: add a new implementation using ss instead of netstat (#124) .IP \(bu 2 Add service, process, and systeminfo support for Alpine (#283) .UNINDENT .SS 1.10.1 .INDENT 0.0 .IP \(bu 2 Fix get_variables() for ansible>=2.0,<2.4 (#274) .IP \(bu 2 Paramiko: Use the RequireTTY setting if specified in a provided SSHConfig (#247) .UNINDENT .SS 1.10.0 .INDENT 0.0 .IP \(bu 2 New iptables module .UNINDENT .SS 1.9.1 .INDENT 0.0 .IP \(bu 2 Fix running testinfra within a suite using doctest (#268) .IP \(bu 2 Service: add is_valid method for systemd .IP \(bu 2 Fix file.linked_to() for Mac OS .UNINDENT .SS 1.9.0 .INDENT 0.0 .IP \(bu 2 Interface: allow to find \(aqip\(aq command ousite of PATH .IP \(bu 2 Fix \-\-nagios option with python 3 .UNINDENT .SS 1.8.0 .INDENT 0.0 .IP \(bu 2 Deprecate testinfra command (will be dropped in 2.0), use py.test instead #135 .IP \(bu 2 Handle \-\-nagios option when using py.test command .UNINDENT .SS 1.7.1 .INDENT 0.0 .IP \(bu 2 Support for ansible 2.4 (#249) .UNINDENT .SS 1.7.0 .INDENT 0.0 .IP \(bu 2 Salt: allow specify config directory (#230) .IP \(bu 2 Add a WinRM backend .IP \(bu 2 Socket: ipv6 sockets can handle ipv4 clients (#234) .IP \(bu 2 Service: Enhance upstart detection (#243) .UNINDENT .SS 1.6.5 .INDENT 0.0 .IP \(bu 2 Service: add is_enabled() support for OpenBSD .IP \(bu 2 Add ssh identity file option for paramiko and ssh backends .IP \(bu 2 Expand tilde (~) to user home directory for ssh\-config, ssh\-identity\-file and ansible\-inventory options .UNINDENT .SS 1.6.4 .INDENT 0.0 .IP \(bu 2 Service: Allow to find \(aqservice\(aq command outside of $PATH #211 .IP \(bu 2 doc fixes .UNINDENT .SS 1.6.3 .INDENT 0.0 .IP \(bu 2 Fix unwanted deprecation warning when running tests with pytest 3.1 #204 .UNINDENT .SS 1.6.2 .INDENT 0.0 .IP \(bu 2 Fix wheel package for 1.6.1 .UNINDENT .SS 1.6.1 .INDENT 0.0 .IP \(bu 2 Support ansible 2.3 with python 3 (#197) .UNINDENT .SS 1.6.0 .INDENT 0.0 .IP \(bu 2 New \(aqhost\(aq fixture as a replacement for all other fixtures. See \fI\%https://testinfra.readthedocs.io/en/latest/modules.html#host\fP (Other fixtures are deprecated and will be removed in 2.0 release). .UNINDENT .SS 1.5.5 .INDENT 0.0 .IP \(bu 2 backends: Fix ansible backend with ansible >= 2.3 (#195) .UNINDENT .SS 1.5.4 .INDENT 0.0 .IP \(bu 2 backends: fallback to UTF\-8 encoding when system encoding is ASCII. .IP \(bu 2 Service: fix is_running() on systems using Upstart .UNINDENT .SS 1.5.3 .INDENT 0.0 .IP \(bu 2 Sudo: restore backend command in case of exceptions .UNINDENT .SS 1.5.2 .INDENT 0.0 .IP \(bu 2 Honnor become_user when using the ansible backend .UNINDENT .SS 1.5.1 .INDENT 0.0 .IP \(bu 2 Add dependency on importlib on python 2.6 .UNINDENT .SS 1.5.0 .INDENT 0.0 .IP \(bu 2 New kubectl backend .IP \(bu 2 Command: check_output strip carriage return and newlines (#164) .IP \(bu 2 Package: rpm improve getting version() and release() .IP \(bu 2 User: add gecos (comment) field (#155) .UNINDENT .SS 1.4.5 .INDENT 0.0 .IP \(bu 2 SystemInfo: detect codename from VERSION_CODENAME in /etc/os\-release (fallback when lsb_release isn\(aqt installed). .IP \(bu 2 Package: add release property for rpm based systems. .UNINDENT .SS Invocation .SS Test multiples hosts .sp By default Testinfra launch tests on local machine, but you can also test remotes systems using \fI\%paramiko\fP (a ssh implementation in python): .INDENT 0.0 .INDENT 3.5 .sp .EX $ pip install paramiko $ py.test \-v \-\-hosts=localhost,root@webserver:2222 test_myinfra.py ====================== test session starts ====================== platform linux \-\- Python 2.7.3 \-\- py\-1.4.26 \-\- pytest\-2.6.4 plugins: testinfra collected 3 items test_myinfra.py::test_passwd_file[localhost] PASSED test_myinfra.py::test_nginx_is_installed[localhost] PASSED test_myinfra.py::test_nginx_running_and_enabled[localhost] PASSED test_myinfra.py::test_passwd_file[root@webserver:2222] PASSED test_myinfra.py::test_nginx_is_installed[root@webserver:2222] PASSED test_myinfra.py::test_nginx_running_and_enabled[root@webserver:2222] PASSED =================== 6 passed in 8.49 seconds ==================== .EE .UNINDENT .UNINDENT .sp You can also set hosts per test module: .INDENT 0.0 .INDENT 3.5 .sp .EX testinfra_hosts = [\(dqlocalhost\(dq, \(dqroot@webserver:2222\(dq] def test_foo(host): [....] .EE .UNINDENT .UNINDENT .SS Parallel execution .sp If you have a lot of tests, you can use the \fI\%pytest\-xdist\fP plugin to run tests using multiples process: .INDENT 0.0 .INDENT 3.5 .sp .EX $ pip install pytest\-xdist # Launch tests using 3 processes $ py.test \-n 3 \-v \-\-host=web1,web2,web3,web4,web5,web6 test_myinfra.py .EE .UNINDENT .UNINDENT .SS Advanced invocation .INDENT 0.0 .INDENT 3.5 .sp .EX # Test recursively all test files (starting with \(gatest_\(ga) in current directory $ py.test # Filter function/hosts with pytest \-k option $ py.test \-\-hosts=webserver,dnsserver \-k webserver \-k nginx .EE .UNINDENT .UNINDENT .sp For more usages and features, see the \fI\%Pytest\fP documentation. .SS Connection backends .sp Testinfra comes with several connections backends for remote command execution. .sp When installing, you should select the backends you require as \fBextras\fP to ensure Python dependencies are satisfied (note various system packaged tools may still be required). For example .INDENT 0.0 .INDENT 3.5 .sp .EX $ pip install pytest\-testinfra[ansible,salt] .EE .UNINDENT .UNINDENT .sp For all backends, commands can be run as superuser with the \fB\-\-sudo\fP option or as specific user with the \fB\-\-sudo\-user\fP option. .SS local .sp This is the default backend when no hosts are provided (either via \fB\-\-hosts\fP or in modules). Commands are run locally in a subprocess under the current user: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-sudo test_myinfra.py .EE .UNINDENT .UNINDENT .SS paramiko .sp This is the default backend when a hosts list is provided. \fI\%Paramiko\fP is a Python implementation of the SSHv2 protocol. Testinfra will not ask you for a password, so you must be able to connect without password (using passwordless keys or using \fBssh\-agent\fP). .sp You can provide an alternate ssh\-config: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-ssh\-config=/path/to/ssh_config \-\-hosts=server .EE .UNINDENT .UNINDENT .SS docker .sp The Docker backend can be used to test \fIrunning\fP Docker containers. It uses the \fI\%docker exec\fP command: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqdocker://[user@]container_id_or_name\(aq .EE .UNINDENT .UNINDENT .sp See also the \fI\%Test Docker images\fP example. .SS podman .sp The Podman backend can be used to test \fIrunning\fP Podman containers. It uses the \fI\%podman exec\fP command: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqpodman://[user@]container_id_or_name\(aq .EE .UNINDENT .UNINDENT .SS ssh .sp This is a pure SSH backend using the \fBssh\fP command. Example: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqssh://server\(aq $ py.test \-\-ssh\-config=/path/to/ssh_config \-\-hosts=\(aqssh://server\(aq $ py.test \-\-ssh\-identity\-file=/path/to/key \-\-hosts=\(aqssh://server\(aq $ py.test \-\-hosts=\(aqssh://server?timeout=60&controlpersist=120\(aq $ py.test \-\-hosts=\(aqssh://server\(aq \-\-ssh\-extra\-args=\(aq\-o StrictHostKeyChecking=no\(aq .EE .UNINDENT .UNINDENT .sp By default timeout is set to 10 seconds and ControlPersist is set to 60 seconds. You can disable persistent connection by passing \fIcontrolpersist=0\fP to the options. .SS salt .sp The salt backend uses the \fI\%salt Python client API\fP and can be used from the salt\-master server: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqsalt://*\(aq $ py.test \-\-hosts=\(aqsalt://minion1,salt://minion2\(aq $ py.test \-\-hosts=\(aqsalt://web*\(aq $ py.test \-\-hosts=\(aqsalt://G@os:Debian\(aq .EE .UNINDENT .UNINDENT .sp Testinfra will use the salt connection channel to run commands. .sp Hosts can be selected by using the \fIglob\fP and \fI\%compound matchers\fP\&. .SS ansible .sp Ansible inventories may be used to describe what hosts Testinfra should use and how to connect them, using Testinfra\(aqs Ansible backend. .sp To use the Ansible backend, prefix the \fB\-\-hosts\fP option with \fBansible://\fP e.g: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqansible://all\(aq # tests all inventory hosts $ py.test \-\-hosts=\(aqansible://host1,ansible://host2\(aq $ py.test \-\-hosts=\(aqansible://web*\(aq .EE .UNINDENT .UNINDENT .sp An inventory may be specified with the \fB\-\-ansible\-inventory\fP option, otherwise the default (\fB/etc/ansible/hosts\fP) is used. .sp The \fBansible_connection\fP value in your inventory will be used to determine which backend to use for individual hosts: \fBlocal\fP, \fBssh\fP, \fBparamiko\fP and \fBdocker\fP are supported values. Other connections (or if you are using the \fB\-\-force\-ansible\fP option) will result in testinfra running all commands via Ansible itself, which is substantially slower than the other backends: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-force\-ansible \-\-hosts=\(aqansible://all\(aq $ py.test \-\-hosts=\(aqansible://host?force_ansible=True\(aq .EE .UNINDENT .UNINDENT .sp By default, the Ansible connection backend will first try to use \fBansible_ssh_private_key_file\fP and \fBansible_private_key_file\fP to authenticate, then fall back to the \fBansible_user\fP with \fBansible_ssh_pass\fP variables (both are required), before finally falling back to your own host\(aqs SSH config. .sp This behavior may be overwritten by specifying either the \fB\-\-ssh\-identity\-file\fP option or the \fB\-\-ssh\-config\fP option .sp Finally, these environment variables are supported and will be passed along to their corresponding ansible variable (See Ansible documentation): .sp \fI\%https://docs.ansible.com/ansible/2.3/intro_inventory.html\fP .sp \fI\%https://docs.ansible.com/ansible/latest/reference_appendices/config.html\fP .INDENT 0.0 .IP \(bu 2 \fBANSIBLE_REMOTE_USER\fP .IP \(bu 2 \fBANSIBLE_SSH_EXTRA_ARGS\fP .IP \(bu 2 \fBANSIBLE_SSH_COMMON_ARGS\fP .IP \(bu 2 \fBANSIBLE_REMOTE_PORT\fP .IP \(bu 2 \fBANSIBLE_BECOME_USER\fP .IP \(bu 2 \fBANSIBLE_BECOME\fP .UNINDENT .SS kubectl .sp The kubectl backend can be used to test containers running in Kubernetes. It uses the \fI\%kubectl exec\fP command and support connecting to a given container name within a pod and using a given namespace: .INDENT 0.0 .INDENT 3.5 .sp .EX # will use the default namespace and default container $ py.test \-\-hosts=\(aqkubectl://mypod\-a1b2c3\(aq # specify container name and namespace $ py.test \-\-hosts=\(aqkubectl://somepod\-2536ab?container=nginx&namespace=web\(aq # specify the kubeconfig context to use $ py.test \-\-hosts=\(aqkubectl://somepod\-2536ab?context=k8s\-cluster\-a&container=nginx\(aq # you can specify kubeconfig either from KUBECONFIG environment variable # or when working with multiple configuration with the \(dqkubeconfig\(dq option $ py.test \-\-hosts=\(aqkubectl://somepod\-123?kubeconfig=/path/kubeconfig,kubectl://otherpod\-123?kubeconfig=/other/kubeconfig\(aq .EE .UNINDENT .UNINDENT .SS openshift .sp The openshift backend can be used to test containers running in OpenShift. It uses the \fI\%oc exec\fP command and support connecting to a given container name within a pod and using a given namespace: .INDENT 0.0 .INDENT 3.5 .sp .EX # will use the default namespace and default container $ py.test \-\-hosts=\(aqopenshift://mypod\-a1b2c3\(aq # specify container name and namespace $ py.test \-\-hosts=\(aqopenshift://somepod\-2536ab?container=nginx&namespace=web\(aq # you can specify kubeconfig either from KUBECONFIG environment variable # or when working with multiple configuration with the \(dqkubeconfig\(dq option $ py.test \-\-hosts=\(aqopenshift://somepod\-123?kubeconfig=/path/kubeconfig,openshift://otherpod\-123?kubeconfig=/other/kubeconfig\(aq .EE .UNINDENT .UNINDENT .SS winrm .sp The winrm backend uses \fI\%pywinrm\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqwinrm://Administrator:Password@127.0.0.1\(aq $ py.test \-\-hosts=\(aqwinrm://vagrant@127.0.0.1:2200?no_ssl=true&no_verify_ssl=true\(aq .EE .UNINDENT .UNINDENT .sp pywinrm\(aqs default read and operation timeout can be overridden using query arguments \fBread_timeout_sec\fP and \fBoperation_timeout_sec\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqwinrm://vagrant@127.0.0.1:2200?read_timeout_sec=120&operation_timeout_sec=100\(aq .EE .UNINDENT .UNINDENT .SS LXC/LXD .sp The LXC backend can be used to test \fIrunning\fP LXC or LXD containers. It uses the \fI\%lxc exec\fP command: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-\-hosts=\(aqlxc://container_name\(aq .EE .UNINDENT .UNINDENT .SS Modules .sp Testinfra modules are provided through the \fIhost\fP \fI\%fixture\fP, declare it as arguments of your test function to make it available within it. .INDENT 0.0 .INDENT 3.5 .sp .EX def test_foo(host): # [...] .EE .UNINDENT .UNINDENT .SS host .INDENT 0.0 .TP .B class Host(backend: BaseBackend) .INDENT 7.0 .INDENT 3.5 .INDENT 0.0 .TP .B ansible \fI\%testinfra.modules.ansible.Ansible\fP class .UNINDENT .INDENT 0.0 .TP .B addr \fI\%testinfra.modules.addr.Addr\fP class .UNINDENT .INDENT 0.0 .TP .B blockdevice \fI\%testinfra.modules.blockdevice.BlockDevice\fP class .UNINDENT .INDENT 0.0 .TP .B docker \fI\%testinfra.modules.docker.Docker\fP class .UNINDENT .INDENT 0.0 .TP .B environment \fI\%testinfra.modules.environment.Environment\fP class .UNINDENT .INDENT 0.0 .TP .B file \fI\%testinfra.modules.file.File\fP class .UNINDENT .INDENT 0.0 .TP .B group \fI\%testinfra.modules.group.Group\fP class .UNINDENT .INDENT 0.0 .TP .B interface \fI\%testinfra.modules.interface.Interface\fP class .UNINDENT .INDENT 0.0 .TP .B iptables \fI\%testinfra.modules.iptables.Iptables\fP class .UNINDENT .INDENT 0.0 .TP .B mount_point \fI\%testinfra.modules.mountpoint.MountPoint\fP class .UNINDENT .INDENT 0.0 .TP .B package \fI\%testinfra.modules.package.Package\fP class .UNINDENT .INDENT 0.0 .TP .B pip \fI\%testinfra.modules.pip.Pip\fP class .UNINDENT .INDENT 0.0 .TP .B podman \fI\%testinfra.modules.podman.Podman\fP class .UNINDENT .INDENT 0.0 .TP .B process \fI\%testinfra.modules.process.Process\fP class .UNINDENT .INDENT 0.0 .TP .B puppet_resource \fI\%testinfra.modules.puppet.PuppetResource\fP class .UNINDENT .INDENT 0.0 .TP .B facter \fI\%testinfra.modules.puppet.Facter\fP class .UNINDENT .INDENT 0.0 .TP .B salt \fI\%testinfra.modules.salt.Salt\fP class .UNINDENT .INDENT 0.0 .TP .B service \fI\%testinfra.modules.service.Service\fP class .UNINDENT .INDENT 0.0 .TP .B socket \fI\%testinfra.modules.socket.Socket\fP class .UNINDENT .INDENT 0.0 .TP .B sudo \fI\%testinfra.modules.sudo.Sudo\fP class .UNINDENT .INDENT 0.0 .TP .B supervisor \fI\%testinfra.modules.supervisor.Supervisor\fP class .UNINDENT .INDENT 0.0 .TP .B sysctl \fI\%testinfra.modules.sysctl.Sysctl\fP class .UNINDENT .INDENT 0.0 .TP .B system_info \fI\%testinfra.modules.systeminfo.SystemInfo\fP class .UNINDENT .INDENT 0.0 .TP .B user \fI\%testinfra.modules.user.User\fP class .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B property has_command_v: bool Return True if \fIcommand \-v\fP is available .UNINDENT .INDENT 7.0 .TP .B exists(command: str) -> bool Return True if given command exist in $PATH .UNINDENT .INDENT 7.0 .TP .B find_command(command: str, extrapaths: Iterable[str] = (\(aq/sbin\(aq, \(aq/usr/sbin\(aq)) -> str Return path of given command .sp raise ValueError if command cannot be found .UNINDENT .INDENT 7.0 .TP .B run(command: str, *args: str, **kwargs: Any) -> \fI\%CommandResult\fP Run given command and return rc (exit status), stdout and stderr .sp .EX >>> cmd = host.run(\(dqls \-l /etc/passwd\(dq) >>> cmd.rc 0 >>> cmd.stdout \(aq\-rw\-r\-\-r\-\- 1 root root 1790 Feb 11 00:28 /etc/passwd\en\(aq >>> cmd.stderr \(aq\(aq >>> cmd.succeeded True >>> cmd.failed False .EE .sp Good practice: always use shell arguments quoting to avoid shell injection .sp .EX >>> cmd = host.run(\(dqls \-l \-\- %s\(dq, \(dq/;echo inject\(dq) CommandResult( rc=2, stdout=\(aq\(aq, stderr=( \(aqls: cannot access /;echo inject: No such file or directory\en\(aq), command=\(dqls \-l \(aq/;echo inject\(aq\(dq) .EE .UNINDENT .INDENT 7.0 .TP .B run_expect(expected: list[int], command: str, *args: str, **kwargs: Any) -> \fI\%CommandResult\fP Run command and check it return an expected exit status .INDENT 7.0 .TP .B Parameters \fBexpected\fP \-\- A list of expected exit status .TP .B Raises AssertionError .UNINDENT .UNINDENT .INDENT 7.0 .TP .B run_test(command: str, *args: str, **kwargs: Any) -> \fI\%CommandResult\fP Run command and check it return an exit status of 0 or 1 .INDENT 7.0 .TP .B Raises AssertionError .UNINDENT .UNINDENT .INDENT 7.0 .TP .B check_output(command: str, *args: str, **kwargs: Any) -> str Get stdout of a command which has run successfully .INDENT 7.0 .TP .B Returns stdout without trailing newline .TP .B Raises AssertionError .UNINDENT .UNINDENT .INDENT 7.0 .TP .B classmethod get_host(hostspec: str, **kwargs: Any) -> \fI\%Host\fP Return a Host instance from \fIhostspec\fP .sp \fIhostspec\fP should be like \fI://?param1=value1¶m2=value2\fP .sp Params can also be passed in \fI**kwargs\fP (eg. get_host(\(dqlocal://\(dq, sudo=True) is equivalent to get_host(\(dqlocal://?sudo=true\(dq)) .sp Examples: .INDENT 7.0 .INDENT 3.5 .sp .EX >>> get_host(\(dqlocal://\(dq, sudo=True) >>> get_host(\(dqparamiko://user@host\(dq, ssh_config=\(dq/path/my_ssh_config\(dq) >>> get_host(\(dqansible://all?ansible_inventory=/etc/ansible/inventory\(dq) .EE .UNINDENT .UNINDENT .UNINDENT .UNINDENT .SS Ansible .INDENT 0.0 .TP .B class Ansible(module_name, module_args=None, check=True) Run Ansible module functions .sp This module is only available with the \fI\%ansible\fP connection backend. .sp \fI\%Check mode\fP is enabled by default, you can disable it with \fIcheck=False\fP\&. .sp \fI\%Become\fP is \fIFalse\fP by default. You can enable it with \fIbecome=True\fP\&. .sp Ansible arguments that are not related to the Ansible inventory or connection (both managed by testinfra) are also accepted through keyword arguments: .INDENT 7.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 \fBbecome_method\fP \fIstr\fP sudo, su, doas, etc. .IP \(bu 2 \fBbecome_user\fP \fIstr\fP become this user. .IP \(bu 2 \fBdiff\fP \fIbool\fP: when changing (small) files and templates, show the differences in those files. .IP \(bu 2 \fBextra_vars\fP \fIdict\fP serialized to a JSON string, passed to Ansible. .IP \(bu 2 \fBone_line\fP \fIbool\fP: condense output. .IP \(bu 2 \fBuser\fP \fIstr\fP connect as this user. .IP \(bu 2 \fBverbose\fP \fIint\fP level of verbosity .UNINDENT .UNINDENT .UNINDENT .sp .EX >>> host.ansible(\(dqapt\(dq, \(dqname=nginx state=present\(dq)[\(dqchanged\(dq] False >>> host.ansible(\(dqapt\(dq, \(dqname=nginx state=present\(dq, become=True)[\(dqchanged\(dq] False >>> host.ansible(\(dqcommand\(dq, \(dqecho foo\(dq, check=False)[\(dqstdout\(dq] \(aqfoo\(aq >>> host.ansible(\(dqsetup\(dq)[\(dqansible_facts\(dq][\(dqansible_lsb\(dq][\(dqcodename\(dq] \(aqjessie\(aq >>> host.ansible(\(dqfile\(dq, \(dqpath=/etc/passwd\(dq)[\(dqmode\(dq] \(aq0640\(aq >>> host.ansible( \&... \(dqcommand\(dq, \&... \(dqid \-\-user \-\-name\(dq, \&... check=False, \&... become=True, \&... become_user=\(dqhttp\(dq, \&... )[\(dqstdout\(dq] \(aqhttp\(aq >>> host.ansible( \&... \(dqapt\(dq, \&... \(dqname={{ packages }}\(dq, \&... check=False, \&... extra_vars={\(dqpackages\(dq: [\(dqneovim\(dq, \(dqvim\(dq]}, \&... ) # Installs neovim and vim. .EE .INDENT 7.0 .TP .B exception AnsibleException(result) Exception raised when an error occur in an ansible call .sp result from ansible can be accessed through the \fBresult\fP attribute .sp .EX >>> try: \&... host.ansible(\(dqcommand\(dq, \(dqecho foo\(dq) \&... except host.ansible.AnsibleException as exc: \&... assert exc.result[\(aqfailed\(aq] is True \&... assert exc.result[\(aqmsg\(aq] == \(aqSkipped. You might want to try check=False\(aq # noqa .EE .UNINDENT .INDENT 7.0 .TP .B get_variables() Returns a dict of ansible variables .sp .EX >>> host.ansible.get_variables() { \(aqinventory_hostname\(aq: \(aqlocalhost\(aq, \(aqgroup_names\(aq: [\(aqungrouped\(aq], \(aqfoo\(aq: \(aqbar\(aq, } .EE .UNINDENT .UNINDENT .SS Addr .INDENT 0.0 .TP .B class Addr(name) Test remote address .sp Example: .sp .EX >>> google = host.addr(\(dqgoogle.com\(dq) >>> google.is_resolvable True >>> \(aq173.194.32.225\(aq in google.ipv4_addresses True >>> google.is_reachable True >>> google.port(443).is_reachable True >>> google.port(666).is_reachable False .EE .sp Can also be use within a network \fI\%namespace\fP\&. .sp .EX >>> localhost = host.addr(\(dqlocalhost\(dq, \(dqns1\(dq) >>> localhost.is_resolvable True .EE .sp Network namespaces can only be used if \fI\%ip\fP command is available because in this case, the module use \fI\%ip\-netns\fP as command prefix. In the other case, it will raise NotImplementedError. .INDENT 7.0 .TP .B property name Return host name .UNINDENT .INDENT 7.0 .TP .B property namespace Return network namespace .UNINDENT .INDENT 7.0 .TP .B property namespace_exists Test if the network namespace exists .UNINDENT .INDENT 7.0 .TP .B property is_resolvable Return if address is resolvable .UNINDENT .INDENT 7.0 .TP .B property is_reachable Return if address is reachable .UNINDENT .INDENT 7.0 .TP .B property ip_addresses Return IP addresses of host .UNINDENT .INDENT 7.0 .TP .B property ipv4_addresses Return IPv4 addresses of host .UNINDENT .INDENT 7.0 .TP .B property ipv6_addresses Return IPv6 addresses of host .UNINDENT .INDENT 7.0 .TP .B port(port) Return address\-port pair .UNINDENT .UNINDENT .SS BlockDevice .INDENT 0.0 .TP .B class BlockDevice(name) Information for block device. .sp Should be used with sudo or under root. .sp If device is not a block device, RuntimeError is raised. .INDENT 7.0 .TP .B property is_partition Return True if the device is a partition. .sp .EX >>> host.block_device(\(dq/dev/sda1\(dq).is_partition True .EE .sp .EX >>> host.block_device(\(dq/dev/sda\(dq).is_partition False .EE .UNINDENT .INDENT 7.0 .TP .B property size Return size if the device in bytes. .sp .EX >>> host.block_device(\(dq/dev/sda1\(dq).size 512110190592 .EE .UNINDENT .INDENT 7.0 .TP .B property sector_size Return sector size for the device in bytes. .sp .EX >>> host.block_device(\(dq/dev/sda1\(dq).sector_size 512 .EE .UNINDENT .INDENT 7.0 .TP .B property block_size Return block size for the device in bytes. .sp .EX >>> host.block_device(\(dq/dev/sda\(dq).block_size 4096 .EE .UNINDENT .INDENT 7.0 .TP .B property start_sector Return start sector of the device on the underlying device. .INDENT 7.0 .INDENT 3.5 Usually the value is zero for full devices and is non\-zero for partitions. .UNINDENT .UNINDENT .sp .EX >>> host.block_device(\(dq/dev/sda1\(dq).start_sector 2048 .EE .sp .EX >>> host.block_device(\(dq/dev/md0\(dq).start_sector 0 .EE .UNINDENT .INDENT 7.0 .TP .B property is_writable Return True if device is writable (have no RO status) .sp .EX >>> host.block_device(\(dq/dev/sda\(dq).is_writable True .EE .sp .EX >>> host.block_device(\(dq/dev/loop1\(dq).is_writable False .EE .UNINDENT .INDENT 7.0 .TP .B property ra Return Read Ahead for the device in 512\-bytes sectors. .sp .EX >>> host.block_device(\(dq/dev/sda\(dq).ra 256 .EE .UNINDENT .UNINDENT .SS Docker .INDENT 0.0 .TP .B class Docker(name) Test docker containers running on system. .sp Example: .sp .EX >>> nginx = host.docker(\(dqapp_nginx\(dq) >>> nginx.is_running True >>> nginx.id \(aq7e67dc7495ca8f451d346b775890bdc0fb561ecdc97b68fb59ff2f77b509a8fe\(aq >>> nginx.name \(aqapp_nginx\(aq .EE .INDENT 7.0 .TP .B classmethod client_version() Docker client version .UNINDENT .INDENT 7.0 .TP .B classmethod server_version() Docker server version .UNINDENT .INDENT 7.0 .TP .B classmethod version(format=None) Docker \fI\%version\fP with an optional format (Go template). .sp .EX >>> host.docker.version() Client: Docker Engine \- Community \&... >>> host.docker.version(\(dq{{.Client.Context}}\(dq)) default .EE .UNINDENT .INDENT 7.0 .TP .B classmethod get_containers(**filters) Return a list of containers .sp By default return list of all containers, including non\-running containers. .sp Filtering can be done using filters keys defined on \fI\%https://docs.docker.com/engine/reference/commandline/ps/#filtering\fP .sp Multiple filters for a given key is handled by giving a list of string as value. .sp .EX >>> host.docker.get_containers() [, , ] # Get all running containers >>> host.docker.get_containers(status=\(dqrunning\(dq) [] # Get containers named \(dqnginx\(dq >>> host.docker.get_containers(name=\(dqnginx\(dq) [] # Get containers named \(dqnginx\(dq or \(dqredis\(dq >>> host.docker.get_containers(name=[\(dqnginx\(dq, \(dqredis\(dq]) [, ] .EE .UNINDENT .UNINDENT .SS Environment .INDENT 0.0 .TP .B class Environment(name) Get Environment variables .sp Example: .sp .EX >>> host.environment() { \(dqEDITOR\(dq: \(dqvim\(dq, \(dqSHELL\(dq: \(dq/bin/bash\(dq, [...] } .EE .UNINDENT .SS File .INDENT 0.0 .TP .B class File(path) Test various files attributes .INDENT 7.0 .TP .B property exists Test if file exists .sp .EX >>> host.file(\(dq/etc/passwd\(dq).exists True >>> host.file(\(dq/nonexistent\(dq).exists False .EE .UNINDENT .INDENT 7.0 .TP .B property is_file Test if the path is a regular file .UNINDENT .INDENT 7.0 .TP .B property is_directory Test if the path exists and a directory .UNINDENT .INDENT 7.0 .TP .B property is_executable Test if the path exists and permission to execute is granted .UNINDENT .INDENT 7.0 .TP .B property is_pipe Test if the path exists and is a pipe .UNINDENT .INDENT 7.0 .TP .B property is_socket Test if the path exists and is a socket .UNINDENT .INDENT 7.0 .TP .B property is_symlink Test if the path exists and is a symbolic link .UNINDENT .INDENT 7.0 .TP .B property linked_to Resolve symlink .sp .EX >>> host.file(\(dq/var/lock\(dq).linked_to \(aq/run/lock\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property user Return file owner as string .sp .EX >>> host.file(\(dq/etc/passwd\(dq).user \(aqroot\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property uid Return file user id as integer .sp .EX >>> host.file(\(dq/etc/passwd\(dq).uid 0 .EE .UNINDENT .INDENT 7.0 .TP .B property group Return file group name as string .UNINDENT .INDENT 7.0 .TP .B property gid Return file group id as integer .UNINDENT .INDENT 7.0 .TP .B property mode Return file mode as octal integer .sp .EX >>> host.file(\(dq/etc/shadow\(dq).mode 416 # Oo640 octal >>> host.file(\(dq/etc/shadow\(dq).mode == 0o640 True >>> oct(host.file(\(dq/etc/shadow\(dq).mode) == \(aq0o640\(aq True .EE .sp You can also utilize the file mode constants from the \fI\%stat\fP library for testing file mode. .sp .EX >>> import stat >>> host.file(\(dq/etc/shadow\(dq).mode == stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP True .EE .UNINDENT .INDENT 7.0 .TP .B contains(pattern) Checks content of file for pattern .sp This uses grep and thus follows the grep regex syntax. .UNINDENT .INDENT 7.0 .TP .B property md5sum Compute the MD5 message digest of the file content .UNINDENT .INDENT 7.0 .TP .B property sha256sum Compute the SHA256 message digest of the file content .UNINDENT .INDENT 7.0 .TP .B property content Return file content as bytes .sp .EX >>> host.file(\(dq/tmp/foo\(dq).content b\(aqcaf\exc3\exa9\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property content_string Return file content as string .sp .EX >>> host.file(\(dq/tmp/foo\(dq).content_string \(aqcafé\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property mtime Return time of last modification as datetime.datetime object .sp .EX >>> host.file(\(dq/etc/passwd\(dq).mtime datetime.datetime(2015, 3, 15, 20, 25, 40) .EE .UNINDENT .INDENT 7.0 .TP .B property size Return size of file in bytes .UNINDENT .INDENT 7.0 .TP .B listdir() Return list of items under the directory .sp .EX >>> host.file(\(dq/tmp\(dq).listdir() [\(aqfoo_file\(aq, \(aqbar_dir\(aq] .EE .UNINDENT .UNINDENT .SS Group .INDENT 0.0 .TP .B class Group(name) Test unix group .INDENT 7.0 .TP .B property exists Test if group exists .sp .EX >>> host.group(\(dqwheel\(dq).exists True >>> host.group(\(dqnosuchgroup\(dq).exists False .EE .UNINDENT .INDENT 7.0 .TP .B property gid .UNINDENT .INDENT 7.0 .TP .B property members Return all users that are members of this group. .UNINDENT .UNINDENT .SS Interface .INDENT 0.0 .TP .B class Interface(name, family=None) Test network interfaces .sp .EX >>> host.interface(\(dqeth0\(dq).exists True .EE .sp Optionally, the protocol family to use can be enforced. .sp .EX >>> host.interface(\(dqeth0\(dq, \(dqinet6\(dq).addresses [\(aqfe80::e291:f5ff:fe98:6b8c\(aq] .EE .INDENT 7.0 .TP .B property exists .UNINDENT .INDENT 7.0 .TP .B property speed .UNINDENT .INDENT 7.0 .TP .B property addresses Return ipv4 and ipv6 addresses on the interface .sp .EX >>> host.interface(\(dqeth0\(dq).addresses [\(aq192.168.31.254\(aq, \(aq192.168.31.252\(aq, \(aqfe80::e291:f5ff:fe98:6b8c\(aq] .EE .UNINDENT .INDENT 7.0 .TP .B property link Return the link properties associated with the interface. .sp .EX >>> host.interface(\(dqlo\(dq).link {\(aqaddress\(aq: \(aq00:00:00:00:00:00\(aq, \(aqbroadcast\(aq: \(aq00:00:00:00:00:00\(aq, \(aqflags\(aq: [\(aqLOOPBACK\(aq, \(aqUP\(aq, \(aqLOWER_UP\(aq], \(aqgroup\(aq: \(aqdefault\(aq, \(aqifindex\(aq: 1, \(aqifname\(aq: \(aqlo\(aq, \(aqlink_type\(aq: \(aqloopback\(aq, \(aqlinkmode\(aq: \(aqDEFAULT\(aq, \(aqmtu\(aq: 65536, \(aqoperstate\(aq: \(aqUNKNOWN\(aq, \(aqqdisc\(aq: \(aqnoqueue\(aq, \(aqtxqlen\(aq: 1000} .EE .UNINDENT .INDENT 7.0 .TP .B routes(scope=None) Return the routes associated with the interface, optionally filtered by scope (\(dqhost\(dq, \(dqlink\(dq or \(dqglobal\(dq). .sp .EX >>> host.interface(\(dqeth0\(dq).routes() [{\(aqdst\(aq: \(aqdefault\(aq, \(aqflags\(aq: [], \(aqgateway\(aq: \(aq192.0.2.1\(aq, \(aqmetric\(aq: 3003, \(aqprefsrc\(aq: \(aq192.0.2.5\(aq, \(aqprotocol\(aq: \(aqdhcp\(aq}, {\(aqdst\(aq: \(aq192.0.2.0/24\(aq, \(aqflags\(aq: [], \(aqmetric\(aq: 3003, \(aqprefsrc\(aq: \(aq192.0.2.5\(aq, \(aqprotocol\(aq: \(aqdhcp\(aq, \(aqscope\(aq: \(aqlink\(aq}] .EE .UNINDENT .INDENT 7.0 .TP .B classmethod names() Return the names of all the interfaces. .sp .EX >>> host.interface.names() [\(aqlo\(aq, \(aqtunl0\(aq, \(aqip6tnl0\(aq, \(aqeth0\(aq] .EE .UNINDENT .INDENT 7.0 .TP .B classmethod default(family=None) Return the interface used for the default route. .sp .EX >>> host.interface.default() .EE .sp Optionally, the protocol family to use can be enforced. .sp .EX >>> host.interface.default(\(dqinet6\(dq) None .EE .UNINDENT .UNINDENT .SS Iptables .INDENT 0.0 .TP .B class Iptables Test iptables rule exists .INDENT 7.0 .TP .B rules(table=\(aqfilter\(aq, chain=None, version=4) Returns list of iptables rules .INDENT 7.0 .INDENT 3.5 Based on output of \fIiptables \-t TABLE \-S CHAIN\fP command .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .TP .B optionally takes takes the following arguments: .INDENT 7.0 .IP \(bu 2 table: defaults to \fIfilter\fP .IP \(bu 2 chain: defaults to all chains .IP \(bu 2 version: default 4 (iptables), optionally 6 (ip6tables) .UNINDENT .UNINDENT .UNINDENT .UNINDENT .UNINDENT .UNINDENT .sp .EX >>> host.iptables.rules() [ \(aq\-P INPUT ACCEPT\(aq, \(aq\-P FORWARD ACCEPT\(aq, \(aq\-P OUTPUT ACCEPT\(aq, \(aq\-A INPUT \-i lo \-j ACCEPT\(aq, \(aq\-A INPUT \-j REJECT\(aq \(aq\-A FORWARD \-j REJECT\(aq ] >>> host.iptables.rules(\(dqnat\(dq, \(dqINPUT\(dq) [\(aq\-P PREROUTING ACCEPT\(aq] .EE .UNINDENT .UNINDENT .SS MountPoint .INDENT 0.0 .TP .B class MountPoint(path) Test Mount Points .INDENT 7.0 .TP .B property exists Return True if the mountpoint exists .sp .EX >>> host.mount_point(\(dq/\(dq).exists True .EE .sp .EX >>> host.mount_point(\(dq/not/a/mountpoint\(dq).exists False .EE .UNINDENT .INDENT 7.0 .TP .B property filesystem Returns the filesystem type associated .sp .EX >>> host.mount_point(\(dq/\(dq).filesystem \(aqext4\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property device Return the device associated .sp .EX >>> host.mount_point(\(dq/\(dq).device \(aq/dev/sda1\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property options Return a list of options that a mount point has been created with .sp .EX >>> host.mount_point(\(dq/\(dq).options [\(aqrw\(aq, \(aqrelatime\(aq, \(aqdata=ordered\(aq] .EE .UNINDENT .INDENT 7.0 .TP .B classmethod get_mountpoints() Returns a list of MountPoint instances .sp .EX >>> host.mount_point.get_mountpoints() [, ] .EE .UNINDENT .UNINDENT .SS Package .INDENT 0.0 .TP .B class Package(name) Test packages status and version .INDENT 7.0 .TP .B property is_installed Test if the package is installed .sp .EX >>> host.package(\(dqnginx\(dq).is_installed True .EE .sp Supported package systems: .INDENT 7.0 .IP \(bu 2 apk (Alpine) .IP \(bu 2 apt (Debian, Ubuntu, ...) .IP \(bu 2 brew (macOS) .IP \(bu 2 pacman (Arch, Manjaro ) .IP \(bu 2 pkg (FreeBSD) .IP \(bu 2 pkg_info (NetBSD) .IP \(bu 2 pkg_info (OpenBSD) .IP \(bu 2 rpm (RHEL, RockyLinux, Fedora, ...) .UNINDENT .UNINDENT .INDENT 7.0 .TP .B property release Return the release specific info from the package version .sp .EX >>> host.package(\(dqnginx\(dq).release \(aq1.el6\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property version Return package version as returned by the package system .sp .EX >>> host.package(\(dqnginx\(dq).version \(aq1.2.1\-2.2+wheezy3\(aq .EE .UNINDENT .UNINDENT .SS Pip .INDENT 0.0 .TP .B class Pip(name, pip_path=\(aqpip\(aq) Test pip package manager and packages .INDENT 7.0 .TP .B property is_installed Test if the package is installed .sp .EX >>> host.package(\(dqpip\(dq).is_installed True .EE .UNINDENT .INDENT 7.0 .TP .B property version Return package version as returned by pip .sp .EX >>> host.package(\(dqpip\(dq).version \(aq18.1\(aq .EE .UNINDENT .INDENT 7.0 .TP .B classmethod check(pip_path=\(aqpip\(aq) Verify installed packages have compatible dependencies. .sp .EX >>> cmd = host.pip.check() >>> cmd.rc 0 >>> cmd.stdout No broken requirements found. .EE .sp Can only be used if \fI\%pip check\fP command is available, for pip versions >= \fI\%9.0.0\fP\&. .UNINDENT .INDENT 7.0 .TP .B classmethod get_packages(pip_path=\(aqpip\(aq) Get all installed packages and versions returned by \fIpip list\fP: .sp .EX >>> host.pip.get_packages(pip_path=\(aq~/venv/website/bin/pip\(aq) {\(aqDjango\(aq: {\(aqversion\(aq: \(aq1.10.2\(aq}, \(aqmywebsite\(aq: {\(aqversion\(aq: \(aq1.0a3\(aq, \(aqpath\(aq: \(aq/srv/website\(aq}, \(aqpsycopg2\(aq: {\(aqversion\(aq: \(aq2.6.2\(aq}} .EE .UNINDENT .INDENT 7.0 .TP .B classmethod get_outdated_packages(pip_path=\(aqpip\(aq) Get all outdated packages with current and latest version .sp .EX >>> host.pip.get_outdated_packages( \&... pip_path=\(aq~/venv/website/bin/pip\(aq) {\(aqDjango\(aq: {\(aqcurrent\(aq: \(aq1.10.2\(aq, \(aqlatest\(aq: \(aq1.10.3\(aq}} .EE .UNINDENT .UNINDENT .SS Podman .INDENT 0.0 .TP .B class Podman(name) Test podman containers running on system. .sp Example: .sp .EX >>> nginx = host.podman(\(dqapp_nginx\(dq) >>> nginx.is_running True >>> nginx.id \(aq7e67dc7495ca8f451d346b775890bdc0fb561ecdc97b68fb59ff2f77b509a8fe\(aq >>> nginx.name \(aqapp_nginx\(aq .EE .INDENT 7.0 .TP .B classmethod get_containers(**filters) Return a list of containers .sp By default return list of all containers, including non\-running containers. .sp Filtering can be done using filters keys defined in podman\-ps(1). .sp Multiple filters for a given key is handled by giving a list of string as value. .sp .EX >>> host.podman.get_containers() [, , ] # Get all running containers >>> host.podman.get_containers(status=\(dqrunning\(dq) [] # Get containers named \(dqnginx\(dq >>> host.podman.get_containers(name=\(dqnginx\(dq) [] # Get containers named \(dqnginx\(dq or \(dqredis\(dq >>> host.podman.get_containers(name=[\(dqnginx\(dq, \(dqredis\(dq]) [, ] .EE .UNINDENT .UNINDENT .SS Process .INDENT 0.0 .TP .B class Process Test Processes attributes .sp Processes are selected using \fBfilter()\fP or \fBget()\fP, attributes names are described in the \fI\%ps(1) man page\fP\&. .sp .EX >>> master = host.process.get(user=\(dqroot\(dq, comm=\(dqnginx\(dq) # Here is the master nginx process (running as root) >>> master.args \(aqnginx: master process /usr/sbin/nginx \-g daemon on; master_process on;\(aq # Here are the worker processes (Parent PID = master PID) >>> workers = host.process.filter(ppid=master.pid) >>> len(workers) 4 # Nginx don\(aqt eat memory >>> sum([w.pmem for w in workers]) 0.8 # But php does ! >>> sum([p.pmem for p in host.process.filter(comm=\(dqphp5\-fpm\(dq)]) 19.2 .EE .INDENT 7.0 .TP .B filter(**filters) Get a list of matching process .sp .EX >>> host.process.filter(user=\(dqroot\(dq, comm=\(dqzsh\(dq) [, , ...] .EE .UNINDENT .INDENT 7.0 .TP .B get(**filters) Get one matching process .sp Raise \fBRuntimeError\fP if no process found or multiple process matching filters. .UNINDENT .UNINDENT .SS PuppetResource .INDENT 0.0 .TP .B class PuppetResource(type, name=None) Get puppet resources .sp Run \fBpuppet resource \-\-types\fP to get a list of available types. .sp .EX >>> host.puppet_resource(\(dquser\(dq, \(dqwww\-data\(dq) { \(aqwww\-data\(aq: { \(aqensure\(aq: \(aqpresent\(aq, \(aqcomment\(aq: \(aqwww\-data\(aq, \(aqgid\(aq: \(aq33\(aq, \(aqhome\(aq: \(aq/var/www\(aq, \(aqshell\(aq: \(aq/usr/sbin/nologin\(aq, \(aquid\(aq: \(aq33\(aq, }, } .EE .UNINDENT .SS Facter .INDENT 0.0 .TP .B class Facter(*facts) Get facts with \fI\%facter\fP .sp .EX >>> host.facter() { \(dqoperatingsystem\(dq: \(dqDebian\(dq, \(dqkernel\(dq: \(dqlinux\(dq, [...] } >>> host.facter(\(dqkernelversion\(dq, \(dqis_virtual\(dq) { \(dqkernelversion\(dq: \(dq3.16.0\(dq, \(dqis_virtual\(dq: \(dqfalse\(dq } .EE .UNINDENT .SS Salt .INDENT 0.0 .TP .B class Salt(function, args=None, local=False, config=None) Run salt module functions .sp .EX >>> host.salt(\(dqpkg.version\(dq, \(dqnginx\(dq) \(aq1.6.2\-5\(aq >>> host.salt(\(dqpkg.version\(dq, [\(dqnginx\(dq, \(dqphp5\-fpm\(dq]) {\(aqnginx\(aq: \(aq1.6.2\-5\(aq, \(aqphp5\-fpm\(aq: \(aq5.6.7+dfsg\-1\(aq} >>> host.salt(\(dqgrains.item\(dq, [\(dqosarch\(dq, \(dqmem_total\(dq, \(dqnum_cpus\(dq]) {\(aqosarch\(aq: \(aqamd64\(aq, \(aqnum_cpus\(aq: 4, \(aqmem_total\(aq: 15520} .EE .sp Run \fBsalt\-call sys.doc\fP to get a complete list of functions .UNINDENT .SS Service .INDENT 0.0 .TP .B class Service(name) Test services .sp Implementations: .INDENT 7.0 .IP \(bu 2 Linux: detect Systemd, Upstart or OpenRC, fallback to SysV .IP \(bu 2 FreeBSD: service(1) .IP \(bu 2 OpenBSD: \fB/etc/rc.d/$name check\fP for \fBis_running\fP \fBrcctl ls on\fP for \fBis_enabled\fP (only OpenBSD >= 5.8) .IP \(bu 2 NetBSD: \fB/etc/rc.d/$name onestatus\fP for \fBis_running\fP (\fBis_enabled\fP is not yet implemented) .UNINDENT .INDENT 7.0 .TP .B property exists Test if service is exists .UNINDENT .INDENT 7.0 .TP .B property is_running Test if service is running .UNINDENT .INDENT 7.0 .TP .B property is_enabled Test if service is enabled .UNINDENT .INDENT 7.0 .TP .B property is_valid Test if service is valid .sp This method is only available in the systemd implementation, it will raise \fBNotImplementedError\fP in others implementation .UNINDENT .INDENT 7.0 .TP .B property is_masked Test if service is masked .sp This method is only available in the systemd implementation, it will raise \fBNotImplementedError\fP in others implementations .UNINDENT .INDENT 7.0 .TP .B property systemd_properties Properties of the service (unit). .sp Return service properties as a \fIdict\fP, empty properties are not returned. .sp .EX >>> ntp = host.service(\(dqntp\(dq) >>> ntp.systemd_properties[\(dqFragmentPath\(dq] \(aq/lib/systemd/system/ntp.service\(aq .EE .sp This method is only available in the systemd implementation, it will raise \fBNotImplementedError\fP in others implementations .sp Note: based on \fI\%systemctl show\fP .UNINDENT .UNINDENT .SS Socket .INDENT 0.0 .TP .B class Socket(socketspec) Test listening tcp/udp and unix sockets .sp \fBsocketspec\fP must be specified as \fB://:\fP .sp This module requires the \fBnetstat\fP command to on the target host. .sp Example: .INDENT 7.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 Unix sockets: \fBunix:///var/run/docker.sock\fP .IP \(bu 2 All ipv4 and ipv6 tcp sockets on port 22: \fBtcp://22\fP .IP \(bu 2 All ipv4 sockets on port 22: \fBtcp://0.0.0.0:22\fP .IP \(bu 2 All ipv6 sockets on port 22: \fBtcp://:::22\fP .IP \(bu 2 udp socket on 127.0.0.1 port 69: \fBudp://127.0.0.1:69\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B property is_listening Test if socket is listening .sp .EX >>> host.socket(\(dqunix:///var/run/docker.sock\(dq).is_listening False >>> # This HTTP server listen on all ipv4 addresses but not on ipv6 >>> host.socket(\(dqtcp://0.0.0.0:80\(dq).is_listening True >>> host.socket(\(dqtcp://:::80\(dq).is_listening False >>> host.socket(\(dqtcp://80\(dq).is_listening False .EE .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 If you don\(aqt specify a host for udp and tcp sockets, then the socket is listening if and only if the socket listen on \fBboth\fP all ipv4 and ipv6 addresses (ie 0.0.0.0 and ::) .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B property clients: List[Tuple[str, int] | None] Return a list of clients connected to a listening socket .sp For tcp and udp sockets a list of pair (address, port) is returned. For unix sockets a list of None is returned (thus you can make a len() for counting clients). .sp .EX >>> host.socket(\(dqtcp://22\(dq).clients [(\(aq2001:db8:0:1\(aq, 44298), (\(aq192.168.31.254\(aq, 34866)] >>> host.socket(\(dqunix:///var/run/docker.sock\(dq) [None, None, None] .EE .UNINDENT .INDENT 7.0 .TP .B classmethod get_listening_sockets() Return a list of all listening sockets .sp .EX >>> host.socket.get_listening_sockets() [\(aqtcp://0.0.0.0:22\(aq, \(aqtcp://:::22\(aq, \(aqunix:///run/systemd/private\(aq, ...] .EE .UNINDENT .UNINDENT .SS Sudo .INDENT 0.0 .TP .B class Sudo(user=None) Sudo module allow to run certain portion of code under another user. .sp It is used as a context manager and can be nested. .sp .EX >>> Command.check_output(\(dqwhoami\(dq) \(aqphil\(aq >>> with host.sudo(): \&... host.check_output(\(dqwhoami\(dq) \&... with host.sudo(\(dqwww\-data\(dq): \&... host.check_output(\(dqwhoami\(dq) \&... \(aqroot\(aq \(aqwww\-data\(aq .EE .UNINDENT .SS Supervisor .INDENT 0.0 .TP .B class Supervisor(name, supervisorctl_path=\(aqsupervisorctl\(aq, supervisorctl_conf=None, _attrs_cache=None) Test supervisor managed services .sp .EX >>> gunicorn = host.supervisor(\(dqgunicorn\(dq) >>> gunicorn.status \(aqRUNNING\(aq >>> gunicorn.is_running True >>> gunicorn.pid 4242 .EE .sp The path where supervisorctl and its configuration file reside can be specified. .sp .EX >>> gunicorn = host.supervisor(\(dqgunicorn\(dq, \(dq/usr/bin/supervisorctl\(dq, \(dq/etc/supervisor/supervisord.conf\(dq) >>> gunicorn.status \(aqRUNNING\(aq .EE .INDENT 7.0 .TP .B property is_running Return True if managed service is in status RUNNING .UNINDENT .INDENT 7.0 .TP .B property status Return the status of the managed service .sp Status can be STOPPED, STARTING, RUNNING, BACKOFF, STOPPING, EXITED, FATAL, UNKNOWN. .sp See \fI\%http://supervisord.org/subprocess.html#process\-states\fP .UNINDENT .INDENT 7.0 .TP .B property pid Return the pid (as int) of the managed service .UNINDENT .INDENT 7.0 .TP .B classmethod get_services(supervisorctl_path=\(aqsupervisorctl\(aq, supervisorctl_conf=None) Get a list of services running under supervisor .sp .EX >>> host.supervisor.get_services() [ ] .EE .sp The path where supervisorctl and its configuration file reside can be specified. .sp .EX >>> host.supervisor.get_services(\(dq/usr/bin/supervisorctl\(dq, \(dq/etc/supervisor/supervisord.conf\(dq) [ ] .EE .UNINDENT .UNINDENT .SS Sysctl .INDENT 0.0 .TP .B class Sysctl(name) Test kernel parameters .sp .EX >>> host.sysctl(\(dqkernel.osrelease\(dq) \(dq3.16.0\-4\-amd64\(dq >>> host.sysctl(\(dqvm.dirty_ratio\(dq) 20 .EE .UNINDENT .SS SystemInfo .INDENT 0.0 .TP .B class SystemInfo Return system information .INDENT 7.0 .TP .B property type OS type .sp .EX >>> host.system_info.type \(aqlinux\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property distribution Distribution name .sp .EX >>> host.system_info.distribution \(aqdebian\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property release Distribution release number .sp .EX >>> host.system_info.release \(aq10.2\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property codename Release code name .sp .EX >>> host.system_info.codename \(aqbullseye\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property arch Host architecture .sp .EX >>> host.system_info.arch \(aqx86_64\(aq .EE .UNINDENT .UNINDENT .SS User .INDENT 0.0 .TP .B class User(name=None) Test unix users .sp If name is not supplied, test the current user .INDENT 7.0 .TP .B property name Return user name .UNINDENT .INDENT 7.0 .TP .B property exists Test if user exists .sp .EX >>> host.user(\(dqroot\(dq).exists True >>> host.user(\(dqnosuchuser\(dq).exists False .EE .UNINDENT .INDENT 7.0 .TP .B property uid Return user ID .UNINDENT .INDENT 7.0 .TP .B property gid Return effective group ID .UNINDENT .INDENT 7.0 .TP .B property group Return effective group name .UNINDENT .INDENT 7.0 .TP .B property gids Return the list of user group IDs .UNINDENT .INDENT 7.0 .TP .B property groups Return the list of user group names .UNINDENT .INDENT 7.0 .TP .B property home Return the user home directory .UNINDENT .INDENT 7.0 .TP .B property shell Return the user login shell .UNINDENT .INDENT 7.0 .TP .B property password Return the encrypted user password .UNINDENT .INDENT 7.0 .TP .B property password_max_days Return the maximum number of days between password changes .UNINDENT .INDENT 7.0 .TP .B property password_min_days Return the minimum number of days between password changes .UNINDENT .INDENT 7.0 .TP .B property gecos Return the user comment/gecos field .UNINDENT .INDENT 7.0 .TP .B property expiration_date Return the account expiration date .sp .EX >>> host.user(\(dqphil\(dq).expiration_date datetime.datetime(2020, 1, 1, 0, 0) >>> host.user(\(dqroot\(dq).expiration_date None .EE .UNINDENT .UNINDENT .SS CommandResult .INDENT 0.0 .TP .B class CommandResult(backend: BaseBackend, exit_status: int, command: bytes, _stdout: str | bytes, _stderr: str | bytes) Object that encapsulates all returned details of the command execution. .sp Example: .sp .EX >>> cmd = host.run(\(dqls \-l /etc/passwd\(dq) >>> cmd.rc 0 >>> cmd.stdout \(aq\-rw\-r\-\-r\-\- 1 root root 1790 Feb 11 00:28 /etc/passwd\en\(aq >>> cmd.stderr \(aq\(aq >>> cmd.succeeded True >>> cmd.failed False .EE .INDENT 7.0 .TP .B property succeeded: bool Returns whether the command was successful .sp .EX >>> host.run(\(dqtrue\(dq).succeeded True .EE .UNINDENT .INDENT 7.0 .TP .B property failed: bool Returns whether the command failed .sp .EX >>> host.run(\(dqfalse\(dq).failed True .EE .UNINDENT .INDENT 7.0 .TP .B property rc: int Gets the returncode of a command .sp .EX >>> host.run(\(dqtrue\(dq).rc 0 .EE .UNINDENT .INDENT 7.0 .TP .B property stdout: str Gets standard output (stdout) stream of an executed command .sp .EX >>> host.run(\(dqmkdir \-v new_directory\(dq).stdout mkdir: created directory \(aqnew_directory\(aq .EE .UNINDENT .INDENT 7.0 .TP .B property stderr: str Gets standard error (stderr) stream of an executed command .sp .EX >>> host.run(\(dqmkdir new_directory\(dq).stderr mkdir: cannot create directory \(aqnew_directory\(aq: File exists .EE .UNINDENT .INDENT 7.0 .TP .B property stdout_bytes: bytes Gets standard output (stdout) stream of an executed command as bytes .sp .EX >>> host.run(\(dqmkdir \-v new_directory\(dq).stdout_bytes b\(dqmkdir: created directory \(aqnew_directory\(aq\(dq .EE .UNINDENT .INDENT 7.0 .TP .B property stderr_bytes: bytes Gets standard error (stderr) stream of an executed command as bytes .sp .EX >>> host.run(\(dqmkdir new_directory\(dq).stderr_bytes b\(dqmkdir: cannot create directory \(aqnew_directory\(aq: File exists\(dq .EE .UNINDENT .UNINDENT .SS API .SS Connection API .sp You can use testinfra outside of pytest. You can dynamically get a \fIhost\fP instance and call functions or access members of the respective modules: .INDENT 0.0 .INDENT 3.5 .sp .EX >>> import testinfra >>> host = testinfra.get_host(\(dqparamiko://root@server:2222\(dq, sudo=True) >>> host.file(\(dq/etc/shadow\(dq).mode == 0o640 True .EE .UNINDENT .UNINDENT .sp For instance you could make a test to compare two files on two different servers: .INDENT 0.0 .INDENT 3.5 .sp .EX import testinfra def test_same_passwd(): a = testinfra.get_host(\(dqssh://a\(dq) b = testinfra.get_host(\(dqssh://b\(dq) assert a.file(\(dq/etc/passwd\(dq).content == b.file(\(dq/etc/passwd\(dq).content .EE .UNINDENT .UNINDENT .SS Examples .SS Parametrize your tests .sp Pytest support \fI\%test parametrization\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX # BAD: If the test fails on nginx, python is not tested def test_packages(host): for name, version in ( (\(dqnginx\(dq, \(dq1.6\(dq), (\(dqpython\(dq, \(dq2.7\(dq), ): pkg = host.package(name) assert pkg.is_installed assert pkg.version.startswith(version) # GOOD: Each package is tested # $ py.test \-v test.py # [...] # test.py::test_package[local\-nginx\-1.6] PASSED # test.py::test_package[local\-python\-2.7] PASSED # [...] import pytest @pytest.mark.parametrize(\(dqname,version\(dq, [ (\(dqnginx\(dq, \(dq1.6\(dq), (\(dqpython\(dq, \(dq2.7\(dq), ]) def test_packages(host, name, version): pkg = host.package(name) assert pkg.is_installed assert pkg.version.startswith(version) .EE .UNINDENT .UNINDENT .SS Using unittest .sp Testinfra can be used with the standard Python unit test framework \fI\%unittest\fP instead of pytest: .INDENT 0.0 .INDENT 3.5 .sp .EX import unittest import testinfra class Test(unittest.TestCase): def setUp(self): self.host = testinfra.get_host(\(dqparamiko://root@host\(dq) def test_nginx_config(self): self.assertEqual(self.host.run(\(dqnginx \-t\(dq).rc, 0) def test_nginx_service(self): service = self.host.service(\(dqnginx\(dq) self.assertTrue(service.is_running) self.assertTrue(service.is_enabled) if __name__ == \(dq__main__\(dq: unittest.main() .EE .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .EX $ python test.py \&.. \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- Ran 2 tests in 0.705s OK .EE .UNINDENT .UNINDENT .SS Integration with Vagrant .sp \fI\%Vagrant\fP is a tool to setup and provision development environments (virtual machines). .sp When your Vagrant machine is up and running, you can easily run your testinfra test suite on it: .INDENT 0.0 .INDENT 3.5 .sp .EX vagrant ssh\-config > .vagrant/ssh\-config py.test \-\-hosts=default \-\-ssh\-config=.vagrant/ssh\-config tests.py .EE .UNINDENT .UNINDENT .SS Integration with Jenkins .sp \fI\%Jenkins\fP is a well known open source continuous integration server. .sp If your Jenkins slave can run Vagrant, your build scripts can be like: .INDENT 0.0 .INDENT 3.5 .sp .EX pip install pytest\-testinfra paramiko vagrant up vagrant ssh\-config > .vagrant/ssh\-config py.test \-\-hosts=default \-\-ssh\-config=.vagrant/ssh\-config \-\-junit\-xml junit.xml tests.py .EE .UNINDENT .UNINDENT .sp Then configure Jenkins to get tests results from the \fIjunit.xml\fP file. .SS Integration with Nagios .sp Your tests will usually be validating that the services you are deploying run correctly. This kind of tests are close to monitoring checks, so let\(aqs push them to \fI\%Nagios\fP ! .sp The Testinfra option \fI\-\-nagios\fP enables a behavior compatible with a nagios plugin: .INDENT 0.0 .INDENT 3.5 .sp .EX $ py.test \-qq \-\-nagios \-\-tb line test_ok.py; echo $? TESTINFRA OK \- 2 passed, 0 failed, 0 skipped in 2.30 seconds \&.. 0 $ py.test \-qq \-\-nagios \-\-tb line test_fail.py; echo $? TESTINFRA CRITICAL \- 1 passed, 1 failed, 0 skipped in 2.24 seconds \&.F /usr/lib/python3/dist\-packages/example/example.py:95: error: [Errno 111] error msg 2 .EE .UNINDENT .UNINDENT .sp You can run these tests from the nagios master or in the target host with \fI\%NRPE\fP\&. .SS Integration with KitchenCI .sp KitchenCI (aka Test Kitchen) can use testinfra via its \fBshell\fP verifier. Add the following to your \fB\&.kitchen.yml\fP, this requires installing \fIparamiko\fP additionally (on your host machine, not in the VM handled by kitchen) .INDENT 0.0 .INDENT 3.5 .sp .EX verifier: name: shell command: py.test \-\-hosts=\(dqparamiko://${KITCHEN_USERNAME}@${KITCHEN_HOSTNAME}:${KITCHEN_PORT}?ssh_identity_file=${KITCHEN_SSH_KEY}\(dq \-\-junit\-xml \(dqjunit\-${KITCHEN_INSTANCE}.xml\(dq \(dqtest/integration/${KITCHEN_SUITE}\(dq .EE .UNINDENT .UNINDENT .SS Test Docker images .sp Docker is a handy way to test your infrastructure code. This recipe shows how to build and run Docker containers with Testinfra by overloading the \fIhost\fP fixture. .INDENT 0.0 .INDENT 3.5 .sp .EX import pytest import subprocess import testinfra # scope=\(aqsession\(aq uses the same container for all the tests; # scope=\(aqfunction\(aq uses a new container per test function. @pytest.fixture(scope=\(aqsession\(aq) def host(request): # build local ./Dockerfile subprocess.check_call([\(aqdocker\(aq, \(aqbuild\(aq, \(aq\-t\(aq, \(aqmyimage\(aq, \(aq.\(aq]) # run a container docker_id = subprocess.check_output( [\(aqdocker\(aq, \(aqrun\(aq, \(aq\-d\(aq, \(aqmyimage\(aq]).decode().strip() # return a testinfra connection to the container yield testinfra.get_host(\(dqdocker://\(dq + docker_id) # at the end of the test suite, destroy the container subprocess.check_call([\(aqdocker\(aq, \(aqrm\(aq, \(aq\-f\(aq, docker_id]) def test_myimage(host): # \(aqhost\(aq now binds to the container assert host.check_output(\(aqmyapp \-v\(aq) == \(aqMyapp 1.0\(aq .EE .UNINDENT .UNINDENT .SS Support .sp If you have questions or need help with testinfra please consider one of the following .SS Issue Tracker .sp Checkout existing issues on \fI\%project issue tracker\fP .SS IRC .sp You can also ask questions on IRC in \fI\%#pytest\fP channel on [libera.chat](\fI\%https://libera.chat/\fP) network. .SS pytest documentation .sp testinfra is implemented as pytest plugin so to get the most out of please read \fI\%pytest documentation\fP .SS Community Contributions .INDENT 0.0 .IP \(bu 2 \fI\%Molecule\fP is an Automated testing framework for Ansible roles, with native Testinfra support. .UNINDENT .SH AUTHOR Philippe Pepiot .SH COPYRIGHT 2024, Philippe Pepiot .\" Generated by docutils manpage writer. .