diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b64a3ff..ce6e0bc2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: with: fetch-depth: 0 # needed by setuptools-scm - name: Switch to using Python 3.9 by default - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" - name: Install dependencies diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 5a7526fb..c9dc37ad 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -47,7 +47,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" - name: Install dependencies diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 652a6f21..739f87db 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,27 @@ Changelog ========= +10.1.0 +====== + +* [NEW] Add Interface.link property +* [FIX] Make file properties follow symlinks +* [FIX] Require pytest>=6 and use future annotations for pytest<7 compatibility + +10.0.0 +====== + +* [FIX] Ansible: Fix for missing group names in get_variables() +* [FIX] testinfra/modules/blockdevice: Don't fail on stderr +* [DOC] Extend and show the documentation of CommandResult +* [FIX] Extend list of valid suffixes for systemd units +* [DOC] Add missing Environment doc section +* [MISC] Define types for plugin.py +* [FIX] Missing RHEL distribution in package module +* [NEW] Add brew support in package module +* [NEW] Add Service.exists +* [MISC] Make CommandResult a dataclass + 9.0.0 ===== diff --git a/setup.cfg b/setup.cfg index 07cd1b56..37e681ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,7 +32,7 @@ packages = find: setup_requires = setuptools_scm install_requires = - pytest!=3.0.2 + pytest>=6 extras_require = [options.extras_require] diff --git a/testinfra/modules/file.py b/testinfra/modules/file.py index 25354851..13307f49 100644 --- a/testinfra/modules/file.py +++ b/testinfra/modules/file.py @@ -223,38 +223,38 @@ def get_module_class(cls, host): class GNUFile(File): @property def user(self): - return self.check_output("stat -c %%U %s", self.path) + return self.check_output("stat -Lc %%U %s", self.path) @property def uid(self): - return int(self.check_output("stat -c %%u %s", self.path)) + return int(self.check_output("stat -Lc %%u %s", self.path)) @property def group(self): - return self.check_output("stat -c %%G %s", self.path) + return self.check_output("stat -Lc %%G %s", self.path) @property def gid(self): - return int(self.check_output("stat -c %%g %s", self.path)) + return int(self.check_output("stat -Lc %%g %s", self.path)) @property def mode(self): # Supply a base of 8 when parsing an octal integer # e.g. int('644', 8) -> 420 - return int(self.check_output("stat -c %%a %s", self.path), 8) + return int(self.check_output("stat -Lc %%a %s", self.path), 8) @property def mtime(self): - ts = self.check_output("stat -c %%Y %s", self.path) + ts = self.check_output("stat -Lc %%Y %s", self.path) return datetime.datetime.fromtimestamp(float(ts)) @property def size(self): - return int(self.check_output("stat -c %%s %s", self.path)) + return int(self.check_output("stat -Lc %%s %s", self.path)) @property def inode(self): - return int(self.check_output("stat -c %%i %s", self.path)) + return int(self.check_output("stat -Lc %%i %s", self.path)) @property def md5sum(self): diff --git a/testinfra/modules/interface.py b/testinfra/modules/interface.py index d5caec2f..ce6b3a3b 100644 --- a/testinfra/modules/interface.py +++ b/testinfra/modules/interface.py @@ -51,6 +51,26 @@ def addresses(self): """ raise NotImplementedError + @property + def link(self): + """Return the link properties associated with the interface. + + >>> host.interface("lo").link + {'address': '00:00:00:00:00:00', + 'broadcast': '00:00:00:00:00:00', + 'flags': ['LOOPBACK', 'UP', 'LOWER_UP'], + 'group': 'default', + 'ifindex': 1, + 'ifname': 'lo', + 'link_type': 'loopback', + 'linkmode': 'DEFAULT', + 'mtu': 65536, + 'operstate': 'UNKNOWN', + 'qdisc': 'noqueue', + 'txqlen': 1000} + """ + raise NotImplementedError + def routes(self, scope=None): """Return the routes associated with the interface, optionally filtered by scope ("host", "link" or "global"). @@ -132,6 +152,12 @@ def addresses(self): addrs.append(splitted[1].split("/", 1)[0]) return addrs + @property + def link(self): + return json.loads( + self.check_output(f"{self._ip} --json link show %s", self.name) + ) + def routes(self, scope=None): cmd = f"{self._ip} --json route list dev %s" diff --git a/testinfra/plugin.py b/testinfra/plugin.py index d1371dbb..db3541b0 100644 --- a/testinfra/plugin.py +++ b/testinfra/plugin.py @@ -10,6 +10,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import logging import shutil import sys