1
0
mirror of https://github.com/gryf/boxpy.git synced 2025-12-20 14:17:57 +01:00

Added ability to select distro. Yet, we still have only one ;)

This commit is contained in:
2021-06-04 18:37:03 +02:00
parent d78ed6db8b
commit de61390d5e

125
box.py
View File

@@ -21,7 +21,6 @@ META_DATA_TPL = string.Template('''\
instance-id: $instance_id instance-id: $instance_id
local-hostname: $vmhostname local-hostname: $vmhostname
''') ''')
UBUNTU_VERSION = '20.04'
USER_DATA = '''\ USER_DATA = '''\
#cloud-config #cloud-config
users: users:
@@ -133,8 +132,8 @@ _boxpy() {
fi fi
;; ;;
create|rebuild) create|rebuild)
items=(--cpus --disk-size --key --memory --hostname --port items=(--cpus --disk-size --distro --key --memory --hostname
--config --version) --port --config --version)
if [[ ${prev} == ${cmd} ]]; then if [[ ${prev} == ${cmd} ]]; then
if [[ ${cmd} = "rebuild" ]]; then if [[ ${cmd} = "rebuild" ]]; then
_vms_comp vms _vms_comp vms
@@ -153,6 +152,9 @@ _boxpy() {
--key) --key)
_ssh_identityfile _ssh_identityfile
;; ;;
--distro)
COMPREPLY=( $(compgen -W "ubuntu" -- ${cur}) )
;;
--*) --*)
COMPREPLY=( ) COMPREPLY=( )
;; ;;
@@ -176,6 +178,7 @@ _boxpy() {
complete -o default -F _boxpy boxpy complete -o default -F _boxpy boxpy
'''} '''}
def convert_to_mega(size): def convert_to_mega(size):
""" """
Vritualbox uses MB as a common denominator for amount of memory or disk Vritualbox uses MB as a common denominator for amount of memory or disk
@@ -224,11 +227,12 @@ class BoxSysCommandError(BoxError):
class Config: class Config:
ATTRS = ('cpus', 'config', 'disk_size', 'hostname', 'key', 'memory', ATTRS = ('cpus', 'config', 'disk_size', 'distro', 'hostname', 'key',
'name', 'port', 'version') 'memory', 'name', 'port', 'version')
def __init__(self, args, vbox=None): def __init__(self, args, vbox=None):
self.advanced = None self.advanced = None
self.distro = args.distro
self.cpus = None self.cpus = None
self.disk_size = None self.disk_size = None
self.hostname = None self.hostname = None
@@ -258,7 +262,7 @@ class Config:
# combine it with the defaults, set attributes by boxpy_data # combine it with the defaults, set attributes by boxpy_data
# definition, if found # definition, if found
self._combine_cc(vbox) self._combine_cc()
# than, override all of the attributes with provided arguments from # than, override all of the attributes with provided arguments from
# the command line # the command line
@@ -269,6 +273,9 @@ class Config:
continue continue
setattr(self, attr, str(val)) setattr(self, attr, str(val))
if not self.version:
self.version = DISTROS[self.distro]['default_version']
# finally, figure out host name # finally, figure out host name
self.hostname = self.hostname or self._normalize_name() self.hostname = self.hostname or self._normalize_name()
self._set_ssh_key_path() self._set_ssh_key_path()
@@ -338,7 +345,7 @@ class Config:
name = name.decode('utf-8') name = name.decode('utf-8')
return ''.join(x for x in name if x.isalnum() or x == '-') return ''.join(x for x in name if x.isalnum() or x == '-')
def _combine_cc(self, vbox): def _combine_cc(self):
conf = yaml.safe_load(USER_DATA) conf = yaml.safe_load(USER_DATA)
# read user custom cloud config (if present) and update config dict # read user custom cloud config (if present) and update config dict
@@ -574,56 +581,94 @@ class VBoxManage:
class Image: class Image:
def __init__(self, vbox, version, arch='amd64'): URL = ""
self.version = version IMG = ""
self.arch = arch
def __init__(self, vbox, version, arch, release):
self.vbox = vbox self.vbox = vbox
self._tmp = tempfile.mkdtemp(prefix='boxpy_') self._tmp = tempfile.mkdtemp(prefix='boxpy_')
self._img = f"ubuntu-{self.version}-server-cloudimg-{self.arch}.img" self._img_fname = None
def convert_to_vdi(self, disk_img, size): def convert_to_vdi(self, disk_img, size):
self._download_image() self._download_image()
self._convert_to_raw() self._convert_to_raw()
raw_path = os.path.join(self._tmp, self._img + ".raw") raw_path = os.path.join(self._tmp, self._img_fname + ".raw")
vdi_path = os.path.join(self._tmp, disk_img) vdi_path = os.path.join(self._tmp, disk_img)
self.vbox.convertfromraw(raw_path, vdi_path) self.vbox.convertfromraw(raw_path, vdi_path)
return self.vbox.move_and_resize_image(vdi_path, disk_img, size) return self.vbox.move_and_resize_image(vdi_path, disk_img, size)
def cleanup(self):
subprocess.call(['rm', '-fr', self._tmp])
def _convert_to_raw(self):
img_path = os.path.join(CACHE_DIR, self._img_fname)
raw_path = os.path.join(self._tmp, self._img_fname + ".raw")
if subprocess.call(['qemu-img', 'convert', '-O', 'raw',
img_path, raw_path]) != 0:
raise BoxConvertionError(f'Cannot convert image {self._img_fname} '
'to RAW.')
def _download_image(self):
raise NotImplementedError()
class Ubuntu(Image):
URL = "https://cloud-images.ubuntu.com/releases/%s/release/%s"
IMG = "ubuntu-%s-server-cloudimg-%s.img"
def __init__(self, vbox, version, arch, release):
super().__init__(vbox, version, arch, release)
self._img_fname = self.IMG % (version, arch)
self._img_url = self.URL % (version, self._img_fname)
self._checksum_file = 'SHA256SUMS'
self._checksum_url = self.URL % (version, self._checksum_file)
def _checksum(self): def _checksum(self):
""" """
Get and check checkusm for downloaded image. Return True if the Get and check checkusm for downloaded image. Return True if the
checksum is correct, False otherwise. checksum is correct, False otherwise.
""" """
if not os.path.exists(os.path.join(CACHE_DIR, self._img)): if not os.path.exists(os.path.join(CACHE_DIR, self._img_fname)):
return False return False
expected_sum = None expected_sum = None
fname = 'SHA256SUMS' fname = os.path.join(self._tmp, self._checksum_file)
url = "https://cloud-images.ubuntu.com/releases/"
url += f"{self.version}/release/{fname}"
# TODO: make the verbosity switch be dependent from verbosity of the # TODO: make the verbosity switch be dependent from verbosity of the
# script. # script.
subprocess.call(['wget', url, '-q', '-O', subprocess.call(['wget', self._checksum_url, '-q', '-O', fname])
os.path.join(self._tmp, fname)])
with open(os.path.join(self._tmp, fname)) as fobj: with open(fname) as fobj:
for line in fobj.readlines(): for line in fobj.readlines():
if self._img in line: if self._img_fname in line:
expected_sum = line.split(' ')[0] expected_sum = line.split(' ')[0]
break break
if not expected_sum: if not expected_sum:
raise BoxError('Cannot find provided cloud image') raise BoxError('Cannot find provided cloud image')
if os.path.exists(os.path.join(CACHE_DIR, self._img)): if os.path.exists(os.path.join(CACHE_DIR, self._img_fname)):
cmd = 'sha256sum ' + os.path.join(CACHE_DIR, self._img) cmd = 'sha256sum ' + os.path.join(CACHE_DIR, self._img_fname)
calulated_sum = subprocess.getoutput(cmd).split(' ')[0] calulated_sum = subprocess.getoutput(cmd).split(' ')[0]
return calulated_sum == expected_sum return calulated_sum == expected_sum
return False return False
def cleanup(self): def _download_image(self):
subprocess.call(['rm', '-fr', self._tmp]) if self._checksum():
print(f'Image already downloaded: {self._img_fname}')
return
fname = os.path.join(CACHE_DIR, self._img_fname)
print(f'Downloading image {self._img_fname}')
subprocess.call(['wget', '-q', self._img_url, '-O', fname])
if not self._checksum():
# TODO: make some retry mechanism?
raise BoxSysCommandError('Checksum for downloaded image differ '
'from expected.')
else:
print(f'Downloaded image {self._img_fname}')
def _convert_to_raw(self): def _convert_to_raw(self):
img_path = os.path.join(CACHE_DIR, self._img) img_path = os.path.join(CACHE_DIR, self._img)
@@ -654,7 +699,18 @@ class Image:
DISTROS = {'ubuntu': {'username': 'ubuntu', DISTROS = {'ubuntu': {'username': 'ubuntu',
'realname': 'ubuntu'}} 'realname': 'ubuntu',
'img_class': Ubuntu,
'amd64': 'amd64',
'default_version': '20.04'}}
def get_image(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'],
release)
class IsoImage: class IsoImage:
@@ -712,7 +768,7 @@ def vmcreate(args, conf=None):
if conf.user_data: if conf.user_data:
vbox.setextradata('user_data', conf.user_data) vbox.setextradata('user_data', conf.user_data)
image = Image(vbox, conf.version) image = get_image(vbox, conf.version, image=args.distro)
path_to_disk = image.convert_to_vdi(conf.name + '.vdi', conf.disk_size) path_to_disk = image.convert_to_vdi(conf.name + '.vdi', conf.disk_size)
iso = IsoImage(conf) iso = IsoImage(conf)
@@ -760,7 +816,8 @@ def vmcreate(args, conf=None):
_cleanup(vbox, iso, image, path_to_iso) _cleanup(vbox, iso, image, path_to_iso)
vbox.poweron() vbox.poweron()
print('You can access your VM by issuing:') print('You can access your VM by issuing:')
print(f'ssh -p {conf.port} -i {conf.ssh_key_path[:-4]} ubuntu@localhost') print(f'ssh -p {conf.port} -i {conf.ssh_key_path[:-4]} '
f'{DISTROS[args.distro]["username"]}@localhost')
return 0 return 0
@@ -813,9 +870,7 @@ def main():
create.add_argument('name', help='name of the VM') create.add_argument('name', help='name of the VM')
create.add_argument('-c', '--config', create.add_argument('-c', '--config',
help="Alternative user-data template filepath") help="Alternative user-data template filepath")
create.add_argument('-d', '--disk-size', help="disk size to be expanded " create.add_argument('-d', '--distro', help="Image name. 'ubuntu' is "
"to. By default to 10GB")
create.add_argument('-i', '--distro', help="Image name. 'ubuntu' is "
"default") "default")
create.add_argument('-k', '--key', help="SSH key to be add to the config " create.add_argument('-k', '--key', help="SSH key to be add to the config "
"drive. Default ~/.ssh/id_rsa") "drive. Default ~/.ssh/id_rsa")
@@ -825,10 +880,12 @@ def main():
help="VM hostname. Default same as vm name") help="VM hostname. Default same as vm name")
create.add_argument('-p', '--port', help="set ssh port for VM, default " create.add_argument('-p', '--port', help="set ssh port for VM, default "
"2222") "2222")
create.add_argument('-s', '--disk-size', help="disk size to be expanded "
"to. By default to 10GB")
create.add_argument('-u', '--cpus', type=int, help="amount of CPUs to be " create.add_argument('-u', '--cpus', type=int, help="amount of CPUs to be "
"configured. Default 1.") "configured. Default 1.")
create.add_argument('-v', '--version', help=f"Ubuntu server version. " create.add_argument('-v', '--version', help=f"Ubuntu server version. "
f"Default {UBUNTU_VERSION}") f"Default {DISTROS['ubuntu']['default_version']}")
destroy = subparsers.add_parser('destroy', help='destroy VM') destroy = subparsers.add_parser('destroy', help='destroy VM')
destroy.add_argument('name', help='name or UUID of the VM') destroy.add_argument('name', help='name or UUID of the VM')
@@ -848,15 +905,15 @@ def main():
rebuild.add_argument('name', help='name or UUID of the VM') rebuild.add_argument('name', help='name or UUID of the VM')
rebuild.add_argument('-c', '--config', rebuild.add_argument('-c', '--config',
help="Alternative user-data template filepath") help="Alternative user-data template filepath")
rebuild.add_argument('-d', '--disk-size', rebuild.add_argument('-d', '--distro', help="Image name.")
help='disk size to be expanded to')
rebuild.add_argument('-i', '--distro', help="Image name.")
rebuild.add_argument('-k', '--key', rebuild.add_argument('-k', '--key',
help='SSH key to be add to the config drive') help='SSH key to be add to the config drive')
rebuild.add_argument('-m', '--memory', help='amount of memory in ' rebuild.add_argument('-m', '--memory', help='amount of memory in '
'Megabytes') 'Megabytes')
rebuild.add_argument('-n', '--hostname', help="set VM hostname") rebuild.add_argument('-n', '--hostname', help="set VM hostname")
rebuild.add_argument('-p', '--port', help="set ssh port for VM") rebuild.add_argument('-p', '--port', help="set ssh port for VM")
rebuild.add_argument('-s', '--disk-size',
help='disk size to be expanded to')
rebuild.add_argument('-u', '--cpus', type=int, rebuild.add_argument('-u', '--cpus', type=int,
help='amount of CPUs to be configured') help='amount of CPUs to be configured')
rebuild.add_argument('-v', '--version', help='Ubuntu server version') rebuild.add_argument('-v', '--version', help='Ubuntu server version')