Commit f84093be authored by Nikos Skalkotos's avatar Nikos Skalkotos

Merge branch 'master' into debian

parents 5b0eed3d de327119
2016-07-28, v0.10
* Detect Bitnami images
* Support changing the default "images" container when registering an
image with a Synnefo deployment
* Make the image metadata visible as environment variables to the
host-run scripts
* Detect the image's bootloader and reinstall it after shrinking the
image if it is extlinux
* Support having multiple clouds and keep separate registration status
for each one of them
* Fix windows shrinking to work on non-English localized images
* Add Linux sysprep for removing /etc/machine-id
* Fix bugs
2015-03-02, v0.9
* Add Linux syspreps for disabling the IPv6 privacy extensions and for
changing the boot timeout
......
......@@ -297,7 +297,7 @@ And install the Ubuntu system on this file:
.. warning::
During the installation, you will be asked about the partition scheme. Don't
During the installation, you will be asked about the partition scheme. Don't
use LVM partitions. They are not supported by snf-image-creator.
You will be able to boot your installed OS and make any changes you want
......@@ -336,7 +336,7 @@ It has been successfully tested with:
* Raw disk images
* VMDK (VMware)
* VHD (Microsoft Hyper-V)
* VHD (Microsoft Hyper-V) [#f1]_
* VDI (VirtualBox)
* qcow2 (QEMU)
......
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2014 GRNET S.A.
# Copyright (C) 2011-2016 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -18,7 +18,6 @@
"""Package for creating images to be used with Synnefo open source cloud
software.
"""
from image_creator.version import __version__
from image_creator.version import __version__ # noqa
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Module hosting code for determining the installed boot loader on an image.
The bootloader signatures are taken from the "Boot Info Script" project:
https://github.com/arvidjaar/bootinfoscript
"""
# Master Boot Record Bootloaders
MBR_LDR = [
# GRUB Legacy
"grub1", # 0
# GRUB 2
"grub2",
# LILO (LInux LOader)
"lilo",
# SYSLINUX
"syslinux",
# Windows
"Windows",
# FreeBSD's boot0
"freebsd", # 5
# NetBSD's Stage 0/SUSE generic MBR
"netbsd",
# OpenBSD's MBR
"openbsd",
# ReactOS
"reactos",
# FreeDOS
"freedos",
# MS-DOS
"msdos", # 10
# Solaris
"solaris",
# ThinkPad MBR
"thinkpad",
# HP/Gateway
"hp",
# Plop Boot Manager
"plop",
# TrueCrypt boot loader
"truecrypt", # 15
# Paragon Partition Manager
"paragon",
# Testdisk MBR
"testdist",
# GAG Graphical Boot Manager
"gag",
# BootIt Boot Manager
"bootit",
# DiskCryptor
"diskcryptor", # 20
# xOSL (Extended Operating System Loader)
"xosl",
# Fbinst
"fbinst",
# Grub4Dos
"grub4dos",
# WEE boot manager
"wee",
# mbldr (Master Boot LoaDeR)
"mbldr", # 25
# Libparted generic boot code
"libparted",
# ISOhybrid
"isohybrid",
# Acer PQservice MBR
"pqservice",
]
# 2-bytes MBR signature
MBR_SIG2 = {
"\x3b\x48": 0,
"\xeb\x4c": 1,
"\xeb\x63": 1,
"\xeb\x04": 11,
"\x0e\xbe": 12,
"\x33\xed": 27,
"\x33\xff": 13,
"\xb8\x00": 14,
"\xea\x1e": 15,
"\xeb\x04": 11,
"\xeb\x31": 16,
"\xfa\x33": 10,
"\xfa\xeb": 2,
"\xfa\xfc": 8,
"\xfc\x31": 17,
"\xfc\x33": 18,
"\xfc\xeb": 19,
}
# 3-bytes MBR signature
MBR_SIG3 = {
"\x33\xc0\x8e": 4,
"\x33\xc0\x90": 20,
"\x33\xc0\xfa": 3,
"\xea\x05\x00": 7,
"\xea\x05\x01": 21,
"\xeb\x5e\x00": 22,
"\xeb\x5e\x80": 23,
"\xeb\x5e\x90": 24,
"\xfa\x31\xc0": 3,
"\xfa\x31\xc9": 25,
"\xfa\x31\xed": 27,
}
# 4-bytes MBR signature
MBR_SIG4 = {
"\xfa\xb8\x00\x00": 9,
"\xfa\xb8\x00\x10": 26,
}
# 8-bytes MBR signature
MBR_SIG8 = {
"\x31\xc0\x8e\xd0\xbc\x00\x7c\x8e": 6,
"\x31\xc0\x8e\xd0\xbc\x00\x7c\xfb": 28,
}
def mbr_bootinfo(mbr):
"""Inspect a Master Boot Record and return the installed bootloader"""
if mbr[:2] == '\x00\x00':
return "none"
try:
ret = MBR_SIG2[mbr[:2]]
except KeyError:
try:
ret = MBR_SIG3[mbr[:3]]
except KeyError:
try:
ret = MBR_SIG4[mbr[:4]]
except KeyError:
try:
ret = MBR_SIG8[mbr[:8]]
except KeyError:
return "unknown"
return MBR_LDR[ret]
# Volume Boot Record Bootloaders
VBR_LDR = [
# GRUB Legacy
"grub1", # 0
# GRUB 2
"grub2",
# LILO (LInux LOader)
"lilo"
# SYSLINUX
"syslinux",
# Windows
"windows",
# FreeBSD's boot1 (First sector of FreeBSD slice)
"freebsd", # 5
# NetBSD's PBR
"netbsd",
# OpenBSD
"openbsd",
# ReactOS
"reactos",
# FreeDOS
"freedos",
# MS-DOS
"msdos", # 10
# HP Recovery
"hp",
# Dell Utility/Recovery
"dell",
# ISOhybrid
"isohybrid",
# Grub4Dos
"grub4dos",
# BootIt Boot Manager
"bootit", # 15
# Acronis Secure Zone
"acronis_sz",
]
# 2-byte VBR "no bootloader" signature
VBR_SIG2_NONE = (
"\x00\x00",
"\x55\xcd",
"\x69\x6e",
"\x6f\x6e",
)
# 2-byte VBR signature
VBR_SIG2 = {
"\x00\x20": 1,
"\x00\x69": 13,
"\x01\x0f": 11,
"\x02\x11": 12,
"\x04\x88": 1,
"\x06\x89": 3,
"\x07\x34": 10,
"\x07\x45": 4,
"\x08\x9e": 10,
"\x08\xcd": 4,
"\x08\xc7": 7,
"\x0b\x60": 12,
"\x0b\xd0": 4,
"\x0e\x00": 12,
"\x0f\xb6": 13,
"\x2a\x00": 8,
"\x2d\x5e": 10,
"\x31\xc0": 3,
"\x3c\x9a": 6,
"\x40\x7c": 3,
"\x42\x16": 14,
"\x44\x45": 12,
"\x48\xb4": 1,
"\x52\x72": 0,
"\x56\x26": 14,
"\x63\x8b": 9,
"\x66\x16": 4,
"\x69\x74": 15,
"\x6a\x02": 5,
"\x6f\x65": 15,
"\x74\x05": 4,
"\x7c\x3c": 1,
"\x7c\xc6": 4,
"\x7e\x1e": 14,
"\x80\x53": 2,
"\x8a\x56": 16,
"\x83\xe1": 13,
"\x8e\xc0": 4,
"\x8e\xd0": 12,
"\xaa\x75": 0,
"\xb1\x06": 3,
"\xb6\x00": 12,
"\xb6\xc6": 13,
"\xb6\xd1": 4,
"\xe8\x79": 13,
"\xe9\xd8": 4,
"\xf6\xc1": 4,
"\xfa\x33": 4,
}
# 4-byte VBR signature
VBR_SIG4 = {
"\x55\xaa\x75\x0a": 14,
"\x55\xaa\x75\x06": 4,
"\x78\x15\xb1\x06": 3,
}
def vbr_bootinfo(vbr):
"""Inspect a Volume Boot Record and return the installed bootloader"""
if vbr[0x80:0x82] in VBR_SIG2_NONE:
return "none"
try:
ret = VBR_SIG2[vbr[0x80:0x82]]
except KeyError:
try:
ret = VBR_SIG4[vbr[0x80:0x84]]
except KeyError:
return "unknown"
return MBR_LDR[ret]
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2014 GRNET S.A.
# Copyright (C) 2011-2016 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -219,7 +219,7 @@ class BundleVolume(object):
if root.startswith("UUID=") or root.startswith("LABEL="):
root = findfs(root).stdout.strip()
if not re.match('/dev/[hsv]d[a-z][1-9]*$', root):
if not re.match('/dev/x?[hsv]d[a-z][1-9]*$', root):
raise FatalError("Don't know how to handle root device: %s" % root)
out.success(root)
......@@ -270,8 +270,13 @@ class BundleVolume(object):
"""
image_disk = parted.Disk(parted.Device(image))
is_extended = lambda p: p.type == parted.PARTITION_EXTENDED
is_logical = lambda p: p.type == parted.PARTITION_LOGICAL
def is_extended(partition):
"""Returns True if the partition is extended"""
return partition.type == parted.PARTITION_EXTENDED
def is_logical(partition):
"""Returns True if the partition is logical"""
return partition.type == parted.PARTITION_LOGICAL
partitions = get_partitions(self.disk)
......
......@@ -21,11 +21,13 @@ snf-image-creator program. The main function will create a dialog where the
user is asked if he wants to use the program in expert or wizard mode.
"""
from __future__ import unicode_literals
import dialog
import sys
import os
import signal
import optparse
import argparse
import types
import termios
import traceback
......@@ -91,7 +93,7 @@ def create_image(d, media, out, tmp, snapshot):
"you still want to continue with the image creation process." \
% image._unsupported
if not d.yesno(msg, width=WIDTH, defaultno=1, height=12):
if d.yesno(msg, width=WIDTH, defaultno=1, height=12) == d.OK:
main_menu(session)
d.infobox("Thank you for using snf-image-creator. Bye", width=53)
......@@ -111,10 +113,10 @@ def create_image(d, media, out, tmp, snapshot):
while True:
code = d.yesno(msg, width=WIDTH, height=12, yes_label="Wizard",
no_label="Expert")
if code == d.DIALOG_OK:
if code == d.OK:
if start_wizard(session):
break
elif code == d.DIALOG_CANCEL:
elif code == d.CANCEL:
main_menu(session)
break
......@@ -128,30 +130,37 @@ def create_image(d, media, out, tmp, snapshot):
return 0
def _dialog_form(self, text, height=20, width=60, form_height=15, fields=[],
def _dialog_form(self, text, elements, height=20, width=60, form_height=15,
**kwargs):
"""Display a form box.
fields is in the form: [(label1, item1, item_length1), ...]
Each element of *elements* must itself be a sequence
:samp:`({label}, {yl}, {xl}, {item}, {yi}, {xi}, {field_length},
{input_length})` containing the various parameters concerning a
given field and the associated label.
*label* is a string that will be displayed at row *yl*, column
*xl*. *item* is a string giving the initial value for the field,
which will be displayed at row *yi*, column *xi* (row and column
numbers starting from 1).
*field_length* and *input_length* are integers that respectively
specify the number of characters used for displaying the field
and the maximum number of characters that can be entered for
this field. These two integers also determine whether the
contents of the field can be modified, as follows:
- if *field_length* is zero, the field cannot be altered and
its contents determines the displayed length;
- if *field_length* is negative, the field cannot be altered
and the opposite of *field_length* gives the displayed
length;
- if *input_length* is zero, it is set to *field_length*.
"""
cmd = ["--form", text, str(height), str(width), str(form_height)]
label_len = 0
for field in fields:
if len(field[0]) > label_len:
label_len = len(field[0])
for element in elements:
label, yl, xl, item, yi, xi, field_len, input_len = element[:8]
input_len = width - label_len - 1
line = 1
for field in fields:
label = field[0]
item = field[1]
item_len = field[2]
cmd.extend((label, str(line), str(1), item, str(line),
str(label_len + 1), str(input_len), str(item_len)))
line += 1
cmd.extend((label, unicode(yl), unicode(xl), item, unicode(yi),
unicode(xi), unicode(field_len), unicode(input_len)))
code, output = self._perform(*(cmd,), **kwargs)
......@@ -175,26 +184,47 @@ def dialog_main(media, **kwargs):
d = dialog.Dialog(dialog="dialog")
# Add extra button in dialog library
dialog._common_args_syntax["extra_button"] = \
lambda enable: dialog._simple_option("--extra-button", enable)
dialog._common_args_syntax["extra_label"] = \
lambda string: ("--extra-label", string)
# Allow yes-no label overwriting
dialog._common_args_syntax["yes_label"] = \
lambda string: ("--yes-label", string)
dialog._common_args_syntax["no_label"] = \
lambda string: ("--no-label", string)
# Add exit label overwriting
dialog._common_args_syntax["exit_label"] = \
lambda string: ("--exit-label", string)
# Add extra button in dialog library if missing
if 'extra_button' not in dialog._common_args_syntax:
dialog._common_args_syntax["extra_button"] = \
lambda enable: dialog._simple_option("--extra-button", enable)
if 'extra_label' not in dialog._common_args_syntax:
dialog._common_args_syntax["extra_label"] = \
lambda string: ("--extra-label", string)
# Allow yes-no label overwriting if missing
if 'yes_label' not in dialog._common_args_syntax:
dialog._common_args_syntax["yes_label"] = \
lambda string: ("--yes-label", string)
if 'no_label' not in dialog._common_args_syntax:
dialog._common_args_syntax["no_label"] = \
lambda string: ("--no-label", string)
# Add exit label overwriting if missing
if 'exit_label' not in dialog._common_args_syntax:
dialog._common_args_syntax["exit_label"] = \
lambda string: ("--exit-label", string)
# Monkey-patch pythondialog to include support for form dialog boxes
if not hasattr(dialog, 'form'):
if not hasattr(d, 'form'):
d.form = types.MethodType(_dialog_form, d)
# Add sort dialog constants if missing
if not hasattr(d, 'OK'):
d.OK = d.DIALOG_OK
if not hasattr(d, 'CANCEL'):
d.CANCEL = d.DIALOG_CANCEL
if not hasattr(d, 'ESC'):
d.ESC = d.DIALOG_ESC
if not hasattr(d, 'EXTRA'):
d.EXTRA = d.DIALOG_EXTRA
if not hasattr(d, 'HELP'):
d.HELP = d.DIALOG_HELP
d.setBackgroundTitle('snf-image-creator')
# Pick input media
......@@ -214,7 +244,8 @@ def dialog_main(media, **kwargs):
logs = []
try:
stream = logfile if logfile else tmplog
logs.append(SimpleOutput(colored=False, stderr=stream, stdout=stream))
logs.append(SimpleOutput(colored=False, stderr=stream, stdout=stream,
timestamp=True))
if syslog:
logs.append(SyslogOutput())
......@@ -249,27 +280,22 @@ def main():
sys.stderr.write("Error: You must run %s as root\n" % PROGNAME)
sys.exit(2)
usage = "Usage: %prog [options] [<input_media>]"
parser = optparse.OptionParser(version=version, usage=usage)
parser.add_option("-l", "--logfile", type="string", dest="logfile",
default=None, help="log all messages to FILE",
metavar="FILE")
parser.add_option("--no-snapshot", dest="snapshot", default=True,
help="don't snapshot the input media. (THIS IS "
"DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)",
action="store_false")
parser.add_option("--syslog", dest="syslog", default=False,
help="log to syslog", action="store_true")
parser.add_option("--tmpdir", type="string", dest="tmp", default=None,
help="create large temporary image files under DIR",
metavar="DIR")
opts, args = parser.parse_args(sys.argv[1:])
if len(args) > 1:
parser.error("Wrong number of arguments")
media = args[0] if len(args) == 1 else None
description = "Dialog-based tool for creating OS images"
parser = argparse.ArgumentParser(version=version, description=description)
parser.add_argument("-l", "--logfile", dest="logfile", metavar="FILE",
default=None, help="log all messages to FILE")
parser.add_argument("--no-snapshot", dest="snapshot", default=True,
help="don't snapshot the input media. (THIS IS "
"DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)",
action="store_false")
parser.add_argument("--syslog", dest="syslog", default=False,
help="log to syslog", action="store_true")
parser.add_argument("--tmpdir", dest="tmp", default=None, metavar="DIR",
help="create large temporary image files under DIR")
parser.add_argument("source", metavar="SOURCE", default=None, nargs='?',
help="Image file, block device or /")
opts = parser.parse_args()
if opts.tmp is not None and not os.path.isdir(opts.tmp):
parser.error("Directory: `%s' specified with --tmpdir is not valid"
......@@ -285,7 +311,7 @@ def main():
# Save the terminal attributes
attr = termios.tcgetattr(sys.stdin.fileno())
try:
ret = dialog_main(media, logfile=logfile, tmpdir=opts.tmp,
ret = dialog_main(opts.source, logfile=logfile, tmpdir=opts.tmp,
snapshot=opts.snapshot, syslog=opts.syslog)
finally:
# Restore the terminal attributes. If an error occurs make sure
......
This diff is collapsed.
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2014 GRNET S.A.
# Copyright (C) 2011-2015 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -98,9 +98,9 @@ def select_file(d, **kwargs):
(code, fname) = d.fselect(default, 10, 60, extra_button=extra_button,
title=title, extra_label="Bundle Host")
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
if code in (d.CANCEL, d.ESC):
return None
elif code == d.DIALOG_EXTRA:
elif code == d.EXTRA:
return os.sep
return fname
......@@ -126,13 +126,13 @@ def update_background_title(session):
def confirm_exit(d, msg=''):
"""Ask the user to confirm when exiting the program"""
return not d.yesno("%s Do you want to exit?" % msg, width=SMALL_WIDTH)
return d.yesno("%s Do you want to exit?" % msg, width=SMALL_WIDTH) == d.OK
def confirm_reset(d):
"""Ask the user to confirm a reset action"""
return not d.yesno("Are you sure you want to reset everything?",
width=SMALL_WIDTH, defaultno=1)
return d.yesno("Are you sure you want to reset everything?",
width=SMALL_WIDTH, defaultno=1) == d.OK
class Reset(Exception):
......@@ -148,8 +148,8 @@ def extract_metadata_string(session):
for key in session['task_metadata']:
metadata[key] = 'yes'
return unicode(json.dumps({'properties': metadata,
'disk-format': 'diskdump'}, ensure_ascii=False))
return json.dumps({'properties': metadata, 'disk-format': 'diskdump'},
ensure_ascii=False)
def extract_image(session):
......@@ -198,7 +198,7 @@ def extract_image(session):
if len(overwrite) > 0:
if d.yesno("The following file(s) exist:\n"
"%s\nDo you want to overwrite them?" %
"\n".join(overwrite), width=SMALL_WIDTH):
"\n".join(overwrite), width=SMALL_WIDTH) != d.OK:
continue
gauge = GaugeOutput(d, "Image Extraction", "Extracting image...")
......@@ -278,10 +278,11 @@ def add_cloud(session):
("Authentication URL: ", url, 200),