summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.flake815
-rw-r--r--.github/dependabot.yml16
-rw-r--r--.github/workflows/release.yml9
-rw-r--r--.github/workflows/tox.yml34
-rw-r--r--.pre-commit-config.yaml36
-rw-r--r--.pylintrc28
-rw-r--r--MANIFEST.in2
-rw-r--r--PKG-INFO260
-rw-r--r--README.md8
-rw-r--r--debian/changelog23
-rw-r--r--mypy.ini13
-rw-r--r--pyproject.toml90
-rw-r--r--requirements.txt17
-rw-r--r--setup.cfg52
-rw-r--r--src/python_vagrant.egg-info/PKG-INFO260
-rw-r--r--src/python_vagrant.egg-info/SOURCES.txt40
-rw-r--r--src/python_vagrant.egg-info/dependency_links.txt1
-rw-r--r--src/python_vagrant.egg-info/requires.txt5
-rw-r--r--src/python_vagrant.egg-info/top_level.txt1
-rw-r--r--src/vagrant/__init__.py170
-rw-r--r--src/vagrant/_version.py5
-rw-r--r--src/vagrant/compat.py6
-rw-r--r--src/vagrant/test.py20
-rw-r--r--tests/test_vagrant.py69
-rw-r--r--tests/test_vagrant_test_case.py2
-rwxr-xr-xtests/tools/create_dummy_box.sh16
-rw-r--r--tox.ini37
27 files changed, 437 insertions, 798 deletions
diff --git a/.flake8 b/.flake8
index 8884218..bdf0cfa 100644
--- a/.flake8
+++ b/.flake8
@@ -40,21 +40,20 @@ max-line-length = 100
extend-ignore =
E203,
E501,
- F401, # duplicate of pylint W0611 (unused-import)
- F821, # duplicate of pylint E0602 (undefined-variable)
- F841, # duplicate of pylint W0612 (unused-variable)
+ F401,
+ F821,
+ F841,
H,
- # Temporary disabled during initial adoption:
D103,
D104,
D200,
D205,
D400,
D401,
- S101, # Use of assert detected
- S404, # Consider possible security implications associated with subprocess module
- S602, # subprocess call with shell=True identified
- S603, # subprocess call
+ S101,
+ S404,
+ S602,
+ S603,
# Allow certain violations in certain files:
per-file-ignores =
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..2672657
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+---
+version: 2
+updates:
+ - package-ecosystem: pip
+ directory: /
+ schedule:
+ interval: daily
+ labels:
+ - skip-changelog
+ versioning-strategy: lockfile-only
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: daily
+ labels:
+ - "skip-changelog"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0d3c8e6..9c960f7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,16 +18,17 @@ jobs:
TOX_PARALLEL_NO_SPINNER: 1
steps:
- - name: Switch to using Python 3.8 by default
- uses: actions/setup-python@v2
+ - name: Switch to using Python 3.9 by default
+ uses: actions/setup-python@v4
with:
- python-version: 3.8
+ cache: "pip"
+ python-version: 3.9
- name: Install tox
run: python3 -m pip install --user tox
- name: Check out src from Git
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
fetch-depth: 0 # needed by setuptools-scm
diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml
index 658ecb8..3423a3f 100644
--- a/.github/workflows/tox.yml
+++ b/.github/workflows/tox.yml
@@ -27,28 +27,34 @@ jobs:
include:
- tox_env: lint
os: ubuntu-20.04
- python-version: "3.10"
+ python-version: "3.9"
unit: false
skip_vagrant: true
- tox_env: packaging
os: ubuntu-20.04
- python-version: 3.8
+ python-version: "3.10"
+ unit: false
+ skip_vagrant: true
+ - tox_env: packaging
+ os: ubuntu-22.04
+ python-version: "3.11"
unit: false
skip_vagrant: true
- - tox_env: py38
- os: macos-10.15
- python-version: 3.8
- tox_env: py39
os: macos-10.15
- python-version: 3.9
+ python-version: "3.9"
- tox_env: py310
os: macos-10.15
python-version: "3.10"
+ - tox_env: py311
+ os: macos-10.15
+ python-version: "3.11"
env:
TOX_PARALLEL_NO_SPINNER: 1
TOXENV: ${{ matrix.tox_env }}
FORCE_COLOR: 1
+ PYTEST_REQPASS: 24
steps:
- name: Check vagrant presence
@@ -68,12 +74,20 @@ jobs:
fetch-depth: 0 # needed by setuptools-scm
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
+ cache: "pip"
python-version: ${{ matrix.python-version }}
+ - name: Enable vagrant box caching
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.vagrant.d/boxes
+ key: ${{ runner.os }}-${{ matrix.tox_env }}-vagrantbox-${{ hashFiles('tests/tools/create_dummy_box.sh', 'tests/test_vagrant*py', '**/Vagrantfile', 'tests/vagrantfiles/*Vagrantfile') }}
+
- name: Pip cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-${{ matrix.tox_env }}-pip-${{ hashFiles('constraints.txt', 'setup.cfg', 'tox.ini', 'pyproject.toml', '.pre-commit-config.yaml', 'pytest.ini') }}
@@ -88,7 +102,7 @@ jobs:
run: python3 -m tox
- name: Upload coverage data
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
directory: .tox
files: "coverage-*.xml"
@@ -97,7 +111,7 @@ jobs:
if: ${{ startsWith(matrix.tox_env, 'py') }}
- name: Archive logs
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: logs.zip
path: .tox/**/log/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2c914b1..4088315 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,19 +7,19 @@ ci:
chore: auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
-default_language_version:
- python: python3.8
+ skip:
+ - pip-compile
repos:
- repo: https://github.com/pre-commit/mirrors-prettier
# keep it before yamllint
- rev: v2.6.1
+ rev: v3.0.0-alpha.4
hooks:
- id: prettier
additional_dependencies:
- prettier
- prettier-plugin-toml
- repo: https://github.com/pre-commit/pre-commit-hooks.git
- rev: v4.1.0
+ rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
@@ -29,11 +29,11 @@ repos:
- id: check-merge-conflict
- id: debug-statements
- repo: https://github.com/psf/black
- rev: 22.3.0
+ rev: 22.10.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8.git
- rev: 4.0.1
+ rev: 6.0.0
hooks:
- id: flake8
language_version: python3
@@ -42,14 +42,34 @@ repos:
- flake8-docstrings>=1.6.0
- flake8-pytest-style>=1.6.0
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.931
+ rev: v0.991
hooks:
- id: mypy
# empty args needed in order to match mypy cli behavior
# args: [--strict]
- repo: https://github.com/pycqa/pylint
- rev: v2.12.2
+ rev: v2.15.8
hooks:
- id: pylint
additional_dependencies:
- pytest
+ - repo: https://github.com/jazzband/pip-tools
+ rev: 6.11.0
+ hooks:
+ - id: pip-compile
+ entry: pip-compile --resolver=backtracking -q --no-annotate --output-file=requirements.txt pyproject.toml --extra test --strip-extras
+ language: python
+ files: ^(requirements\.txt|pyproject\.toml)$
+ alias: deps
+ language_version: "3.9" # minimal we support officially
+ additional_dependencies:
+ - pip>=22.3.1
+ - id: pip-compile
+ entry: pip-compile --resolver=backtracking -q --no-annotate --output-file=requirements.txt pyproject.toml --extra test --strip-extras --upgrade
+ language: python
+ files: ^(requirements\.txt|pyproject\.toml)$
+ alias: up
+ stages: [manual]
+ language_version: "3.9" # minimal we support officially
+ additional_dependencies:
+ - pip>=22.3.1
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 3b44710..0000000
--- a/.pylintrc
+++ /dev/null
@@ -1,28 +0,0 @@
-[IMPORTS]
-preferred-modules =
- py:pathlib,
- unittest:pytest,
-
-[MASTER]
-# pylint defaults + f,fh,v,id
-good-names =i,j,k,_,f,fh,v,id,T
-
-[MESSAGES CONTROL]
-disable =
- # On purpose disabled as we rely on black
- line-too-long,
- # TODO(ssbarnea): remove temporary skips adding during initial adoption:
- consider-using-f-string,
- duplicate-code,
- invalid-name,
- missing-function-docstring,
- no-else-return,
- no-self-use,
- preferred-module,
- protected-access,
- too-many-arguments,
- too-many-branches,
- too-many-lines,
- too-many-public-methods,
- too-many-statements,
- unused-variable,
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 46f8500..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include *.md *.txt
-recursive-include tests *.py Vagrantfile
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index d7d8f08..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,260 +0,0 @@
-Metadata-Version: 2.1
-Name: python-vagrant
-Version: 1.0.0
-Summary: Python bindings for interacting with Vagrant virtual machines.
-Home-page: https://github.com/pycontribs/python-vagrant
-Author: Todd Francis DeLuca
-Author-email: todddeluca@yahoo.com
-License: MIT
-Project-URL: Bug Tracker, https://github.com/pycontribs/python-vagrant/issues
-Project-URL: CI: GitHub, https://github.com/pycontribs/python-vagrant/actions?query=workflow:gh+branch:main+event:push
-Project-URL: Source Code, https://github.com/pycontribs/python-vagrant
-Keywords: box,vagrant,vagrantfile,virtual-machine,virtualbox
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Topic :: Utilities
-Requires-Python: >=3.8
-Description-Content-Type: text/markdown
-Provides-Extra: test
-License-File: LICENSE.txt
-
-## Introduction
-
-Python-vagrant is a python module that provides a _thin_ wrapper around the
-`vagrant` command line executable, allowing programmatic control of Vagrant
-virtual machines (boxes). This module is useful for:
-
-- Starting a Vagrant virtual machine (VM) (`up`).
-- Terminating a Vagrant VM (`destroy`).
-- Halting a Vagrant VM without destroying it (`halt`).
-- Querying the status of a VM or VMs (`status`).
-- Getting ssh configuration information useful for SSHing into the VM. (`host`, `port`, ...)
-- Running `vagrant` commands in a [multi-VM environment](http://vagrantup.com/v1/docs/multivm.html)
- by using `vm_name` parameter.
-- Initializing the VM based on a named base box, using init().
-- Adding, Removing, and Listing boxes (`box add`, `box remove`, `box list`).
-- Provisioning VMs - up() accepts options like `no_provision`, `provision`, and `provision_with`, and there is a `provision()` method.
-- Using sandbox mode from the [Sahara](https://github.com/jedi4ever/sahara) gem.
-
-This project began because I wanted python bindings for Vagrant so I could
-programmatically access my vagrant box using Fabric. Drop me a line to let me
-know how you use python-vagrant. I'd love to share more use cases. -Todd DeLuca
-
-## Versioning and API Stability
-
-This package is _beta_ and its API is not guaranteed to be stable. The API
-attempts to be congruent with the `vagrant` API terminology, to facilitate
-knowledge transfer for users already familiar with Vagrant. Over time, the
-python-vagrant API has changed to better match the underling `vagrant` CLI and
-to evolve with the changes in that CLI.
-
-The package version numbering is in the form `0.X.Y`. The initial `0` reflects
-the _beta_ nature of this project. The number `X` is incremented when
-backwards-incompatible changes occur. The number `Y` is incremented when
-backwards-compatible features or bug fixes are added.
-
-## Requirements
-
-- Vagrant 2.2 or greater. Using the latest version of Vagrant is strongly
- recommended.
-- Vagrant requires VirtualBox, VMWare, or another supported provider.
-- Python 3.8 or newer.
-- The Sahara gem for Vagrant is optional. It will allow you to use
- `SandboxVagrant`.
-
-## Installation
-
-### Install from pypi.python.org
-
-Download and install python-vagrant:
-
-```shell
-pip install python-vagrant
-```
-
-### Install from github.com
-
-Clone and install python-vagrant
-
-```shell
-cd ~
-git clone git@github.com:pycontribs/python-vagrant.git
-cd python-vagrant
-python setup.py install
-```
-
-## Usage
-
-A contrived example of starting a vagrant box (using a Vagrantfile from the
-current directory) and running a fabric task on it:
-
-```python
-import vagrant
-from fabric.api import env, execute, task, run
-
-@task
-def mytask():
- run('echo $USER')
-
-v = vagrant.Vagrant()
-v.up()
-env.hosts = [v.user_hostname_port()]
-env.key_filename = v.keyfile()
-env.disable_known_hosts = True # useful for when the vagrant box ip changes.
-execute(mytask) # run a fabric task on the vagrant host.
-```
-
-Another example showing how to use vagrant multi-vm feature with fabric:
-
-```python
-import vagrant
-from fabric.api import *
-
-@task
-def start(machine_name):
- """Starts the specified machine using vagrant"""
- v = vagrant.Vagrant()
- v.up(vm_name=machine_name)
- with settings(host_string= v.user_hostname_port(vm_name=machine_name),
- key_filename = v.keyfile(vm_name=machine_name),
- disable_known_hosts = True):
- run("echo hello")
-```
-
-By default python vagrant instances are quiet, meaning that they capture stdout
-and stderr. For a "loud" instance, use `vagrant.Vagrant(quiet_stdout=False)`.
-Set `quiet_stderr=False` for an even louder version.
-
-### Interacting With the Vagrant Subprocess
-
-The `Vagrant` class works by executing `vagrant` commands in a subprocess and
-interpreting the output. Depending on the needs of the user, the communication
-to and from the subprocess can be tailored by altering its environment and
-where it sends its stdout and stderr.
-
-#### Silencing the Stdout or Stderr of the Vagrant Subprocess
-
-The stdout and stderr of the underlying vagrant process can be silenced by
-using the `out_cm` and `err_cm` parameters, or by using the `quiet_stdout` and
-`quiet_stderr` parameters of `Vagrant.__init__`.
-
-Using `out_cm` and `err_cm` to redirect stdout and stderr to `/dev/null`:
-
-```python
-v = vagrant.Vagrant(out_cm=vagrant.devnull_cm, err_cm=vagrant.devnull_cm)
-v.up() # normally noisy
-```
-
-Using `quiet_stdout` and `quiet_stderr` to redirect stdout and stderr to
-`/dev/null`:
-
-```python
-v = vagrant.Vagrant(quiet_stdout=True, quiet_stderr=True)
-v.up() # normally noisy
-```
-
-These are functionally equivalent.
-
-#### Logging the Stdout or Stderr of the Vagrant Subprocess
-
-A user might wish to direct the stdout and stderr of a vagrant subprocess to
-a file, perhaps to log and analyze the results of an automated process. This
-can be accomplished using the `out_cm` and `err_cm` parameters of
-`Vagrant.__init__`.
-
-For example, log the stdout and stderr of the subprocess to the file
-'deployment.log':
-
-```python
-log_cm = vagrant.make_file_cm('deployment.log')
-v = vagrant.Vagrant(out_cm=log_cm, err_cm=log_cm)
-v.up() # normally noisy
-```
-
-#### Altering the Environment of the Vagrant Subprocess
-
-It's possible to communicate with the Vagrant subprocess using environment
-variables. The `Vagrantfile` could expect environment variables to be present
-and act accordingly. The environment variables can be set by `python-vagrant`.
-
-```python
-import vagrant
-
-v = vagrant.Vagrant()
-
-os_env = os.environ.copy()
-os_env['USE_NFS'] = '1'
-
-v.env = os_env
-v.up() # will pass env to the vagrant subprocess
-```
-
-Alternatively, the environment can be passed at instantiation time.
-
-```python
-import vagrant
-
-os_env = os.environ.copy()
-os_env['USE_NFS'] = '1'
-
-v = vagrant.Vagrant(env=env)
-assert v.env is env # True
-v.up() # will pass env to the vagrant subprocess
-```
-
-## Contribute
-
-If you use python and vagrant and this project does not do what you want,
-please open an issue or a pull request on
-[github](https://github.com/pycontribs/python-vagrant/edit/main/README.md).
-
-Please see CHANGELOG.md for a detailed list of contributions and authors.
-
-When making a pull request, please include unit tests that test your changes
-and make sure any existing tests still work. See the Testing section below.
-
-## Testing
-
-Running the full suite of tests might take 10 minutes or so. It involves
-downloading boxes and starting and stopping virtual machines several times.
-
-Run the tests from the top-level directory of the repository:
-
-```shell
-tox -e py
-```
-
-Here is an example of running an individual test:
-
-```shell
-tox -e py -- -k tests.test_vagrant:test_boxes
-```
-
-Manual test of functionality for controlling where the vagrant subcommand
-output is sent -- console or devnull:
-
- >>> import vagrant
- >>> import os
- >>> vagrantfile = '/Users/tfd/proj/python-vagrant/tests/vagrantfiles/single_box'
- >>> # Demonstrate a quiet Vagrant. Equivalent to out_cm=vagrant.devnull_cm
- ... v1 = vagrant.Vagrant(vagrantfile)
- >>> v1.destroy() # output to /dev/null
- >>> # Demonstrate a loud Vagrant. Equivalent to out_cm=vagrant.stdout_cm
- ... v2 = vagrant.Vagrant(vagrantfile, quiet_stdout=False)
- >>> v2.destroy() # stdout sent to console
- ==> default: VM not created. Moving on...
- >>> # Demonstrate that out_cm takes precedence over quiet_stdout=True
- ... v3 = vagrant.Vagrant(vagrantfile, out_cm=vagrant.stdout_cm)
- >>> v3.destroy() # output to console
- ==> default: VM not created. Moving on...
- >>> # Demonstrate a quiet Vagrant using devnull_cm directly
- ... v4 = vagrant.Vagrant(vagrantfile, out_cm=vagrant.devnull_cm)
- >>> v4.destroy() # output to console
- >>>
-
-
diff --git a/README.md b/README.md
index 35a8927..a67464f 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ backwards-compatible features or bug fixes are added.
- Vagrant 2.2 or greater. Using the latest version of Vagrant is strongly
recommended.
- Vagrant requires VirtualBox, VMWare, or another supported provider.
-- Python 3.8 or newer.
+- Python 3.9 or newer.
- The Sahara gem for Vagrant is optional. It will allow you to use
`SandboxVagrant`.
@@ -186,9 +186,11 @@ v.up() # will pass env to the vagrant subprocess
If you use python and vagrant and this project does not do what you want,
please open an issue or a pull request on
-[github](https://github.com/pycontribs/python-vagrant/edit/main/README.md).
+[GitHub](https://github.com/pycontribs/python-vagrant).
-Please see CHANGELOG.md for a detailed list of contributions and authors.
+Please see [CHANGELOG.md](CHANGELOG.md) and
+[Releases](https://github.com/pycontribs/python-vagrant/releases)
+for a detailed list of updates, contributions and authors.
When making a pull request, please include unit tests that test your changes
and make sure any existing tests still work. See the Testing section below.
diff --git a/debian/changelog b/debian/changelog
index 7c78eaa..bde644b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-vagrant (1.1.0-1) unstable; urgency=medium
+
+ * New upstream version 1.1.0
+
+ -- Hans-Christoph Steiner <hans@eds.org> Mon, 06 Feb 2023 01:08:42 +0100
+
python-vagrant (1.0.0-1) unstable; urgency=medium
[ Ondřej Nový ]
@@ -19,6 +25,23 @@ python-vagrant (1.0.0-1) unstable; urgency=medium
-- Hans-Christoph Steiner <hans@eds.org> Thu, 13 Oct 2022 23:16:40 +0200
+python-vagrant (0.5.15-4) unstable; urgency=medium
+
+ [ Ondřej Nový ]
+ * Bump Standards-Version to 4.4.1.
+ * d/control: Update Maintainer field with new Debian Python Team
+ contact address.
+ * d/control: Update Vcs-* fields with new Debian Python Team Salsa
+ layout.
+
+ [ Debian Janitor ]
+ * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository,
+ Repository-Browse.
+ * Bump debhelper from old 12 to 13.
+ * Update standards version to 4.5.1, no changes needed.
+
+ -- Sandro Tosi <morph@debian.org> Sat, 04 Jun 2022 16:02:05 -0400
+
python-vagrant (0.5.15-3) unstable; urgency=medium
* Team upload.
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index 9c05db2..0000000
--- a/mypy.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-[mypy]
-python_version = 3.8
-color_output = True
-error_summary = True
-; disallow_untyped_calls = True
-; disallow_untyped_defs = True
-; disallow_any_generics = True
-; disallow_any_unimported = True
-; warn_redundant_casts = True
-; warn_return_any = True
-; warn_unused_configs = True
-# site-packages is here to help vscode mypy integration getting confused
-exclude = (build|test/local-content|site-packages)
diff --git a/pyproject.toml b/pyproject.toml
index 9e36c93..6b8268d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,46 @@
[build-system]
requires = [
- "setuptools >= 45.0.0", # required by pyproject+setuptools_scm integration
- "setuptools_scm >= 6.3.1", # required for "no-local-version" scheme
- "setuptools_scm_git_archive >= 1.0",
- "wheel",
+ "setuptools >= 61.0", # PEP-621
+ "setuptools_scm[toml] >= 7.0.0",
]
build-backend = "setuptools.build_meta"
+[project]
+# https://peps.python.org/pep-0621/#readme
+requires-python = ">=3.9"
+dynamic = ["version"]
+name = "python-vagrant"
+description = "Python bindings for interacting with Vagrant virtual machines."
+readme = "README.md"
+authors = [{ "name" = "Todd Francis DeLuca", "email" = "todddeluca@yahoo.com" }]
+maintainers = [
+ { "name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com" }
+]
+license = { text = "MIT" }
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Topic :: Utilities",
+]
+keywords = ["box", "vagrant", "vagrantfile", "virtual-machine", "virtualbox"]
+
+[project.urls]
+homepage = "https://github.com/pycontribs/python-vagrant"
+repository = "https://github.com/pycontribs/python-vagrant"
+changelog = "https://github.com/pycontribs/python-vagrant/releases"
+
+[project.optional-dependencies]
+test = [
+ "coverage>=6.3",
+ "pytest-cov>=3.0.0",
+ "pytest>=7.0.0",
+ "pytest-plus>=0.2",
+]
+
[tool.coverage.run]
source = ["src"]
branch = true
@@ -17,10 +51,58 @@ exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:"]
[tool.isort]
profile = "black"
+[tool.mypy]
+python_version = 3.9
+color_output = true
+error_summary = true
+check_untyped_defs = true
+disallow_untyped_calls = true
+# disallow_untyped_defs = True
+# disallow_any_generics = True
+# disallow_any_unimported = True
+# warn_redundant_casts = True
+# warn_return_any = True
+# warn_unused_configs = True
+# site-packages is here to help vscode mypy integration getting confused
+exclude = "(build|test/local-content|site-packages)"
+
+[tool.pylint.IMPORTS]
+preferred-modules = ["py:pathlib", "unittest:pytest"]
+
+[tool.pylint.MASTER]
+# pylint defaults + f,fh,v,id
+good-names = "i,j,k,_,f,fh,v,id,T"
+
+[tool.pylint."MESSAGES CONTROL"]
+disable = [
+ # On purpose disabled as we rely on black
+ "line-too-long",
+ # TODO(ssbarnea): remove temporary skips adding during initial adoption:
+ "consider-using-f-string",
+ "duplicate-code",
+ "invalid-name",
+ "missing-function-docstring",
+ "no-else-return",
+ "no-self-use",
+ "preferred-module",
+ "protected-access",
+ "too-many-arguments",
+ "too-many-branches",
+ "too-many-lines",
+ "too-many-public-methods",
+ "too-many-statements",
+ "unused-variable",
+]
+
[tool.pytest.ini_options]
# ensure we treat warnings as error
filterwarnings = ["error"]
+addopts = ["-p", "no:pytest_cov"]
+# https://code.visualstudio.com/docs/python/testing
+# coverage is re-enabled in `tox.ini`. That approach is safer than
+# `--no-cov` which prevents activation from tox.ini and which also fails
+# when plugin is effectively missing.
[tool.setuptools_scm]
local_scheme = "no-local-version"
write_to = "src/vagrant/_version.py"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..dbe1962
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,17 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# pip-compile --extra=test --no-annotate --output-file=requirements.txt --resolver=backtracking --strip-extras pyproject.toml
+#
+attrs==22.1.0
+coverage==6.5.0
+exceptiongroup==1.0.4
+iniconfig==1.1.1
+more-itertools==9.0.0
+packaging==22.0
+pluggy==1.0.0
+pytest==7.2.0
+pytest-cov==4.0.0
+pytest-plus==0.2
+tomli==2.0.1
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 51854de..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,52 +0,0 @@
-[metadata]
-name = python-vagrant
-url = https://github.com/pycontribs/python-vagrant
-project_urls =
- Bug Tracker = https://github.com/pycontribs/python-vagrant/issues
- CI: GitHub = https://github.com/pycontribs/python-vagrant/actions?query=workflow:gh+branch:main+event:push
- Source Code = https://github.com/pycontribs/python-vagrant
-description = Python bindings for interacting with Vagrant virtual machines.
-long_description = file: README.md
-long_description_content_type = text/markdown
-author = Todd Francis DeLuca
-author_email = todddeluca@yahoo.com
-license = MIT
-license_file = LICENSE.txt
-classifiers =
- Development Status :: 4 - Beta
- License :: OSI Approved :: MIT License
- Operating System :: OS Independent
- Programming Language :: Python :: 3.10
- Programming Language :: Python :: 3.8
- Programming Language :: Python :: 3.9
- Topic :: Utilities
-keywords =
- box
- vagrant
- vagrantfile
- virtual-machine
- virtualbox
-
-[options]
-use_scm_version = True
-python_requires = >=3.8
-package_dir =
- = src
-packages = find:
-
-[options.extras_require]
-test =
- coverage>=6.3
- pytest-cov>=3.0.0
- pytest>=7.0.0
-
-[options.packages.find]
-where = src
-
-[options.package_data]
-vagrant = py.typed
-
-[egg_info]
-tag_build =
-tag_date = 0
-
diff --git a/src/python_vagrant.egg-info/PKG-INFO b/src/python_vagrant.egg-info/PKG-INFO
deleted file mode 100644
index d7d8f08..0000000
--- a/src/python_vagrant.egg-info/PKG-INFO
+++ /dev/null
@@ -1,260 +0,0 @@
-Metadata-Version: 2.1
-Name: python-vagrant
-Version: 1.0.0
-Summary: Python bindings for interacting with Vagrant virtual machines.
-Home-page: https://github.com/pycontribs/python-vagrant
-Author: Todd Francis DeLuca
-Author-email: todddeluca@yahoo.com
-License: MIT
-Project-URL: Bug Tracker, https://github.com/pycontribs/python-vagrant/issues
-Project-URL: CI: GitHub, https://github.com/pycontribs/python-vagrant/actions?query=workflow:gh+branch:main+event:push
-Project-URL: Source Code, https://github.com/pycontribs/python-vagrant
-Keywords: box,vagrant,vagrantfile,virtual-machine,virtualbox
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Topic :: Utilities
-Requires-Python: >=3.8
-Description-Content-Type: text/markdown
-Provides-Extra: test
-License-File: LICENSE.txt
-
-## Introduction
-
-Python-vagrant is a python module that provides a _thin_ wrapper around the
-`vagrant` command line executable, allowing programmatic control of Vagrant
-virtual machines (boxes). This module is useful for:
-
-- Starting a Vagrant virtual machine (VM) (`up`).
-- Terminating a Vagrant VM (`destroy`).
-- Halting a Vagrant VM without destroying it (`halt`).
-- Querying the status of a VM or VMs (`status`).
-- Getting ssh configuration information useful for SSHing into the VM. (`host`, `port`, ...)
-- Running `vagrant` commands in a [multi-VM environment](http://vagrantup.com/v1/docs/multivm.html)
- by using `vm_name` parameter.
-- Initializing the VM based on a named base box, using init().
-- Adding, Removing, and Listing boxes (`box add`, `box remove`, `box list`).
-- Provisioning VMs - up() accepts options like `no_provision`, `provision`, and `provision_with`, and there is a `provision()` method.
-- Using sandbox mode from the [Sahara](https://github.com/jedi4ever/sahara) gem.
-
-This project began because I wanted python bindings for Vagrant so I could
-programmatically access my vagrant box using Fabric. Drop me a line to let me
-know how you use python-vagrant. I'd love to share more use cases. -Todd DeLuca
-
-## Versioning and API Stability
-
-This package is _beta_ and its API is not guaranteed to be stable. The API
-attempts to be congruent with the `vagrant` API terminology, to facilitate
-knowledge transfer for users already familiar with Vagrant. Over time, the
-python-vagrant API has changed to better match the underling `vagrant` CLI and
-to evolve with the changes in that CLI.
-
-The package version numbering is in the form `0.X.Y`. The initial `0` reflects
-the _beta_ nature of this project. The number `X` is incremented when
-backwards-incompatible changes occur. The number `Y` is incremented when
-backwards-compatible features or bug fixes are added.
-
-## Requirements
-
-- Vagrant 2.2 or greater. Using the latest version of Vagrant is strongly
- recommended.
-- Vagrant requires VirtualBox, VMWare, or another supported provider.
-- Python 3.8 or newer.
-- The Sahara gem for Vagrant is optional. It will allow you to use
- `SandboxVagrant`.
-
-## Installation
-
-### Install from pypi.python.org
-
-Download and install python-vagrant:
-
-```shell
-pip install python-vagrant
-```
-
-### Install from github.com
-
-Clone and install python-vagrant
-
-```shell
-cd ~
-git clone git@github.com:pycontribs/python-vagrant.git
-cd python-vagrant
-python setup.py install
-```
-
-## Usage
-
-A contrived example of starting a vagrant box (using a Vagrantfile from the
-current directory) and running a fabric task on it:
-
-```python
-import vagrant
-from fabric.api import env, execute, task, run
-
-@task
-def mytask():
- run('echo $USER')
-
-v = vagrant.Vagrant()
-v.up()
-env.hosts = [v.user_hostname_port()]
-env.key_filename = v.keyfile()
-env.disable_known_hosts = True # useful for when the vagrant box ip changes.
-execute(mytask) # run a fabric task on the vagrant host.
-```
-
-Another example showing how to use vagrant multi-vm feature with fabric:
-
-```python
-import vagrant
-from fabric.api import *
-
-@task
-def start(machine_name):
- """Starts the specified machine using vagrant"""
- v = vagrant.Vagrant()
- v.up(vm_name=machine_name)
- with settings(host_string= v.user_hostname_port(vm_name=machine_name),
- key_filename = v.keyfile(vm_name=machine_name),
- disable_known_hosts = True):
- run("echo hello")
-```
-
-By default python vagrant instances are quiet, meaning that they capture stdout
-and stderr. For a "loud" instance, use `vagrant.Vagrant(quiet_stdout=False)`.
-Set `quiet_stderr=False` for an even louder version.
-
-### Interacting With the Vagrant Subprocess
-
-The `Vagrant` class works by executing `vagrant` commands in a subprocess and
-interpreting the output. Depending on the needs of the user, the communication
-to and from the subprocess can be tailored by altering its environment and
-where it sends its stdout and stderr.
-
-#### Silencing the Stdout or Stderr of the Vagrant Subprocess
-
-The stdout and stderr of the underlying vagrant process can be silenced by
-using the `out_cm` and `err_cm` parameters, or by using the `quiet_stdout` and
-`quiet_stderr` parameters of `Vagrant.__init__`.
-
-Using `out_cm` and `err_cm` to redirect stdout and stderr to `/dev/null`:
-
-```python
-v = vagrant.Vagrant(out_cm=vagrant.devnull_cm, err_cm=vagrant.devnull_cm)
-v.up() # normally noisy
-```
-
-Using `quiet_stdout` and `quiet_stderr` to redirect stdout and stderr to
-`/dev/null`:
-
-```python
-v = vagrant.Vagrant(quiet_stdout=True, quiet_stderr=True)
-v.up() # normally noisy
-```
-
-These are functionally equivalent.
-
-#### Logging the Stdout or Stderr of the Vagrant Subprocess
-
-A user might wish to direct the stdout and stderr of a vagrant subprocess to
-a file, perhaps to log and analyze the results of an automated process. This
-can be accomplished using the `out_cm` and `err_cm` parameters of
-`Vagrant.__init__`.
-
-For example, log the stdout and stderr of the subprocess to the file
-'deployment.log':
-
-```python
-log_cm = vagrant.make_file_cm('deployment.log')
-v = vagrant.Vagrant(out_cm=log_cm, err_cm=log_cm)
-v.up() # normally noisy
-```
-
-#### Altering the Environment of the Vagrant Subprocess
-
-It's possible to communicate with the Vagrant subprocess using environment
-variables. The `Vagrantfile` could expect environment variables to be present
-and act accordingly. The environment variables can be set by `python-vagrant`.
-
-```python
-import vagrant
-
-v = vagrant.Vagrant()
-
-os_env = os.environ.copy()
-os_env['USE_NFS'] = '1'
-
-v.env = os_env
-v.up() # will pass env to the vagrant subprocess
-```
-
-Alternatively, the environment can be passed at instantiation time.
-
-```python
-import vagrant
-
-os_env = os.environ.copy()
-os_env['USE_NFS'] = '1'
-
-v = vagrant.Vagrant(env=env)
-assert v.env is env # True
-v.up() # will pass env to the vagrant subprocess
-```
-
-## Contribute
-
-If you use python and vagrant and this project does not do what you want,
-please open an issue or a pull request on
-[github](https://github.com/pycontribs/python-vagrant/edit/main/README.md).
-
-Please see CHANGELOG.md for a detailed list of contributions and authors.
-
-When making a pull request, please include unit tests that test your changes
-and make sure any existing tests still work. See the Testing section below.
-
-## Testing
-
-Running the full suite of tests might take 10 minutes or so. It involves
-downloading boxes and starting and stopping virtual machines several times.
-
-Run the tests from the top-level directory of the repository:
-
-```shell
-tox -e py
-```
-
-Here is an example of running an individual test:
-
-```shell
-tox -e py -- -k tests.test_vagrant:test_boxes
-```
-
-Manual test of functionality for controlling where the vagrant subcommand
-output is sent -- console or devnull:
-
- >>> import vagrant
- >>> import os
- >>> vagrantfile = '/Users/tfd/proj/python-vagrant/tests/vagrantfiles/single_box'
- >>> # Demonstrate a quiet Vagrant. Equivalent to out_cm=vagrant.devnull_cm
- ... v1 = vagrant.Vagrant(vagrantfile)
- >>> v1.destroy() # output to /dev/null
- >>> # Demonstrate a loud Vagrant. Equivalent to out_cm=vagrant.stdout_cm
- ... v2 = vagrant.Vagrant(vagrantfile, quiet_stdout=False)
- >>> v2.destroy() # stdout sent to console
- ==> default: VM not created. Moving on...
- >>> # Demonstrate that out_cm takes precedence over quiet_stdout=True
- ... v3 = vagrant.Vagrant(vagrantfile, out_cm=vagrant.stdout_cm)
- >>> v3.destroy() # output to console
- ==> default: VM not created. Moving on...
- >>> # Demonstrate a quiet Vagrant using devnull_cm directly
- ... v4 = vagrant.Vagrant(vagrantfile, out_cm=vagrant.devnull_cm)
- >>> v4.destroy() # output to console
- >>>
-
-
diff --git a/src/python_vagrant.egg-info/SOURCES.txt b/src/python_vagrant.egg-info/SOURCES.txt
deleted file mode 100644
index 799f675..0000000
--- a/src/python_vagrant.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-.flake8
-.gitignore
-.pre-commit-config.yaml
-.pylintrc
-CHANGELOG.md
-LICENSE.txt
-MANIFEST.in
-README.md
-codecov.yml
-mypy.ini
-pyproject.toml
-pytest.ini
-setup.cfg
-tox.ini
-.github/CODEOWNERS
-.github/FUNDING.yml
-.github/release-drafter.yml
-.github/workflows/ack.yml
-.github/workflows/push.yml
-.github/workflows/release.yml
-.github/workflows/tox.yml
-src/python_vagrant.egg-info/PKG-INFO
-src/python_vagrant.egg-info/SOURCES.txt
-src/python_vagrant.egg-info/dependency_links.txt
-src/python_vagrant.egg-info/requires.txt
-src/python_vagrant.egg-info/top_level.txt
-src/vagrant/__init__.py
-src/vagrant/_version.py
-src/vagrant/compat.py
-src/vagrant/py.typed
-src/vagrant/test.py
-tests/__init__.py
-tests/test_vagrant.py
-tests/test_vagrant_test_case.py
-tests/tools/create_dummy_box.sh
-tests/vagrantfiles/multivm_Vagrantfile
-tests/vagrantfiles/shell_provision_Vagrantfile
-tests/vagrantfiles/vm_Vagrantfile
-tests/vagrantfiles/multi_box/Vagrantfile
-tests/vagrantfiles/single_box/Vagrantfile \ No newline at end of file
diff --git a/src/python_vagrant.egg-info/dependency_links.txt b/src/python_vagrant.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/src/python_vagrant.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/python_vagrant.egg-info/requires.txt b/src/python_vagrant.egg-info/requires.txt
deleted file mode 100644
index 5fd473b..0000000
--- a/src/python_vagrant.egg-info/requires.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-
-[test]
-coverage>=6.3
-pytest-cov>=3.0.0
-pytest>=7.0.0
diff --git a/src/python_vagrant.egg-info/top_level.txt b/src/python_vagrant.egg-info/top_level.txt
deleted file mode 100644
index d6a9762..0000000
--- a/src/python_vagrant.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-vagrant
diff --git a/src/vagrant/__init__.py b/src/vagrant/__init__.py
index 9ace84d..299a156 100644
--- a/src/vagrant/__init__.py
+++ b/src/vagrant/__init__.py
@@ -19,6 +19,8 @@ import re
import subprocess
import sys
import logging
+import typing
+from typing import Dict, Iterator, List, Optional, Union
# local
from . import compat
@@ -42,7 +44,7 @@ VAGRANT_NOT_FOUND_WARNING = (
)
-def which(program): # noqa C901
+def which(program) -> Optional[str]: # noqa C901
"""
Emulate unix 'which' command. If program is a path to an executable file
(i.e. it contains any directory components, like './myscript'), return
@@ -62,7 +64,7 @@ def which(program): # noqa C901
https://hg.python.org/cpython/file/default/Lib/shutil.py
"""
- def is_exe(fpath):
+ def is_exe(fpath) -> bool:
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
# Shortcut: If program contains any dir components, do not search the path
@@ -128,7 +130,7 @@ def which(program): # noqa C901
# The full path to the vagrant executable, e.g. '/usr/bin/vagrant'
-def get_vagrant_executable():
+def get_vagrant_executable() -> Optional[str]:
return which("vagrant")
@@ -138,6 +140,9 @@ if get_vagrant_executable() is None:
# Classes for listings of Statuses, Boxes, and Plugins
Status = collections.namedtuple("Status", ["name", "state", "provider"])
+GlobalStatus = collections.namedtuple(
+ "GlobalStatus", ["id", "state", "provider", "home"]
+)
Box = collections.namedtuple("Box", ["name", "provider", "version"])
Plugin = collections.namedtuple("Plugin", ["name", "version", "system"])
@@ -171,7 +176,7 @@ def none_cm():
yield None
-def make_file_cm(filename, mode="a"):
+def make_file_cm(filename, mode="a") -> typing.Callable[[], typing.ContextManager]:
"""
Open a file for appending and yield the open filehandle. Close the
filehandle after yielding it. This is useful for creating a context
@@ -227,7 +232,7 @@ class Vagrant:
env=None,
out_cm=None,
err_cm=None,
- ):
+ ) -> None:
"""
root: a directory containing a file named Vagrantfile. Defaults to
os.getcwd(). This is the directory and Vagrantfile that the Vagrant
@@ -255,8 +260,8 @@ class Vagrant:
will be sent to devnull.
"""
self.root = os.path.abspath(root) if root is not None else os.getcwd()
- self._cached_conf = {}
- self._vagrant_exe = None # cache vagrant executable path
+ self._cached_conf: Dict[str, Optional[Dict[str, str]]] = {}
+ self._vagrant_exe: Optional[str] = None # cache vagrant executable path
self.env = env
if out_cm is not None:
self.out_cm = out_cm
@@ -276,7 +281,7 @@ class Vagrant:
else:
self.err_cm = none_cm
- def version(self):
+ def version(self) -> str:
"""
Return the installed vagrant version, as a string, e.g. '1.5.0'
"""
@@ -288,7 +293,7 @@ class Vagrant:
)
return m.group("version")
- def init(self, box_name=None, box_url=None):
+ def init(self, box_name=None, box_url=None) -> None:
"""
From the Vagrant docs:
@@ -312,7 +317,7 @@ class Vagrant:
provision=None,
provision_with=None,
stream_output=False,
- ):
+ ) -> Optional[Iterator[str]]:
"""
Invoke `vagrant up` to start a box or boxes, possibly streaming the
command output.
@@ -365,7 +370,7 @@ class Vagrant:
self._cached_conf[vm_name] = None # remove cached configuration
return generator if stream_output else None
- def provision(self, vm_name=None, provision_with=None):
+ def provision(self, vm_name=None, provision_with=None) -> None:
"""
Runs the provisioners defined in the Vagrantfile.
vm_name: optional VM name string.
@@ -378,7 +383,7 @@ class Vagrant:
def reload(
self, vm_name=None, provision=None, provision_with=None, stream_output=False
- ):
+ ) -> Optional[Iterator[str]]:
"""
Quoting from Vagrant docs:
> The equivalent of running a halt followed by an up.
@@ -418,21 +423,21 @@ class Vagrant:
self._cached_conf[vm_name] = None # remove cached configuration
return generator if stream_output else None
- def suspend(self, vm_name=None):
+ def suspend(self, vm_name=None) -> None:
"""
Suspend/save the machine.
"""
self._call_vagrant_command(["suspend", vm_name])
self._cached_conf[vm_name] = None # remove cached configuration
- def resume(self, vm_name=None):
+ def resume(self, vm_name=None) -> None:
"""
Resume suspended machine.
"""
self._call_vagrant_command(["resume", vm_name])
self._cached_conf[vm_name] = None # remove cached configuration
- def halt(self, vm_name=None, force=False):
+ def halt(self, vm_name=None, force=False) -> None:
"""
Halt the Vagrant box.
@@ -442,14 +447,14 @@ class Vagrant:
self._call_vagrant_command(["halt", vm_name, force_opt])
self._cached_conf[vm_name] = None # remove cached configuration
- def destroy(self, vm_name=None):
+ def destroy(self, vm_name=None) -> None:
"""
Terminate the running Vagrant box.
"""
self._call_vagrant_command(["destroy", vm_name, "--force"])
self._cached_conf[vm_name] = None # remove cached configuration
- def status(self, vm_name=None):
+ def status(self, vm_name=None) -> List[Status]:
r"""
Return the results of a `vagrant status` call as a list of one or more
Status objects. A Status contains the following attributes:
@@ -525,7 +530,25 @@ class Vagrant:
output = self._run_vagrant_command(["status", "--machine-readable", vm_name])
return self._parse_status(output)
- def _normalize_status(self, status, provider):
+ def global_status(self, prune=False):
+ """
+ Return the results of a `vagrant global-status` call as a list of one or more
+ GlobalStatus objects. A GlobalStatus contains the following attributes:
+
+ - id: The VM id.
+ - state: The state of the underlying guest machine (i.e. VM).
+ - provider: the name of the VM provider, e.g. 'virtualbox'. None
+ if no provider is output by vagrant.
+ - home: the path to the machine vagrantfile for the VM
+ """
+ # machine-readable output are CSV lines
+ cmd = ["global-status", "--machine-readable"]
+ if prune is True:
+ cmd.append("--prune")
+ output = self._run_vagrant_command(cmd)
+ return self._parse_global_status(output)
+
+ def _normalize_status(self, status, provider) -> str:
"""
Normalise VM status to cope with state name being different
between providers
@@ -541,7 +564,7 @@ class Vagrant:
return status
- def _parse_status(self, output):
+ def _parse_status(self, output) -> List[Status]:
"""
Unit testing is so much easier when Vagrant is removed from the
equation.
@@ -562,7 +585,33 @@ class Vagrant:
return statuses
- def conf(self, ssh_config=None, vm_name=None):
+ def _parse_global_status(self, output: str) -> List[GlobalStatus]:
+ """
+ Unit testing is so much easier when Vagrant is removed from the
+ equation.
+ """
+ parsed = self._parse_machine_readable_output(output)
+ statuses = []
+ vm_id = state = provider = home = None
+ for timestamp, target, kind, data in parsed:
+ if kind == "machine-id":
+ vm_id = data
+ elif kind == "provider-name":
+ provider = data
+ elif kind == "machine-home":
+ home = data
+ elif kind == "state":
+ state = data
+ if vm_id and provider and home and state:
+ state = self._normalize_status(state, provider)
+ status = GlobalStatus(
+ id=vm_id, state=state, provider=provider, home=home
+ )
+ statuses.append(status)
+ vm_id = state = provider = home = None
+ return statuses
+
+ def conf(self, ssh_config=None, vm_name=None) -> Dict[str, str]:
"""
Parse ssh_config into a dict containing the keys defined in ssh_config,
which should include these keys (listed with example values): 'User'
@@ -584,15 +633,15 @@ class Vagrant:
the value returned from ssh_config(). For speed, the configuration
parsed from ssh_config is cached for subsequent calls.
"""
- if self._cached_conf.get(vm_name) is None or ssh_config is not None:
+ conf = self._cached_conf.get(vm_name)
+ if conf is None or ssh_config is not None:
if ssh_config is None:
ssh_config = self.ssh_config(vm_name=vm_name)
conf = self._parse_config(ssh_config)
self._cached_conf[vm_name] = conf
+ return conf
- return self._cached_conf[vm_name]
-
- def ssh_config(self, vm_name=None):
+ def ssh_config(self, vm_name=None) -> str:
"""
Return the output of 'vagrant ssh-config' which appears to be a valid
Host section suitable for use in an ssh config file.
@@ -615,7 +664,7 @@ class Vagrant:
# capture ssh configuration from vagrant
return self._run_vagrant_command(["ssh-config", vm_name])
- def user(self, vm_name=None):
+ def user(self, vm_name=None) -> Optional[str]:
"""
Return the ssh user of the vagrant box, e.g. 'vagrant'
or None if there is no user in the ssh_config.
@@ -625,7 +674,7 @@ class Vagrant:
"""
return self.conf(vm_name=vm_name).get("User")
- def hostname(self, vm_name=None):
+ def hostname(self, vm_name=None) -> Optional[str]:
"""
Return the vagrant box hostname, e.g. '127.0.0.1'
or None if there is no hostname in the ssh_config.
@@ -635,7 +684,7 @@ class Vagrant:
"""
return self.conf(vm_name=vm_name).get("HostName")
- def port(self, vm_name=None):
+ def port(self, vm_name=None) -> Optional[str]:
"""
Return the vagrant box ssh port, e.g. '2222'
or None if there is no port in the ssh_config.
@@ -645,7 +694,7 @@ class Vagrant:
"""
return self.conf(vm_name=vm_name).get("Port")
- def keyfile(self, vm_name=None):
+ def keyfile(self, vm_name=None) -> Optional[str]:
"""
Return the path to the private key used to log in to the vagrant box
or None if there is no keyfile (IdentityFile) in the ssh_config.
@@ -658,10 +707,10 @@ class Vagrant:
"""
return self.conf(vm_name=vm_name).get("IdentityFile")
- def user_hostname(self, vm_name=None):
+ def user_hostname(self, vm_name=None) -> str:
"""
Return a string combining user and hostname, e.g. 'vagrant@127.0.0.1'.
- This string is suitable for use in an ssh commmand. If user is None
+ This string is suitable for use in an ssh command. If user is None
or empty, it will be left out of the string, e.g. 'localhost'. If
hostname is None, have bigger problems.
@@ -669,10 +718,13 @@ class Vagrant:
has been destroyed.
"""
user = self.user(vm_name=vm_name)
+ hostname = self.hostname(vm_name=vm_name)
+ if hostname is None:
+ raise ValueError("Missing hostname for vm_name={vm_name!r}")
user_prefix = user + "@" if user else ""
- return user_prefix + self.hostname(vm_name=vm_name)
+ return user_prefix + hostname
- def user_hostname_port(self, vm_name=None):
+ def user_hostname_port(self, vm_name=None) -> str:
"""
Return a string combining user, hostname and port, e.g.
'vagrant@127.0.0.1:2222'. This string is suitable for use with Fabric,
@@ -686,11 +738,14 @@ class Vagrant:
"""
user = self.user(vm_name=vm_name)
port = self.port(vm_name=vm_name)
+ hostname = self.hostname(vm_name=vm_name)
+ if hostname is None:
+ raise ValueError("Missing hostname for vm_name={vm_name!r}")
user_prefix = user + "@" if user else ""
port_suffix = ":" + port if port else ""
- return user_prefix + self.hostname(vm_name=vm_name) + port_suffix
+ return user_prefix + hostname + port_suffix
- def box_add(self, name, url, provider=None, force=False):
+ def box_add(self, name, url, provider=None, force=False) -> None:
"""
Adds a box with given name, from given url.
@@ -703,7 +758,7 @@ class Vagrant:
self._call_vagrant_command(cmd)
- def box_list(self):
+ def box_list(self) -> List[Box]:
"""
Run `vagrant box list --machine-readable` and return a list of Box
objects containing the results. A Box object has the following
@@ -800,7 +855,7 @@ class Vagrant:
"""
self._call_vagrant_command(["snapshot", "delete", name])
- def ssh(self, vm_name=None, command=None, extra_ssh_args=None):
+ def ssh(self, vm_name=None, command=None, extra_ssh_args=None) -> str:
"""
Execute a command via ssh on the vm specified.
command: The command to execute via ssh.
@@ -813,7 +868,7 @@ class Vagrant:
return self._run_vagrant_command(cmd)
- def _parse_box_list(self, output):
+ def _parse_box_list(self, output) -> List[Box]:
"""
Remove Vagrant usage for unit testing
"""
@@ -844,14 +899,14 @@ class Vagrant:
return boxes
- def box_update(self, name, provider):
+ def box_update(self, name, provider) -> None:
"""
Updates the box matching name and provider. It is an error if no box
matches name and provider.
"""
self._call_vagrant_command(["box", "update", name, provider])
- def box_remove(self, name, provider):
+ def box_remove(self, name, provider) -> None:
"""
Removes the box matching name and provider. It is an error if no box
matches name and provider.
@@ -894,7 +949,7 @@ class Vagrant:
output = self._run_vagrant_command(["plugin", "list", "--machine-readable"])
return self._parse_plugin_list(output)
- def validate(self, directory):
+ def validate(self, directory) -> subprocess.CompletedProcess:
"""
This command validates present Vagrantfile.
"""
@@ -908,7 +963,7 @@ class Vagrant:
return validate
- def _parse_plugin_list(self, output):
+ def _parse_plugin_list(self, output) -> List[Plugin]:
"""
Remove Vagrant from the equation for unit testing.
"""
@@ -945,7 +1000,7 @@ class Vagrant:
return plugins
- def _parse_machine_readable_output(self, output):
+ def _parse_machine_readable_output(self, output: str) -> List[List[str]]:
"""Parse machine readable output from vagrant commands.
param output: a string containing the output of a vagrant command with the `--machine-readable` option.
@@ -976,7 +1031,7 @@ class Vagrant:
parsed_lines = list(filter(lambda x: x[2] not in unneeded_kind, parsed_lines))
return parsed_lines
- def _parse_config(self, ssh_config):
+ def _parse_config(self, ssh_config: str) -> Dict[str, str]:
r"""
This lame parser does not parse the full grammar of an ssh config
file. It makes assumptions that are (hopefully) correct for the output
@@ -1011,7 +1066,7 @@ class Vagrant:
conf[key] = value.strip('"')
return conf
- def _make_vagrant_command(self, args):
+ def _make_vagrant_command(self, args: List[Union[str, None]]) -> List[str]:
if self._vagrant_exe is None:
self._vagrant_exe = get_vagrant_executable()
@@ -1019,11 +1074,11 @@ class Vagrant:
raise RuntimeError(VAGRANT_NOT_FOUND_WARNING)
# filter out None args. Since vm_name is None in non-Multi-VM
- # environments, this quitely removes it from the arguments list
+ # environments, this quietly removes it from the arguments list
# when it is not specified.
return [self._vagrant_exe] + [arg for arg in args if arg is not None]
- def _call_vagrant_command(self, args):
+ def _call_vagrant_command(self, args) -> None:
"""
Run a vagrant command. Return None.
args: A sequence of arguments to a vagrant command line.
@@ -1036,7 +1091,7 @@ class Vagrant:
command, cwd=self.root, stdout=out_fh, stderr=err_fh, env=self.env
)
- def _run_vagrant_command(self, args):
+ def _run_vagrant_command(self, args) -> str:
"""
Run a vagrant command and return its stdout.
args: A sequence of arguments to a vagrant command line.
@@ -1052,7 +1107,7 @@ class Vagrant:
)
)
- def _stream_vagrant_command(self, args):
+ def _stream_vagrant_command(self, args) -> Iterator[str]:
"""
Execute a vagrant command, returning a generator of the output lines.
Caller should consume the entire generator to avoid the hanging the
@@ -1076,8 +1131,9 @@ class Vagrant:
# Iterate over output lines.
# See http://stackoverflow.com/questions/2715847/python-read-streaming-input-from-subprocess-communicate#17698359
with subprocess.Popen(**sp_args) as p:
- with p.stdout:
- for line in iter(p.stdout.readline, b""):
+ stdout = typing.cast(typing.IO, p.stdout)
+ with stdout:
+ for line in iter(stdout.readline, b""):
yield compat.decode(line) # if PY3 decode bytestrings
p.wait()
# Raise CalledProcessError for consistency with _call_vagrant_command
@@ -1090,22 +1146,22 @@ class SandboxVagrant(Vagrant):
Support for sandbox mode using the Sahara gem (https://github.com/jedi4ever/sahara).
"""
- def _run_sandbox_command(self, args):
+ def _run_sandbox_command(self, args) -> str:
return self._run_vagrant_command(["sandbox"] + list(args))
- def sandbox_commit(self, vm_name=None):
+ def sandbox_commit(self, vm_name=None) -> None:
"""
Permanently writes all the changes made to the VM.
"""
self._run_sandbox_command(["commit", vm_name])
- def sandbox_off(self, vm_name=None):
+ def sandbox_off(self, vm_name=None) -> None:
"""
Disables the sandbox mode.
"""
self._run_sandbox_command(["off", vm_name])
- def sandbox_on(self, vm_name=None):
+ def sandbox_on(self, vm_name=None) -> None:
"""
Enables the sandbox mode.
@@ -1114,13 +1170,13 @@ class SandboxVagrant(Vagrant):
"""
self._run_sandbox_command(["on", vm_name])
- def sandbox_rollback(self, vm_name=None):
+ def sandbox_rollback(self, vm_name=None) -> None:
"""
Reverts all the changes made to the VM since the last commit.
"""
self._run_sandbox_command(["rollback", vm_name])
- def sandbox_status(self, vm_name=None):
+ def sandbox_status(self, vm_name=None) -> str:
"""
Returns the status of the sandbox mode.
@@ -1133,7 +1189,7 @@ class SandboxVagrant(Vagrant):
vagrant_sandbox_output = self._run_sandbox_command(["status", vm_name])
return self._parse_vagrant_sandbox_status(vagrant_sandbox_output)
- def _parse_vagrant_sandbox_status(self, vagrant_output):
+ def _parse_vagrant_sandbox_status(self, vagrant_output) -> str:
"""
Returns the status of the sandbox mode given output from
'vagrant sandbox status'.
diff --git a/src/vagrant/_version.py b/src/vagrant/_version.py
deleted file mode 100644
index 7f0e082..0000000
--- a/src/vagrant/_version.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# coding: utf-8
-# file generated by setuptools_scm
-# don't change, don't track in version control
-version = '1.0.0'
-version_tuple = (1, 0, 0)
diff --git a/src/vagrant/compat.py b/src/vagrant/compat.py
index 8ea0e0f..f520acf 100644
--- a/src/vagrant/compat.py
+++ b/src/vagrant/compat.py
@@ -2,7 +2,7 @@
vagrant.compat
--------------
-Python 2/3 compatiblity module.
+Python 2/3 compatibility module.
"""
# std
@@ -13,7 +13,7 @@ import sys
PY2 = sys.version_info[0] == 2
-def decode(value):
+def decode(value: bytes) -> str:
"""Decode binary data to text if needed (for Python 3).
Use with the functions that return in Python 2 value of `str` type and for Python 3 encoded bytes.
@@ -21,4 +21,4 @@ def decode(value):
:param value: Encoded bytes for Python 3 and `str` for Python 2.
:return: Value as a text.
"""
- return value.decode(locale.getpreferredencoding()) if not PY2 else value
+ return value.decode(locale.getpreferredencoding()) if not PY2 else value # type: ignore
diff --git a/src/vagrant/test.py b/src/vagrant/test.py
index 2287144..457319d 100644
--- a/src/vagrant/test.py
+++ b/src/vagrant/test.py
@@ -4,7 +4,7 @@ A TestCase class, tying together the Vagrant class.
It also removes some of the boilerplate involved in writing tests that leverage
vagrant boxes.
"""
-from typing import Dict, List
+from typing import Dict, List, Optional
from unittest import TestCase
from vagrant import Vagrant, stderr_cm
@@ -22,7 +22,7 @@ class VagrantTestCase(TestCase):
"""
vagrant_boxes: List[str] = []
- vagrant_root = None
+ vagrant_root: Optional[str] = None
restart_boxes = False
__initial_box_statuses: Dict[str, str] = {}
@@ -43,7 +43,7 @@ class VagrantTestCase(TestCase):
self.vagrant_boxes = boxes
super().__init__(*args, **kwargs)
- def assertBoxStatus(self, box, status):
+ def assertBoxStatus(self, box: str, status: str) -> None:
"""Assertion for a box status"""
box_status = [s.state for s in self.vagrant.status() if s.name == box][0]
if box_status != status:
@@ -51,19 +51,19 @@ class VagrantTestCase(TestCase):
"{} has status {}, not {}".format(box, box_status, status)
)
- def assertBoxUp(self, box):
+ def assertBoxUp(self, box: str) -> None:
"""Assertion for a box being up"""
self.assertBoxStatus(box, Vagrant.RUNNING)
- def assertBoxSuspended(self, box):
+ def assertBoxSuspended(self, box: str) -> None:
"""Assertion for a box being up"""
self.assertBoxStatus(box, Vagrant.SAVED)
- def assertBoxHalted(self, box):
+ def assertBoxHalted(self, box: str) -> None:
"""Assertion for a box being up"""
self.assertBoxStatus(box, Vagrant.POWEROFF)
- def assertBoxNotCreated(self, box):
+ def assertBoxNotCreated(self, box: str) -> None:
"""Assertion for a box being up"""
self.assertBoxStatus(box, Vagrant.NOT_CREATED)
@@ -74,18 +74,18 @@ class VagrantTestCase(TestCase):
self.tearDownOnce()
return run
- def setUpOnce(self):
+ def setUpOnce(self) -> None:
"""Collect the box states before starting"""
for box_name in self.vagrant_boxes:
s = self.vagrant.status(vm_name=box_name)[0]
self.__initial_box_statuses[box_name] = s.state
- def tearDownOnce(self):
+ def tearDownOnce(self) -> None:
"""Restore all boxes to their initial states after running all tests, unless tearDown handled it already"""
if not self.restart_boxes:
self.restore_box_states()
- def restore_box_states(self):
+ def restore_box_states(self) -> None:
"""Restores all boxes to their original states"""
for box_name in self.vagrant_boxes:
action = self.__cleanup_actions.get(self.__initial_box_statuses[box_name])
diff --git a/tests/test_vagrant.py b/tests/test_vagrant.py
index de601aa..f35d2b6 100644
--- a/tests/test_vagrant.py
+++ b/tests/test_vagrant.py
@@ -22,7 +22,8 @@ import subprocess
import sys
import tempfile
import time
-from typing import Generator
+import typing
+from typing import Generator, List, Optional
import pytest
@@ -37,7 +38,7 @@ def get_provider() -> str:
"""
Return the provider to use for testing and allow to set it
with PYTHON_VAGRANT_TEST_PROVIDER environment variable is set.
- Defauts to virtualbox
+ Defaults to virtualbox.
"""
my_prov = "virtualbox"
if "PYTHON_VAGRANT_TEST_PROVIDER" in os.environ:
@@ -46,7 +47,7 @@ def get_provider() -> str:
# location of Vagrant executable
-VAGRANT_EXE = vagrant.get_vagrant_executable()
+VAGRANT_EXE = typing.cast(str, vagrant.get_vagrant_executable())
# location of a test file on the created box by provisioning in vm_Vagrantfile
TEST_FILE_PATH = "/home/vagrant/python_vagrant_test_file"
@@ -106,7 +107,7 @@ def fixture_test_dir() -> Generator[str, None, None]:
shutil.rmtree(my_dir)
-def list_box_names():
+def list_box_names() -> List[str]:
"""
Return a list of the currently installed vagrant box names. This is
implemented outside of `vagrant.Vagrant`, so that it will still work
@@ -222,6 +223,54 @@ def test_parse_status(vm_dir):
)
+def test_parse_global_status(vm_dir):
+ """
+ Test the parsing the output of the `vagrant global-status` command.
+ """
+ listing = """1651503808,,metadata,machine-count,2
+1651503808,,machine-id,9ec0e5d
+1651503808,,provider-name,libvirt
+1651503808,,machine-home,/tmp
+1651503808,,state,preparing
+1651503808,,machine-id,61395ad
+1651503808,,provider-name,libvirt
+1651503808,,machine-home,/home/rtp/.cache/molecule/sbd/default
+1651503808,,state,running
+1651504022,,ui,info,id
+1651504022,,ui,info,name
+1651504022,,ui,info,provider
+1651504022,,ui,info,state
+1651504022,,ui,info,directory
+1651504022,,ui,info,
+1651504022,,ui,info,-------------------------------------------------------------------------------------------------------------
+1651504022,,ui,info,9ec0e5d
+1651504022,,ui,info,bionic
+1651504022,,ui,info,libvirt
+1651504022,,ui,info,preparing
+1651504022,,ui,info,/tmp
+1651504022,,ui,info,
+1651504022,,ui,info,61395ad
+1651504022,,ui,info,instance-sbd-default
+1651504022,,ui,info,libvirt
+1651504022,,ui,info,running
+1651504022,,ui,info,/home/rtp/.cache/molecule/sbd/default
+1651504022,,ui,info,
+11651504022,,ui,info, \\nThe above shows information about all known Vagrant environments\\non this machine...
+"""
+ # Can compare tuples to GlobalStatus class b/c GlobalStatus is a collections.namedtuple.
+ goal = [
+ ("9ec0e5d", "preparing", "libvirt", "/tmp"),
+ ("61395ad", "running", "libvirt", "/home/rtp/.cache/molecule/sbd/default"),
+ ]
+ v = vagrant.Vagrant(vm_dir)
+ parsed = v._parse_global_status(listing)
+ assert (
+ goal == parsed
+ ), "The parsing of the test listing did not match the goal.\nlisting={!r}\ngoal={!r}\nparsed_listing={!r}".format(
+ listing, goal, parsed
+ )
+
+
def test_parse_aws_status(vm_dir):
"""
Test the parsing the output of the `vagrant status` command for an aws instance.
@@ -666,7 +715,7 @@ def test_streaming_output(vm_dir):
v.up(vm_name="incorrect-name")
streaming_up = False
- for line in v.up(stream_output=True):
+ for line in v.up(stream_output=True): # type: ignore
print("output line:", line)
if test_string in line:
streaming_up = True
@@ -674,7 +723,7 @@ def test_streaming_output(vm_dir):
assert streaming_up
streaming_reload = False
- for line in v.reload(stream_output=True):
+ for line in v.reload(stream_output=True): # type: ignore
print("output line:", line)
if test_string in line:
streaming_reload = True
@@ -711,7 +760,7 @@ def test_vagrant_version():
assert version_result is True
-def _execute_command_in_vm(v, command):
+def _execute_command_in_vm(v, command) -> str:
"""
Run command via ssh on the test vagrant box. Returns a tuple of the
return code and output of the command.
@@ -721,7 +770,7 @@ def _execute_command_in_vm(v, command):
return compat.decode(subprocess.check_output(ssh_command, cwd=v.root))
-def _write_test_file(v, file_contents):
+def _write_test_file(v, file_contents) -> None:
"""
Writes given contents to the test file.
"""
@@ -729,7 +778,7 @@ def _write_test_file(v, file_contents):
_execute_command_in_vm(v, command)
-def _read_test_file(v):
+def _read_test_file(v) -> Optional[str]:
"""
Returns the contents of the test file stored in the VM or None if there
is no file.
@@ -742,6 +791,6 @@ def _read_test_file(v):
return None
-def _plugin_installed(v, plugin_name):
+def _plugin_installed(v, plugin_name) -> bool:
plugins = v.plugin_list()
return plugin_name in [plugin.name for plugin in plugins]
diff --git a/tests/test_vagrant_test_case.py b/tests/test_vagrant_test_case.py
index 7519db6..43945ee 100644
--- a/tests/test_vagrant_test_case.py
+++ b/tests/test_vagrant_test_case.py
@@ -9,7 +9,7 @@ from vagrant.test import VagrantTestCase
from .test_vagrant import TEST_BOX_NAME
-def get_vagrant_root(test_vagrant_root_path):
+def get_vagrant_root(test_vagrant_root_path) -> str:
return (
os.path.dirname(os.path.realpath(__file__))
+ "/vagrantfiles/"
diff --git a/tests/tools/create_dummy_box.sh b/tests/tools/create_dummy_box.sh
index 2cf4631..8c131ad 100755
--- a/tests/tools/create_dummy_box.sh
+++ b/tests/tools/create_dummy_box.sh
@@ -2,21 +2,21 @@
set -euxo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+PROVIDER="$1"
+TMPDIR="$(mktemp -d)"
if [ $# -ne 1 ]; then
echo "Missing provider" >&2
exit 1
fi
-if [ -f "$DIR/dummy.box" ]; then
+if [ -f "${DIR}/dummy-${PROVIDER}.box" ]; then
echo "Box already created"
exit 0
fi
-PROVIDER="$1"
-TMPDIR=`mktemp -d`
-cd "$TMPDIR"
-echo "{ \"provider\": \"$PROVIDER\"}" > metadata.json
-tar czf "$DIR/dummy-$PROVIDER.box" .
-cd "$DIR"
-rm -rf "$TMPDIR"
+cd "${TMPDIR}" || exit 1
+echo "{ \"provider\": \"${PROVIDER}\"}" > metadata.json
+tar czf "${DIR}/dummy-${PROVIDER}.box" .
+cd "${DIR}" || exit 1
+rm -rvf "${TMPDIR}"
diff --git a/tox.ini b/tox.ini
index 130c33e..63b4594 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,10 +5,12 @@ envlist =
py
# needed by PEP-517 projects:
isolated_build = True
-minversion = 3.24.5
+minversion = 4.0.2
requires =
+ tox>=4.0.2
+ # temporary disabled due to problem with tox v4
# fail build if git reports dirty after test run
- tox-extra
+ # tox-extra>=1.0.1a0
[testenv]
deps =
@@ -30,19 +32,44 @@ passenv =
# Pass HOME to the test environment as it is required by
# vagrant. Otherwise error happens due to missing HOME env variable.
HOME
+ PYTEST_*
setenv =
- COVERAGE_FILE = {env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}}
+ COVERAGE_FILE = {env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}}
+ PIP_CONSTRAINT = requirements.txt
allowlist_externals =
- sh
+ git
+ sh
+ */create_dummy_box.sh
[testenv:dev]
commands = {posargs}
+[testenv:deps]
+description = Bump all test dependencies
+# we reuse the lint environment
+envdir = {toxworkdir}/lint
+skip_install = true
+deps =
+ {[testenv:lint]deps}
+setenv =
+ # without his upgrade would likely not do anything
+ PIP_CONSTRAINT = /dev/null
+commands_pre =
+commands =
+ pre-commit run --all-files --show-diff-on-failure --hook-stage manual up
+ # Update pre-commit hooks
+ pre-commit autoupdate
+ # We fail if files are modified at the end
+ git diff --exit-code
+
[testenv:lint]
description = Run all linters
deps =
pre-commit>=2.6.0
+setenv =
+ PIP_CONSTRAINT = /dev/null
skip_install = true
+commands_pre =
commands =
{envpython} -m pre_commit run --all-files --show-diff-on-failure {posargs:}
@@ -50,7 +77,7 @@ commands =
description =
Build package, verify metadata, install package and assert behavior when ansible is missing.
deps =
- build >= 0.7.0
+ build >= 0.9.0
twine
skip_install = true
# Ref: https://twitter.com/di_codes/status/1044358639081975813