Revision 0969420b
b/image_creator/bundle_volume.py | ||
---|---|---|
304 | 304 |
mpoints = [] |
305 | 305 |
for entry in self._read_fstable('/proc/mounts'): |
306 | 306 |
if entry.mpoint.startswith(os.path.abspath(target)): |
307 |
mpoints.append(entry.mpoint)
|
|
307 |
mpoints.append(entry.mpoint) |
|
308 | 308 |
|
309 | 309 |
mpoints.sort() |
310 | 310 |
for mpoint in reversed(mpoints): |
... | ... | |
336 | 336 |
continue |
337 | 337 |
|
338 | 338 |
dirname = mpoint |
339 |
basename = '' |
|
340 | 339 |
found_ancestor = False |
341 | 340 |
while dirname != '/': |
342 |
(dirname, basename) = os.path.split(dirname)
|
|
341 |
(dirname, _) = os.path.split(dirname)
|
|
343 | 342 |
if dirname in excluded: |
344 | 343 |
found_ancestor = True |
345 | 344 |
break |
b/image_creator/dialog_menu.py | ||
---|---|---|
68 | 68 |
("File injection", ["EnforcePersonality"], ["windows", "linux"]) |
69 | 69 |
] |
70 | 70 |
|
71 |
SYSPREP_PARAM_MAXLEN = 20 |
|
71 | 72 |
|
72 | 73 |
class MetadataMonitor(object): |
73 | 74 |
"""Monitors image metadata chages""" |
... | ... | |
395 | 396 |
if len(Kamaki.get_clouds()): |
396 | 397 |
default_item = "Cloud" |
397 | 398 |
else: |
398 |
default_time = "Add/Edit"
|
|
399 |
default_item = "Add/Edit"
|
|
399 | 400 |
else: |
400 |
default_time = "Delete"
|
|
401 |
default_item = "Delete"
|
|
401 | 402 |
elif choice == "Cloud": |
402 | 403 |
default_item = "Cloud" |
403 | 404 |
clouds = Kamaki.get_clouds() |
... | ... | |
616 | 617 |
|
617 | 618 |
|
618 | 619 |
def sysprep_params(session): |
619 |
|
|
620 |
"""Collect the needed sysprep parameters""" |
|
620 | 621 |
d = session['dialog'] |
621 | 622 |
image = session['image'] |
622 | 623 |
|
... | ... | |
631 | 632 |
for name in names: |
632 | 633 |
param = needed[name] |
633 | 634 |
default = available[name] if name in available else "" |
634 |
fields.append(("%s: " % param.description, default, param.maxlen)) |
|
635 |
fields.append(("%s: " % param.description, default, |
|
636 |
SYSPREP_PARAM_MAXLEN)) |
|
635 | 637 |
|
636 | 638 |
txt = "Please provide the following system preparation parameters:" |
637 | 639 |
code, output = d.form(txt, height=13, width=WIDTH, form_height=len(fields), |
... | ... | |
640 | 642 |
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): |
641 | 643 |
return False |
642 | 644 |
|
643 |
sysprep_params = {} |
|
644 | 645 |
for i in range(len(fields)): |
645 | 646 |
param = needed[names[i]] |
646 | 647 |
if param.validator(output[i]): |
647 | 648 |
image.os.sysprep_params[names[i]] = output[i] |
648 | 649 |
else: |
649 | 650 |
d.msgbox("The value you provided for parameter: %s is not valid" % |
650 |
name, width=SMALL_WIDTH) |
|
651 |
names[i], width=SMALL_WIDTH)
|
|
651 | 652 |
return False |
652 | 653 |
|
653 | 654 |
return True |
b/image_creator/dialog_wizard.py | ||
---|---|---|
49 | 49 |
|
50 | 50 |
PAGE_WIDTH = 70 |
51 | 51 |
PAGE_HEIGHT = 10 |
52 |
SYSPREP_PARAM_MAXLEN = 20 |
|
52 | 53 |
|
53 | 54 |
|
54 | 55 |
class WizardExit(Exception): |
... | ... | |
221 | 222 |
|
222 | 223 |
field_lenght = len(self.fields()) |
223 | 224 |
form_height = field_lenght if field_lenght < PAGE_HEIGHT - 4 \ |
224 |
else PAGET_HEIGHT - 4
|
|
225 |
else PAGE_HEIGHT - 4 |
|
225 | 226 |
|
226 | 227 |
(code, output) = d.form( |
227 | 228 |
self.text, width=PAGE_WIDTH, height=PAGE_HEIGHT, |
... | ... | |
343 | 344 |
for name in param_names: |
344 | 345 |
text = needed[name].description |
345 | 346 |
default = available[name] if name in available else "" |
346 |
fields.append(("%s: " % text, default, needed[name].maxlen))
|
|
347 |
fields.append(("%s: " % text, default, SYSPREP_PARAM_MAXLEN))
|
|
347 | 348 |
return fields |
348 | 349 |
|
349 | 350 |
def sysprep_params_validate(answer): |
b/image_creator/os_type/__init__.py | ||
---|---|---|
64 | 64 |
def add_prefix(target): |
65 | 65 |
def wrapper(self, *args): |
66 | 66 |
prefix = args[0] |
67 |
return map(lambda x: prefix + x, target(self, *args))
|
|
67 |
return [prefix + path for path in target(self, *args)]
|
|
68 | 68 |
return wrapper |
69 | 69 |
|
70 | 70 |
|
71 | 71 |
def sysprep(message, enabled=True, **kwargs): |
72 | 72 |
"""Decorator for system preparation tasks""" |
73 |
def wrapper(func):
|
|
74 |
func.sysprep = True
|
|
75 |
func.enabled = enabled
|
|
76 |
func.executed = False
|
|
73 |
def wrapper(method):
|
|
74 |
method.sysprep = True
|
|
75 |
method.enabled = enabled
|
|
76 |
method.executed = False
|
|
77 | 77 |
|
78 | 78 |
for key, val in kwargs.items(): |
79 |
setattr(func, key, val)
|
|
79 |
setattr(method, key, val)
|
|
80 | 80 |
|
81 |
@wraps(func)
|
|
81 |
@wraps(method)
|
|
82 | 82 |
def inner(self, print_message=True): |
83 | 83 |
if print_message: |
84 | 84 |
self.out.output(message) |
85 |
return func(self)
|
|
85 |
return method(self)
|
|
86 | 86 |
|
87 | 87 |
return inner |
88 |
|
|
89 | 88 |
return wrapper |
90 | 89 |
|
91 | 90 |
|
92 |
def add_sysprep_param(name, descr, maxlen, default=None, |
|
93 |
validator=lambda x: True): |
|
94 |
"""Decorator for init that adds the definition for a system preparation |
|
95 |
parameter |
|
91 |
def add_sysprep_param(name, default, description, validator=lambda x: True): |
|
92 |
"""Decorator for __init__ that adds the definition for a system preparation |
|
93 |
parameter in an instance of a os_type class |
|
96 | 94 |
""" |
97 |
def wrapper(func): |
|
98 |
|
|
99 |
@wraps(func) |
|
95 |
def wrapper(init): |
|
96 |
@wraps(init) |
|
100 | 97 |
def inner(self, *args, **kwargs): |
101 |
|
|
102 |
func(self, *args, **kwargs) |
|
103 |
|
|
104 |
if not hasattr(self, 'needed_sysprep_params'): |
|
105 |
self.needed_sysprep_params = {} |
|
106 |
getattr(self, 'needed_sysprep_params')[name] = \ |
|
107 |
self.SysprepParam(descr, maxlen, validator) |
|
98 |
init(self, *args, **kwargs) |
|
99 |
self.needed_sysprep_params[name] = \ |
|
100 |
self.SysprepParam(default, description, validator) |
|
108 | 101 |
return inner |
109 |
|
|
110 | 102 |
return wrapper |
111 | 103 |
|
112 | 104 |
|
113 | 105 |
def del_sysprep_param(name): |
114 |
"""Decorator for init that deletes a previously added sysprep parameter
|
|
115 |
definition . |
|
106 |
"""Decorator for __init__ that deletes a previously added sysprep parameter
|
|
107 |
definition from an instance of a os_type class.
|
|
116 | 108 |
""" |
117 | 109 |
def wrapper(func): |
118 |
|
|
119 | 110 |
@wraps(func) |
120 | 111 |
def inner(self, *args, **kwargs): |
121 |
del self.needed_sysprep_params[nam] |
|
112 |
del self.needed_sysprep_params[name]
|
|
122 | 113 |
func(self, *args, **kwargs) |
123 |
|
|
124 | 114 |
return inner |
125 |
|
|
126 | 115 |
return wrapper |
127 | 116 |
|
128 | 117 |
|
129 | 118 |
class OSBase(object): |
130 | 119 |
"""Basic operating system class""" |
131 | 120 |
|
132 |
SysprepParam = namedtuple('SysprepParam', 'description maxlen validator')
|
|
121 |
SysprepParam = namedtuple('SysprepParam', 'default description validator')
|
|
133 | 122 |
|
134 | 123 |
def __init__(self, image, **kargs): |
135 | 124 |
self.image = image |
... | ... | |
138 | 127 |
self.g = image.g |
139 | 128 |
self.out = image.out |
140 | 129 |
|
130 |
self.needed_sysprep_params = {} |
|
141 | 131 |
self.sysprep_params = \ |
142 | 132 |
kargs['sysprep_params'] if 'sysprep_params' in kargs else {} |
143 | 133 |
|
144 | 134 |
self.meta = {} |
135 |
self.mounted = False |
|
145 | 136 |
|
146 | 137 |
def collect_metadata(self): |
147 | 138 |
"""Collect metadata about the OS""" |
... | ... | |
202 | 193 |
"""Print enabled and disabled system preparation operations.""" |
203 | 194 |
|
204 | 195 |
syspreps = self.list_syspreps() |
205 |
enabled = filter(lambda x: x.enabled, syspreps)
|
|
206 |
disabled = filter(lambda x: not x.enabled, syspreps)
|
|
196 |
enabled = [sysprep for sysprep in syspreps if sysprep.enabled]
|
|
197 |
disabled = [sysprep for sysprep in syspreps if not sysprep.enabled]
|
|
207 | 198 |
|
208 | 199 |
wrapper = textwrap.TextWrapper() |
209 | 200 |
wrapper.subsequent_indent = '\t' |
... | ... | |
233 | 224 |
|
234 | 225 |
self.out.output("Needed system preparation parameters:") |
235 | 226 |
|
236 |
params = self.needed_sysprep_params() |
|
237 |
|
|
238 |
if len(params) == 0: |
|
227 |
if len(self.needed_sysprep_params) == 0: |
|
239 | 228 |
self.out.output("(none)") |
240 | 229 |
return |
241 | 230 |
|
242 |
for param in params:
|
|
231 |
for name, param in self.needed_sysprep_params.items():
|
|
243 | 232 |
self.out.output("\t%s (%s): %s" % |
244 |
(param.description, param.name,
|
|
245 |
self.sysprep_params[param.name] if param.name in
|
|
233 |
(param.description, name, |
|
234 |
self.sysprep_params[name] if name in
|
|
246 | 235 |
self.sysprep_params else "(none)")) |
247 | 236 |
|
248 | 237 |
def do_sysprep(self): |
... | ... | |
254 | 243 |
|
255 | 244 |
self.out.output('Preparing system for image creation:') |
256 | 245 |
|
257 |
tasks = self.list_syspreps() |
|
258 |
enabled = filter(lambda x: x.enabled, tasks) |
|
246 |
enabled = [task for task in self.list_syspreps() if task.enabled] |
|
259 | 247 |
|
260 | 248 |
size = len(enabled) |
261 | 249 |
cnt = 0 |
b/image_creator/os_type/freebsd.py | ||
---|---|---|
110 | 110 |
# libguestfs can't handle correct freebsd partitions on a GUID |
111 | 111 |
# Partition Table. We have to do the translation to linux device names |
112 | 112 |
# ourselves |
113 |
guid_device = re.compile('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$') |
|
113 |
guid_device = re.compile(r'^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$')
|
|
114 | 114 |
|
115 | 115 |
mopts = "ufstype=ufs2,%s" % ('ro' if readonly else 'rw') |
116 | 116 |
for mp, dev in self._mountpoints(): |
b/image_creator/os_type/linux.py | ||
---|---|---|
310 | 310 |
def _get_passworded_users(self): |
311 | 311 |
"""Returns a list of non-locked user accounts""" |
312 | 312 |
users = [] |
313 |
regexp = re.compile('(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}') |
|
313 |
regexp = re.compile(r'(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}')
|
|
314 | 314 |
|
315 | 315 |
for line in self.g.cat('/etc/shadow').splitlines(): |
316 | 316 |
match = regexp.match(line) |
b/image_creator/os_type/unix.py | ||
---|---|---|
35 | 35 |
|
36 | 36 |
"""This module hosts OS-specific code common to all Unix-like OSs.""" |
37 | 37 |
|
38 |
import re |
|
39 |
|
|
40 | 38 |
from image_creator.os_type import OSBase, sysprep |
41 | 39 |
|
42 | 40 |
|
... | ... | |
87 | 85 |
|
88 | 86 |
return True |
89 | 87 |
|
90 |
@sysprep('Removing files u)nder /var/cache')
|
|
88 |
@sysprep('Removing files under /var/cache') |
|
91 | 89 |
def cleanup_cache(self): |
92 | 90 |
"""Remove all regular files under /var/cache""" |
93 | 91 |
|
b/image_creator/os_type/windows.py | ||
---|---|---|
37 | 37 |
Windows OSs.""" |
38 | 38 |
|
39 | 39 |
from image_creator.os_type import OSBase, sysprep, add_sysprep_param |
40 |
from image_creator.util import FatalError, check_guestfs_version, get_command
|
|
40 |
from image_creator.util import FatalError, check_guestfs_version |
|
41 | 41 |
from image_creator.winexe import WinEXE, WinexeTimeout |
42 | 42 |
|
43 | 43 |
import hivex |
... | ... | |
108 | 108 |
class Windows(OSBase): |
109 | 109 |
"""OS class for Windows""" |
110 | 110 |
|
111 |
@add_sysprep_param('password', 'Image Administrator Password', 20)
|
|
111 |
@add_sysprep_param('password', None, 'Image Administrator Password')
|
|
112 | 112 |
def __init__(self, image, **kargs): |
113 | 113 |
super(Windows, self).__init__(image, **kargs) |
114 | 114 |
|
... | ... | |
127 | 127 |
assert self.system_drive |
128 | 128 |
|
129 | 129 |
self.product_name = self.g.inspect_get_product_name(self.root) |
130 |
self.syspreped = False |
|
130 | 131 |
|
131 | 132 |
@sysprep('Disabling IPv6 privacy extensions') |
132 | 133 |
def disable_ipv6_privacy_extensions(self): |
... | ... | |
202 | 203 |
return |
203 | 204 |
|
204 | 205 |
self._guest_exec( |
205 |
"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key) |
|
206 |
r"cscript \Windows\system32\slmgr.vbs /ipk %s" % setup_key)
|
|
206 | 207 |
|
207 | 208 |
@sysprep('Shrinking the last filesystem') |
208 | 209 |
def shrink(self): |
... | ... | |
273 | 274 |
raise FatalError("Image is already syspreped!") |
274 | 275 |
|
275 | 276 |
txt = "System preparation parameter: `%s' is needed but missing!" |
276 |
for param in self.needed_sysprep_params:
|
|
277 |
if param not in self.sysprep_params:
|
|
277 |
for name, param in self.needed_sysprep_params.items():
|
|
278 |
if name not in self.sysprep_params:
|
|
278 | 279 |
raise FatalError(txt % param) |
279 | 280 |
|
280 | 281 |
self.mount(readonly=False) |
... | ... | |
298 | 299 |
# guestfs_shutdown which is the prefered way to shutdown the backend |
299 | 300 |
# process was introduced in version 1.19.16 |
300 | 301 |
if check_guestfs_version(self.g, 1, 19, 16) >= 0: |
301 |
ret = self.g.shutdown()
|
|
302 |
self.g.shutdown() |
|
302 | 303 |
else: |
303 |
ret = self.g.kill_subprocess()
|
|
304 |
self.g.kill_subprocess() |
|
304 | 305 |
|
305 | 306 |
self.out.success('done') |
306 | 307 |
|
... | ... | |
329 | 330 |
self.out.output('Preparing system for image creation:') |
330 | 331 |
|
331 | 332 |
tasks = self.list_syspreps() |
332 |
enabled = filter(lambda x: x.enabled, tasks)
|
|
333 |
enabled = [task for task in tasks if task.enabled]
|
|
333 | 334 |
size = len(enabled) |
334 | 335 |
|
335 | 336 |
# Make sure shrink runs in the end, before ms sysprep |
336 |
enabled = filter(lambda x: self.sysprep_info(x).name != 'shrink',
|
|
337 |
enabled)
|
|
337 |
enabled = [task for task in enabled if
|
|
338 |
self.sysprep_info(task).name != 'shrink']
|
|
338 | 339 |
|
339 |
shrink_enabled = False |
|
340 | 340 |
if len(enabled) != size: |
341 | 341 |
enabled.append(self.shrink) |
342 |
shrink_enabled = True |
|
343 | 342 |
|
344 | 343 |
# Make sure the ms sysprep is the last task to run if it is enabled |
345 |
enabled = filter( |
|
346 |
lambda x: self.sysprep_info(x).name != 'microsoft-sysprep', |
|
347 |
enabled) |
|
344 |
enabled = [task for task in enabled if |
|
345 |
self.sysprep_info(task).name != 'microsoft-sysprep'] |
|
348 | 346 |
|
349 | 347 |
ms_sysprep_enabled = False |
350 | 348 |
if len(enabled) != size: |
... | ... | |
397 | 395 |
def _wait_vm_boot(self, vm, fname, msg): |
398 | 396 |
"""Wait until a message appears on a file or the vm process dies""" |
399 | 397 |
|
400 |
for i in range(BOOT_TIMEOUT):
|
|
398 |
for _ in range(BOOT_TIMEOUT):
|
|
401 | 399 |
time.sleep(1) |
402 | 400 |
with open(fname) as f: |
403 | 401 |
for line in f: |
... | ... | |
425 | 423 |
path = "%s/system32/config/%s" % (systemroot, regfile) |
426 | 424 |
try: |
427 | 425 |
path = self.g.case_sensitive_path(path) |
428 |
except RuntimeError as e: |
|
426 |
except RuntimeError as error:
|
|
429 | 427 |
raise FatalError("Unable to retrieve registry file: %s. Reason: %s" |
430 |
% (regfile, str(e))) |
|
428 |
% (regfile, str(error)))
|
|
431 | 429 |
return path |
432 | 430 |
|
433 | 431 |
def _enable_os_monitor(self): |
... | ... | |
634 | 632 |
|
635 | 633 |
def _get_users(self): |
636 | 634 |
"""Returns a list of users found in the images""" |
637 |
path = self._registry_file_path('SAM') |
|
638 | 635 |
samfd, sam = tempfile.mkstemp() |
639 | 636 |
try: |
640 | 637 |
os.close(samfd) |
641 |
self.g.download(path, sam)
|
|
638 |
self.g.download(self._registry_file_path('SAM'), sam)
|
|
642 | 639 |
|
643 | 640 |
h = hivex.Hivex(sam) |
644 | 641 |
|
... | ... | |
701 | 698 |
log.file.write(stdout) |
702 | 699 |
finally: |
703 | 700 |
log.close() |
704 |
self.out.output("failed! See: `%' for the full output" % log.name) |
|
701 |
self.out.output("failed! See: `%s' for the full output" % log.name)
|
|
705 | 702 |
if i < CONNECTION_RETRIES - 1: |
706 | 703 |
self.out.output("Retrying ...", False) |
707 | 704 |
raise FatalError("Connection to the VM failed after %d retries" % |
... | ... | |
743 | 740 |
self.serial = serial |
744 | 741 |
|
745 | 742 |
def random_mac(): |
743 |
"""creates a random mac address""" |
|
746 | 744 |
mac = [0x00, 0x16, 0x3e, |
747 | 745 |
random.randint(0x00, 0x7f), |
748 | 746 |
random.randint(0x00, 0xff), |
749 | 747 |
random.randint(0x00, 0xff)] |
750 | 748 |
|
751 |
return ':'.join(map(lambda x: "%02x" % x, mac))
|
|
749 |
return ':'.join(['%02x' % x for x in mac])
|
|
752 | 750 |
|
753 | 751 |
# Use ganeti's VNC port range for a random vnc port |
754 | 752 |
self.display = random.randint(11000, 14999) - 5900 |
... | ... | |
780 | 778 |
if self.isalive(): |
781 | 779 |
self.process.kill() |
782 | 780 |
self.process.wait() |
783 |
self.out.output("timed-out") |
|
784 | 781 |
raise FatalError("VM destroy timed-out") |
785 | 782 |
|
786 | 783 |
signal.signal(signal.SIGALRM, handler) |
Also available in: Unified diff