2
This is some of the code behind 'cobbler sync'.
4
Copyright 2006-2009, Red Hat, Inc
5
Michael DeHaan <mdehaan@redhat.com>
6
John Eckersberg <jeckersb@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
33
from utils import popen2
34
from shlex import shlex
38
from cexceptions import *
48
# Import aptsources module if available to obtain repo mirror.
50
from aptsources import distro
51
from aptsources import sourceslist
58
The mandatory cobbler module registration hook.
60
return "manage/import"
63
class ImportDebianUbuntuManager:
65
def __init__(self,config,logger):
72
self.distros = config.distros()
73
self.profiles = config.profiles()
74
self.systems = config.systems()
75
self.settings = config.settings()
76
self.repos = config.repos()
77
self.templar = templar.Templar(config)
79
# required function for import modules
81
return "import/debian_ubuntu"
83
# required function for import modules
84
def check_for_signature(self,path,cli_breed):
90
#self.logger.info("scanning %s for a debian/ubuntu distro signature" % path)
91
for signature in signatures:
92
d = os.path.join(path,signature)
94
self.logger.info("Found a debian/ubuntu compatible signature: %s" % signature)
95
return (True,signature)
97
if cli_breed and cli_breed in self.get_valid_breeds():
98
self.logger.info("Warning: No distro signature for kernel at %s, using value from command line" % path)
103
# required function for import modules
104
def run(self,pkgdir,name,path,network_root=None,kickstart_file=None,rsync_flags=None,arch=None,breed=None,os_version=None):
107
self.network_root = network_root
108
self.kickstart_file = kickstart_file
109
self.rsync_flags = rsync_flags
112
self.os_version = os_version
115
# some fixups for the XMLRPC interface, which does not use "None"
116
if self.arch == "": self.arch = None
117
if self.kickstart_file == "": self.kickstart_file = None
118
if self.os_version == "": self.os_version = None
119
if self.rsync_flags == "": self.rsync_flags = None
120
if self.network_root == "": self.network_root = None
122
# If no breed was specified on the command line, figure it out
123
if self.breed == None:
124
self.breed = self.get_breed_from_directory()
126
utils.die(self.logger,"import failed - could not determine breed of debian-based distro")
128
# if we're going to do any copying, set where to put things
129
# and then make sure nothing is already there.
131
# import takes a --kickstart for forcing selection that can't be used in all circumstances
133
if self.kickstart_file and not self.breed:
134
utils.die(self.logger,"Kickstart file can only be specified when a specific breed is selected")
136
if self.os_version and not self.breed:
137
utils.die(self.logger,"OS version can only be specified when a specific breed is selected")
139
if self.breed and self.breed.lower() not in self.get_valid_breeds():
140
utils.die(self.logger,"Supplied import breed is not supported by this module")
142
# try to detect arch and os_version when importing an Ubuntu mini ISO. This is,
143
# if the signature is the file '.disk/mini-info' which will only be found in mini iso.
144
# This is determined when when detecting the signature, and if this is a readable file.
145
# This file contains info in the way:
146
# 'Ubuntu-Server 11.04 "Natty Narwhal" - Beta i386 (20110407)'
147
# 'Ubuntu 11.10 "oneiric" - i386 (20101020ubuntu35)'
148
info_file_path = "%s/%s" % (self.path, self.pkgdir)
149
if os.path.isfile(info_file_path):
150
info_file = open(info_file_path, "r")
151
# traverse through the split info_line looking for a supportted os_version/arch.
152
for str in info_file.readline().split():
153
info = str.strip('"').lower()
154
# Only set os_version/arch if not already done so.
155
if info in self.get_valid_os_versions() and not self.os_version:
156
self.os_version = info
157
# if info is arch amd64 replace it with x86_64 because cobbler does not suppor
161
if info in self.get_valid_arches() and not self.arch:
164
# now walk the filesystem looking for distributions that match certain patterns
166
self.logger.info("adding distros")
168
# FIXME : search below self.path for isolinux configurations or known directories from TRY_LIST
169
os.path.walk(self.path, self.distro_adder, distros_added)
171
# find out if we can auto-create any repository records from the install tree
173
if self.network_root is None:
174
self.logger.info("associating repos")
175
# FIXME: this automagic is not possible (yet) without mirroring
176
self.repo_finder(distros_added)
178
# find the most appropriate answer files for each profile object
180
self.logger.info("associating kickstarts")
181
self.kickstart_finder(distros_added)
183
# ensure bootloaders are present
184
self.api.pxegen.copy_bootloaders()
188
# required function for import modules
189
def get_valid_arches(self):
190
return ["i386", "ppc", "x86_64", "x86", "arm",]
192
# required function for import modules
193
def get_valid_breeds(self):
194
return ["debian","ubuntu"]
196
# required function for import modules
197
def get_valid_os_versions(self):
198
if self.breed == "debian":
199
return codes.VALID_OS_VERSIONS["debian"]
200
elif self.breed == "ubuntu":
201
return codes.VALID_OS_VERSIONS["ubuntu"]
205
def get_valid_repo_breeds(self):
208
def get_release_files(self):
210
Find distro release packages.
212
return glob.glob(os.path.join(self.get_rootdir(), "dists/*"))
214
def get_breed_from_directory(self):
215
for breed in self.get_valid_breeds():
216
# NOTE : Although we break the loop after the first match,
217
# multiple debian derived distros can actually live at the same pool -- JP
218
d = os.path.join(self.path, breed)
219
if (os.path.islink(d) and os.path.isdir(d) and os.path.realpath(d) == os.path.realpath(self.path)) or os.path.basename(self.path) == breed:
224
def get_tree_location(self, distro):
226
Once a distribution is identified, find the part of the distribution
227
that has the URL in it that we want to use for kickstarting the
228
distribution, and create a ksmeta variable $tree that contains this.
231
base = self.get_rootdir()
233
if self.network_root is None:
234
dists_path = os.path.join(self.path, "dists")
235
if os.path.isdir(dists_path):
236
tree = "http://@@http_server@@/cblr/ks_mirror/%s" % (self.name)
238
tree = "http://@@http_server@@/cblr/repo_mirror/%s" % (distro.name)
239
self.set_install_tree(distro, tree)
241
# where we assign the kickstart source is relative to our current directory
242
# and the input start directory in the crawl. We find the path segments
243
# between and tack them on the network source path to find the explicit
244
# network path to the distro that Anaconda can digest.
245
tail = utils.path_tail(self.path, base)
246
tree = self.network_root[:-1] + tail
247
self.set_install_tree(distro, tree)
251
def repo_finder(self, distros_added):
252
for distro in distros_added:
253
self.logger.info("traversing distro %s" % distro.name)
254
# FIXME : Shouldn't decide this the value of self.network_root ?
255
if distro.kernel.find("ks_mirror") != -1:
256
basepath = os.path.dirname(distro.kernel)
257
top = self.get_rootdir()
258
self.logger.info("descent into %s" % top)
259
dists_path = os.path.join(self.path, "dists")
260
if not os.path.isdir(dists_path):
261
self.process_repos(self, distro)
263
self.logger.info("this distro isn't mirrored")
265
def get_repo_mirror_from_apt(self):
267
This tries to determine the apt mirror/archive to use (when processing repos)
268
if the host machine is Debian or Ubuntu.
271
sources = sourceslist.SourcesList()
272
release = distro.get_distro()
273
release.get_sources(sources)
274
mirrors = release.get_server_list()
275
for mirror in mirrors:
276
if mirror[2] == True:
284
def distro_adder(self,distros_added,dirname,fnames):
286
This is an os.path.walk routine that finds distributions in the directory
287
to be scanned and then creates them.
290
# FIXME: If there are more than one kernel or initrd image on the same directory,
291
# results are unpredictable
299
fullname = os.path.join(dirname,x)
300
if os.path.islink(fullname) and os.path.isdir(fullname):
301
if fullname.startswith(self.path):
302
self.logger.warning("avoiding symlink loop")
304
self.logger.info("following symlink: %s" % fullname)
305
os.path.walk(fullname, self.distro_adder, distros_added)
307
if ( x.startswith("initrd.gz") or x.startswith("uInitrd") ) and x != "initrd.size":
308
initrd = os.path.join(dirname,x)
309
if ( x.startswith("linux") or x.startswith("uImage") ) and x.find("initrd") == -1:
310
kernel = os.path.join(dirname,x)
312
# if we've collected a matching kernel and initrd pair, turn the in and add them to the list
313
if initrd is not None and kernel is not None:
314
adtls.append(self.add_entry(dirname,kernel,initrd))
319
distros_added.extend(adtl)
321
def add_entry(self,dirname,kernel,initrd):
323
When we find a directory with a valid kernel/initrd in it, create the distribution objects
324
as appropriate and save them. This includes creating xen and rescue distros/profiles
328
proposed_name = self.get_proposed_name(dirname,kernel)
329
proposed_arch = self.get_proposed_arch(dirname)
331
if self.arch and proposed_arch and self.arch != proposed_arch:
332
self.logger.error("Arch from pathname (%s) does not match with supplied one (%s)"%(proposed_arch,self.arch))
335
archs = self.learn_arch_from_tree()
338
archs.append( self.arch )
340
if self.arch and self.arch not in archs:
341
utils.die(self.logger, "Given arch (%s) not found on imported tree %s"%(self.arch,self.get_pkgdir()))
343
if archs and proposed_arch not in archs:
344
self.logger.warning("arch from pathname (%s) not found on imported tree %s" % (proposed_arch,self.get_pkgdir()))
347
archs = [ proposed_arch ]
350
self.logger.warning("- Warning : Multiple archs found : %s" % (archs))
354
for pxe_arch in archs:
355
name = proposed_name + "-" + pxe_arch
356
existing_distro = self.distros.find(name=name)
358
if existing_distro is not None:
359
self.logger.warning("skipping import, as distro name already exists: %s" % name)
363
self.logger.info("creating new distro: %s" % name)
364
distro = self.config.new_distro()
366
if name.find("-autoboot") != -1:
367
# this is an artifact of some EL-3 imports
370
distro.set_name(name)
371
distro.set_kernel(kernel)
372
distro.set_initrd(initrd)
373
distro.set_arch(pxe_arch)
374
distro.set_breed(self.breed)
375
# If a version was supplied on command line, we set it now
377
distro.set_os_version(self.os_version)
379
self.distros.add(distro,save=True)
380
distros_added.append(distro)
382
existing_profile = self.profiles.find(name=name)
384
# see if the profile name is already used, if so, skip it and
385
# do not modify the existing profile
387
if existing_profile is None:
388
self.logger.info("creating new profile: %s" % name)
389
#FIXME: The created profile holds a default kickstart, and should be breed specific
390
profile = self.config.new_profile()
392
self.logger.info("skipping existing profile, name already exists: %s" % name)
395
# save our minimal profile which just points to the distribution and a good
396
# default answer file
398
profile.set_name(name)
399
profile.set_distro(name)
400
profile.set_kickstart(self.kickstart_file)
402
# depending on the name of the profile we can define a good virt-type
403
# for usage with koan
405
if name.find("-xen") != -1:
406
profile.set_virt_type("xenpv")
407
elif name.find("vmware") != -1:
408
profile.set_virt_type("vmware")
410
profile.set_virt_type("qemu")
412
# save our new profile to the collection
414
self.profiles.add(profile,save=True)
418
def get_proposed_name(self,dirname,kernel=None):
420
Given a directory name where we have a kernel/initrd pair, try to autoname
421
the distribution (and profile) object based on the contents of that path
424
if self.network_root is not None:
425
name = self.name #+ "-".join(utils.path_tail(os.path.dirname(self.path),dirname).split("/"))
427
# remove the part that says /var/www/cobbler/ks_mirror/name
428
name = "-".join(dirname.split("/")[5:])
430
if kernel is not None and kernel.find("PAE") != -1:
433
# These are all Ubuntu's doing, the netboot images are buried pretty
435
name = name.replace("-netboot","")
436
name = name.replace("-ubuntu-installer","")
437
name = name.replace("-amd64","")
438
name = name.replace("-i386","")
440
# we know that some kernel paths should not be in the name
442
name = name.replace("-images","")
443
name = name.replace("-pxeboot","")
444
name = name.replace("-install","")
445
name = name.replace("-isolinux","")
447
# some paths above the media root may have extra path segments we want
450
name = name.replace("-os","")
451
name = name.replace("-tree","")
452
name = name.replace("var-www-cobbler-", "")
453
name = name.replace("ks_mirror-","")
454
name = name.replace("--","-")
456
# remove any architecture name related string, as real arch will be appended later
458
name = name.replace("chrp","ppc64")
460
for separator in [ '-' , '_' , '.' ] :
461
for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc32", "ppc", "x86" , "s390x", "s390" , "386" , "amd", "arm" ]:
462
name = name.replace("%s%s" % ( separator , arch ),"")
466
def get_proposed_arch(self,dirname):
468
Given an directory name, can we infer an architecture from a path segment?
470
if dirname.find("x86_64") != -1 or dirname.find("amd") != -1:
472
if dirname.find("ia64") != -1:
474
if dirname.find("i386") != -1 or dirname.find("386") != -1 or dirname.find("x86") != -1:
476
if dirname.find("s390x") != -1:
478
if dirname.find("s390") != -1:
480
if dirname.find("ppc64") != -1 or dirname.find("chrp") != -1:
482
if dirname.find("ppc32") != -1:
484
if dirname.find("ppc") != -1:
488
def arch_walker(self,foo,dirname,fnames):
490
See docs on learn_arch_from_tree.
492
The TRY_LIST is used to speed up search, and should be dropped for default importer
493
Searched kernel names are kernel-header, linux-headers-, kernel-largesmp, kernel-hugemem
495
This method is useful to get the archs, but also to package type and a raw guess of the breed
498
# try to find a kernel header RPM and then look at it's arch.
500
if self.match_kernelarch_file(x):
501
for arch in self.get_valid_arches():
502
if x.find(arch) != -1:
504
for arch in [ "i686" , "amd64" ]:
505
if x.find(arch) != -1:
508
def kickstart_finder(self,distros_added):
510
For all of the profiles in the config w/o a kickstart, use the
511
given kickstart file, or look at the kernel path, from that,
512
see if we can guess the distro, and if we can, assign a kickstart
513
if one is available for it.
515
for profile in self.profiles:
516
distro = self.distros.find(name=profile.get_conceptual_parent().name)
517
if distro is None or not (distro in distros_added):
520
kdir = os.path.dirname(distro.kernel)
521
if self.kickstart_file == None:
522
for file in self.get_release_files():
523
results = self.scan_pkg_filename(file)
524
# FIXME : If os is not found on tree but set with CLI, no kickstart is searched
526
self.logger.warning("skipping %s" % file)
528
(flavor, major, minor, release) = results
529
# Why use set_variance()? scan_pkg_filename() does everything we need now - jcammarata
530
#version , ks = self.set_variance(flavor, major, minor, distro.arch)
532
if self.os_version != flavor:
533
utils.die(self.logger,"CLI version differs from tree : %s vs. %s" % (self.os_version,flavor))
534
distro.set_comment("%s %s (%s.%s.%s) %s" % (self.breed,flavor,major,minor,release,self.arch))
535
distro.set_os_version(flavor)
536
# is this even valid for debian/ubuntu? - jcammarata
537
#ds = self.get_datestamp()
539
# distro.set_tree_build_time(ds)
540
profile.set_kickstart("/var/lib/cobbler/kickstarts/sample.seed")
541
self.profiles.add(profile,save=True)
543
self.configure_tree_location(distro)
544
self.distros.add(distro,save=True) # re-save
547
def configure_tree_location(self, distro):
549
Once a distribution is identified, find the part of the distribution
550
that has the URL in it that we want to use for kickstarting the
551
distribution, and create a ksmeta variable $tree that contains this.
554
base = self.get_rootdir()
556
if self.network_root is None:
557
dists_path = os.path.join( self.path , "dists" )
558
if os.path.isdir( dists_path ):
559
tree = "http://@@http_server@@/cblr/ks_mirror/%s" % (self.name)
561
tree = "http://@@http_server@@/cblr/repo_mirror/%s" % (distro.name)
562
self.set_install_tree(distro, tree)
564
# where we assign the kickstart source is relative to our current directory
565
# and the input start directory in the crawl. We find the path segments
566
# between and tack them on the network source path to find the explicit
567
# network path to the distro that Anaconda can digest.
568
tail = utils.path_tail(self.path, base)
569
tree = self.network_root[:-1] + tail
570
self.set_install_tree(distro, tree)
572
def get_rootdir(self):
575
def get_pkgdir(self):
578
return os.path.join(self.get_rootdir(),self.pkgdir)
580
def set_install_tree(self, distro, url):
581
distro.ks_meta["tree"] = url
583
def learn_arch_from_tree(self):
585
If a distribution is imported from DVD, there is a good chance the path doesn't
586
contain the arch and we should add it back in so that it's part of the
587
meaningful name ... so this code helps figure out the arch name. This is important
588
for producing predictable distro names (and profile names) from differing import sources
591
# FIXME : this is called only once, should not be a walk
592
if self.get_pkgdir():
593
os.path.walk(self.get_pkgdir(), self.arch_walker, result)
594
if result.pop("amd64",False):
596
if result.pop("i686",False):
598
if result.pop("x86",False):
602
def match_kernelarch_file(self, filename):
604
Is the given filename a kernel filename?
606
if not filename.endswith("deb"):
608
if filename.startswith("linux-headers-"):
612
def scan_pkg_filename(self, file):
614
Determine what the distro is based on the release package filename.
616
# FIXME: all of these dist_names should probably be put in a function
617
# which would be called in place of looking in codes.py. Right now
618
# you have to update both codes.py and this to add a new release
619
if self.breed == "debian":
620
dist_names = ['etch','lenny',]
621
elif self.breed == "ubuntu":
622
dist_names = ['dapper','hardy','intrepid','jaunty','karmic','lynx','maverick','natty',]
626
if os.path.basename(file) in dist_names:
627
release_file = os.path.join(file,'Release')
628
self.logger.info("Found %s release file: %s" % (self.breed,release_file))
630
f = open(release_file,'r')
631
lines = f.readlines()
635
if line.lower().startswith('version: '):
636
version = line.split(':')[1].strip()
637
values = version.split('.')
639
# I don't think you'd ever hit this currently with debian or ubuntu,
640
# just including it for safety reasons
641
return (os.path.basename(file), values[0], "0", "0")
642
elif len(values) == 2:
643
return (os.path.basename(file), values[0], values[1], "0")
644
elif len(values) > 2:
645
return (os.path.basename(file), values[0], values[1], values[2])
648
def get_datestamp(self):
650
Not used for debian/ubuntu... should probably be removed? - jcammarata
654
def set_variance(self, flavor, major, minor, arch):
656
Set distro specific versioning.
658
# I don't think this is required anymore, as the scan_pkg_filename() function
659
# above does everything we need it to - jcammarata
661
#if self.breed == "debian":
662
# dist_names = { '4.0' : "etch" , '5.0' : "lenny" }
663
# dist_vers = "%s.%s" % ( major , minor )
664
# os_version = dist_names[dist_vers]
666
# return os_version , "/var/lib/cobbler/kickstarts/sample.seed"
667
#elif self.breed == "ubuntu":
668
# # Release names taken from wikipedia
669
# dist_names = { '6.4' :"dapper",
671
# '8.10' :"intrepid",
675
# '10.10':"maverick",
678
# dist_vers = "%s.%s" % ( major , minor )
679
# if not dist_names.has_key( dist_vers ):
680
# dist_names['4ubuntu2.0'] = "IntrepidIbex"
681
# os_version = dist_names[dist_vers]
683
# return os_version , "/var/lib/cobbler/kickstarts/sample.seed"
688
def process_repos(self, main_importer, distro):
689
# Create a disabled repository for the new distro, and the security updates
691
# NOTE : We cannot use ks_meta nor os_version because they get fixed at a later stage
693
# Obtain repo mirror from APT if available
696
# Example returned URL: http://us.archive.ubuntu.com/ubuntu
697
mirror = self.get_repo_mirror_from_apt()
699
mirror = mirror + "/dists"
701
mirror = "http://archive.ubuntu.com/ubuntu/dists/"
703
repo = item_repo.Repo(main_importer.config)
704
repo.set_breed( "apt" )
705
repo.set_arch( distro.arch )
706
repo.set_keep_updated( False )
707
repo.yumopts["--ignore-release-gpg"] = None
708
repo.yumopts["--verbose"] = None
709
repo.set_name( distro.name )
710
repo.set_os_version( distro.os_version )
712
if distro.breed == "ubuntu":
713
repo.set_mirror( "%s/%s" % (mirror, distro.os_version) )
715
# NOTE : The location of the mirror should come from timezone
716
repo.set_mirror( "http://ftp.%s.debian.org/debian/dists/%s" % ( 'us' , distro.os_version ) )
718
security_repo = item_repo.Repo(main_importer.config)
719
security_repo.set_breed( "apt" )
720
security_repo.set_arch( distro.arch )
721
security_repo.set_keep_updated( False )
722
security_repo.yumopts["--ignore-release-gpg"] = None
723
security_repo.yumopts["--verbose"] = None
724
security_repo.set_name( distro.name + "-security" )
725
security_repo.set_os_version( distro.os_version )
726
# There are no official mirrors for security updates
727
if distro.breed == "ubuntu":
728
security_repo.set_mirror( "%s/%s-security" % (mirror, distro.os_version) )
730
security_repo.set_mirror( "http://security.debian.org/debian-security/dists/%s/updates" % distro.os_version )
732
self.logger.info("Added repos for %s" % distro.name)
733
repos = main_importer.config.repos()
734
repos.add(repo,save=True)
735
repos.add(security_repo,save=True)
737
# ==========================================================================
739
def get_import_manager(config,logger):
740
return ImportDebianUbuntuManager(config,logger)