1
0
mirror of https://github.com/gryf/boxpy.git synced 2026-02-02 14:15:49 +01:00

26 Commits
1.6 ... 1.11.1

Author SHA1 Message Date
88172acd67 Refactor Run class.
When using capture=True together with subprocess.run, interactive
session will not work, as all the output will be captured. Perfectly
well seen with `boxpy ssh`. This commit will fix this issue.
2025-05-05 13:09:09 +02:00
4c3aef3a85 Fix ssh command.
There was a leftover call for changed Run object initialization.
With this commit it's correct now.
2025-05-02 12:42:17 +02:00
189fbab17d Removed license classifier in favor of SPDX entry. 2025-04-18 16:04:42 +02:00
eeebab74ad Fix some linting issues.
Also, changing workflow for image downloading fails.
2024-11-17 19:27:37 +01:00
81ab5de7c4 Bump Fedora version 2024-11-15 08:18:46 +01:00
62b86d5f81 Added poweroff option for stopping machine 2024-05-14 19:30:56 +02:00
14cd805e00 Bump the version.
Also, clean up raising exceptions.
2024-04-30 15:14:39 +02:00
86b02fca1b Keep image information right in the appropriate image subclass. 2024-04-30 15:14:13 +02:00
3d840de3ee Bump Debian, Fedora and Ubuntu versions.
Also, set Ubuntu latest LTS as default.
2024-04-30 15:10:03 +02:00
2a1a4cf40a Use pyproject.toml for installing box module. 2024-03-30 20:34:01 +01:00
71bf5b6d99 Bump Fedora version, remove archived versions prior to 37 2023-12-05 11:54:05 +01:00
e73d5ce458 Update kuryr multinode setup example 2023-07-23 12:58:12 +02:00
d623114f69 Update example for wmaker 2023-07-23 12:54:09 +02:00
51f642bea0 Updated ubuntu devstack example 2023-07-23 12:39:18 +02:00
12df0e97df Updated fedora devstack example 2023-07-23 11:53:59 +02:00
5f0def3103 Bump distribution latest version.
Also, updated mapping for the Fedora images.
2023-07-05 15:58:56 +02:00
43eabb9947 Fix nasty bug for debian images 2023-07-04 18:48:18 +02:00
55416db13d Added --type switch for start command 2023-04-20 19:42:21 +02:00
86a5655025 Improve detecting VM Operating System. 2023-04-20 19:21:24 +02:00
259d11e409 Don't reset Debian Bookworm on first run.
Issue/glitch with kernel panic on first run seems to be fixed on Debian
Bookworm (12). Do not reset VM for that version (and hopefully
upcoming).
2023-04-20 17:01:57 +02:00
b2457d497e Add experimental Debian support.
Note, there is something weird with those images, as the will segfault
on the first run (I've checked that with latest Debian 11 images, maybe
it will change in the future), so there is forced reboot after certain
amount of time as a workaround.
2023-04-18 21:48:19 +02:00
15a6ecb540 Use predefined os types for vbox VMs.
It might have impact on how operating system inside VM is treated by
VirtualBox, so let's stick with proper distro for every OS. As an
outcome, it turns out, that for graphical side of the vm, for Linux OS
the vmsvga driver is recommended, as a little more amount of vram.
2023-04-18 21:39:56 +02:00
10543cb506 Don't create vm till it's still there during rebuild 2023-04-02 10:05:06 +02:00
1f1084f294 Readme update. 2023-02-26 19:20:11 +01:00
f5ed3d37ac readme update 2023-02-22 21:10:59 +01:00
1483f49461 Added debug info about ssh connection to the vm 2023-02-22 21:08:43 +01:00
9 changed files with 428 additions and 155 deletions

View File

@@ -2,8 +2,8 @@
box.py box.py
====== ======
Box.py is a simple automation tool meant to run Ubuntu, Fedora or Centos Stream Box.py is a simple automation tool meant to run Ubuntu, Fedora, Centos Stream
cloud images on top of VirtualBox. or Debian cloud images on top of VirtualBox.
What it does is simply download official cloud image, set up VM, tweak it up What it does is simply download official cloud image, set up VM, tweak it up
and do the initial pre-configuration using generated config drive. and do the initial pre-configuration using generated config drive.
@@ -16,7 +16,7 @@ weird named options for ``vboxmanage`` ;P)
Requirements Requirements
------------ ------------
- Python >=3.7 - Python >=3.8
- `pyyaml`_ - `pyyaml`_
- `requests`_ - `requests`_
@@ -24,30 +24,61 @@ Requirements
- Virtualbox (obviously) - Virtualbox (obviously)
- ``mkisofs`` or ``genisoimage`` command for generating ISO image - ``mkisofs`` or ``genisoimage`` command for generating ISO image
- ``wget`` command for fetching images - ``wget`` command for fetching images
- ``sha256sum`` command for checksum check - ``sha256sum`` and ``sha512sum`` commands for checksum check
- ``qemu-img`` from *qemu-utils* package command for converting between images - ``qemu-img`` from *qemu-utils* package command for converting between images
formats formats
Tested distros
--------------
- Ubuntu
- 18.04
- 20.04
- 22.04
- 24.04
- Fedora
- 37
- 38
- 39
- 40
- 41
- Centos Stream
- 8
- 9
- Debian
- 10 (buster)
- 11 (bullseye)
- 12 (bookworm)
- 13 (trixie) - prerelease
There is possibility to use whatever OS image which supports cloud-init. Use
the ``--image`` param for ``create`` command to pass image filename, although
it's wise to at least discover (or not, but it may be easier in certain
distributions) what username is supposed to be used as a default user and pass
it with ``--username`` param.
How to run it How to run it
------------- -------------
First, make sure you fulfill the requirements; either by using packages from First, make sure you fulfill the requirements; either by using packages from
your operating system, or by using virtualenv for Python requirements, i.e.: your operating system, or by using virtualenv, i.e.:
.. code:: shell-session .. code:: shell-session
$ python -m virtualenv .venv $ python -m virtualenv .venv
$ . .venv/bin/activate $ . .venv/bin/activate
(.venv) $ pip install requirements.txt (.venv) $ pip install .
then you can issue: You'll have ``boxpy`` command created for you as well.
.. code:: shell-session .. code:: shell-session
$ alias boxpy='python /path/to/box.py' $ boxpy -V
boxpy 1.9.2
or simply link it somewhere in the path: Other option is simply link it somewhere in the path:
.. code:: shell-session .. code:: shell-session
@@ -55,25 +86,25 @@ or simply link it somewhere in the path:
$ chmod +x ~/bin/boxpy $ chmod +x ~/bin/boxpy
and now you can issue some command. For example, to spin up a VM with Ubuntu and now you can issue some command. For example, to spin up a VM with Ubuntu
18.04 with one CPU, 1GB of memory and 6GB of disk: 20.04 with one CPU, 1GB of memory and 6GB of disk:
.. code:: shell-session .. code:: shell-session
$ boxpy create --version 18.04 myvm $ boxpy create --version 20.04 myvm
note, that Ubuntu is default distribution you don't need to specify note, that Ubuntu is default distribution you don't need to specify
``--distro`` nor ``--version`` it will pick up latest LTS version. Now, let's ``--distro`` nor ``--version`` it will pick up latest LTS version. Now, let's
recreate it with 20.04: recreate it with 22.04:
.. code:: shell-session .. code:: shell-session
$ boxpy rebuild --version 20.04 myvm $ boxpy rebuild --version 22.04 myvm
or recreate it with Fedora and add additional CPU: or recreate it with Fedora and add additional CPU:
.. code:: shell-session .. code:: shell-session
$ boxpy rebuild --distro fedora --version 34 --cpu 2 myvm $ boxpy rebuild --distro fedora --version 39 --cpu 2 myvm
now, let's connect to the VM using either ssh command, which is printed out at now, let's connect to the VM using either ssh command, which is printed out at
as last ``boxpy`` output line, or simply by using ssh boxpy command: as last ``boxpy`` output line, or simply by using ssh boxpy command:
@@ -91,13 +122,15 @@ use it ad-hoc, or place on your ``.bashrc`` or whatever:
Currently, following commands are available: Currently, following commands are available:
- ``list`` - for quickly listing all/running VMs - ``completion`` - as described above
- ``info`` - to get summary about VM
- ``destroy`` - that is probably obvious one
- ``create`` - create new VM - ``create`` - create new VM
- ``destroy`` - that is probably obvious one
- ``info`` - to get summary about VM
- ``list`` - for quickly listing all/running VMs
- ``rebuild`` - recreate specified VM - ``rebuild`` - recreate specified VM
- ``ssh`` - connect to the VM using ssh - ``ssh`` - connect to the VM using ssh
- ``completion`` - as described above - ``start`` - stop the running VM
- ``stop`` - start stopped VM
All of the commands have a range of options, and can be examined by using All of the commands have a range of options, and can be examined by using
``--help`` option. ``--help`` option.

