2
Builds out filesystem trees/data based on the object tree.
3
This is the code behind 'cobbler sync'.
5
Copyright 2006-2009, Red Hat, Inc
6
Michael DeHaan <mdehaan@redhat.com>
8
This program is free software; you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation; either version 2 of the License, or
11
(at your option) any later version.
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
GNU General Public License for more details.
18
You should have received a copy of the GNU General Public License
19
along with this program; if not, write to the Free Software
20
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
34
import subprocess as sub_process
41
from cexceptions import *
55
Handles building out PXE stuff
58
def __init__(self, config, logger):
65
self.distros = config.distros()
66
self.profiles = config.profiles()
67
self.systems = config.systems()
68
self.settings = config.settings()
69
self.repos = config.repos()
70
self.images = config.images()
71
self.templar = templar.Templar(config)
72
self.bootloc = utils.tftpboot_location()
73
# FIXME: not used anymore, can remove?
76
def copy_bootloaders(self):
78
Copy bootloaders to the configured tftpboot directory
79
NOTE: we support different arch's if defined in
80
/etc/cobbler/settings.
83
grub_dst = os.path.join(dst, "grub")
84
image_dst = os.path.join(dst, "images")
86
# copy syslinux from one of two locations
89
utils.copyfile_pattern('/var/lib/cobbler/loaders/pxelinux.0',
90
dst, api=self.api, cache=False, logger=self.logger)
91
utils.copyfile_pattern('/var/lib/cobbler/loaders/menu.c32',
92
dst, api=self.api, cache=False, logger=self.logger)
94
if self.settings.use_gpxe:
95
utils.copyfile_pattern('/var/lib/cobbler/loaders/gpxelinux.0',
96
dst, api=self.api, cache=False, logger=self.logger)
98
utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0',
99
dst, api=self.api, cache=False, logger=self.logger)
100
utils.copyfile_pattern('/usr/share/syslinux/menu.c32',
101
dst, api=self.api, cache=False, logger=self.logger)
103
if self.settings.use_gpxe:
104
utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0',
105
dst, api=self.api, cache=False, logger=self.logger)
108
utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0',
109
dst, api=self.api, cache=False, logger=self.logger)
110
utils.copyfile_pattern('/usr/lib/syslinux/menu.c32',
111
dst, api=self.api, cache=False, logger=self.logger)
113
if self.settings.use_gpxe:
114
utils.copyfile_pattern('/usr/lib/syslinux/gpxelinux.0',
115
dst, api=self.api, cache=False, logger=self.logger)
117
# copy memtest only if we find it
118
utils.copyfile_pattern('/boot/memtest*', image_dst,
119
require_match=False, api=self.api, cache=False, logger=self.logger)
121
# copy elilo which we include for IA64 targets
122
utils.copyfile_pattern('/var/lib/cobbler/loaders/elilo.efi', dst,
123
require_match=False, api=self.api, cache=False, logger=self.logger)
125
# copy yaboot which we include for PowerPC targets
126
utils.copyfile_pattern('/var/lib/cobbler/loaders/yaboot', dst,
127
require_match=False, api=self.api, cache=False, logger=self.logger)
130
utils.copyfile_pattern('/usr/lib/syslinux/memdisk',
131
dst, api=self.api, cache=False, logger=self.logger)
133
utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst,
134
require_match=False, api=self.api, cache=False, logger=self.logger)
136
# Copy grub EFI bootloaders if possible:
137
utils.copyfile_pattern('/var/lib/cobbler/loaders/grub*.efi', grub_dst,
138
require_match=False, api=self.api, cache=False, logger=self.logger)
141
def copy_images(self):
143
Like copy_distros except for images.
146
for i in self.images:
148
self.copy_single_image_files(i)
151
self.logger.error(e.value)
153
# FIXME: using logging module so this ends up in cobbler.log?
155
def copy_single_distro_files(self, d, dirtree, symlink_ok):
156
distros = os.path.join(dirtree, "images")
157
distro_dir = os.path.join(distros,d.name)
158
utils.mkdir(distro_dir)
159
kernel = utils.find_kernel(d.kernel) # full path
160
initrd = utils.find_initrd(d.initrd) # full path
163
raise CX("kernel not found: %(file)s, distro: %(distro)s" %
164
{ "file" : d.kernel, "distro" : d.name })
167
raise CX("initrd not found: %(file)s, distro: %(distro)s" %
168
{ "file" : d.initrd, "distro" : d.name })
170
# Kernels referenced by remote URL are passed through to koan directly,
171
# no need for copying the kernel locally:
172
if not utils.file_is_remote(kernel):
173
b_kernel = os.path.basename(kernel)
174
dst1 = os.path.join(distro_dir, b_kernel)
175
utils.linkfile(kernel, dst1, symlink_ok=symlink_ok,
176
api=self.api, logger=self.logger)
178
if not utils.file_is_remote(initrd):
179
b_initrd = os.path.basename(initrd)
180
dst2 = os.path.join(distro_dir, b_initrd)
181
utils.linkfile(initrd, dst2, symlink_ok=symlink_ok,
182
api=self.api, logger=self.logger)
184
def copy_single_image_files(self, img):
185
images_dir = os.path.join(self.bootloc, "images2")
187
if not os.path.exists(filename):
188
# likely for virtual usage, cannot use
190
if not os.path.exists(images_dir):
191
os.makedirs(images_dir)
192
basename = os.path.basename(img.file)
193
newfile = os.path.join(images_dir, img.name)
194
utils.linkfile(filename, newfile, api=self.api, logger=self.logger)
197
def write_all_system_files(self, system):
199
profile = system.get_conceptual_parent()
201
raise CX("system %(system)s references a missing profile %(profile)s" % { "system" : system.name, "profile" : system.profile})
203
distro = profile.get_conceptual_parent()
207
if profile.COLLECTION_TYPE == "profile":
208
raise CX("profile %(profile)s references a missing distro %(distro)s" % { "profile" : system.profile, "distro" : profile.distro})
213
# hack: s390 generates files per system not per interface
214
if not image_based and distro.arch.startswith("s390"):
215
# Always write a system specific _conf and _parm file
216
f2 = os.path.join(self.bootloc, "s390x", "s_%s" % system.name)
219
template_cf = open("/etc/cobbler/pxe/s390x_conf.template")
220
template_pf = open("/etc/cobbler/pxe/s390x_parm.template")
221
blended = utils.blender(self.api, True, system)
222
self.templar.render(template_cf, blended, cf)
223
# FIXME: profiles also need this data!
224
# FIXME: the _conf and _parm files are limited to 80 characters in length
225
ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0]
226
kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (ipaddress, system.name)
227
# gather default kernel_options and default kernel_options_s390x
228
kopts = blended.get("kernel_options","")
229
hkopts = shlex.split(utils.hash_to_string(kopts))
230
blended["kickstart_expanded"] = "ks=%s" % kickstart_path
231
blended["kernel_options"] = hkopts
232
self.templar.render(template_pf, blended, pf)
234
# Write system specific zPXE file
235
if system.is_management_supported():
236
self.write_pxe_file(f2, system, profile, distro, distro.arch)
238
# ensure the file doesn't exist
242
# generate one record for each described NIC ..
244
for (name,interface) in system.interfaces.iteritems():
246
ip = interface["ip_address"]
248
f1 = utils.get_config_filename(system, interface=name)
250
self.logger.warning("invalid interface recorded for system (%s,%s)" % (system.name,name))
254
working_arch = image.arch
256
working_arch = distro.arch
258
if working_arch is None:
259
raise "internal error, invalid arch supplied"
263
if working_arch in [ "i386", "x86", "x86_64", "standard"]:
264
if self.settings.use_gpxe:
265
# pxelinux wants a file named $name under pxelinux.cfg
266
f2 = os.path.join(self.bootloc, "gpxe", f1)
268
# pxelinux wants a file named $name under pxelinux.cfg
269
f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1)
271
# Only generating grub menus for these arch's:
272
grub_path = os.path.join(self.bootloc, "grub", f1.upper())
274
elif working_arch == "ia64":
275
# elilo expects files to be named "$name.conf" in the root
276
# and can not do files based on the MAC address
277
if ip is not None and ip != "":
278
self.logger.warning("Warning: Itanium system object (%s) needs an IP address to PXE" % system.name)
280
filename = "%s.conf" % utils.get_config_filename(system,interface=name)
281
f2 = os.path.join(self.bootloc, filename)
283
elif working_arch.startswith("ppc"):
284
# Determine filename for system-specific yaboot.conf
285
filename = "%s" % utils.get_config_filename(system, interface=name).lower()
286
f2 = os.path.join(self.bootloc, "etc", filename)
288
# Link to the yaboot binary
289
f3 = os.path.join(self.bootloc, "ppc", filename)
290
if os.path.lexists(f3):
292
os.symlink("../yaboot", f3)
296
if system.is_management_supported():
298
self.write_pxe_file(f2, system, profile, distro, working_arch)
300
self.write_pxe_file(grub_path, system, profile, distro,
301
working_arch, format="grub")
303
self.write_pxe_file(f2, system, None, None, working_arch, image=profile)
305
# ensure the file doesn't exist
308
utils.rmfile(grub_path)
310
def make_pxe_menu(self):
311
self.make_s390_pseudo_pxe_menu()
312
self.make_actual_pxe_menu()
314
def make_s390_pseudo_pxe_menu(self):
315
s390path = os.path.join(self.bootloc, "s390x")
316
if not os.path.exists(s390path):
317
utils.mkdir(s390path)
318
profile_list = [profile for profile in self.profiles]
319
image_list = [image for image in self.images]
321
return cmp(a.name,b.name)
322
profile_list.sort(sort_name)
323
image_list.sort(sort_name)
324
listfile = open(os.path.join(s390path, "profile_list"),"w+")
325
for profile in profile_list:
326
distro = profile.get_conceptual_parent()
328
raise CX("profile is missing distribution: %s, %s" % (profile.name, profile.distro))
329
if distro.arch.startswith("s390"):
330
listfile.write("%s\n" % profile.name)
331
f2 = os.path.join(self.bootloc, "s390x", "p_%s" % profile.name)
332
self.write_pxe_file(f2,None,profile,distro,distro.arch)
335
template_cf = open("/etc/cobbler/pxe/s390x_conf.template")
336
template_pf = open("/etc/cobbler/pxe/s390x_parm.template")
337
blended = utils.blender(self.api, True, profile)
338
self.templar.render(template_cf, blended, cf)
339
# FIXME: profiles also need this data!
340
# FIXME: the _conf and _parm files are limited to 80 characters in length
341
ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0]
342
kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (ipaddress, profile.name)
343
# gather default kernel_options and default kernel_options_s390x
344
kopts = blended.get("kernel_options","")
345
hkopts = shlex.split(utils.hash_to_string(kopts))
346
blended["kickstart_expanded"] = "ks=%s" % kickstart_path
347
blended["kernel_options"] = hkopts
348
self.templar.render(template_pf, blended, pf)
352
def make_actual_pxe_menu(self):
354
Generates both pxe and grub boot menus.
356
# only do this if there is NOT a system named default.
357
default = self.systems.find(name="default")
360
timeout_action = "local"
362
timeout_action = default.profile
365
profile_list = [profile for profile in self.profiles]
367
return cmp(a.name,b.name)
368
profile_list.sort(sort_name)
371
image_list = [image for image in self.images]
372
image_list.sort(sort_name)
374
# Build out menu items and append each to this master list, used for
379
# For now, profiles are the only items we want grub EFI boot menu entries for:
380
for profile in profile_list:
381
if not profile.enable_menu:
382
# This profile has been excluded from the menu
384
distro = profile.get_conceptual_parent()
385
# xen distros can be ruled out as they won't boot
386
if distro.name.find("-xen") != -1 or distro.arch not in ["i386", "x86_64"]:
389
contents = self.write_pxe_file(filename=None, system=None,
390
profile=profile, distro=distro,
391
arch=distro.arch, include_header=False)
392
if contents is not None:
393
pxe_menu_items = pxe_menu_items + contents + "\n"
395
grub_contents = self.write_pxe_file(filename=None, system=None,
396
profile=profile, distro=distro,
397
arch=distro.arch, include_header=False, format="grub")
398
if grub_contents is not None:
399
grub_menu_items = grub_menu_items + grub_contents + "\n"
402
# image names towards the bottom
403
for image in image_list:
404
if os.path.exists(image.file):
405
contents = self.write_pxe_file(filename=None, system=None,
406
profile=None, distro=None, arch=image.arch, image=image)
407
if contents is not None:
408
pxe_menu_items = pxe_menu_items + contents + "\n"
410
# if we have any memtest files in images, make entries for them
411
# after we list the profiles
412
memtests = glob.glob(self.bootloc + "/memtest*")
413
if len(memtests) > 0:
414
pxe_menu_items = pxe_menu_items + "\n\n"
415
for memtest in glob.glob(self.bootloc + '/memtest*'):
416
base = os.path.basename(memtest)
417
contents = self.write_memtest_pxe("/%s" % base)
418
pxe_menu_items = pxe_menu_items + contents + "\n"
420
# Write the PXE menu:
421
metadata = { "pxe_menu_items" : pxe_menu_items, "pxe_timeout_profile" : timeout_action}
422
if self.settings.use_gpxe:
423
outfile = os.path.join(self.bootloc, "gpxe", "default")
424
gpxe_menu = os.path.join(self.bootloc, "gpxe", "menu.gpxe")
425
gpxe_template = os.path.join(self.settings.pxe_template_dir,"gpxemenu.template")
426
utils.copyfile_pattern(gpxe_template, gpxe_menu, api=self.api,
427
cache=False, logger=self.logger)
429
outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default")
430
template_src = open(os.path.join(self.settings.pxe_template_dir,"pxedefault.template"))
431
template_data = template_src.read()
432
self.templar.render(template_data, metadata, outfile, None)
435
# Write the grub menu:
436
metadata = { "grub_menu_items" : grub_menu_items }
437
outfile = os.path.join(self.bootloc, "grub", "efidefault")
438
template_src = open(os.path.join(self.settings.pxe_template_dir, "efidefault.template"))
439
template_data = template_src.read()
440
self.templar.render(template_data, metadata, outfile, None)
443
def write_memtest_pxe(self,filename):
445
Write a configuration file for memtest
448
# FIXME: this should be handled via "cobbler image add" now that it is available,
449
# though it would be nice if there was a less-manual way to add those as images.
451
# just some random variables
456
template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
458
# store variables for templating
459
metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename)
460
metadata["profile_name"] = os.path.basename(filename)
461
metadata["kernel_path"] = "/%s" % os.path.basename(filename)
462
metadata["initrd_path"] = ""
463
metadata["append_line"] = ""
466
template_fh = open(template)
467
template_data = template_fh.read()
471
buffer = self.templar.render(template_data, metadata, None)
475
def write_pxe_file(self, filename, system, profile, distro, arch,
476
image=None, include_header=True, metadata=None, format="pxe"):
478
Write a configuration file for the boot loader(s).
479
More system-specific configuration may come in later, if so
480
that would appear inside the system object in api.py
482
NOTE: relevant to tftp and pseudo-PXE (s390) only
484
ia64 is mostly the same as syslinux stuff, s390 is a bit
485
short-circuited and simpler. All of it goes through the
486
templating engine, see the templates in /etc/cobbler for
489
Can be used for different formats, "pxe" (default) and "grub".
495
if image and not os.path.exists(image.file):
496
return None # nfs:// URLs or something, can't use for TFTP
501
# just some random variables
506
kickstart_path = None
512
if self.settings.use_gpxe:
513
# not image based, it's something normalish
514
img_path = os.path.join("/images",distro.name)
516
blended = utils.blender(self.api, True, system)
517
ipaddress = socket.gethostbyname_ex(bleneded["http_server"])[2][0]
518
kernel_path = "http://%s/cobbler/images%s" % (ipaddress, distro.kernel)
519
initrd_path = "http://%s/cobbler/images%s" % (ipaddress, distro.initrd)
521
# not image based, it's something normalish
522
img_path = os.path.join("/images",distro.name)
523
kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
524
initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd))
526
# Find the kickstart if we inherit from another profile
528
blended = utils.blender(self.api, True, system)
530
blended = utils.blender(self.api, True, profile)
531
kickstart_path = blended.get("kickstart","")
534
# this is an image we are making available, not kernel+initrd
535
if image.image_type == "direct":
536
kernel_path = os.path.join("/images2",image.name)
537
elif image.image_type == "memdisk":
538
kernel_path = "/memdisk"
539
initrd_path = os.path.join("/images2",image.name)
541
# CD-ROM ISO or virt-clone image? We can't PXE boot it.
545
if img_path is not None and not metadata.has_key("img_path"):
546
metadata["img_path"] = img_path
547
if kernel_path is not None and not metadata.has_key("kernel_path"):
548
metadata["kernel_path"] = kernel_path
549
if initrd_path is not None and not metadata.has_key("initrd_path"):
550
metadata["initrd_path"] = initrd_path
556
template = os.path.join(self.settings.pxe_template_dir, "grubsystem.template")
558
if system.netboot_enabled:
559
template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template")
561
if arch.startswith("s390"):
562
template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template")
564
template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template")
565
elif arch.startswith("ppc"):
566
template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template")
567
elif distro.os_version.startswith("esxi"):
568
# ESXi uses a very different pxe method, using more files than
569
# a standard kickstart and different options - so giving it a dedicated
570
# PXE template makes more sense than shoe-horning it into the existing
572
template = os.path.join(self.settings.pxe_template_dir,"pxesystem_esxi.template")
574
# local booting on ppc requires removing the system-specific dhcpd.conf filename
575
if arch is not None and arch.startswith("ppc"):
576
# Disable yaboot network booting for all interfaces on the system
577
for (name,interface) in system.interfaces.iteritems():
579
filename = "%s" % utils.get_config_filename(system, interface=name).lower()
581
# Remove symlink to the yaboot binary
582
f3 = os.path.join(self.bootloc, "ppc", filename)
583
if os.path.lexists(f3):
586
# Remove the interface-specific config file
587
f3 = os.path.join(self.bootloc, "etc", filename)
588
if os.path.lexists(f3):
591
# Yaboot/OF doesn't support booting locally once you've
592
# booted off the network, so nothing left to do
594
elif arch is not None and arch.startswith("s390"):
595
template = os.path.join(self.settings.pxe_template_dir,"pxelocal_s390x.template")
596
elif arch is not None and arch.startswith("ia64"):
597
template = os.path.join(self.settings.pxe_template_dir,"pxelocal_ia64.template")
599
template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template")
601
# not a system record, so this is a profile record
603
if arch.startswith("s390"):
604
template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_s390x.template")
605
elif format == "grub":
606
template = os.path.join(self.settings.pxe_template_dir,"grubprofile.template")
607
elif distro.os_version.startswith("esxi"):
608
# ESXi uses a very different pxe method, see comment above in the system section
609
template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_esxi.template")
611
template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
614
if kernel_path is not None:
615
metadata["kernel_path"] = kernel_path
616
if initrd_path is not None:
617
metadata["initrd_path"] = initrd_path
619
# generate the kernel options and append line:
620
kernel_options = self.build_kernel_options(system, profile, distro,
621
image, arch, kickstart_path)
622
metadata["kernel_options"] = kernel_options
624
if distro.os_version.startswith("esxi") and filename is not None:
625
append_line = "BOOTIF=%s" % (os.path.basename(filename))
626
elif metadata.has_key("initrd_path") and (not arch or arch not in ["ia64", "ppc", "ppc64"]):
627
append_line = "append initrd=%s" % (metadata["initrd_path"])
629
append_line = "append "
630
append_line = "%s%s" % (append_line, kernel_options)
631
if arch.startswith("ppc") or arch.startswith("s390"):
632
# remove the prefix "append"
633
# TODO: this looks like it's removing more than append, really
634
# not sure what's up here...
635
append_line = append_line[7:]
636
metadata["append_line"] = append_line
638
# store variables for templating
639
metadata["menu_label"] = ""
641
if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
642
metadata["menu_label"] = "MENU LABEL %s" % profile.name
643
metadata["profile_name"] = profile.name
645
metadata["menu_label"] = "MENU LABEL %s" % image.name
646
metadata["profile_name"] = image.name
649
metadata["system_name"] = system.name
653
if kernel_path is not None:
654
template_fh = open(template)
655
template_data = template_fh.read()
658
# this is something we can't PXE boot
661
# save file and/or return results, depending on how called.
662
buffer = self.templar.render(template_data, metadata, None)
663
if filename is not None:
664
self.logger.info("generating: %s" % filename)
665
fd = open(filename, "w")
670
def build_kernel_options(self, system, profile, distro, image, arch,
673
Builds the full kernel options line.
676
if system is not None:
677
blended = utils.blender(self.api, False, system)
678
elif profile is not None:
679
blended = utils.blender(self.api, False, profile)
681
blended = utils.blender(self.api, False, image)
684
kopts = blended.get("kernel_options", dict())
685
# support additional initrd= entries in kernel options.
686
if "initrd" in kopts:
687
append_line = ",%s" % kopts.pop("initrd")
688
hkopts = utils.hash_to_string(kopts)
689
append_line = "%s %s" % (append_line, hkopts)
690
# FIXME - the append_line length limit is architecture specific
691
# TODO: why is this checked here, before we finish adding everything?
692
if len(append_line) >= 255:
693
self.logger.warning("warning: kernel option length exceeds 255")
695
# kickstart path rewriting (get URLs for local files)
696
if kickstart_path is not None and kickstart_path != "":
698
# FIXME: need to make shorter rewrite rules for these URLs
700
ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0]
701
if system is not None and kickstart_path.startswith("/"):
702
kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (ipaddress, system.name)
703
elif kickstart_path.startswith("/"):
704
kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (ipaddress, profile.name)
706
if distro.breed is None or distro.breed == "redhat":
707
append_line = "%s ks=%s" % (append_line, kickstart_path)
708
elif distro.breed == "suse":
709
append_line = "%s autoyast=%s" % (append_line, kickstart_path)
710
elif distro.breed == "debian" or distro.breed == "ubuntu":
711
append_line = "%s auto url=%s" % (append_line, kickstart_path)
713
# rework kernel options for debian distros
714
translations = { 'ksdevice':"interface" , 'lang':"locale" }
715
for k,v in translations.iteritems():
716
append_line = append_line.replace("%s="%k,"%s="%v)
718
# interface=bootif causes a failure
719
append_line = append_line.replace("interface=bootif","")
720
elif distro.breed == "vmware":
721
if distro.os_version.find("esxi") != -1:
722
# ESXi is very picky, it's easier just to redo the
723
# entire append line here since
724
append_line = " ks=%s %s" % (kickstart_path, hkopts)
725
# ESXi likes even fewer options, so we remove them too
726
append_line = append_line.replace("kssendmac","")
728
append_line = "%s vmkopts=debugLogToSerial:1 mem=512M ks=%s" % \
729
(append_line, kickstart_path)
730
# interface=bootif causes a failure
731
append_line = append_line.replace("ksdevice=bootif","")
733
if distro is not None and (distro.breed in [ "debian", "ubuntu" ]):
734
# Hostname is required as a parameter, the one in the preseed is
735
# not respected, so calculate if we have one here.
736
# We're trying: first part of FQDN in hostname field, then system
737
# name, then profile name.
738
# In Ubuntu, this is at least used for the volume group name when
741
if system is not None:
742
if system.hostname is not None:
743
# If this is a FQDN, grab the first bit
744
hostname = system.hostname.split(".")[0]
745
_domain = system.hostname.split(".")[1:]
747
domain = ".".join( _domain )
749
hostname = system.name
751
hostname = profile.name
753
# At least for debian deployments configured for DHCP networking
754
# this values are not used, but specifying here avoids questions
755
append_line = "%s hostname=%s" % (append_line, hostname)
756
append_line = "%s domain=%s" % (append_line, domain)
758
# A similar issue exists with suite name, as installer requires
759
# the existence of "stable" in the dists directory
760
append_line = "%s suite=%s" % (append_line, distro.os_version)
764
def write_templates(self,obj,write_file=False,path=None):
766
A semi-generic function that will take an object
767
with a template_files hash {source:destiation}, and
768
generate a rendered file. The write_file option
769
allows for generating of the rendered output without
770
actually creating any files.
772
The return value is a hash of the destination file
773
names (after variable substitution is done) and the
780
templates = obj.template_files
784
blended = utils.blender(self.api, False, obj)
786
ksmeta = blended.get("ks_meta",{})
788
del blended["ks_meta"]
791
blended.update(ksmeta) # make available at top level
793
templates = blended.get("template_files",{})
795
del blended["template_files"]
798
blended.update(templates) # make available at top level
800
(success, templates) = utils.input_string_or_hash(templates)
806
for template in templates.keys():
807
dest = templates[template]
811
# Run the source and destination files through
812
# templar first to allow for variables in the path
813
template = self.templar.render(template, blended, None).strip()
814
dest = self.templar.render(dest, blended, None).strip()
815
# Get the path for the destination output
816
dest_dir = os.path.dirname(dest)
818
# If we're looking for a single template, skip if this ones
819
# destination is not it.
820
if not path is None and path != dest:
823
# If we are writing output to a file, force all templated
824
# configs into the rendered directory to ensure that a user
825
# granted cobbler privileges via sudo can't overwrite
826
# arbitrary system files (This also makes cleanup easier).
827
if os.path.isabs(dest_dir):
829
raise CX(" warning: template destination (%s) is an absolute path, skipping." % dest_dir)
832
dest_dir = os.path.join(self.settings.webdir, "rendered", dest_dir)
833
dest = os.path.join(dest_dir, os.path.basename(dest))
834
if not os.path.exists(dest_dir):
835
utils.mkdir(dest_dir)
838
if not os.path.exists(template):
839
raise CX("template source %s does not exist" % template)
841
elif write_file and not os.path.isdir(dest_dir):
842
raise CX("template destination (%s) is invalid" % dest_dir)
844
elif write_file and os.path.exists(dest):
845
raise CX("template destination (%s) already exists" % dest)
847
elif write_file and os.path.isdir(dest):
848
raise CX("template destination (%s) is a directory" % dest)
850
elif template == "" or dest == "":
851
raise CX("either the template source or destination was blank (unknown variable used?)" % dest)
854
template_fh = open(template)
855
template_data = template_fh.read()
858
buffer = self.templar.render(template_data, blended, None)
859
results[dest] = buffer
862
self.logger.info("generating: %s" % dest)