mirror of
https://github.com/gryf/boxpy.git
synced 2025-12-20 22:27:58 +01:00
Added ability to select distro. Yet, we still have only one ;)
This commit is contained in:
125
box.py
125
box.py
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user