335
box.py
View File

@@ -18,14 +18,13 @@ import requests
import yaml import yaml
__version__ = "1.3" __version__ = "1.11.1"
CACHE_DIR = os.environ.get('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) CACHE_DIR = os.environ.get('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
CLOUD_IMAGE = "ci.iso" CLOUD_IMAGE = "ci.iso"
FEDORA_RELEASE_MAP = {'32': '1.6', '33': '1.2', '34': '1.2'}
TYPE_MAP = {'HardDisk': 'disk', 'DVD': 'dvd', 'Floppy': 'floppy'} TYPE_MAP = {'HardDisk': 'disk', 'DVD': 'dvd', 'Floppy': 'floppy'}
DISTRO_MAP = {'ubuntu': 'Ubuntu', 'fedora': 'Fedora', DISTRO_MAP = {'ubuntu': 'Ubuntu', 'fedora': 'Fedora',
'centos': 'Centos Stream'} 'centos': 'Centos Stream', 'debian': 'Debian'}
META_DATA_TPL = string.Template('''\ META_DATA_TPL = string.Template('''\
instance-id: $instance_id instance-id: $instance_id
local-hostname: $vmhostname local-hostname: $vmhostname
@@ -136,13 +135,37 @@ _boxpy() {
if [[ ${prev} == ${cmd} ]]; then if [[ ${prev} == ${cmd} ]]; then
COMPREPLY=( $(compgen -W "bash" -- ${cur}) ) COMPREPLY=( $(compgen -W "bash" -- ${cur}) )
fi fi
;;
start)
items=(--type)
if [[ ${prev} == ${cmd} ]]; then
if [[ ${cmd} = "start" ]]; then
_vms_comp vms
else
COMPREPLY=( $(compgen -W "${items[*]}" -- ${cur}) )
fi
else
_get_excluded_items "${items[@]}"
COMPREPLY=( $(compgen -W "$result" -- ${cur}) )
case "${prev}" in
--type)
COMPREPLY=( $(compgen -W "gui headless sdl separate" \
-- ${cur}) )
;;
--*)
COMPREPLY=( )
;;
esac
fi
;; ;;
create|rebuild) create|rebuild)
items=(--cpus --disable-nested --disk-size --default-user --distro items=(--cpus --disable-nested --disk-size --default-user --distro
--forwarding --image --key --memory --hostname --port --config --forwarding --image --key --memory --hostname --port --config
--version --type) --version --type)
if [[ ${prev} == ${cmd} ]]; then if [[ ${prev} == ${cmd} ]]; then
if [[ ${cmd} = "rebuild" ]]; then if [[ ${cmd} = "rebuild" || ${cmd} == "start" ]]; then
_vms_comp vms _vms_comp vms
else else
COMPREPLY=( $(compgen -W "${items[*]}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${items[*]}" -- ${cur}) )
@@ -160,8 +183,8 @@ _boxpy() {
_ssh_identityfile _ssh_identityfile
;; ;;
--distro) --distro)
COMPREPLY=( $(compgen -W "ubuntu fedora centos" \ COMPREPLY=( $(compgen -W "ubuntu fedora centos
-- ${cur}) ) debian" -- ${cur}) )
;; ;;
--type) --type)
COMPREPLY=( $(compgen -W "gui headless sdl separate" \ COMPREPLY=( $(compgen -W "gui headless sdl separate" \
@@ -194,14 +217,23 @@ _boxpy() {
_vms_comp runningvms _vms_comp runningvms
fi fi
;; ;;
start)
if [[ ${prev} == ${cmd} ]]; then
_vms_comp vms
fi
;;
stop) stop)
items=(--poweroff)
if [[ ${prev} == ${cmd} ]]; then if [[ ${prev} == ${cmd} ]]; then
if [[ ${cmd} = "stop" ]]; then
_vms_comp runningvms _vms_comp runningvms
else
COMPREPLY=( $(compgen -W "${items[*]}" -- ${cur}) )
fi
else
_get_excluded_items "${items[@]}"
COMPREPLY=( $(compgen -W "$result" -- ${cur}) )
case "${prev}" in
--*)
COMPREPLY=( )
;;
esac
fi fi
;; ;;
esac esac
@@ -243,28 +275,29 @@ class Run:
Helper class on subprocess.run() Helper class on subprocess.run()
command is a list with command and its params to execute command is a list with command and its params to execute
""" """
def __init__(self, command, capture_output=True): def __init__(self, command, capture=True):
result = subprocess.run(command, encoding='utf-8', result = subprocess.run(command, encoding='utf-8',
capture_output=capture_output) capture_output=capture)
if result.stdout:
LOG.debug2(result.stdout)
if result.stderr:
LOG.debug2(result.stderr)
self.returncode = result.returncode self.returncode = result.returncode
self.stdout = result.stdout.strip() if result.stdout else '' self.stdout = result.stdout.strip() if result.stdout else ''
self.stderr = result.stderr.strip() if result.stderr else '' self.stderr = result.stderr.strip() if result.stderr else ''
if self.stdout:
LOG.debug2(self.stdout)
if self.stderr:
LOG.debug2(self.stderr)
class BoxError(Exception): class BoxError(Exception):
pass pass
class BoxNotFound(BoxError): class BoxNotFoundError(BoxError):
pass pass
class BoxVBoxFailure(BoxError): class BoxVBoxError(BoxError):
pass pass
@@ -492,7 +525,7 @@ class Config:
def _read_filename(self, fname): def _read_filename(self, fname):
fullpath = os.path.expanduser(os.path.expandvars(fname)) fullpath = os.path.expanduser(os.path.expandvars(fname))
if not os.path.exists(fullpath): if not os.path.exists(fullpath):
return return None
with open(fname) as fobj: with open(fname) as fobj:
return fobj.read() return fobj.read()
@@ -585,6 +618,50 @@ class Config:
return source return source
class OsTypes:
def __init__(self, conf):
self._conf = conf
self._ostypes = []
self._gather_os_types()
def _gather_os_types(self):
out = Run(['vboxmanage', 'list', 'ostypes']).stdout
for line in out.split('\n'):
if not line.startswith('ID:'):
continue
self._ostypes.append(line.split(':')[1].strip())
def ubuntu(self):
lts = ''
major, minor = (int(x) for x in self._conf.version.split('.'))
if major % 2 == 0 and minor == 4:
lts = '_LTS'
name = f"Ubuntu{major}{lts}_64"
if name not in self._ostypes:
return 'Ubuntu_64'
return name
def fedora(self):
return "Fedora_64"
def debian(self):
name = f"Debian{self._conf.version}_64"
if name not in self._ostypes:
return 'Debian_64'
return name
def get(self):
if not hasattr(self, self._conf.distro):
return "Linux_64"
return getattr(self, self._conf.distro)()
class VBoxManage: class VBoxManage:
""" """
Class for dealing with vboxmanage commands Class for dealing with vboxmanage commands
@@ -598,7 +675,7 @@ class VBoxManage:
def get_vm_base_path(self): def get_vm_base_path(self):
path = self._get_vm_config() path = self._get_vm_config()
if not path: if not path:
return return None
return os.path.dirname(path) return os.path.dirname(path)
@@ -606,12 +683,12 @@ class VBoxManage:
path = self._get_vm_config() path = self._get_vm_config()
if not path: if not path:
LOG.warning('Configuration for "%s" not found', self.name_or_uuid) LOG.warning('Configuration for "%s" not found', self.name_or_uuid)
return return None
dom = xml.dom.minidom.parse(path) dom = xml.dom.minidom.parse(path)
if len(dom.getElementsByTagName('HardDisk')) != 1: if len(dom.getElementsByTagName('HardDisk')) != 1:
# don't know what to do with multiple discs # don't know what to do with multiple discs
raise BoxError() raise BoxError
disk = dom.getElementsByTagName('HardDisk')[0] disk = dom.getElementsByTagName('HardDisk')[0]
location = disk.getAttribute('location') location = disk.getAttribute('location')
@@ -633,6 +710,7 @@ class VBoxManage:
return line return line
return line.split(' ')[0].strip() return line.split(' ')[0].strip()
return None
def get_vm_info(self): def get_vm_info(self):
out = Run(['vboxmanage', 'showvminfo', self.name_or_uuid]) out = Run(['vboxmanage', 'showvminfo', self.name_or_uuid])
@@ -733,6 +811,7 @@ class VBoxManage:
'--delete']).returncode != 0: '--delete']).returncode != 0:
LOG.fatal('Removing VM "%s" failed', self.name_or_uuid) LOG.fatal('Removing VM "%s" failed', self.name_or_uuid)
return 7 return 7
return None
def create(self, conf): def create(self, conf):
memory = convert_to_mega(conf.memory) memory = convert_to_mega(conf.memory)
@@ -754,7 +833,8 @@ class VBoxManage:
self.uuid = line.split('UUID:')[1].strip() self.uuid = line.split('UUID:')[1].strip()
if not self.uuid: if not self.uuid:
raise BoxVBoxFailure(f'Cannot create VM "{self.name_or_uuid}".') msg = f'Cannot create VM "{self.name_or_uuid}".'
raise BoxVBoxError(msg)
port = conf.port if conf.port else self._find_unused_port() port = conf.port if conf.port else self._find_unused_port()
@@ -765,7 +845,11 @@ class VBoxManage:
'--acpi', 'on', '--acpi', 'on',
'--audio', 'none', '--audio', 'none',
'--nic1', 'nat', '--nic1', 'nat',
'--natpf1', f'boxpyssh,tcp,,{port},,22'] '--natpf1', f'boxpyssh,tcp,,{port},,22',
'--graphicscontroller', 'vmsvga',
'--vram', '16',
'--ostype', OsTypes(conf).get()]
for count, (hostport, vmport) in enumerate(conf.forwarding.items(), for count, (hostport, vmport) in enumerate(conf.forwarding.items(),
start=1): start=1):
cmd.extend(['--natpf1', f'custom-pf-{count},tcp,,{hostport},' cmd.extend(['--natpf1', f'custom-pf-{count},tcp,,{hostport},'
@@ -773,14 +857,15 @@ class VBoxManage:
if Run(cmd).returncode != 0: if Run(cmd).returncode != 0:
LOG.fatal(f'Cannot modify VM "{self.name_or_uuid}"') LOG.fatal(f'Cannot modify VM "{self.name_or_uuid}"')
raise BoxVBoxFailure() raise BoxVBoxError
if conf.disable_nested == 'False': if conf.disable_nested == 'False' and Run(['vboxmanage', 'modifyvm',
if Run(['vboxmanage', 'modifyvm', self.name_or_uuid, self.name_or_uuid,
'--nested-hw-virt', 'on']).returncode != 0: '--nested-hw-virt',
'on']).returncode != 0:
LOG.fatal(f'Cannot set nested virtualization for VM ' LOG.fatal(f'Cannot set nested virtualization for VM '
f'"{self.name_or_uuid}"') f'"{self.name_or_uuid}"')
raise BoxVBoxFailure() raise BoxVBoxError
return self.uuid return self.uuid
@@ -816,7 +901,7 @@ class VBoxManage:
if Run(['vboxmanage', 'modifymedium', 'disk', src, '--resize', if Run(['vboxmanage', 'modifymedium', 'disk', src, '--resize',
str(size), '--move', fullpath]).returncode != 0: str(size), '--move', fullpath]).returncode != 0:
LOG.fatal('Resizing and moving image %s has failed', dst) LOG.fatal('Resizing and moving image %s has failed', dst)
raise BoxVBoxFailure() raise BoxVBoxError
return fullpath return fullpath
def storageattach(self, controller_name, port, type_, image): def storageattach(self, controller_name, port, type_, image):
@@ -840,7 +925,7 @@ class VBoxManage:
if Run(['vboxmanage', 'startvm', self.name_or_uuid, '--type', if Run(['vboxmanage', 'startvm', self.name_or_uuid, '--type',
type_]).returncode != 0: type_]).returncode != 0:
LOG.fatal('Failed to start: %s', self.name_or_uuid) LOG.fatal('Failed to start: %s', self.name_or_uuid)
raise BoxVBoxFailure() raise BoxVBoxError
def setextradata(self, key, val): def setextradata(self, key, val):
res = Run(['vboxmanage', 'setextradata', self.name_or_uuid, key, val]) res = Run(['vboxmanage', 'setextradata', self.name_or_uuid, key, val])
@@ -854,7 +939,7 @@ class VBoxManage:
if Run(['vboxmanage', 'modifyvm', self.name_or_uuid, f'--{nic}', if Run(['vboxmanage', 'modifyvm', self.name_or_uuid, f'--{nic}',
kind]).returncode != 0: kind]).returncode != 0:
LOG.fatal('Cannot modify VM "%s"', self.name_or_uuid) LOG.fatal('Cannot modify VM "%s"', self.name_or_uuid)
raise BoxVBoxFailure() raise BoxVBoxError
def is_port_in_use(self, port): def is_port_in_use(self, port):
used_ports = self._get_defined_ports() used_ports = self._get_defined_ports()
@@ -914,8 +999,9 @@ class VBoxManage:
class Image: class Image:
URL = "" URL = ""
IMG = "" IMG = ""
CHECKSUMTOOL = 'sha256sum'
def __init__(self, vbox, version, arch, release, fname=None): def __init__(self, vbox, version, arch, fname=None):
self.vbox = vbox self.vbox = vbox
self._tmp = tempfile.mkdtemp(prefix='boxpy_') self._tmp = tempfile.mkdtemp(prefix='boxpy_')
self._img_fname = fname self._img_fname = fname
@@ -956,6 +1042,7 @@ class Image:
return False return False
LOG.info('Calculating checksum for "%s"', self._img_fname) LOG.info('Calculating checksum for "%s"', self._img_fname)
LOG.debug('Checksum file: "%s"', self._checksum_file)
fname = os.path.join(self._tmp, self._checksum_file) fname = os.path.join(self._tmp, self._checksum_file)
expected_sum = self._get_checksum(fname) expected_sum = self._get_checksum(fname)
@@ -964,7 +1051,7 @@ class Image:
return False return False
if os.path.exists(os.path.join(CACHE_DIR, self._img_fname)): if os.path.exists(os.path.join(CACHE_DIR, self._img_fname)):
cmd = ['sha256sum', os.path.join(CACHE_DIR, self._img_fname)] cmd = [self.CHECKSUMTOOL, os.path.join(CACHE_DIR, self._img_fname)]
calulated_sum = Run(cmd).stdout.split(' ')[0] calulated_sum = Run(cmd).stdout.split(' ')[0]
LOG.details('Checksum for image: %s, expected: %s', calulated_sum, LOG.details('Checksum for image: %s, expected: %s', calulated_sum,
expected_sum) expected_sum)
@@ -978,9 +1065,11 @@ class Image:
return True return True
fname = os.path.join(CACHE_DIR, self._img_fname) fname = os.path.join(CACHE_DIR, self._img_fname)
LOG.header('Downloading image %s from %s', self._img_fname, LOG.header('Downloading image %s', self._img_url)
self._img_url) result = Run(['wget', '-q', self._img_url, '-O', fname])
Run(['wget', '-q', self._img_url, '-O', fname]) if result.returncode != 0:
LOG.fatal("Error downloading image %s", self._img_url)
return False
if not self._checksum(): if not self._checksum():
# TODO: make some retry mechanism? # TODO: make some retry mechanism?
@@ -991,15 +1080,15 @@ class Image:
return True return True
def _get_checksum(self, fname): def _get_checksum(self, fname):
raise NotImplementedError() raise NotImplementedError
class Ubuntu(Image): class Ubuntu(Image):
URL = "https://cloud-images.ubuntu.com/releases/%s/release/%s" URL = "https://cloud-images.ubuntu.com/releases/%s/release/%s"
IMG = "ubuntu-%s-server-cloudimg-%s.img" IMG = "ubuntu-%s-server-cloudimg-%s.img"
def __init__(self, vbox, version, arch, release, fname=None): def __init__(self, vbox, version, arch, fname=None):
super().__init__(vbox, version, arch, release) super().__init__(vbox, version, arch)
self._img_fname = self.IMG % (version, arch) self._img_fname = self.IMG % (version, arch)
self._img_url = self.URL % (version, self._img_fname) self._img_url = self.URL % (version, self._img_fname)
self._checksum_file = 'SHA256SUMS' self._checksum_file = 'SHA256SUMS'
@@ -1017,17 +1106,64 @@ class Ubuntu(Image):
return expected_sum return expected_sum
class Debian(Image):
URL = "https://cloud.debian.org/images/cloud/%s/daily/latest/%s"
IMG = "debian-%s-generic-%s-daily.qcow2"
CHECKSUMTOOL = 'sha512sum'
CODENAME_MAP = {'13': 'trixie',
'12': 'bookworm',
'11': 'bullseye',
'10': 'buster'}
def __init__(self, vbox, version, arch, fname=None):
super().__init__(vbox, version, arch)
codename = self.CODENAME_MAP[version]
self._img_fname = self.IMG % (version, arch)
self._img_url = self.URL % (codename, self._img_fname)
self._checksum_file = 'SHA512SUMS'
self._checksum_url = self.URL % (codename, self._checksum_file)
def _get_checksum(self, fname):
expected_sum = None
Run(['wget', self._checksum_url, '-q', '-O', fname])
with open(fname) as fobj:
for line in fobj.readlines():
if self._img_fname in line:
expected_sum = line.split(' ')[0]
break
return expected_sum
class Fedora(Image): class Fedora(Image):
URL = ("https://download.fedoraproject.org/pub/fedora/linux/releases/%s/" URL = ("https://download.fedoraproject.org/pub/fedora/linux/releases/%s/"
"Cloud/%s/images/%s") "Cloud/%s/images/%s")
IMG = "Fedora-Cloud-Base-%s-%s.%s.qcow2" IMG = "Fedora-Cloud-Base-%s-%s.%s.qcow2"
CHKS = "Fedora-Cloud-%s-%s-%s-CHECKSUM" CHKS = "Fedora-Cloud-%s-%s-%s-CHECKSUM"
REVISION = {'37': '1.7',
'38': '1.6',
'39': '1.5',
'40': '1.14',
'41': '1.4'}
def __init__(self, vbox, version, arch, release, fname=None): def __init__(self, vbox, version, arch, fname=None):
super().__init__(vbox, version, arch, release) super().__init__(vbox, version, arch)
self._img_fname = self.IMG % (version, release, arch) revision = self.REVISION[version]
if int(version) >= 40:
if int(version) == 40:
# Started from Fedora 40 there is "Generic" in the image names.
self.IMG = "Fedora-Cloud-Base-Generic.%s-%s-%s.qcow2"
# Fedora 40 have messed up position of the items in filename.
self._img_fname = self.IMG % (arch, version, revision)
else:
# But in Fedora 41 there is no dot between Generic and
# version, but between version and arch.
self.IMG = "Fedora-Cloud-Base-Generic-%s-%s.%s.qcow2"
self._img_fname = self.IMG % (version, revision, arch)
else:
self._img_fname = self.IMG % (version, revision, arch)
self._img_url = self.URL % (version, arch, self._img_fname) self._img_url = self.URL % (version, arch, self._img_fname)
self._checksum_file = self.CHKS % (version, release, arch) self._checksum_file = self.CHKS % (version, revision, arch)
self._checksum_url = self.URL % (version, arch, self._checksum_file) self._checksum_url = self.URL % (version, arch, self._checksum_file)
def _get_checksum(self, fname): def _get_checksum(self, fname):
@@ -1051,8 +1187,7 @@ class CentosStream(Image):
def __init__(self, vbox, version, arch, release, fname=None): def __init__(self, vbox, version, arch, release, fname=None):
super().__init__(vbox, version, arch, release) super().__init__(vbox, version, arch, release)
self._checksum_file = '%s-centos-stream-%s-%s' % (self.CHKS, version, self._checksum_file = f'{self.CHKS}-centos-stream-{version}-{arch}'
arch)
self._checksum_url = self.URL % (version, arch, self.CHKS) self._checksum_url = self.URL % (version, arch, self.CHKS)
# there is assumption, that we always need latest relese for specific # there is assumption, that we always need latest relese for specific
# version and architecture. # version and architecture.
@@ -1079,6 +1214,7 @@ class CentosStream(Image):
images.reverse() images.reverse()
if images: if images:
return images[0] return images[0]
return None
def _get_checksum(self, fname): def _get_checksum(self, fname):
expected_sum = None expected_sum = None
@@ -1105,25 +1241,27 @@ DISTROS = {'ubuntu': {'username': 'ubuntu',
'realname': 'ubuntu', 'realname': 'ubuntu',
'img_class': Ubuntu, 'img_class': Ubuntu,
'amd64': 'amd64', 'amd64': 'amd64',
'default_version': '22.04'}, 'default_version': '24.04'},
'fedora': {'username': 'fedora', 'fedora': {'username': 'fedora',
'realname': 'fedora', 'realname': 'fedora',
'img_class': Fedora, 'img_class': Fedora,
'amd64': 'x86_64', 'amd64': 'x86_64',
'default_version': '34'}, 'default_version': '41'},
'centos': {'username': 'centos', 'centos': {'username': 'centos',
'realname': 'centos', 'realname': 'centos',
'img_class': CentosStream, 'img_class': CentosStream,
'amd64': 'x86_64', 'amd64': 'x86_64',
'default_version': '8'}} 'default_version': '9'},
'debian': {'username': 'debian',
'realname': 'debian',
'img_class': Debian,
'amd64': 'amd64',
'default_version': '12'}}
def get_image_object(vbox, version, image='ubuntu', arch='amd64'): def get_image_object(vbox, version, image='ubuntu', arch='amd64'):
release = None
if image == 'fedora':
release = FEDORA_RELEASE_MAP[version]
return DISTROS[image]['img_class'](vbox, version, DISTROS[image]['amd64'], return DISTROS[image]['img_class'](vbox, version, DISTROS[image]['amd64'],
release, DISTROS[image].get('image')) DISTROS[image].get('image'))
class IsoImage: class IsoImage:
@@ -1205,8 +1343,7 @@ def vmcreate(args, conf=None):
if not vbox.setextradata(key, getattr(conf, key)): if not vbox.setextradata(key, getattr(conf, key)):
return 5 return 5
if conf.user_data: if conf.user_data and not vbox.setextradata('user_data', conf.user_data):
if not vbox.setextradata('user_data', conf.user_data):
return 6 return 6
if not vbox.setextradata('creator', 'boxpy'): if not vbox.setextradata('creator', 'boxpy'):
@@ -1247,6 +1384,7 @@ def vmcreate(args, conf=None):
f'ssh://{username}@localhost:{vbox.vm_info["port"]}', f'ssh://{username}@localhost:{vbox.vm_info["port"]}',
'sudo cloud-init status'] 'sudo cloud-init status']
try: try:
counter = 0
while True: while True:
out = Run(cmd) out = Run(cmd)
LOG.debug('Out: %s', out.stdout) LOG.debug('Out: %s', out.stdout)
@@ -1266,6 +1404,22 @@ def vmcreate(args, conf=None):
f'VM with ssh for user {username}. ' f'VM with ssh for user {username}. '
f'Check output in debug mode.') f'Check output in debug mode.')
time.sleep(3) time.sleep(3)
counter += 1
# TODO: there is something odd with debian cloud images prior
# to 12 (bookworm), as on first run system crashes. In that
# case after ~20 seconds there should already be panic, reset
# machine as a workaround. Remove this after debian 12
# stabilization later this year.
if (counter == 8 and conf.distro == 'debian'
and conf.version != '12'):
LOG.debug('Resetting `%s`, due to the issue with kernel '
'panic on Debian %s the first run', conf.name,
conf.version)
counter += 1
vbox.poweroff()
time.sleep(3)
vbox.poweron(args.type)
continue continue
LOG.info(' done') LOG.info(' done')
@@ -1296,24 +1450,21 @@ def vmcreate(args, conf=None):
# reread config to update fields # reread config to update fields
conf = Config(args, vbox) conf = Config(args, vbox)
if conf.username and conf.username != username:
username = conf.username
else:
username = DISTROS[conf.distro]["username"] username = DISTROS[conf.distro]["username"]
LOG.info('You can access your VM by issuing:') LOG.info('You can access your VM by issuing:')
if conf.username and conf.username != username: LOG.info('ssh -p %s -i %s %s@localhost', conf.port, conf.ssh_key_path[:-4],
LOG.info(f'ssh -p {conf.port} -i {conf.ssh_key_path[:-4]} ' username)
f'{conf.username}@localhost')
else:
LOG.info(f'ssh -p {conf.port} -i {conf.ssh_key_path[:-4]} '
f'{username}@localhost')
LOG.info('or simply:') LOG.info('or simply:')
LOG.info(f'boxpy ssh {conf.name}') LOG.info('boxpy ssh %s', conf.name)
return 0 return 0
def vmdestroy(args): def vmdestroy(args):
if isinstance(args.name, list):
vm_names = args.name vm_names = args.name if isinstance(args.name, list) else [args.name]
else:
vm_names = [args.name]
for name in vm_names: for name in vm_names:
vbox = VBoxManage(name) vbox = VBoxManage(name)
@@ -1407,7 +1558,8 @@ def vminfo(args):
LOG.info(line) LOG.info(line)
if 'user_data' in info: if 'user_data' in info:
LOG.info(f'User data file path:\t{info["user_data"]}') LOG.info('User data file path:\t%s', info['user_data'])
return 0
def vmrebuild(args): def vmrebuild(args):
@@ -1415,12 +1567,12 @@ def vmrebuild(args):
if not vbox.get_vm_info(): if not vbox.get_vm_info():
LOG.fatal(f'Cannot rebuild VM "{args.name}" - it doesn\'t exists.') LOG.fatal(f'Cannot rebuild VM "{args.name}" - it doesn\'t exists.')
return 20 return 20
else:
LOG.header('Rebuilding VM: %s', args.name) LOG.header('Rebuilding VM: %s', args.name)
try: try:
conf = Config(args, vbox) conf = Config(args, vbox)
except BoxNotFound as ex: except BoxNotFoundError as ex:
LOG.fatal(f'Error with parsing config: {ex}') LOG.fatal(f'Error with parsing config: {ex}')
return 8 return 8
except yaml.YAMLError: except yaml.YAMLError:
@@ -1445,6 +1597,13 @@ def vmrebuild(args):
conf.disk_size = vbox.get_media_size(disk_path) conf.disk_size = vbox.get_media_size(disk_path)
vmdestroy(args) vmdestroy(args)
# Wait till VM is gone
while True:
vbox = VBoxManage(args.name)
if not vbox.get_vm_info():
break
vmcreate(args, conf) vmcreate(args, conf)
return 0 return 0
@@ -1462,7 +1621,7 @@ def connect(args):
try: try:
conf = Config(args, vbox) conf = Config(args, vbox)
except BoxNotFound: except BoxNotFoundError:
return 11 return 11
except yaml.YAMLError: except yaml.YAMLError:
LOG.fatal(f'Cannot read or parse file `{args.config}` as YAML ' LOG.fatal(f'Cannot read or parse file `{args.config}` as YAML '
@@ -1470,14 +1629,17 @@ def connect(args):
return 16 return 16
username = conf.username or DISTROS[conf.distro]["username"] username = conf.username or DISTROS[conf.distro]["username"]
return Run(['ssh', '-o', 'StrictHostKeyChecking=no', cmd = ['ssh', '-o', 'StrictHostKeyChecking=no',
'-o', 'UserKnownHostsFile=/dev/null', '-o', 'UserKnownHostsFile=/dev/null',
'-i', conf.ssh_key_path[:-4], '-i', conf.ssh_key_path[:-4],
f'ssh://{username}' f'ssh://{username}'
f'@localhost:{conf.port}'], False).returncode f'@localhost:{conf.port}']
LOG.debug('Connecting to vm `%s` using command:\n%s', args.name,
' '.join(cmd))
return Run(cmd, capture=False).returncode
def _set_vmstate(name, state): def _set_vmstate(name, state, guitype=None, poweroff=False):
vbox = VBoxManage(name) vbox = VBoxManage(name)
if not vbox.get_vm_info(): if not vbox.get_vm_info():
@@ -1485,25 +1647,28 @@ def _set_vmstate(name, state):
return 20 return 20
if vbox.running and state == "start": if vbox.running and state == "start":
LOG.info(f'VM "{name}" is already running.') LOG.info('VM "%s" is already running.', name)
return return 1
if not vbox.running and state == "stop": if not vbox.running and state == "stop":
LOG.info(f'VM "{name}" is already stopped.') LOG.info('VM "%s" is already stopped.', name)
return return 1
if state == "start": if state == "start":
vbox.poweron() vbox.poweron(guitype)
elif poweroff:
vbox.poweroff()
else: else:
vbox.acpipowerbutton() vbox.acpipowerbutton()
return 0
def vmstart(args): def vmstart(args):
_set_vmstate(args.name, 'start') _set_vmstate(args.name, 'start', guitype=args.type)
def vmstop(args): def vmstop(args):
_set_vmstate(args.name, 'stop') _set_vmstate(args.name, 'stop', poweroff=args.poweroff)
def main(): def main():
@@ -1614,10 +1779,15 @@ def main():
start = subparsers.add_parser('start', help='start VM') start = subparsers.add_parser('start', help='start VM')
start.add_argument('name', help='name or UUID of the VM') start.add_argument('name', help='name or UUID of the VM')
start.add_argument('-t', '--type', default='headless',
help="VM run type, headless by default.",
choices=['gui', 'headless', 'sdl', 'separate'])
start.set_defaults(func=vmstart) start.set_defaults(func=vmstart)
stop = subparsers.add_parser('stop', help='stop VM') stop = subparsers.add_parser('stop', help='stop VM')
stop.add_argument('name', help='name or UUID of the VM') stop.add_argument('name', help='name or UUID of the VM')
stop.add_argument('-p', '--poweroff', action='store_true', help='poweroff '
'machine instead of using ACPI power signal')
stop.set_defaults(func=vmstop) stop.set_defaults(func=vmstop)
completion = subparsers.add_parser('completion', help='generate shell ' completion = subparsers.add_parser('completion', help='generate shell '
@@ -1643,7 +1813,7 @@ def main():
LOG.set_verbose(args.verbose, args.quiet) LOG.set_verbose(args.verbose, args.quiet)
if 'func' not in args and args.version: if 'func' not in args and args.version:
LOG.info(f'boxpy {__version__}') LOG.info('boxpy %s', __version__)
parser.exit() parser.exit()
if hasattr(args, 'func'): if hasattr(args, 'func'):
@@ -1651,6 +1821,7 @@ def main():
parser.print_help() parser.print_help()
parser.exit() parser.exit()
return 23
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -1,9 +1,7 @@
# Note, that cloud init will fail, due to old cloudinit package, which module
# cc_keys_to_console doesn't recognize skipping option. Just ignore this error.
package_update: true package_update: true
packages: packages:
- bash-completion - bash-completion
- ctags-etags - ctags
- git - git
- git-review - git-review
- htop - htop
@@ -25,13 +23,13 @@ write_files:
disable_service n-vnc disable_service n-vnc
disable_service dstat disable_service dstat
runcmd: runcmd:
- [su, -, fedora, -c, "git clone https://opendev.org/openstack/devstack ~/devstack"] - su - fedora -c "git clone https://opendev.org/openstack/devstack ~/devstack"
- [su, -, fedora, -c, "cp /tmp/local.conf /home/fedora/devstack/"] - su - fedora -c "cp /tmp/local.conf /home/fedora/devstack/"
- [su, -, fedora, -c, "echo 'export HOST_IP=10.0.2.15' >> .bashrc"] - su - fedora -c "echo 'export HOST_IP=10.0.2.15' >> .bashrc"
boxpy_data: boxpy_data:
key: vm key: vm
cpus: 2 cpus: 2
memory: 4GB memory: 4GB
disk_size: 10GB disk_size: 10GB
distro: fedora distro: fedora
version: 34 version: 36

View File

@@ -7,7 +7,7 @@ packages:
- ipython3 - ipython3
- jq - jq
- mc - mc
- python-apsw - python3-apsw
- python3-flake8 - python3-flake8
- python3-jedi - python3-jedi
- python3-pip - python3-pip
@@ -26,17 +26,15 @@ write_files:
RABBIT_PASSWORD=$$ADMIN_PASSWORD RABBIT_PASSWORD=$$ADMIN_PASSWORD
SERVICE_PASSWORD=$$ADMIN_PASSWORD SERVICE_PASSWORD=$$ADMIN_PASSWORD
runcmd: runcmd:
- [apt, purge, '-y', python3-pyasn1-modules] - apt purge -y python3-pyasn1-modules
- [apt, purge, '-y', python3-simplejson] - apt purge -y python3-simplejson
- [su, -, ubuntu, -c, "git clone https://github.com/gryf/vmstrap"] - su - ubuntu -c "git clone https://opendev.org/openstack/devstack ~/devstack"
- [su, -, ubuntu, -c, "vmstrap/bootstrap.sh -c"] - su - ubuntu -c "cp /tmp/local.conf /home/ubuntu/devstack/"
- [rm, -fr, /home/ubuntu/vmstrap] - su - ubuntu -c "echo 'export HOST_IP=10.0.2.15' >> .bashrc"
- [su, -, ubuntu, -c, "cp /tmp/local.conf /home/ubuntu/devstack/"]
- [su, -, ubuntu, -c, "echo 'export HOST_IP=10.0.2.15' >> .bashrc"]
boxpy_data: boxpy_data:
key: vm key: vm
cpus: 2 cpus: 2
memory: 6GB memory: 6GB
disk_size: 10GB disk_size: 10GB
distro: ubuntu distro: ubuntu
version: 20.04 version: 22.04

View File

@@ -7,7 +7,7 @@ packages:
- ipython3 - ipython3
- jq - jq
- mc - mc
- python-apsw - python3-apsw
- python3-flake8 - python3-flake8
- python3-jedi - python3-jedi
- python3-pip - python3-pip
@@ -122,6 +122,7 @@ write_files:
# Vars # Vars
ADMIN_PASSWORD="secretadmin" ADMIN_PASSWORD="secretadmin"
CONTAINER_ENGINE="crio"
DATABASE_PASSWORD="secretdatabase" DATABASE_PASSWORD="secretdatabase"
ETCD_USE_RAMDISK="True" ETCD_USE_RAMDISK="True"
@@ -131,9 +132,10 @@ write_files:
KURYR_K8S_MULTI_WORKER_TESTS="True" KURYR_K8S_MULTI_WORKER_TESTS="True"
KURYR_K8S_OCTAVIA_MEMBER_MODE="L2" KURYR_K8S_OCTAVIA_MEMBER_MODE="L2"
KURYR_LB_ALGORITHM="SOURCE_IP_PORT" KURYR_LB_ALGORITHM="SOURCE_IP_PORT"
KURYR_NEUTRON_DEFAULT_ROUTER="router1" KURYR_NEUTRON_DEFAULT_ROUTER="kuryr-router"
KURYR_SG_DRIVER="policy" KURYR_SG_DRIVER="policy"
KURYR_SUBNET_DRIVER="namespace" KURYR_SUBNET_DRIVER="namespace"
KURYR_SUPPORT_POD_SECURITY="True"
LOGFILE="/opt/stack/logs/devstacklog.txt" LOGFILE="/opt/stack/logs/devstacklog.txt"
LOG_COLOR="False" LOG_COLOR="False"
@@ -142,10 +144,11 @@ write_files:
OCTAVIA_AMP_IMAGE_NAME="test-only-amphora-x64-haproxy-ubuntu-bionic" OCTAVIA_AMP_IMAGE_NAME="test-only-amphora-x64-haproxy-ubuntu-bionic"
OCTAVIA_AMP_IMAGE_SIZE="3" OCTAVIA_AMP_IMAGE_SIZE="3"
OVN_BRANCH="v20.06.2" OVN_BRANCH="v21.06.0"
OVN_BUILD_FROM_SOURCE="True" OVN_BUILD_FROM_SOURCE="True"
OVN_DBS_LOG_LEVEL="dbg" OVN_DBS_LOG_LEVEL="dbg"
OVN_L3_CREATE_PUBLIC_NETWORK="True" OVN_L3_CREATE_PUBLIC_NETWORK="True"
OVS_BRANCH="a4b04276ab5934d087669ff2d191a23931335c87"
VAR_RUN_PATH="/usr/local/var/run" VAR_RUN_PATH="/usr/local/var/run"
@@ -167,23 +170,25 @@ write_files:
enable_plugin octavia-tempest-plugin https://opendev.org/openstack/octavia-tempest-plugin enable_plugin octavia-tempest-plugin https://opendev.org/openstack/octavia-tempest-plugin
enable_plugin ovn-octavia-provider https://opendev.org/openstack/ovn-octavia-provider enable_plugin ovn-octavia-provider https://opendev.org/openstack/ovn-octavia-provider
[[post-config|$OCTAVIA_CONF]] [[post-config|$$OCTAVIA_CONF]]
[api_settings] [api_settings]
enabled_provider_drivers = amphora:'Octavia Amphora driver',ovn:'Octavia OVN driver' enabled_provider_drivers = amphora:'Octavia Amphora driver',ovn:'Octavia OVN driver'
runcmd: runcmd:
- [apt, purge, '-y', python3-pyasn1-modules] - systemctl stop systemd-resolved.service
- [apt, purge, '-y', python3-simplejson] - systemctl disable systemd-resolved.service
- [su, -, ubuntu, -c, "git clone https://github.com/gryf/vmstrap"] - echo "nameserver 1.1.1.1" > /etc/resolv.conf
- [su, -, ubuntu, -c, "vmstrap/bootstrap.sh -c"] - apt purge -y snapd
- [rm, -fr, /home/ubuntu/vmstrap] - apt purge -y python3-pyasn1-modules
- [su, -, ubuntu, -c, "echo 'export HOST_IP=192.168.10.10' >> .bashrc"] - apt purge -y python3-simplejson
- [su, -, ubuntu, -c, "cp /tmp/local.conf /home/ubuntu/devstack/"] - su - ubuntu -c "git clone https://opendev.org/openstack/devstack"
- [systemctl, restart, systemd-networkd] - su - ubuntu -c "cp /tmp/local.conf /home/ubuntu/devstack/"
- su - ubuntu -c "echo 'export HOST_IP=192.168.10.10' >> .bashrc"
- systemctl restart systemd-networkd
boxpy_data: boxpy_data:
key: vm key: vm
cpus: 4 cpus: 4
memory: 16GB memory: 16GB
disk_size: 50GB disk_size: 50GB
version: 20.04
advanced: advanced:
nic2: intnet nic2: intnet
version: 20.04

View File

@@ -7,7 +7,7 @@ packages:
- ipython3 - ipython3
- jq - jq
- mc - mc
- python-apsw - python3-apsw
- python3-flake8 - python3-flake8
- python3-jedi - python3-jedi
- python3-pip - python3-pip
@@ -79,6 +79,7 @@ write_files:
# Vars # Vars
ADMIN_PASSWORD="secretadmin" ADMIN_PASSWORD="secretadmin"
CONTAINER_ENGINE="crio"
DATABASE_HOST="192.168.10.10" DATABASE_HOST="192.168.10.10"
DATABASE_PASSWORD="secretdatabase" DATABASE_PASSWORD="secretdatabase"
ENABLE_CHASSIS_AS_GW="False" ENABLE_CHASSIS_AS_GW="False"
@@ -89,7 +90,7 @@ write_files:
KURYR_EP_DRIVER_OCTAVIA_PROVIDER="ovn" KURYR_EP_DRIVER_OCTAVIA_PROVIDER="ovn"
KURYR_K8S_OCTAVIA_MEMBER_MODE="L2" KURYR_K8S_OCTAVIA_MEMBER_MODE="L2"
KURYR_LB_ALGORITHM="SOURCE_IP_PORT" KURYR_LB_ALGORITHM="SOURCE_IP_PORT"
KURYR_NEUTRON_DEFAULT_ROUTER="router1" KURYR_NEUTRON_DEFAULT_ROUTER="kuryr-router"
VAR_RUN_PATH="/usr/local/var/run" VAR_RUN_PATH="/usr/local/var/run"
KURYR_ENABLED_HANDLERS="vif,endpoints,service,namespace,pod_label,policy,kuryrnetworkpolicy,kuryrnetwork,kuryrport,kuryrloadbalancer" KURYR_ENABLED_HANDLERS="vif,endpoints,service,namespace,pod_label,policy,kuryrnetworkpolicy,kuryrnetwork,kuryrport,kuryrloadbalancer"
@@ -97,10 +98,11 @@ write_files:
KURYR_SUBNET_DRIVER="namespace" KURYR_SUBNET_DRIVER="namespace"
OVN_BRANCH="v20.06.2" OVN_BRANCH="v21.06.0"
OVN_BUILD_FROM_SOURCE="True" OVN_BUILD_FROM_SOURCE="True"
OVN_DBS_LOG_LEVEL="dbg" OVN_DBS_LOG_LEVEL="dbg"
OVN_L3_CREATE_PUBLIC_NETWORK="True" OVN_L3_CREATE_PUBLIC_NETWORK="True"
OVS_BRANCH="a4b04276ab5934d087669ff2d191a23931335c87"
LIBVIRT_TYPE="qemu" LIBVIRT_TYPE="qemu"
@@ -120,19 +122,21 @@ write_files:
enable_plugin devstack-plugin-container https://opendev.org/openstack/devstack-plugin-container enable_plugin devstack-plugin-container https://opendev.org/openstack/devstack-plugin-container
enable_plugin kuryr-kubernetes https://opendev.org/openstack/kuryr enable_plugin kuryr-kubernetes https://opendev.org/openstack/kuryr
runcmd: runcmd:
- [apt, purge, '-y', python3-pyasn1-modules] - systemctl stop systemd-resolved.service
- [apt, purge, '-y', python3-simplejson] - systemctl disable systemd-resolved.service
- [su, -, ubuntu, -c, "git clone https://github.com/gryf/vmstrap"] - echo "nameserver 1.1.1.1" > /etc/resolv.conf
- [su, -, ubuntu, -c, "vmstrap/bootstrap.sh -c"] - apt purge -y snapd
- [rm, -fr, /home/ubuntu/vmstrap] - apt purge -y python3-pyasn1-modules
- [su, -, ubuntu, -c, "echo 'export HOST_IP=192.168.10.11' >> .bashrc"] - apt purge -y python3-simplejson
- [su, -, ubuntu, -c, "cp /tmp/local.conf /home/ubuntu/devstack/"] - su - ubuntu -c "git clone https://opendev.org/openstack/devstack"
- [systemctl, restart, systemd-networkd] - su - ubuntu -c "cp /tmp/local.conf /home/ubuntu/devstack/"
- su - ubuntu -c "echo 'export HOST_IP=192.168.10.11' >> .bashrc"
- systemctl restart systemd-networkd
boxpy_data: boxpy_data:
key: vm key: vm
cpus: 4 cpus: 4
memory: 16GB memory: 16GB
disk_size: 50GB disk_size: 50GB
version: 20.04
advanced: advanced:
nic2: intnet nic2: intnet
version: 20.04

View File

@@ -29,10 +29,10 @@ packages:
- vim-nox - vim-nox
- xinit - xinit
runcmd: runcmd:
- [su, -, ubuntu, -c, "git clone https://github.com/gryf/wmaker -b experimental"] - su - ubuntu -c "git clone https://github.com/gryf/wmaker -b experimental"
- [su, -, ubuntu, -c, "git clone https://github.com/gryf/vmstrap"] - su - ubuntu -c "git clone https://github.com/gryf/vmstrap"
- [su, -, ubuntu, -c, "vmstrap/bootstrap.sh -c"] - su - ubuntu -c "vmstrap/bootstrap.sh -c"
- [rm, -fr, /home/ubuntu/vmstrap] - rm -fr /home/ubuntu/vmstrap
boxpy_data: boxpy_data:
key: vm key: vm
cpus: 4 cpus: 4

66
pyproject.toml Normal file
View File

@@ -0,0 +1,66 @@
[build-system]
requires = ["setuptools >= 77.0"]
build-backend = "setuptools.build_meta"
[project]
name = "boxpy"
dynamic = ["version"]
authors = [
{name = "Roman Dobosz", email = "gryf73@gmail.com"}
]
license = "GPL-3.0-or-later"
description = "Run Linux cloud image on top of VirtualBox using commandline tool"
readme = "README.rst"
requires-python = ">=3.8"
keywords = ["vboxmanage", "virtualbox", "vm", "virtual machine", "automation"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: End Users/Desktop",
"Topic :: Terminals",
"Topic :: Utilities",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only"
]
dependencies = [
"pyyaml>=5.4.1",
"requests>=2.26.0"
]
[project.urls]
Homepage = "https://github.com/gryf/boxpy"
[project.scripts]
boxpy = "box:main"
[tool.setuptools]
py-modules = ["box"]
[tool.setuptools.dynamic]
version = {attr = "box.__version__"}
[tool.ruff]
line-length = 79
indent-width = 4
[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"E", # pycodestyle
"F", # pyflakes
"FA", # flake8-future-annotations
"G", # flake8-logging-format
"N", # pep8-naming
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"RET", # flake8-return
"SIM", # flake8-simplify
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
]

View File

@@ -1,2 +0,0 @@
pyyaml>=5.4.1
requests>=2.26.0