From 5c0dc39c0805f850104e773fd4046962ac384af0 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Thu, 1 Aug 2019 13:25:20 -0400 Subject: [PATCH] import cobbler-2.0.7.1-6.module+el8.1.0+3115+d1f204ad --- .cobbler.metadata | 1 + .gitignore | 1 + ...s-module-doesn-t-have-to-be-imported.patch | 99 + ...IENT_PYTHON-stuff-and-unused-imports.patch | 3541 +++++++++++++++++ ...-urllib2-and-local-imports-in-Python.patch | 195 + SOURCES/0004-Python-3-compatible-prints.patch | 715 ++++ .../0005-Python-3-compatible-exceptions.patch | 448 +++ SOURCES/0006-octal-number-Python-3-fix.patch | 25 + ...ython-3-compatible-string-operations.patch | 111 + SOURCES/0008-do-not-require-urlgrabber.patch | 33 + .../0009-replace-iteritems-with-items.patch | 25 + ...0010-open-target-file-in-binary-mode.patch | 25 + SOURCES/0011-make-sure-it-s-a-string.patch | 34 + SOURCES/0012-make-sure-list-is-returned.patch | 25 + ...thon-3-ethtool-and-indentation-fixes.patch | 121 + SOURCES/0014-has_key-is-not-in-Python-3.patch | 129 + ...ts-don-t-work-on-both-Python-2-and-3.patch | 394 ++ ...ys-and-sort-doesn-t-work-on-Python-3.patch | 26 + ...-raise-is-a-function-call-in-python3.patch | 671 ++++ ...-adapt-setup.py-for-both-py2-and-py3.patch | 518 +++ SOURCES/buildiso-boot-options.patch | 19 + SOURCES/buildiso-no-local-hdd.patch | 135 + SOURCES/cobbler-bootproto-post-install.patch | 17 + SOURCES/cobbler-buildiso.patch | 222 ++ SOURCES/cobbler-bz-253274.patch | 92 + SOURCES/cobbler-bz1052857.patch | 53 + SOURCES/cobbler-catch-cheetah-exception.patch | 68 + SOURCES/cobbler-concurrency.patch | 141 + SOURCES/cobbler-daemon.patch | 17 + ...bbler-disable-check-selinux-bz706857.patch | 12 + .../cobbler-disable-hardlinks-bz568801.patch | 26 + SOURCES/cobbler-findks.patch | 76 + SOURCES/cobbler-ipv6-snippet.patch | 392 ++ SOURCES/cobbler-ipv6-xmlrpc.patch | 134 + SOURCES/cobbler-keep-ssh-snippet.patch | 14 + SOURCES/cobbler-koan-rhpl.patch | 56 + SOURCES/cobbler-lvm-installation.patch | 11 + SOURCES/cobbler-lvm-selinux.patch | 12 + SOURCES/cobbler-modprobe-d.patch | 67 + SOURCES/cobbler-netaddr.patch | 23 + SOURCES/cobbler-nic-dash.patch | 12 + SOURCES/cobbler-no-remove-pub-bz707215.patch | 11 + ...obbler-post-install-network-defaults.patch | 59 + SOURCES/cobbler-power-status.patch | 108 + SOURCES/cobbler-power-vulnerability.patch | 127 + SOURCES/cobbler-pxelinux-s390x-bz580072.patch | 14 + SOURCES/cobbler-remote-addr.patch | 13 + SOURCES/cobbler-rhel6-bonding.patch | 13 + SOURCES/cobbler-rhel7-distros.patch | 12 + SOURCES/cobbler-rhel7-snippets.patch | 54 + SOURCES/cobbler-s390-kernel-options.patch | 21 + SOURCES/cobbler-token-validation.patch | 25 + SOURCES/cobbler-triggers.patch | 23 + SOURCES/cobbler-unicode-scripts.patch | 29 + .../cobbler-updating-logrotate-config.patch | 27 + SOURCES/cobbler-uudecode.patch | 39 + SOURCES/cobbler-xenpv-tap-driver.patch | 50 + SOURCES/koan-bz1699743.patch | 63 + SOURCES/koan-cmdline-length.patch | 51 + SOURCES/koan-el6-ks-embed.patch | 11 + SOURCES/koan-fix-TypeError.patch | 31 + SOURCES/koan-grubby480.patch | 11 + SOURCES/koan-no-check_output.patch | 19 + SOURCES/koan-remove-root-argument.patch | 21 + SOURCES/koan-rhel7-initramfs-embedding.patch | 21 + SOURCES/koan-rhel7-ppc.patch | 23 + SOURCES/koan-rhel7-virtinst.patch | 1166 ++++++ SOURCES/koan-rhel71.patch | 34 + SOURCES/koan-s390-kernel-options-parse.patch | 12 + SOURCES/koan-support-kvm-type.patch | 66 + SOURCES/koan-support-osinfo-query.patch | 51 + SOURCES/koan-virt-install-options.patch | 103 + SOURCES/koan-xz-initrd.patch | 23 + SOURCES/koan_no_selinux_set.patch | 37 + SPECS/cobbler.spec | 683 ++++ 75 files changed, 11787 insertions(+) create mode 100644 .cobbler.metadata create mode 100644 .gitignore create mode 100644 SOURCES/0001-exceptions-module-doesn-t-have-to-be-imported.patch create mode 100644 SOURCES/0002-cleanup-ANCIENT_PYTHON-stuff-and-unused-imports.patch create mode 100644 SOURCES/0003-fixing-xmlrpclib-urllib2-and-local-imports-in-Python.patch create mode 100644 SOURCES/0004-Python-3-compatible-prints.patch create mode 100644 SOURCES/0005-Python-3-compatible-exceptions.patch create mode 100644 SOURCES/0006-octal-number-Python-3-fix.patch create mode 100644 SOURCES/0007-Python-3-compatible-string-operations.patch create mode 100644 SOURCES/0008-do-not-require-urlgrabber.patch create mode 100644 SOURCES/0009-replace-iteritems-with-items.patch create mode 100644 SOURCES/0010-open-target-file-in-binary-mode.patch create mode 100644 SOURCES/0011-make-sure-it-s-a-string.patch create mode 100644 SOURCES/0012-make-sure-list-is-returned.patch create mode 100644 SOURCES/0013-Python-3-ethtool-and-indentation-fixes.patch create mode 100644 SOURCES/0014-has_key-is-not-in-Python-3.patch create mode 100644 SOURCES/0015-relative-imports-don-t-work-on-both-Python-2-and-3.patch create mode 100644 SOURCES/0016-keys-and-sort-doesn-t-work-on-Python-3.patch create mode 100644 SOURCES/0017-raise-is-a-function-call-in-python3.patch create mode 100644 SOURCES/0018-adapt-setup.py-for-both-py2-and-py3.patch create mode 100644 SOURCES/buildiso-boot-options.patch create mode 100644 SOURCES/buildiso-no-local-hdd.patch create mode 100644 SOURCES/cobbler-bootproto-post-install.patch create mode 100644 SOURCES/cobbler-buildiso.patch create mode 100644 SOURCES/cobbler-bz-253274.patch create mode 100644 SOURCES/cobbler-bz1052857.patch create mode 100644 SOURCES/cobbler-catch-cheetah-exception.patch create mode 100644 SOURCES/cobbler-concurrency.patch create mode 100644 SOURCES/cobbler-daemon.patch create mode 100644 SOURCES/cobbler-disable-check-selinux-bz706857.patch create mode 100644 SOURCES/cobbler-disable-hardlinks-bz568801.patch create mode 100644 SOURCES/cobbler-findks.patch create mode 100644 SOURCES/cobbler-ipv6-snippet.patch create mode 100644 SOURCES/cobbler-ipv6-xmlrpc.patch create mode 100644 SOURCES/cobbler-keep-ssh-snippet.patch create mode 100644 SOURCES/cobbler-koan-rhpl.patch create mode 100644 SOURCES/cobbler-lvm-installation.patch create mode 100644 SOURCES/cobbler-lvm-selinux.patch create mode 100644 SOURCES/cobbler-modprobe-d.patch create mode 100644 SOURCES/cobbler-netaddr.patch create mode 100644 SOURCES/cobbler-nic-dash.patch create mode 100644 SOURCES/cobbler-no-remove-pub-bz707215.patch create mode 100644 SOURCES/cobbler-post-install-network-defaults.patch create mode 100644 SOURCES/cobbler-power-status.patch create mode 100644 SOURCES/cobbler-power-vulnerability.patch create mode 100644 SOURCES/cobbler-pxelinux-s390x-bz580072.patch create mode 100644 SOURCES/cobbler-remote-addr.patch create mode 100644 SOURCES/cobbler-rhel6-bonding.patch create mode 100644 SOURCES/cobbler-rhel7-distros.patch create mode 100644 SOURCES/cobbler-rhel7-snippets.patch create mode 100644 SOURCES/cobbler-s390-kernel-options.patch create mode 100644 SOURCES/cobbler-token-validation.patch create mode 100644 SOURCES/cobbler-triggers.patch create mode 100644 SOURCES/cobbler-unicode-scripts.patch create mode 100644 SOURCES/cobbler-updating-logrotate-config.patch create mode 100644 SOURCES/cobbler-uudecode.patch create mode 100644 SOURCES/cobbler-xenpv-tap-driver.patch create mode 100644 SOURCES/koan-bz1699743.patch create mode 100644 SOURCES/koan-cmdline-length.patch create mode 100644 SOURCES/koan-el6-ks-embed.patch create mode 100644 SOURCES/koan-fix-TypeError.patch create mode 100644 SOURCES/koan-grubby480.patch create mode 100644 SOURCES/koan-no-check_output.patch create mode 100644 SOURCES/koan-remove-root-argument.patch create mode 100644 SOURCES/koan-rhel7-initramfs-embedding.patch create mode 100644 SOURCES/koan-rhel7-ppc.patch create mode 100644 SOURCES/koan-rhel7-virtinst.patch create mode 100644 SOURCES/koan-rhel71.patch create mode 100644 SOURCES/koan-s390-kernel-options-parse.patch create mode 100644 SOURCES/koan-support-kvm-type.patch create mode 100644 SOURCES/koan-support-osinfo-query.patch create mode 100644 SOURCES/koan-virt-install-options.patch create mode 100644 SOURCES/koan-xz-initrd.patch create mode 100644 SOURCES/koan_no_selinux_set.patch create mode 100644 SPECS/cobbler.spec diff --git a/.cobbler.metadata b/.cobbler.metadata new file mode 100644 index 0000000..5818813 --- /dev/null +++ b/.cobbler.metadata @@ -0,0 +1 @@ +82da77e241cd6b12e99cdfcc577dc2f855017164 SOURCES/cobbler-2.0.7.1.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ab191f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/cobbler-2.0.7.1.tar.gz diff --git a/SOURCES/0001-exceptions-module-doesn-t-have-to-be-imported.patch b/SOURCES/0001-exceptions-module-doesn-t-have-to-be-imported.patch new file mode 100644 index 0000000..d9a5594 --- /dev/null +++ b/SOURCES/0001-exceptions-module-doesn-t-have-to-be-imported.patch @@ -0,0 +1,99 @@ +From 8d1dbdc5e56b32ad1d6493904d30e85aa3b5fa2b Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 10:47:30 +0100 +Subject: [PATCH 01/17] exceptions module doesn't have to be imported + +--- + koan/app.py | 3 +-- + koan/register.py | 3 +-- + koan/utils.py | 3 +-- + koan/vmwcreate.py | 3 +-- + 4 files changed, 4 insertions(+), 8 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index e0b2d66..d911dad 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -49,7 +49,6 @@ except: + True = 1 + False = 0 + +-import exceptions + import time + import shutil + import errno +@@ -252,7 +251,7 @@ def main(): + + #======================================================= + +-class InfoException(exceptions.Exception): ++class InfoException(Exception): + """ + Custom exception for tracking of fatal errors. + """ +diff --git a/koan/register.py b/koan/register.py +index 0d30c7b..9924d85 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -27,7 +27,6 @@ try: + from optparse import OptionParser + except: + from opt_parse import OptionParser # importing this for backwards compat with 2.2 +-import exceptions + try: + import subprocess as sub_process + except: +@@ -100,7 +99,7 @@ def main(): + + #======================================================= + +-class InfoException(exceptions.Exception): ++class InfoException(Exception): + """ + Custom exception for tracking of fatal errors. + """ +diff --git a/koan/utils.py b/koan/utils.py +index 7fcc12d..475bcd0 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -25,7 +25,6 @@ import random + import os + import traceback + import tempfile +-import exceptions + ANCIENT_PYTHON = 0 + try: + try: +@@ -59,7 +58,7 @@ VIRT_STATE_NAME_MAP = { + 6 : "crashed" + } + +-class InfoException(exceptions.Exception): ++class InfoException(Exception): + """ + Custom exception for tracking of fatal errors. + """ +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index f5603d8..1bde891 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -25,7 +25,6 @@ import os, sys, time, stat + import tempfile + import random + from optparse import OptionParser +-import exceptions + import errno + import re + import virtinst +@@ -64,7 +63,7 @@ memsize = "%(MEMORY)s" + """ + #ide1:0.filename = "%(PATH_TO_ISO)s" + +-class VirtCreateException(exceptions.Exception): ++class VirtCreateException(Exception): + pass + + def random_mac(): +-- +2.5.5 + diff --git a/SOURCES/0002-cleanup-ANCIENT_PYTHON-stuff-and-unused-imports.patch b/SOURCES/0002-cleanup-ANCIENT_PYTHON-stuff-and-unused-imports.patch new file mode 100644 index 0000000..1fa5752 --- /dev/null +++ b/SOURCES/0002-cleanup-ANCIENT_PYTHON-stuff-and-unused-imports.patch @@ -0,0 +1,3541 @@ +From 3b70442331338696847abc23b8e0d8d4f9618bba Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:06:22 +0100 +Subject: [PATCH 02/17] cleanup ANCIENT_PYTHON stuff and unused imports + +--- + koan/app.py | 98 +-- + koan/opt_parse.py | 1682 --------------------------------------------------- + koan/register.py | 13 +- + koan/sub_process.py | 1149 ----------------------------------- + koan/text_wrap.py | 354 ----------- + koan/utils.py | 50 +- + koan/vmwcreate.py | 6 +- + 7 files changed, 39 insertions(+), 3313 deletions(-) + delete mode 100644 koan/opt_parse.py + delete mode 100644 koan/sub_process.py + delete mode 100644 koan/text_wrap.py + +diff --git a/koan/app.py b/koan/app.py +index d911dad..801fedd 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -27,40 +27,19 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + import random + import os + import traceback +-import tempfile + import shlex + +-ANCIENT_PYTHON = 0 +-try: +- try: +- from optparse import OptionParser +- except: +- from opt_parse import OptionParser # importing this for backwards compat with 2.2 +- try: +- import subprocess as sub_process +- except: +- import sub_process +-except: +- # the main "replace-self" codepath of koan must support +- # Python 1.5. Other sections may use 2.3 features (nothing newer) +- # provided they are conditionally imported. This is to support +- # EL 2.1. -- mpd +- ANCIENT_PYTHON = 1 +- True = 1 +- False = 0 ++from optparse import OptionParser ++import subprocess as sub_process + + import time + import shutil + import errno +-import re + import sys + import xmlrpclib + import string + import re +-import glob +-import socket + import utils +-import time + + COBBLER_REQUIRED = 1.300 + +@@ -94,11 +73,6 @@ def main(): + # most likely running RHEL3, where we don't need virt logging anyway + pass + +- if ANCIENT_PYTHON: +- print "- command line usage on this version of python is unsupported" +- print "- usage via spacewalk APIs only. Python x>=2.3 required" +- return +- + p = OptionParser() + p.add_option("-k", "--kopts", + dest="kopts_override", +@@ -538,16 +512,15 @@ class Koan: + else: + # FIXME: auto never selects vmware, maybe it should if we find it? + +- if not ANCIENT_PYTHON: +- cmd = sub_process.Popen("/bin/uname -r", stdout=sub_process.PIPE, shell=True) +- uname_str = cmd.communicate()[0] +- if uname_str.find("xen") != -1: +- self.virt_type = "xenpv" +- elif os.path.exists("/usr/bin/qemu-img"): +- self.virt_type = "qemu" +- else: +- # assume Xen, we'll check to see if virt-type is really usable later. +- raise InfoException, "Not running a Xen kernel and qemu is not installed" ++ cmd = sub_process.Popen("/bin/uname -r", stdout=sub_process.PIPE, shell=True) ++ uname_str = cmd.communicate()[0] ++ if uname_str.find("xen") != -1: ++ self.virt_type = "xenpv" ++ elif os.path.exists("/usr/bin/qemu-img"): ++ self.virt_type = "qemu" ++ else: ++ # assume Xen, we'll check to see if virt-type is really usable later. ++ raise InfoException, "Not running a Xen kernel and qemu is not installed" + + print "- no virt-type specified, auto-selecting %s" % self.virt_type + +@@ -811,12 +784,6 @@ class Koan: + #--------------------------------------------------- + + def get_boot_loader_info(self): +- if ANCIENT_PYTHON: +- # FIXME: implement this to work w/o subprocess +- if os.path.exists("/etc/grub.conf"): +- return (0, "grub") +- else: +- return (0, "lilo") + cmd = [ "/sbin/grubby", "--bootloader-probe" ] + probe_process = sub_process.Popen(cmd, stdout=sub_process.PIPE) + which_loader = probe_process.communicate()[0] +@@ -862,11 +829,8 @@ class Koan: + profile_data + ) + +- if not ANCIENT_PYTHON: +- arch_cmd = sub_process.Popen("/bin/uname -m", stdout=sub_process.PIPE, shell=True) +- arch = arch_cmd.communicate()[0] +- else: +- arch = "i386" ++ arch_cmd = sub_process.Popen("/bin/uname -m", stdout=sub_process.PIPE, shell=True) ++ arch = arch_cmd.communicate()[0] + + # Validate kernel argument length (limit depends on architecture -- + # see asm-*/setup.h). For example: +@@ -875,15 +839,14 @@ class Koan: + # asm-powerpc/setup.h:#define COMMAND_LINE_SIZE 512 + # asm-s390/setup.h:#define COMMAND_LINE_SIZE 896 + # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 +- if not ANCIENT_PYTHON: +- if arch.startswith("ppc") or arch.startswith("ia64"): +- if len(k_args) > 511: +- raise InfoException, "Kernel options are too long, 512 chars exceeded: %s" % k_args +- elif arch.startswith("s390"): +- if len(k_args) > 895: +- raise InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args +- elif len(k_args) > 2047: +- raise InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args ++ if arch.startswith("ppc") or arch.startswith("ia64"): ++ if len(k_args) > 511: ++ raise InfoException, "Kernel options are too long, 512 chars exceeded: %s" % k_args ++ elif arch.startswith("s390"): ++ if len(k_args) > 895: ++ raise InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args ++ elif len(k_args) > 2047: ++ raise InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args + + cmd = [ "/sbin/grubby", + "--add-kernel", self.safe_load(profile_data,'kernel_local'), +@@ -910,14 +873,13 @@ class Koan: + cmd.append("--config-file=/tmp/boot/boot/grub/grub.conf") + + # Are we running on ppc? +- if not ANCIENT_PYTHON: +- if arch.startswith("ppc"): +- if "grub2" in probe_output: +- cmd.append("--grub2") +- else: +- cmd.append("--yaboot") +- elif arch.startswith("s390"): +- cmd.append("--zipl") ++ if arch.startswith("ppc"): ++ if "grub2" in probe_output: ++ cmd.append("--grub2") ++ else: ++ cmd.append("--yaboot") ++ elif arch.startswith("s390"): ++ cmd.append("--zipl") + + utils.subprocess_call(cmd) + +@@ -933,12 +895,12 @@ class Koan: + utils.subprocess_call(cmd) + + # Any post-grubby processing required (e.g. ybin, zipl, lilo)? +- if not ANCIENT_PYTHON and arch.startswith("ppc") and "grub2" not in probe_output: ++ if arch.startswith("ppc") and "grub2" not in probe_output: + # FIXME - CHRP hardware uses a 'PPC PReP Boot' partition and doesn't require running ybin + print "- applying ybin changes" + cmd = [ "/sbin/ybin" ] + utils.subprocess_call(cmd) +- elif not ANCIENT_PYTHON and arch.startswith("s390"): ++ elif arch.startswith("s390"): + print "- applying zipl changes" + cmd = [ "/sbin/zipl" ] + utils.subprocess_call(cmd) +diff --git a/koan/opt_parse.py b/koan/opt_parse.py +deleted file mode 100644 +index 233d192..0000000 +--- a/koan/opt_parse.py ++++ /dev/null +@@ -1,1682 +0,0 @@ +-"""optparse - a powerful, extensible, and easy-to-use option parser. +- +-By Greg Ward +- +-Originally distributed as Optik; see http://optik.sourceforge.net/ . +- +-If you have problems with this module, please do not file bugs, +-patches, or feature requests with Python; instead, use Optik's +-SourceForge project page: +- http://sourceforge.net/projects/optik +- +-For support, use the optik-users@lists.sourceforge.net mailing list +-(http://lists.sourceforge.net/lists/listinfo/optik-users). +-""" +- +-# Python developers: please do not make changes to this file, since +-# it is automatically generated from the Optik source code. +- +-__version__ = "1.5.3" +- +-__all__ = ['Option', +- 'SUPPRESS_HELP', +- 'SUPPRESS_USAGE', +- 'Values', +- 'OptionContainer', +- 'OptionGroup', +- 'OptionParser', +- 'HelpFormatter', +- 'IndentedHelpFormatter', +- 'TitledHelpFormatter', +- 'OptParseError', +- 'OptionError', +- 'OptionConflictError', +- 'OptionValueError', +- 'BadOptionError'] +- +-__copyright__ = """ +-Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved. +-Copyright (c) 2002-2006 Python Software Foundation. All rights reserved. +- +-Redistribution and use in source and binary forms, with or without +-modification, are permitted provided that the following conditions are +-met: +- +- * Redistributions of source code must retain the above copyright +- notice, this list of conditions and the following disclaimer. +- +- * Redistributions in binary form must reproduce the above copyright +- notice, this list of conditions and the following disclaimer in the +- documentation and/or other materials provided with the distribution. +- +- * Neither the name of the author nor the names of its +- contributors may be used to endorse or promote products derived from +- this software without specific prior written permission. +- +-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +-IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +-TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-""" +- +-import sys, os +-import types +-import text_wrap as textwrap # repackaged here because not in RHEL3 +- +-def _repr(self): +- return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), self) +- +- +-# This file was generated from: +-# Id: option_parser.py 527 2006-07-23 15:21:30Z greg +-# Id: option.py 522 2006-06-11 16:22:03Z gward +-# Id: help.py 527 2006-07-23 15:21:30Z greg +-# Id: errors.py 509 2006-04-20 00:58:24Z gward +- +-try: +- from gettext import gettext +-except ImportError: +- def gettext(message): +- return message +-_ = gettext +- +- +-class OptParseError (Exception): +- def __init__(self, msg): +- self.msg = msg +- +- def __str__(self): +- return self.msg +- +- +-class OptionError (OptParseError): +- """ +- Raised if an Option instance is created with invalid or +- inconsistent arguments. +- """ +- +- def __init__(self, msg, option): +- self.msg = msg +- self.option_id = str(option) +- +- def __str__(self): +- if self.option_id: +- return "option %s: %s" % (self.option_id, self.msg) +- else: +- return self.msg +- +-class OptionConflictError (OptionError): +- """ +- Raised if conflicting options are added to an OptionParser. +- """ +- +-class OptionValueError (OptParseError): +- """ +- Raised if an invalid option value is encountered on the command +- line. +- """ +- +-class BadOptionError (OptParseError): +- """ +- Raised if an invalid option is seen on the command line. +- """ +- def __init__(self, opt_str): +- self.opt_str = opt_str +- +- def __str__(self): +- return _("no such option: %s") % self.opt_str +- +-class AmbiguousOptionError (BadOptionError): +- """ +- Raised if an ambiguous option is seen on the command line. +- """ +- def __init__(self, opt_str, possibilities): +- BadOptionError.__init__(self, opt_str) +- self.possibilities = possibilities +- +- def __str__(self): +- return (_("ambiguous option: %s (%s?)") +- % (self.opt_str, ", ".join(self.possibilities))) +- +- +-class HelpFormatter: +- +- """ +- Abstract base class for formatting option help. OptionParser +- instances should use one of the HelpFormatter subclasses for +- formatting help; by default IndentedHelpFormatter is used. +- +- Instance attributes: +- parser : OptionParser +- the controlling OptionParser instance +- indent_increment : int +- the number of columns to indent per nesting level +- max_help_position : int +- the maximum starting column for option help text +- help_position : int +- the calculated starting column for option help text; +- initially the same as the maximum +- width : int +- total number of columns for output (pass None to constructor for +- this value to be taken from the $COLUMNS environment variable) +- level : int +- current indentation level +- current_indent : int +- current indentation level (in columns) +- help_width : int +- number of columns available for option help text (calculated) +- default_tag : str +- text to replace with each option's default value, "%default" +- by default. Set to false value to disable default value expansion. +- option_strings : { Option : str } +- maps Option instances to the snippet of help text explaining +- the syntax of that option, e.g. "-h, --help" or +- "-fFILE, --file=FILE" +- _short_opt_fmt : str +- format string controlling how short options with values are +- printed in help text. Must be either "%s%s" ("-fFILE") or +- "%s %s" ("-f FILE"), because those are the two syntaxes that +- Optik supports. +- _long_opt_fmt : str +- similar but for long options; must be either "%s %s" ("--file FILE") +- or "%s=%s" ("--file=FILE"). +- """ +- +- NO_DEFAULT_VALUE = "none" +- +- def __init__(self, +- indent_increment, +- max_help_position, +- width, +- short_first): +- self.parser = None +- self.indent_increment = indent_increment +- self.help_position = self.max_help_position = max_help_position +- if width is None: +- try: +- width = int(os.environ['COLUMNS']) +- except (KeyError, ValueError): +- width = 80 +- width -= 2 +- self.width = width +- self.current_indent = 0 +- self.level = 0 +- self.help_width = None # computed later +- self.short_first = short_first +- self.default_tag = "%default" +- self.option_strings = {} +- self._short_opt_fmt = "%s %s" +- self._long_opt_fmt = "%s=%s" +- +- def set_parser(self, parser): +- self.parser = parser +- +- def set_short_opt_delimiter(self, delim): +- if delim not in ("", " "): +- raise ValueError( +- "invalid metavar delimiter for short options: %r" % delim) +- self._short_opt_fmt = "%s" + delim + "%s" +- +- def set_long_opt_delimiter(self, delim): +- if delim not in ("=", " "): +- raise ValueError( +- "invalid metavar delimiter for long options: %r" % delim) +- self._long_opt_fmt = "%s" + delim + "%s" +- +- def indent(self): +- self.current_indent += self.indent_increment +- self.level += 1 +- +- def dedent(self): +- self.current_indent -= self.indent_increment +- assert self.current_indent >= 0, "Indent decreased below 0." +- self.level -= 1 +- +- def format_usage(self, usage): +- raise NotImplementedError, "subclasses must implement" +- +- def format_heading(self, heading): +- raise NotImplementedError, "subclasses must implement" +- +- def _format_text(self, text): +- """ +- Format a paragraph of free-form text for inclusion in the +- help output at the current indentation level. +- """ +- text_width = self.width - self.current_indent +- indent = " "*self.current_indent +- return textwrap.fill(text, +- text_width, +- initial_indent=indent, +- subsequent_indent=indent) +- +- def format_description(self, description): +- if description: +- return self._format_text(description) + "\n" +- else: +- return "" +- +- def format_epilog(self, epilog): +- if epilog: +- return "\n" + self._format_text(epilog) + "\n" +- else: +- return "" +- +- +- def expand_default(self, option): +- if self.parser is None or not self.default_tag: +- return option.help +- +- default_value = self.parser.defaults.get(option.dest) +- if default_value is NO_DEFAULT or default_value is None: +- default_value = self.NO_DEFAULT_VALUE +- +- return option.help.replace(self.default_tag, str(default_value)) +- +- def format_option(self, option): +- # The help for each option consists of two parts: +- # * the opt strings and metavars +- # eg. ("-x", or "-fFILENAME, --file=FILENAME") +- # * the user-supplied help string +- # eg. ("turn on expert mode", "read data from FILENAME") +- # +- # If possible, we write both of these on the same line: +- # -x turn on expert mode +- # +- # But if the opt string list is too long, we put the help +- # string on a second line, indented to the same column it would +- # start in if it fit on the first line. +- # -fFILENAME, --file=FILENAME +- # read data from FILENAME +- result = [] +- opts = self.option_strings[option] +- opt_width = self.help_position - self.current_indent - 2 +- if len(opts) > opt_width: +- opts = "%*s%s\n" % (self.current_indent, "", opts) +- indent_first = self.help_position +- else: # start help on same line as opts +- opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) +- indent_first = 0 +- result.append(opts) +- if option.help: +- help_text = self.expand_default(option) +- help_lines = textwrap.wrap(help_text, self.help_width) +- result.append("%*s%s\n" % (indent_first, "", help_lines[0])) +- result.extend(["%*s%s\n" % (self.help_position, "", line) +- for line in help_lines[1:]]) +- elif opts[-1] != "\n": +- result.append("\n") +- return "".join(result) +- +- def store_option_strings(self, parser): +- self.indent() +- max_len = 0 +- for opt in parser.option_list: +- strings = self.format_option_strings(opt) +- self.option_strings[opt] = strings +- max_len = max(max_len, len(strings) + self.current_indent) +- self.indent() +- for group in parser.option_groups: +- for opt in group.option_list: +- strings = self.format_option_strings(opt) +- self.option_strings[opt] = strings +- max_len = max(max_len, len(strings) + self.current_indent) +- self.dedent() +- self.dedent() +- self.help_position = min(max_len + 2, self.max_help_position) +- self.help_width = self.width - self.help_position +- +- def format_option_strings(self, option): +- """Return a comma-separated list of option strings & metavariables.""" +- if option.takes_value(): +- metavar = option.metavar or option.dest.upper() +- short_opts = [self._short_opt_fmt % (sopt, metavar) +- for sopt in option._short_opts] +- long_opts = [self._long_opt_fmt % (lopt, metavar) +- for lopt in option._long_opts] +- else: +- short_opts = option._short_opts +- long_opts = option._long_opts +- +- if self.short_first: +- opts = short_opts + long_opts +- else: +- opts = long_opts + short_opts +- +- return ", ".join(opts) +- +-class IndentedHelpFormatter (HelpFormatter): +- """Format help with indented section bodies. +- """ +- +- def __init__(self, +- indent_increment=2, +- max_help_position=24, +- width=None, +- short_first=1): +- HelpFormatter.__init__( +- self, indent_increment, max_help_position, width, short_first) +- +- def format_usage(self, usage): +- return _("Usage: %s\n") % usage +- +- def format_heading(self, heading): +- return "%*s%s:\n" % (self.current_indent, "", heading) +- +- +-class TitledHelpFormatter (HelpFormatter): +- """Format help with underlined section headers. +- """ +- +- def __init__(self, +- indent_increment=0, +- max_help_position=24, +- width=None, +- short_first=0): +- HelpFormatter.__init__ ( +- self, indent_increment, max_help_position, width, short_first) +- +- def format_usage(self, usage): +- return "%s %s\n" % (self.format_heading(_("Usage")), usage) +- +- def format_heading(self, heading): +- return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) +- +- +-def _parse_num(val, type): +- if val[:2].lower() == "0x": # hexadecimal +- radix = 16 +- elif val[:2].lower() == "0b": # binary +- radix = 2 +- val = val[2:] or "0" # have to remove "0b" prefix +- elif val[:1] == "0": # octal +- radix = 8 +- else: # decimal +- radix = 10 +- +- return type(val, radix) +- +-def _parse_int(val): +- return _parse_num(val, int) +- +-def _parse_long(val): +- return _parse_num(val, long) +- +-_builtin_cvt = { "int" : (_parse_int, _("integer")), +- "long" : (_parse_long, _("long integer")), +- "float" : (float, _("floating-point")), +- "complex" : (complex, _("complex")) } +- +-def check_builtin(option, opt, value): +- (cvt, what) = _builtin_cvt[option.type] +- try: +- return cvt(value) +- except ValueError: +- raise OptionValueError( +- _("option %s: invalid %s value: %r") % (opt, what, value)) +- +-def check_choice(option, opt, value): +- if value in option.choices: +- return value +- else: +- choices = ", ".join(map(repr, option.choices)) +- raise OptionValueError( +- _("option %s: invalid choice: %r (choose from %s)") +- % (opt, value, choices)) +- +-# Not supplying a default is different from a default of None, +-# so we need an explicit "not supplied" value. +-NO_DEFAULT = ("NO", "DEFAULT") +- +- +-class Option: +- """ +- Instance attributes: +- _short_opts : [string] +- _long_opts : [string] +- +- action : string +- type : string +- dest : string +- default : any +- nargs : int +- const : any +- choices : [string] +- callback : function +- callback_args : (any*) +- callback_kwargs : { string : any } +- help : string +- metavar : string +- """ +- +- # The list of instance attributes that may be set through +- # keyword args to the constructor. +- ATTRS = ['action', +- 'type', +- 'dest', +- 'default', +- 'nargs', +- 'const', +- 'choices', +- 'callback', +- 'callback_args', +- 'callback_kwargs', +- 'help', +- 'metavar'] +- +- # The set of actions allowed by option parsers. Explicitly listed +- # here so the constructor can validate its arguments. +- ACTIONS = ("store", +- "store_const", +- "store_true", +- "store_false", +- "append", +- "append_const", +- "count", +- "callback", +- "help", +- "version") +- +- # The set of actions that involve storing a value somewhere; +- # also listed just for constructor argument validation. (If +- # the action is one of these, there must be a destination.) +- STORE_ACTIONS = ("store", +- "store_const", +- "store_true", +- "store_false", +- "append", +- "append_const", +- "count") +- +- # The set of actions for which it makes sense to supply a value +- # type, ie. which may consume an argument from the command line. +- TYPED_ACTIONS = ("store", +- "append", +- "callback") +- +- # The set of actions which *require* a value type, ie. that +- # always consume an argument from the command line. +- ALWAYS_TYPED_ACTIONS = ("store", +- "append") +- +- # The set of actions which take a 'const' attribute. +- CONST_ACTIONS = ("store_const", +- "append_const") +- +- # The set of known types for option parsers. Again, listed here for +- # constructor argument validation. +- TYPES = ("string", "int", "long", "float", "complex", "choice") +- +- # Dictionary of argument checking functions, which convert and +- # validate option arguments according to the option type. +- # +- # Signature of checking functions is: +- # check(option : Option, opt : string, value : string) -> any +- # where +- # option is the Option instance calling the checker +- # opt is the actual option seen on the command-line +- # (eg. "-a", "--file") +- # value is the option argument seen on the command-line +- # +- # The return value should be in the appropriate Python type +- # for option.type -- eg. an integer if option.type == "int". +- # +- # If no checker is defined for a type, arguments will be +- # unchecked and remain strings. +- TYPE_CHECKER = { "int" : check_builtin, +- "long" : check_builtin, +- "float" : check_builtin, +- "complex": check_builtin, +- "choice" : check_choice, +- } +- +- +- # CHECK_METHODS is a list of unbound method objects; they are called +- # by the constructor, in order, after all attributes are +- # initialized. The list is created and filled in later, after all +- # the methods are actually defined. (I just put it here because I +- # like to define and document all class attributes in the same +- # place.) Subclasses that add another _check_*() method should +- # define their own CHECK_METHODS list that adds their check method +- # to those from this class. +- CHECK_METHODS = None +- +- +- # -- Constructor/initialization methods ---------------------------- +- +- def __init__(self, *opts, **attrs): +- # Set _short_opts, _long_opts attrs from 'opts' tuple. +- # Have to be set now, in case no option strings are supplied. +- self._short_opts = [] +- self._long_opts = [] +- opts = self._check_opt_strings(opts) +- self._set_opt_strings(opts) +- +- # Set all other attrs (action, type, etc.) from 'attrs' dict +- self._set_attrs(attrs) +- +- # Check all the attributes we just set. There are lots of +- # complicated interdependencies, but luckily they can be farmed +- # out to the _check_*() methods listed in CHECK_METHODS -- which +- # could be handy for subclasses! The one thing these all share +- # is that they raise OptionError if they discover a problem. +- for checker in self.CHECK_METHODS: +- checker(self) +- +- def _check_opt_strings(self, opts): +- # Filter out None because early versions of Optik had exactly +- # one short option and one long option, either of which +- # could be None. +- opts = filter(None, opts) +- if not opts: +- raise TypeError("at least one option string must be supplied") +- return opts +- +- def _set_opt_strings(self, opts): +- for opt in opts: +- if len(opt) < 2: +- raise OptionError( +- "invalid option string %r: " +- "must be at least two characters long" % opt, self) +- elif len(opt) == 2: +- if not (opt[0] == "-" and opt[1] != "-"): +- raise OptionError( +- "invalid short option string %r: " +- "must be of the form -x, (x any non-dash char)" % opt, +- self) +- self._short_opts.append(opt) +- else: +- if not (opt[0:2] == "--" and opt[2] != "-"): +- raise OptionError( +- "invalid long option string %r: " +- "must start with --, followed by non-dash" % opt, +- self) +- self._long_opts.append(opt) +- +- def _set_attrs(self, attrs): +- for attr in self.ATTRS: +- if attrs.has_key(attr): +- setattr(self, attr, attrs[attr]) +- del attrs[attr] +- else: +- if attr == 'default': +- setattr(self, attr, NO_DEFAULT) +- else: +- setattr(self, attr, None) +- if attrs: +- attrs = attrs.keys() +- attrs.sort() +- raise OptionError( +- "invalid keyword arguments: %s" % ", ".join(attrs), +- self) +- +- +- # -- Constructor validation methods -------------------------------- +- +- def _check_action(self): +- if self.action is None: +- self.action = "store" +- elif self.action not in self.ACTIONS: +- raise OptionError("invalid action: %r" % self.action, self) +- +- def _check_type(self): +- if self.type is None: +- if self.action in self.ALWAYS_TYPED_ACTIONS: +- if self.choices is not None: +- # The "choices" attribute implies "choice" type. +- self.type = "choice" +- else: +- # No type given? "string" is the most sensible default. +- self.type = "string" +- else: +- # Allow type objects or builtin type conversion functions +- # (int, str, etc.) as an alternative to their names. (The +- # complicated check of __builtin__ is only necessary for +- # Python 2.1 and earlier, and is short-circuited by the +- # first check on modern Pythons.) +- import __builtin__ +- if ( type(self.type) is types.TypeType or +- (hasattr(self.type, "__name__") and +- getattr(__builtin__, self.type.__name__, None) is self.type) ): +- self.type = self.type.__name__ +- +- if self.type == "str": +- self.type = "string" +- +- if self.type not in self.TYPES: +- raise OptionError("invalid option type: %r" % self.type, self) +- if self.action not in self.TYPED_ACTIONS: +- raise OptionError( +- "must not supply a type for action %r" % self.action, self) +- +- def _check_choice(self): +- if self.type == "choice": +- if self.choices is None: +- raise OptionError( +- "must supply a list of choices for type 'choice'", self) +- elif type(self.choices) not in (types.TupleType, types.ListType): +- raise OptionError( +- "choices must be a list of strings ('%s' supplied)" +- % str(type(self.choices)).split("'")[1], self) +- elif self.choices is not None: +- raise OptionError( +- "must not supply choices for type %r" % self.type, self) +- +- def _check_dest(self): +- # No destination given, and we need one for this action. The +- # self.type check is for callbacks that take a value. +- takes_value = (self.action in self.STORE_ACTIONS or +- self.type is not None) +- if self.dest is None and takes_value: +- +- # Glean a destination from the first long option string, +- # or from the first short option string if no long options. +- if self._long_opts: +- # eg. "--foo-bar" -> "foo_bar" +- self.dest = self._long_opts[0][2:].replace('-', '_') +- else: +- self.dest = self._short_opts[0][1] +- +- def _check_const(self): +- if self.action not in self.CONST_ACTIONS and self.const is not None: +- raise OptionError( +- "'const' must not be supplied for action %r" % self.action, +- self) +- +- def _check_nargs(self): +- if self.action in self.TYPED_ACTIONS: +- if self.nargs is None: +- self.nargs = 1 +- elif self.nargs is not None: +- raise OptionError( +- "'nargs' must not be supplied for action %r" % self.action, +- self) +- +- def _check_callback(self): +- if self.action == "callback": +- if not callable(self.callback): +- raise OptionError( +- "callback not callable: %r" % self.callback, self) +- if (self.callback_args is not None and +- type(self.callback_args) is not types.TupleType): +- raise OptionError( +- "callback_args, if supplied, must be a tuple: not %r" +- % self.callback_args, self) +- if (self.callback_kwargs is not None and +- type(self.callback_kwargs) is not types.DictType): +- raise OptionError( +- "callback_kwargs, if supplied, must be a dict: not %r" +- % self.callback_kwargs, self) +- else: +- if self.callback is not None: +- raise OptionError( +- "callback supplied (%r) for non-callback option" +- % self.callback, self) +- if self.callback_args is not None: +- raise OptionError( +- "callback_args supplied for non-callback option", self) +- if self.callback_kwargs is not None: +- raise OptionError( +- "callback_kwargs supplied for non-callback option", self) +- +- +- CHECK_METHODS = [_check_action, +- _check_type, +- _check_choice, +- _check_dest, +- _check_const, +- _check_nargs, +- _check_callback] +- +- +- # -- Miscellaneous methods ----------------------------------------- +- +- def __str__(self): +- return "/".join(self._short_opts + self._long_opts) +- +- __repr__ = _repr +- +- def takes_value(self): +- return self.type is not None +- +- def get_opt_string(self): +- if self._long_opts: +- return self._long_opts[0] +- else: +- return self._short_opts[0] +- +- +- # -- Processing methods -------------------------------------------- +- +- def check_value(self, opt, value): +- checker = self.TYPE_CHECKER.get(self.type) +- if checker is None: +- return value +- else: +- return checker(self, opt, value) +- +- def convert_value(self, opt, value): +- if value is not None: +- if self.nargs == 1: +- return self.check_value(opt, value) +- else: +- return tuple([self.check_value(opt, v) for v in value]) +- +- def process(self, opt, value, values, parser): +- +- # First, convert the value(s) to the right type. Howl if any +- # value(s) are bogus. +- value = self.convert_value(opt, value) +- +- # And then take whatever action is expected of us. +- # This is a separate method to make life easier for +- # subclasses to add new actions. +- return self.take_action( +- self.action, self.dest, opt, value, values, parser) +- +- def take_action(self, action, dest, opt, value, values, parser): +- if action == "store": +- setattr(values, dest, value) +- elif action == "store_const": +- setattr(values, dest, self.const) +- elif action == "store_true": +- setattr(values, dest, True) +- elif action == "store_false": +- setattr(values, dest, False) +- elif action == "append": +- values.ensure_value(dest, []).append(value) +- elif action == "append_const": +- values.ensure_value(dest, []).append(self.const) +- elif action == "count": +- setattr(values, dest, values.ensure_value(dest, 0) + 1) +- elif action == "callback": +- args = self.callback_args or () +- kwargs = self.callback_kwargs or {} +- self.callback(self, opt, value, parser, *args, **kwargs) +- elif action == "help": +- parser.print_help() +- parser.exit() +- elif action == "version": +- parser.print_version() +- parser.exit() +- else: +- raise RuntimeError, "unknown action %r" % self.action +- +- return 1 +- +-# class Option +- +- +-SUPPRESS_HELP = "SUPPRESS"+"HELP" +-SUPPRESS_USAGE = "SUPPRESS"+"USAGE" +- +-# For compatibility with Python 2.2 +-try: +- True, False +-except NameError: +- (True, False) = (1, 0) +- +-def isbasestring(x): +- return isinstance(x, types.StringType) or isinstance(x, types.UnicodeType) +- +-class Values: +- +- def __init__(self, defaults=None): +- if defaults: +- for (attr, val) in defaults.items(): +- setattr(self, attr, val) +- +- def __str__(self): +- return str(self.__dict__) +- +- __repr__ = _repr +- +- def __cmp__(self, other): +- if isinstance(other, Values): +- return cmp(self.__dict__, other.__dict__) +- elif isinstance(other, types.DictType): +- return cmp(self.__dict__, other) +- else: +- return -1 +- +- def _update_careful(self, dict): +- """ +- Update the option values from an arbitrary dictionary, but only +- use keys from dict that already have a corresponding attribute +- in self. Any keys in dict without a corresponding attribute +- are silently ignored. +- """ +- for attr in dir(self): +- if dict.has_key(attr): +- dval = dict[attr] +- if dval is not None: +- setattr(self, attr, dval) +- +- def _update_loose(self, dict): +- """ +- Update the option values from an arbitrary dictionary, +- using all keys from the dictionary regardless of whether +- they have a corresponding attribute in self or not. +- """ +- self.__dict__.update(dict) +- +- def _update(self, dict, mode): +- if mode == "careful": +- self._update_careful(dict) +- elif mode == "loose": +- self._update_loose(dict) +- else: +- raise ValueError, "invalid update mode: %r" % mode +- +- def read_module(self, modname, mode="careful"): +- __import__(modname) +- mod = sys.modules[modname] +- self._update(vars(mod), mode) +- +- def read_file(self, filename, mode="careful"): +- vars = {} +- execfile(filename, vars) +- self._update(vars, mode) +- +- def ensure_value(self, attr, value): +- if not hasattr(self, attr) or getattr(self, attr) is None: +- setattr(self, attr, value) +- return getattr(self, attr) +- +- +-class OptionContainer: +- +- """ +- Abstract base class. +- +- Class attributes: +- standard_option_list : [Option] +- list of standard options that will be accepted by all instances +- of this parser class (intended to be overridden by subclasses). +- +- Instance attributes: +- option_list : [Option] +- the list of Option objects contained by this OptionContainer +- _short_opt : { string : Option } +- dictionary mapping short option strings, eg. "-f" or "-X", +- to the Option instances that implement them. If an Option +- has multiple short option strings, it will appears in this +- dictionary multiple times. [1] +- _long_opt : { string : Option } +- dictionary mapping long option strings, eg. "--file" or +- "--exclude", to the Option instances that implement them. +- Again, a given Option can occur multiple times in this +- dictionary. [1] +- defaults : { string : any } +- dictionary mapping option destination names to default +- values for each destination [1] +- +- [1] These mappings are common to (shared by) all components of the +- controlling OptionParser, where they are initially created. +- +- """ +- +- def __init__(self, option_class, conflict_handler, description): +- # Initialize the option list and related data structures. +- # This method must be provided by subclasses, and it must +- # initialize at least the following instance attributes: +- # option_list, _short_opt, _long_opt, defaults. +- self._create_option_list() +- +- self.option_class = option_class +- self.set_conflict_handler(conflict_handler) +- self.set_description(description) +- +- def _create_option_mappings(self): +- # For use by OptionParser constructor -- create the master +- # option mappings used by this OptionParser and all +- # OptionGroups that it owns. +- self._short_opt = {} # single letter -> Option instance +- self._long_opt = {} # long option -> Option instance +- self.defaults = {} # maps option dest -> default value +- +- +- def _share_option_mappings(self, parser): +- # For use by OptionGroup constructor -- use shared option +- # mappings from the OptionParser that owns this OptionGroup. +- self._short_opt = parser._short_opt +- self._long_opt = parser._long_opt +- self.defaults = parser.defaults +- +- def set_conflict_handler(self, handler): +- if handler not in ("error", "resolve"): +- raise ValueError, "invalid conflict_resolution value %r" % handler +- self.conflict_handler = handler +- +- def set_description(self, description): +- self.description = description +- +- def get_description(self): +- return self.description +- +- +- def destroy(self): +- """see OptionParser.destroy().""" +- del self._short_opt +- del self._long_opt +- del self.defaults +- +- +- # -- Option-adding methods ----------------------------------------- +- +- def _check_conflict(self, option): +- conflict_opts = [] +- for opt in option._short_opts: +- if self._short_opt.has_key(opt): +- conflict_opts.append((opt, self._short_opt[opt])) +- for opt in option._long_opts: +- if self._long_opt.has_key(opt): +- conflict_opts.append((opt, self._long_opt[opt])) +- +- if conflict_opts: +- handler = self.conflict_handler +- if handler == "error": +- raise OptionConflictError( +- "conflicting option string(s): %s" +- % ", ".join([co[0] for co in conflict_opts]), +- option) +- elif handler == "resolve": +- for (opt, c_option) in conflict_opts: +- if opt.startswith("--"): +- c_option._long_opts.remove(opt) +- del self._long_opt[opt] +- else: +- c_option._short_opts.remove(opt) +- del self._short_opt[opt] +- if not (c_option._short_opts or c_option._long_opts): +- c_option.container.option_list.remove(c_option) +- +- def add_option(self, *args, **kwargs): +- """add_option(Option) +- add_option(opt_str, ..., kwarg=val, ...) +- """ +- if type(args[0]) is types.StringType: +- option = self.option_class(*args, **kwargs) +- elif len(args) == 1 and not kwargs: +- option = args[0] +- if not isinstance(option, Option): +- raise TypeError, "not an Option instance: %r" % option +- else: +- raise TypeError, "invalid arguments" +- +- self._check_conflict(option) +- +- self.option_list.append(option) +- option.container = self +- for opt in option._short_opts: +- self._short_opt[opt] = option +- for opt in option._long_opts: +- self._long_opt[opt] = option +- +- if option.dest is not None: # option has a dest, we need a default +- if option.default is not NO_DEFAULT: +- self.defaults[option.dest] = option.default +- elif not self.defaults.has_key(option.dest): +- self.defaults[option.dest] = None +- +- return option +- +- def add_options(self, option_list): +- for option in option_list: +- self.add_option(option) +- +- # -- Option query/removal methods ---------------------------------- +- +- def get_option(self, opt_str): +- return (self._short_opt.get(opt_str) or +- self._long_opt.get(opt_str)) +- +- def has_option(self, opt_str): +- return (self._short_opt.has_key(opt_str) or +- self._long_opt.has_key(opt_str)) +- +- def remove_option(self, opt_str): +- option = self._short_opt.get(opt_str) +- if option is None: +- option = self._long_opt.get(opt_str) +- if option is None: +- raise ValueError("no such option %r" % opt_str) +- +- for opt in option._short_opts: +- del self._short_opt[opt] +- for opt in option._long_opts: +- del self._long_opt[opt] +- option.container.option_list.remove(option) +- +- +- # -- Help-formatting methods --------------------------------------- +- +- def format_option_help(self, formatter): +- if not self.option_list: +- return "" +- result = [] +- for option in self.option_list: +- if not option.help is SUPPRESS_HELP: +- result.append(formatter.format_option(option)) +- return "".join(result) +- +- def format_description(self, formatter): +- return formatter.format_description(self.get_description()) +- +- def format_help(self, formatter): +- result = [] +- if self.description: +- result.append(self.format_description(formatter)) +- if self.option_list: +- result.append(self.format_option_help(formatter)) +- return "\n".join(result) +- +- +-class OptionGroup (OptionContainer): +- +- def __init__(self, parser, title, description=None): +- self.parser = parser +- OptionContainer.__init__( +- self, parser.option_class, parser.conflict_handler, description) +- self.title = title +- +- def _create_option_list(self): +- self.option_list = [] +- self._share_option_mappings(self.parser) +- +- def set_title(self, title): +- self.title = title +- +- def destroy(self): +- """see OptionParser.destroy().""" +- OptionContainer.destroy(self) +- del self.option_list +- +- # -- Help-formatting methods --------------------------------------- +- +- def format_help(self, formatter): +- result = formatter.format_heading(self.title) +- formatter.indent() +- result += OptionContainer.format_help(self, formatter) +- formatter.dedent() +- return result +- +- +-class OptionParser (OptionContainer): +- +- """ +- Class attributes: +- standard_option_list : [Option] +- list of standard options that will be accepted by all instances +- of this parser class (intended to be overridden by subclasses). +- +- Instance attributes: +- usage : string +- a usage string for your program. Before it is displayed +- to the user, "%prog" will be expanded to the name of +- your program (self.prog or os.path.basename(sys.argv[0])). +- prog : string +- the name of the current program (to override +- os.path.basename(sys.argv[0])). +- epilog : string +- paragraph of help text to print after option help +- +- option_groups : [OptionGroup] +- list of option groups in this parser (option groups are +- irrelevant for parsing the command-line, but very useful +- for generating help) +- +- allow_interspersed_args : bool = true +- if true, positional arguments may be interspersed with options. +- Assuming -a and -b each take a single argument, the command-line +- -ablah foo bar -bboo baz +- will be interpreted the same as +- -ablah -bboo -- foo bar baz +- If this flag were false, that command line would be interpreted as +- -ablah -- foo bar -bboo baz +- -- ie. we stop processing options as soon as we see the first +- non-option argument. (This is the tradition followed by +- Python's getopt module, Perl's Getopt::Std, and other argument- +- parsing libraries, but it is generally annoying to users.) +- +- process_default_values : bool = true +- if true, option default values are processed similarly to option +- values from the command line: that is, they are passed to the +- type-checking function for the option's type (as long as the +- default value is a string). (This really only matters if you +- have defined custom types; see SF bug #955889.) Set it to false +- to restore the behaviour of Optik 1.4.1 and earlier. +- +- rargs : [string] +- the argument list currently being parsed. Only set when +- parse_args() is active, and continually trimmed down as +- we consume arguments. Mainly there for the benefit of +- callback options. +- largs : [string] +- the list of leftover arguments that we have skipped while +- parsing options. If allow_interspersed_args is false, this +- list is always empty. +- values : Values +- the set of option values currently being accumulated. Only +- set when parse_args() is active. Also mainly for callbacks. +- +- Because of the 'rargs', 'largs', and 'values' attributes, +- OptionParser is not thread-safe. If, for some perverse reason, you +- need to parse command-line arguments simultaneously in different +- threads, use different OptionParser instances. +- +- """ +- +- standard_option_list = [] +- +- def __init__(self, +- usage=None, +- option_list=None, +- option_class=Option, +- version=None, +- conflict_handler="error", +- description=None, +- formatter=None, +- add_help_option=True, +- prog=None, +- epilog=None): +- OptionContainer.__init__( +- self, option_class, conflict_handler, description) +- self.set_usage(usage) +- self.prog = prog +- self.version = version +- self.allow_interspersed_args = True +- self.process_default_values = True +- if formatter is None: +- formatter = IndentedHelpFormatter() +- self.formatter = formatter +- self.formatter.set_parser(self) +- self.epilog = epilog +- +- # Populate the option list; initial sources are the +- # standard_option_list class attribute, the 'option_list' +- # argument, and (if applicable) the _add_version_option() and +- # _add_help_option() methods. +- self._populate_option_list(option_list, +- add_help=add_help_option) +- +- self._init_parsing_state() +- +- +- def destroy(self): +- """ +- Declare that you are done with this OptionParser. This cleans up +- reference cycles so the OptionParser (and all objects referenced by +- it) can be garbage-collected promptly. After calling destroy(), the +- OptionParser is unusable. +- """ +- OptionContainer.destroy(self) +- for group in self.option_groups: +- group.destroy() +- del self.option_list +- del self.option_groups +- del self.formatter +- +- +- # -- Private methods ----------------------------------------------- +- # (used by our or OptionContainer's constructor) +- +- def _create_option_list(self): +- self.option_list = [] +- self.option_groups = [] +- self._create_option_mappings() +- +- def _add_help_option(self): +- self.add_option("-h", "--help", +- action="help", +- help=_("show this help message and exit")) +- +- def _add_version_option(self): +- self.add_option("--version", +- action="version", +- help=_("show program's version number and exit")) +- +- def _populate_option_list(self, option_list, add_help=True): +- if self.standard_option_list: +- self.add_options(self.standard_option_list) +- if option_list: +- self.add_options(option_list) +- if self.version: +- self._add_version_option() +- if add_help: +- self._add_help_option() +- +- def _init_parsing_state(self): +- # These are set in parse_args() for the convenience of callbacks. +- self.rargs = None +- self.largs = None +- self.values = None +- +- +- # -- Simple modifier methods --------------------------------------- +- +- def set_usage(self, usage): +- if usage is None: +- self.usage = _("%prog [options]") +- elif usage is SUPPRESS_USAGE: +- self.usage = None +- # For backwards compatibility with Optik 1.3 and earlier. +- elif usage.lower().startswith("usage: "): +- self.usage = usage[7:] +- else: +- self.usage = usage +- +- def enable_interspersed_args(self): +- self.allow_interspersed_args = True +- +- def disable_interspersed_args(self): +- self.allow_interspersed_args = False +- +- def set_process_default_values(self, process): +- self.process_default_values = process +- +- def set_default(self, dest, value): +- self.defaults[dest] = value +- +- def set_defaults(self, **kwargs): +- self.defaults.update(kwargs) +- +- def _get_all_options(self): +- options = self.option_list[:] +- for group in self.option_groups: +- options.extend(group.option_list) +- return options +- +- def get_default_values(self): +- if not self.process_default_values: +- # Old, pre-Optik 1.5 behaviour. +- return Values(self.defaults) +- +- defaults = self.defaults.copy() +- for option in self._get_all_options(): +- default = defaults.get(option.dest) +- if isbasestring(default): +- opt_str = option.get_opt_string() +- defaults[option.dest] = option.check_value(opt_str, default) +- +- return Values(defaults) +- +- +- # -- OptionGroup methods ------------------------------------------- +- +- def add_option_group(self, *args, **kwargs): +- # XXX lots of overlap with OptionContainer.add_option() +- if type(args[0]) is types.StringType: +- group = OptionGroup(self, *args, **kwargs) +- elif len(args) == 1 and not kwargs: +- group = args[0] +- if not isinstance(group, OptionGroup): +- raise TypeError, "not an OptionGroup instance: %r" % group +- if group.parser is not self: +- raise ValueError, "invalid OptionGroup (wrong parser)" +- else: +- raise TypeError, "invalid arguments" +- +- self.option_groups.append(group) +- return group +- +- def get_option_group(self, opt_str): +- option = (self._short_opt.get(opt_str) or +- self._long_opt.get(opt_str)) +- if option and option.container is not self: +- return option.container +- return None +- +- +- # -- Option-parsing methods ---------------------------------------- +- +- def _get_args(self, args): +- if args is None: +- return sys.argv[1:] +- else: +- return args[:] # don't modify caller's list +- +- def parse_args(self, args=None, values=None): +- """ +- parse_args(args : [string] = sys.argv[1:], +- values : Values = None) +- -> (values : Values, args : [string]) +- +- Parse the command-line options found in 'args' (default: +- sys.argv[1:]). Any errors result in a call to 'error()', which +- by default prints the usage message to stderr and calls +- sys.exit() with an error message. On success returns a pair +- (values, args) where 'values' is an Values instance (with all +- your option values) and 'args' is the list of arguments left +- over after parsing options. +- """ +- rargs = self._get_args(args) +- if values is None: +- values = self.get_default_values() +- +- # Store the halves of the argument list as attributes for the +- # convenience of callbacks: +- # rargs +- # the rest of the command-line (the "r" stands for +- # "remaining" or "right-hand") +- # largs +- # the leftover arguments -- ie. what's left after removing +- # options and their arguments (the "l" stands for "leftover" +- # or "left-hand") +- self.rargs = rargs +- self.largs = largs = [] +- self.values = values +- +- try: +- stop = self._process_args(largs, rargs, values) +- except (BadOptionError, OptionValueError), err: +- self.error(str(err)) +- +- args = largs + rargs +- return self.check_values(values, args) +- +- def check_values(self, values, args): +- """ +- check_values(values : Values, args : [string]) +- -> (values : Values, args : [string]) +- +- Check that the supplied option values and leftover arguments are +- valid. Returns the option values and leftover arguments +- (possibly adjusted, possibly completely new -- whatever you +- like). Default implementation just returns the passed-in +- values; subclasses may override as desired. +- """ +- return (values, args) +- +- def _process_args(self, largs, rargs, values): +- """_process_args(largs : [string], +- rargs : [string], +- values : Values) +- +- Process command-line arguments and populate 'values', consuming +- options and arguments from 'rargs'. If 'allow_interspersed_args' is +- false, stop at the first non-option argument. If true, accumulate any +- interspersed non-option arguments in 'largs'. +- """ +- while rargs: +- arg = rargs[0] +- # We handle bare "--" explicitly, and bare "-" is handled by the +- # standard arg handler since the short arg case ensures that the +- # len of the opt string is greater than 1. +- if arg == "--": +- del rargs[0] +- return +- elif arg[0:2] == "--": +- # process a single long option (possibly with value(s)) +- self._process_long_opt(rargs, values) +- elif arg[:1] == "-" and len(arg) > 1: +- # process a cluster of short options (possibly with +- # value(s) for the last one only) +- self._process_short_opts(rargs, values) +- elif self.allow_interspersed_args: +- largs.append(arg) +- del rargs[0] +- else: +- return # stop now, leave this arg in rargs +- +- # Say this is the original argument list: +- # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] +- # ^ +- # (we are about to process arg(i)). +- # +- # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of +- # [arg0, ..., arg(i-1)] (any options and their arguments will have +- # been removed from largs). +- # +- # The while loop will usually consume 1 or more arguments per pass. +- # If it consumes 1 (eg. arg is an option that takes no arguments), +- # then after _process_arg() is done the situation is: +- # +- # largs = subset of [arg0, ..., arg(i)] +- # rargs = [arg(i+1), ..., arg(N-1)] +- # +- # If allow_interspersed_args is false, largs will always be +- # *empty* -- still a subset of [arg0, ..., arg(i-1)], but +- # not a very interesting subset! +- +- def _match_long_opt(self, opt): +- """_match_long_opt(opt : string) -> string +- +- Determine which long option string 'opt' matches, ie. which one +- it is an unambiguous abbrevation for. Raises BadOptionError if +- 'opt' doesn't unambiguously match any long option string. +- """ +- return _match_abbrev(opt, self._long_opt) +- +- def _process_long_opt(self, rargs, values): +- arg = rargs.pop(0) +- +- # Value explicitly attached to arg? Pretend it's the next +- # argument. +- if "=" in arg: +- (opt, next_arg) = arg.split("=", 1) +- rargs.insert(0, next_arg) +- had_explicit_value = True +- else: +- opt = arg +- had_explicit_value = False +- +- opt = self._match_long_opt(opt) +- option = self._long_opt[opt] +- if option.takes_value(): +- nargs = option.nargs +- if len(rargs) < nargs: +- if nargs == 1: +- self.error(_("%s option requires an argument") % opt) +- else: +- self.error(_("%s option requires %d arguments") +- % (opt, nargs)) +- elif nargs == 1: +- value = rargs.pop(0) +- else: +- value = tuple(rargs[0:nargs]) +- del rargs[0:nargs] +- +- elif had_explicit_value: +- self.error(_("%s option does not take a value") % opt) +- +- else: +- value = None +- +- option.process(opt, value, values, self) +- +- def _process_short_opts(self, rargs, values): +- arg = rargs.pop(0) +- stop = False +- i = 1 +- for ch in arg[1:]: +- opt = "-" + ch +- option = self._short_opt.get(opt) +- i += 1 # we have consumed a character +- +- if not option: +- raise BadOptionError(opt) +- if option.takes_value(): +- # Any characters left in arg? Pretend they're the +- # next arg, and stop consuming characters of arg. +- if i < len(arg): +- rargs.insert(0, arg[i:]) +- stop = True +- +- nargs = option.nargs +- if len(rargs) < nargs: +- if nargs == 1: +- self.error(_("%s option requires an argument") % opt) +- else: +- self.error(_("%s option requires %d arguments") +- % (opt, nargs)) +- elif nargs == 1: +- value = rargs.pop(0) +- else: +- value = tuple(rargs[0:nargs]) +- del rargs[0:nargs] +- +- else: # option doesn't take a value +- value = None +- +- option.process(opt, value, values, self) +- +- if stop: +- break +- +- +- # -- Feedback methods ---------------------------------------------- +- +- def get_prog_name(self): +- if self.prog is None: +- return os.path.basename(sys.argv[0]) +- else: +- return self.prog +- +- def expand_prog_name(self, s): +- return s.replace("%prog", self.get_prog_name()) +- +- def get_description(self): +- return self.expand_prog_name(self.description) +- +- def exit(self, status=0, msg=None): +- if msg: +- sys.stderr.write(msg) +- sys.exit(status) +- +- def error(self, msg): +- """error(msg : string) +- +- Print a usage message incorporating 'msg' to stderr and exit. +- If you override this in a subclass, it should not return -- it +- should either exit or raise an exception. +- """ +- self.print_usage(sys.stderr) +- self.exit(2, "%s: error: %s\n" % (self.get_prog_name(), msg)) +- +- def get_usage(self): +- if self.usage: +- return self.formatter.format_usage( +- self.expand_prog_name(self.usage)) +- else: +- return "" +- +- def print_usage(self, file=None): +- """print_usage(file : file = stdout) +- +- Print the usage message for the current program (self.usage) to +- 'file' (default stdout). Any occurence of the string "%prog" in +- self.usage is replaced with the name of the current program +- (basename of sys.argv[0]). Does nothing if self.usage is empty +- or not defined. +- """ +- if self.usage: +- print >>file, self.get_usage() +- +- def get_version(self): +- if self.version: +- return self.expand_prog_name(self.version) +- else: +- return "" +- +- def print_version(self, file=None): +- """print_version(file : file = stdout) +- +- Print the version message for this program (self.version) to +- 'file' (default stdout). As with print_usage(), any occurence +- of "%prog" in self.version is replaced by the current program's +- name. Does nothing if self.version is empty or undefined. +- """ +- if self.version: +- print >>file, self.get_version() +- +- def format_option_help(self, formatter=None): +- if formatter is None: +- formatter = self.formatter +- formatter.store_option_strings(self) +- result = [] +- result.append(formatter.format_heading(_("Options"))) +- formatter.indent() +- if self.option_list: +- result.append(OptionContainer.format_option_help(self, formatter)) +- result.append("\n") +- for group in self.option_groups: +- result.append(group.format_help(formatter)) +- result.append("\n") +- formatter.dedent() +- # Drop the last "\n", or the header if no options or option groups: +- return "".join(result[:-1]) +- +- def format_epilog(self, formatter): +- return formatter.format_epilog(self.epilog) +- +- def format_help(self, formatter=None): +- if formatter is None: +- formatter = self.formatter +- result = [] +- if self.usage: +- result.append(self.get_usage() + "\n") +- if self.description: +- result.append(self.format_description(formatter) + "\n") +- result.append(self.format_option_help(formatter)) +- result.append(self.format_epilog(formatter)) +- return "".join(result) +- +- # used by test suite +- def _get_encoding(self, file): +- encoding = getattr(file, "encoding", None) +- if not encoding: +- encoding = sys.getdefaultencoding() +- return encoding +- +- def print_help(self, file=None): +- """print_help(file : file = stdout) +- +- Print an extended help message, listing all options and any +- help text provided with them, to 'file' (default stdout). +- """ +- if file is None: +- file = sys.stdout +- encoding = self._get_encoding(file) +- file.write(self.format_help().encode(encoding, "replace")) +- +-# class OptionParser +- +- +-def _match_abbrev(s, wordmap): +- """_match_abbrev(s : string, wordmap : {string : Option}) -> string +- +- Return the string key in 'wordmap' for which 's' is an unambiguous +- abbreviation. If 's' is found to be ambiguous or doesn't match any of +- 'words', raise BadOptionError. +- """ +- # Is there an exact match? +- if wordmap.has_key(s): +- return s +- else: +- # Isolate all words with s as a prefix. +- possibilities = [word for word in wordmap.keys() +- if word.startswith(s)] +- # No exact match, so there had better be just one possibility. +- if len(possibilities) == 1: +- return possibilities[0] +- elif not possibilities: +- raise BadOptionError(s) +- else: +- # More than one possible completion: ambiguous prefix. +- possibilities.sort() +- raise AmbiguousOptionError(s, possibilities) +- +- +-# Some day, there might be many Option classes. As of Optik 1.3, the +-# preferred way to instantiate Options is indirectly, via make_option(), +-# which will become a factory function when there are many Option +-# classes. +-make_option = Option +diff --git a/koan/register.py b/koan/register.py +index 9924d85..871b8ac 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -20,26 +20,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + """ + +-import random + import os + import traceback +-try: +- from optparse import OptionParser +-except: +- from opt_parse import OptionParser # importing this for backwards compat with 2.2 +-try: +- import subprocess as sub_process +-except: +- import sub_process ++from optparse import OptionParser + import time +-import errno + import sys + import xmlrpclib +-import glob + import socket + import utils + import string +-import pprint + + # usage: cobbler-register [--server=server] [--hostname=hostname] --profile=foo + +diff --git a/koan/sub_process.py b/koan/sub_process.py +deleted file mode 100644 +index bbd26c7..0000000 +--- a/koan/sub_process.py ++++ /dev/null +@@ -1,1149 +0,0 @@ +-# subprocess - Subprocesses with accessible I/O streams +-# +-# For more information about this module, see PEP 324. +-# +-# This module should remain compatible with Python 2.2, see PEP 291. +-# +-# Copyright (c) 2003-2005 by Peter Astrand +-# +-# Licensed to PSF under a Contributor Agreement. +-# See http://www.python.org/2.4/license for licensing details. +- +-r"""subprocess - Subprocesses with accessible I/O streams +- +-This module allows you to spawn processes, connect to their +-input/output/error pipes, and obtain their return codes. This module +-intends to replace several other, older modules and functions, like: +- +-os.system +-os.spawn* +-os.popen* +-popen2.* +-commands.* +- +-Information about how the subprocess module can be used to replace these +-modules and functions can be found below. +- +- +- +-Using the subprocess module +-=========================== +-This module defines one class called Popen: +- +-class Popen(args, bufsize=0, executable=None, +- stdin=None, stdout=None, stderr=None, +- preexec_fn=None, close_fds=False, shell=False, +- cwd=None, env=None, universal_newlines=False, +- startupinfo=None, creationflags=0): +- +- +-Arguments are: +- +-args should be a string, or a sequence of program arguments. The +-program to execute is normally the first item in the args sequence or +-string, but can be explicitly set by using the executable argument. +- +-On UNIX, with shell=False (default): In this case, the Popen class +-uses os.execvp() to execute the child program. args should normally +-be a sequence. A string will be treated as a sequence with the string +-as the only item (the program to execute). +- +-On UNIX, with shell=True: If args is a string, it specifies the +-command string to execute through the shell. If args is a sequence, +-the first item specifies the command string, and any additional items +-will be treated as additional shell arguments. +- +-On Windows: the Popen class uses CreateProcess() to execute the child +-program, which operates on strings. If args is a sequence, it will be +-converted to a string using the list2cmdline method. Please note that +-not all MS Windows applications interpret the command line the same +-way: The list2cmdline is designed for applications using the same +-rules as the MS C runtime. +- +-bufsize, if given, has the same meaning as the corresponding argument +-to the built-in open() function: 0 means unbuffered, 1 means line +-buffered, any other positive value means use a buffer of +-(approximately) that size. A negative bufsize means to use the system +-default, which usually means fully buffered. The default value for +-bufsize is 0 (unbuffered). +- +-stdin, stdout and stderr specify the executed programs' standard +-input, standard output and standard error file handles, respectively. +-Valid values are PIPE, an existing file descriptor (a positive +-integer), an existing file object, and None. PIPE indicates that a +-new pipe to the child should be created. With None, no redirection +-will occur; the child's file handles will be inherited from the +-parent. Additionally, stderr can be STDOUT, which indicates that the +-stderr data from the applications should be captured into the same +-file handle as for stdout. +- +-If preexec_fn is set to a callable object, this object will be called +-in the child process just before the child is executed. +- +-If close_fds is true, all file descriptors except 0, 1 and 2 will be +-closed before the child process is executed. +- +-if shell is true, the specified command will be executed through the +-shell. +- +-If cwd is not None, the current directory will be changed to cwd +-before the child is executed. +- +-If env is not None, it defines the environment variables for the new +-process. +- +-If universal_newlines is true, the file objects stdout and stderr are +-opened as a text files, but lines may be terminated by any of '\n', +-the Unix end-of-line convention, '\r', the Macintosh convention or +-'\r\n', the Windows convention. All of these external representations +-are seen as '\n' by the Python program. Note: This feature is only +-available if Python is built with universal newline support (the +-default). Also, the newlines attribute of the file objects stdout, +-stdin and stderr are not updated by the communicate() method. +- +-The startupinfo and creationflags, if given, will be passed to the +-underlying CreateProcess() function. They can specify things such as +-appearance of the main window and priority for the new process. +-(Windows only) +- +- +-This module also defines two shortcut functions: +- +-call(*args, **kwargs): +- Run command with arguments. Wait for command to complete, then +- return the returncode attribute. +- +- The arguments are the same as for the Popen constructor. Example: +- +- retcode = call(["ls", "-l"]) +- +- +-Exceptions +----------- +-Exceptions raised in the child process, before the new program has +-started to execute, will be re-raised in the parent. Additionally, +-the exception object will have one extra attribute called +-'child_traceback', which is a string containing traceback information +-from the childs point of view. +- +-The most common exception raised is OSError. This occurs, for +-example, when trying to execute a non-existent file. Applications +-should prepare for OSErrors. +- +-A ValueError will be raised if Popen is called with invalid arguments. +- +- +-Security +--------- +-Unlike some other popen functions, this implementation will never call +-/bin/sh implicitly. This means that all characters, including shell +-metacharacters, can safely be passed to child processes. +- +- +-Popen objects +-============= +-Instances of the Popen class have the following methods: +- +-poll() +- Check if child process has terminated. Returns returncode +- attribute. +- +-wait() +- Wait for child process to terminate. Returns returncode attribute. +- +-communicate(input=None) +- Interact with process: Send data to stdin. Read data from stdout +- and stderr, until end-of-file is reached. Wait for process to +- terminate. The optional stdin argument should be a string to be +- sent to the child process, or None, if no data should be sent to +- the child. +- +- communicate() returns a tuple (stdout, stderr). +- +- Note: The data read is buffered in memory, so do not use this +- method if the data size is large or unlimited. +- +-The following attributes are also available: +- +-stdin +- If the stdin argument is PIPE, this attribute is a file object +- that provides input to the child process. Otherwise, it is None. +- +-stdout +- If the stdout argument is PIPE, this attribute is a file object +- that provides output from the child process. Otherwise, it is +- None. +- +-stderr +- If the stderr argument is PIPE, this attribute is file object that +- provides error output from the child process. Otherwise, it is +- None. +- +-pid +- The process ID of the child process. +- +-returncode +- The child return code. A None value indicates that the process +- hasn't terminated yet. A negative value -N indicates that the +- child was terminated by signal N (UNIX only). +- +- +-Replacing older functions with the subprocess module +-==================================================== +-In this section, "a ==> b" means that b can be used as a replacement +-for a. +- +-Note: All functions in this section fail (more or less) silently if +-the executed program cannot be found; this module raises an OSError +-exception. +- +-In the following examples, we assume that the subprocess module is +-imported with "from subprocess import *". +- +- +-Replacing /bin/sh shell backquote +---------------------------------- +-output=`mycmd myarg` +-==> +-output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] +- +- +-Replacing shell pipe line +-------------------------- +-output=`dmesg | grep hda` +-==> +-p1 = Popen(["dmesg"], stdout=PIPE) +-p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) +-output = p2.communicate()[0] +- +- +-Replacing os.system() +---------------------- +-sts = os.system("mycmd" + " myarg") +-==> +-p = Popen("mycmd" + " myarg", shell=True) +-sts = os.waitpid(p.pid, 0) +- +-Note: +- +-* Calling the program through the shell is usually not required. +- +-* It's easier to look at the returncode attribute than the +- exitstatus. +- +-A more real-world example would look like this: +- +-try: +- retcode = call("mycmd" + " myarg", shell=True) +- if retcode < 0: +- print >>sys.stderr, "Child was terminated by signal", -retcode +- else: +- print >>sys.stderr, "Child returned", retcode +-except OSError, e: +- print >>sys.stderr, "Execution failed:", e +- +- +-Replacing os.spawn* +-------------------- +-P_NOWAIT example: +- +-pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") +-==> +-pid = Popen(["/bin/mycmd", "myarg"]).pid +- +- +-P_WAIT example: +- +-retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") +-==> +-retcode = call(["/bin/mycmd", "myarg"]) +- +- +-Vector example: +- +-os.spawnvp(os.P_NOWAIT, path, args) +-==> +-Popen([path] + args[1:]) +- +- +-Environment example: +- +-os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) +-==> +-Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) +- +- +-Replacing os.popen* +-------------------- +-pipe = os.popen(cmd, mode='r', bufsize) +-==> +-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout +- +-pipe = os.popen(cmd, mode='w', bufsize) +-==> +-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin +- +- +-(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) +-==> +-p = Popen(cmd, shell=True, bufsize=bufsize, +- stdin=PIPE, stdout=PIPE, close_fds=True) +-(child_stdin, child_stdout) = (p.stdin, p.stdout) +- +- +-(child_stdin, +- child_stdout, +- child_stderr) = os.popen3(cmd, mode, bufsize) +-==> +-p = Popen(cmd, shell=True, bufsize=bufsize, +- stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) +-(child_stdin, +- child_stdout, +- child_stderr) = (p.stdin, p.stdout, p.stderr) +- +- +-(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) +-==> +-p = Popen(cmd, shell=True, bufsize=bufsize, +- stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) +-(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) +- +- +-Replacing popen2.* +------------------- +-Note: If the cmd argument to popen2 functions is a string, the command +-is executed through /bin/sh. If it is a list, the command is directly +-executed. +- +-(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) +-==> +-p = Popen(["somestring"], shell=True, bufsize=bufsize +- stdin=PIPE, stdout=PIPE, close_fds=True) +-(child_stdout, child_stdin) = (p.stdout, p.stdin) +- +- +-(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) +-==> +-p = Popen(["mycmd", "myarg"], bufsize=bufsize, +- stdin=PIPE, stdout=PIPE, close_fds=True) +-(child_stdout, child_stdin) = (p.stdout, p.stdin) +- +-The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, +-except that: +- +-* subprocess.Popen raises an exception if the execution fails +-* the capturestderr argument is replaced with the stderr argument. +-* stdin=PIPE and stdout=PIPE must be specified. +-* popen2 closes all filedescriptors by default, but you have to specify +- close_fds=True with subprocess.Popen. +- +- +-""" +- +-import sys +-mswindows = (sys.platform == "win32") +- +-import os +-import types +-import traceback +- +-if mswindows: +- import threading +- import msvcrt +- if 0: # <-- change this to use pywin32 instead of the _subprocess driver +- import pywintypes +- from win32api import GetStdHandle, STD_INPUT_HANDLE, \ +- STD_OUTPUT_HANDLE, STD_ERROR_HANDLE +- from win32api import GetCurrentProcess, DuplicateHandle, \ +- GetModuleFileName, GetVersion +- from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE +- from win32pipe import CreatePipe +- from win32process import CreateProcess, STARTUPINFO, \ +- GetExitCodeProcess, STARTF_USESTDHANDLES, \ +- STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE +- from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 +- else: +- from _subprocess import * +- class STARTUPINFO: +- dwFlags = 0 +- hStdInput = None +- hStdOutput = None +- hStdError = None +- class pywintypes: +- error = IOError +-else: +- import select +- import errno +- import fcntl +- import pickle +- +-__all__ = ["Popen", "PIPE", "STDOUT", "call"] +- +-try: +- MAXFD = os.sysconf("SC_OPEN_MAX") +-except: +- MAXFD = 256 +- +-# True/False does not exist on 2.2.0 +-try: +- False +-except NameError: +- False = 0 +- True = 1 +- +-_active = [] +- +-def _cleanup(): +- for inst in _active[:]: +- inst.poll() +- +-PIPE = -1 +-STDOUT = -2 +- +- +-def call(*args, **kwargs): +- """Run command with arguments. Wait for command to complete, then +- return the returncode attribute. +- +- The arguments are the same as for the Popen constructor. Example: +- +- retcode = call(["ls", "-l"]) +- """ +- return Popen(*args, **kwargs).wait() +- +- +-def list2cmdline(seq): +- """ +- Translate a sequence of arguments into a command line +- string, using the same rules as the MS C runtime: +- +- 1) Arguments are delimited by white space, which is either a +- space or a tab. +- +- 2) A string surrounded by double quotation marks is +- interpreted as a single argument, regardless of white space +- contained within. A quoted string can be embedded in an +- argument. +- +- 3) A double quotation mark preceded by a backslash is +- interpreted as a literal double quotation mark. +- +- 4) Backslashes are interpreted literally, unless they +- immediately precede a double quotation mark. +- +- 5) If backslashes immediately precede a double quotation mark, +- every pair of backslashes is interpreted as a literal +- backslash. If the number of backslashes is odd, the last +- backslash escapes the next double quotation mark as +- described in rule 3. +- """ +- +- # See +- # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp +- result = [] +- needquote = False +- for arg in seq: +- bs_buf = [] +- +- # Add a space to separate this argument from the others +- if result: +- result.append(' ') +- +- needquote = (" " in arg) or ("\t" in arg) +- if needquote: +- result.append('"') +- +- for c in arg: +- if c == '\\': +- # Don't know if we need to double yet. +- bs_buf.append(c) +- elif c == '"': +- # Double backspaces. +- result.append('\\' * len(bs_buf)*2) +- bs_buf = [] +- result.append('\\"') +- else: +- # Normal char +- if bs_buf: +- result.extend(bs_buf) +- bs_buf = [] +- result.append(c) +- +- # Add remaining backspaces, if any. +- if bs_buf: +- result.extend(bs_buf) +- +- if needquote: +- result.extend(bs_buf) +- result.append('"') +- +- return ''.join(result) +- +- +-class Popen(object): +- def __init__(self, args, bufsize=0, executable=None, +- stdin=None, stdout=None, stderr=None, +- preexec_fn=None, close_fds=False, shell=False, +- cwd=None, env=None, universal_newlines=False, +- startupinfo=None, creationflags=0): +- """Create new Popen instance.""" +- _cleanup() +- +- if not isinstance(bufsize, (int, long)): +- raise TypeError("bufsize must be an integer") +- +- if mswindows: +- if preexec_fn is not None: +- raise ValueError("preexec_fn is not supported on Windows " +- "platforms") +- if close_fds: +- raise ValueError("close_fds is not supported on Windows " +- "platforms") +- else: +- # POSIX +- if startupinfo is not None: +- raise ValueError("startupinfo is only supported on Windows " +- "platforms") +- if creationflags != 0: +- raise ValueError("creationflags is only supported on Windows " +- "platforms") +- +- self.stdin = None +- self.stdout = None +- self.stderr = None +- self.pid = None +- self.returncode = None +- self.universal_newlines = universal_newlines +- +- # Input and output objects. The general principle is like +- # this: +- # +- # Parent Child +- # ------ ----- +- # p2cwrite ---stdin---> p2cread +- # c2pread <--stdout--- c2pwrite +- # errread <--stderr--- errwrite +- # +- # On POSIX, the child objects are file descriptors. On +- # Windows, these are Windows file handles. The parent objects +- # are file descriptors on both platforms. The parent objects +- # are None when not using PIPEs. The child objects are None +- # when not redirecting. +- +- (p2cread, p2cwrite, +- c2pread, c2pwrite, +- errread, errwrite) = self._get_handles(stdin, stdout, stderr) +- +- self._execute_child(args, executable, preexec_fn, close_fds, +- cwd, env, universal_newlines, +- startupinfo, creationflags, shell, +- p2cread, p2cwrite, +- c2pread, c2pwrite, +- errread, errwrite) +- +- if p2cwrite: +- self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) +- if c2pread: +- if universal_newlines: +- self.stdout = os.fdopen(c2pread, 'rU', bufsize) +- else: +- self.stdout = os.fdopen(c2pread, 'rb', bufsize) +- if errread: +- if universal_newlines: +- self.stderr = os.fdopen(errread, 'rU', bufsize) +- else: +- self.stderr = os.fdopen(errread, 'rb', bufsize) +- +- _active.append(self) +- +- +- def _translate_newlines(self, data): +- data = data.replace("\r\n", "\n") +- data = data.replace("\r", "\n") +- return data +- +- +- if mswindows: +- # +- # Windows methods +- # +- def _get_handles(self, stdin, stdout, stderr): +- """Construct and return tupel with IO objects: +- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite +- """ +- if stdin == None and stdout == None and stderr == None: +- return (None, None, None, None, None, None) +- +- p2cread, p2cwrite = None, None +- c2pread, c2pwrite = None, None +- errread, errwrite = None, None +- +- if stdin == None: +- p2cread = GetStdHandle(STD_INPUT_HANDLE) +- elif stdin == PIPE: +- p2cread, p2cwrite = CreatePipe(None, 0) +- # Detach and turn into fd +- p2cwrite = p2cwrite.Detach() +- p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) +- elif type(stdin) == types.IntType: +- p2cread = msvcrt.get_osfhandle(stdin) +- else: +- # Assuming file-like object +- p2cread = msvcrt.get_osfhandle(stdin.fileno()) +- p2cread = self._make_inheritable(p2cread) +- +- if stdout == None: +- c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) +- elif stdout == PIPE: +- c2pread, c2pwrite = CreatePipe(None, 0) +- # Detach and turn into fd +- c2pread = c2pread.Detach() +- c2pread = msvcrt.open_osfhandle(c2pread, 0) +- elif type(stdout) == types.IntType: +- c2pwrite = msvcrt.get_osfhandle(stdout) +- else: +- # Assuming file-like object +- c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) +- c2pwrite = self._make_inheritable(c2pwrite) +- +- if stderr == None: +- errwrite = GetStdHandle(STD_ERROR_HANDLE) +- elif stderr == PIPE: +- errread, errwrite = CreatePipe(None, 0) +- # Detach and turn into fd +- errread = errread.Detach() +- errread = msvcrt.open_osfhandle(errread, 0) +- elif stderr == STDOUT: +- errwrite = c2pwrite +- elif type(stderr) == types.IntType: +- errwrite = msvcrt.get_osfhandle(stderr) +- else: +- # Assuming file-like object +- errwrite = msvcrt.get_osfhandle(stderr.fileno()) +- errwrite = self._make_inheritable(errwrite) +- +- return (p2cread, p2cwrite, +- c2pread, c2pwrite, +- errread, errwrite) +- +- +- def _make_inheritable(self, handle): +- """Return a duplicate of handle, which is inheritable""" +- return DuplicateHandle(GetCurrentProcess(), handle, +- GetCurrentProcess(), 0, 1, +- DUPLICATE_SAME_ACCESS) +- +- +- def _find_w9xpopen(self): +- """Find and return absolut path to w9xpopen.exe""" +- w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), +- "w9xpopen.exe") +- if not os.path.exists(w9xpopen): +- # Eeek - file-not-found - possibly an embedding +- # situation - see if we can locate it in sys.exec_prefix +- w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), +- "w9xpopen.exe") +- if not os.path.exists(w9xpopen): +- raise RuntimeError("Cannot locate w9xpopen.exe, which is " +- "needed for Popen to work with your " +- "shell or platform.") +- return w9xpopen +- +- +- def _execute_child(self, args, executable, preexec_fn, close_fds, +- cwd, env, universal_newlines, +- startupinfo, creationflags, shell, +- p2cread, p2cwrite, +- c2pread, c2pwrite, +- errread, errwrite): +- """Execute program (MS Windows version)""" +- +- if not isinstance(args, types.StringTypes): +- args = list2cmdline(args) +- +- # Process startup details +- default_startupinfo = STARTUPINFO() +- if startupinfo == None: +- startupinfo = default_startupinfo +- if not None in (p2cread, c2pwrite, errwrite): +- startupinfo.dwFlags |= STARTF_USESTDHANDLES +- startupinfo.hStdInput = p2cread +- startupinfo.hStdOutput = c2pwrite +- startupinfo.hStdError = errwrite +- +- if shell: +- default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW +- default_startupinfo.wShowWindow = SW_HIDE +- comspec = os.environ.get("COMSPEC", "cmd.exe") +- args = comspec + " /c " + args +- if (GetVersion() >= 0x80000000L or +- os.path.basename(comspec).lower() == "command.com"): +- # Win9x, or using command.com on NT. We need to +- # use the w9xpopen intermediate program. For more +- # information, see KB Q150956 +- # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) +- w9xpopen = self._find_w9xpopen() +- args = '"%s" %s' % (w9xpopen, args) +- # Not passing CREATE_NEW_CONSOLE has been known to +- # cause random failures on win9x. Specifically a +- # dialog: "Your program accessed mem currently in +- # use at xxx" and a hopeful warning about the +- # stability of your system. Cost is Ctrl+C wont +- # kill children. +- creationflags |= CREATE_NEW_CONSOLE +- +- # Start the process +- try: +- hp, ht, pid, tid = CreateProcess(executable, args, +- # no special security +- None, None, +- # must inherit handles to pass std +- # handles +- 1, +- creationflags, +- env, +- cwd, +- startupinfo) +- except pywintypes.error, e: +- # Translate pywintypes.error to WindowsError, which is +- # a subclass of OSError. FIXME: We should really +- # translate errno using _sys_errlist (or simliar), but +- # how can this be done from Python? +- raise WindowsError(*e.args) +- +- # Retain the process handle, but close the thread handle +- self._handle = hp +- self.pid = pid +- ht.Close() +- +- # Child is launched. Close the parent's copy of those pipe +- # handles that only the child should have open. You need +- # to make sure that no handles to the write end of the +- # output pipe are maintained in this process or else the +- # pipe will not close when the child process exits and the +- # ReadFile will hang. +- if p2cread != None: +- p2cread.Close() +- if c2pwrite != None: +- c2pwrite.Close() +- if errwrite != None: +- errwrite.Close() +- +- +- def poll(self): +- """Check if child process has terminated. Returns returncode +- attribute.""" +- if self.returncode == None: +- if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: +- self.returncode = GetExitCodeProcess(self._handle) +- _active.remove(self) +- return self.returncode +- +- +- def wait(self): +- """Wait for child process to terminate. Returns returncode +- attribute.""" +- if self.returncode == None: +- obj = WaitForSingleObject(self._handle, INFINITE) +- self.returncode = GetExitCodeProcess(self._handle) +- _active.remove(self) +- return self.returncode +- +- +- def _readerthread(self, fh, buffer): +- buffer.append(fh.read()) +- +- +- def communicate(self, input=None): +- """Interact with process: Send data to stdin. Read data from +- stdout and stderr, until end-of-file is reached. Wait for +- process to terminate. The optional input argument should be a +- string to be sent to the child process, or None, if no data +- should be sent to the child. +- +- communicate() returns a tuple (stdout, stderr).""" +- stdout = None # Return +- stderr = None # Return +- +- if self.stdout: +- stdout = [] +- stdout_thread = threading.Thread(target=self._readerthread, +- args=(self.stdout, stdout)) +- stdout_thread.setDaemon(True) +- stdout_thread.start() +- if self.stderr: +- stderr = [] +- stderr_thread = threading.Thread(target=self._readerthread, +- args=(self.stderr, stderr)) +- stderr_thread.setDaemon(True) +- stderr_thread.start() +- +- if self.stdin: +- if input != None: +- self.stdin.write(input) +- self.stdin.close() +- +- if self.stdout: +- stdout_thread.join() +- if self.stderr: +- stderr_thread.join() +- +- # All data exchanged. Translate lists into strings. +- if stdout != None: +- stdout = stdout[0] +- if stderr != None: +- stderr = stderr[0] +- +- # Translate newlines, if requested. We cannot let the file +- # object do the translation: It is based on stdio, which is +- # impossible to combine with select (unless forcing no +- # buffering). +- if self.universal_newlines and hasattr(open, 'newlines'): +- if stdout: +- stdout = self._translate_newlines(stdout) +- if stderr: +- stderr = self._translate_newlines(stderr) +- +- self.wait() +- return (stdout, stderr) +- +- else: +- # +- # POSIX methods +- # +- def _get_handles(self, stdin, stdout, stderr): +- """Construct and return tupel with IO objects: +- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite +- """ +- p2cread, p2cwrite = None, None +- c2pread, c2pwrite = None, None +- errread, errwrite = None, None +- +- if stdin == None: +- pass +- elif stdin == PIPE: +- p2cread, p2cwrite = os.pipe() +- elif type(stdin) == types.IntType: +- p2cread = stdin +- else: +- # Assuming file-like object +- p2cread = stdin.fileno() +- +- if stdout == None: +- pass +- elif stdout == PIPE: +- c2pread, c2pwrite = os.pipe() +- elif type(stdout) == types.IntType: +- c2pwrite = stdout +- else: +- # Assuming file-like object +- c2pwrite = stdout.fileno() +- +- if stderr == None: +- pass +- elif stderr == PIPE: +- errread, errwrite = os.pipe() +- elif stderr == STDOUT: +- errwrite = c2pwrite +- elif type(stderr) == types.IntType: +- errwrite = stderr +- else: +- # Assuming file-like object +- errwrite = stderr.fileno() +- +- return (p2cread, p2cwrite, +- c2pread, c2pwrite, +- errread, errwrite) +- +- +- def _set_cloexec_flag(self, fd): +- try: +- cloexec_flag = fcntl.FD_CLOEXEC +- except AttributeError: +- cloexec_flag = 1 +- +- old = fcntl.fcntl(fd, fcntl.F_GETFD) +- fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) +- +- +- def _close_fds(self, but): +- for i in range(3, MAXFD): +- if i == but: +- continue +- try: +- os.close(i) +- except: +- pass +- +- +- def _execute_child(self, args, executable, preexec_fn, close_fds, +- cwd, env, universal_newlines, +- startupinfo, creationflags, shell, +- p2cread, p2cwrite, +- c2pread, c2pwrite, +- errread, errwrite): +- """Execute program (POSIX version)""" +- +- if isinstance(args, types.StringTypes): +- args = [args] +- +- if shell: +- args = ["/bin/sh", "-c"] + args +- +- if executable == None: +- executable = args[0] +- +- # For transferring possible exec failure from child to parent +- # The first char specifies the exception type: 0 means +- # OSError, 1 means some other error. +- errpipe_read, errpipe_write = os.pipe() +- self._set_cloexec_flag(errpipe_write) +- +- self.pid = os.fork() +- if self.pid == 0: +- # Child +- try: +- # Close parent's pipe ends +- if p2cwrite: +- os.close(p2cwrite) +- if c2pread: +- os.close(c2pread) +- if errread: +- os.close(errread) +- os.close(errpipe_read) +- +- # Dup fds for child +- if p2cread: +- os.dup2(p2cread, 0) +- if c2pwrite: +- os.dup2(c2pwrite, 1) +- if errwrite: +- os.dup2(errwrite, 2) +- +- # Close pipe fds. Make sure we doesn't close the same +- # fd more than once. +- if p2cread: +- os.close(p2cread) +- if c2pwrite and c2pwrite not in (p2cread,): +- os.close(c2pwrite) +- if errwrite and errwrite not in (p2cread, c2pwrite): +- os.close(errwrite) +- +- # Close all other fds, if asked for +- if close_fds: +- self._close_fds(but=errpipe_write) +- +- if cwd != None: +- os.chdir(cwd) +- +- if preexec_fn: +- apply(preexec_fn) +- +- if env == None: +- os.execvp(executable, args) +- else: +- os.execvpe(executable, args, env) +- +- except: +- exc_type, exc_value, tb = sys.exc_info() +- # Save the traceback and attach it to the exception object +- exc_lines = traceback.format_exception(exc_type, +- exc_value, +- tb) +- exc_value.child_traceback = ''.join(exc_lines) +- os.write(errpipe_write, pickle.dumps(exc_value)) +- +- # This exitcode won't be reported to applications, so it +- # really doesn't matter what we return. +- os._exit(255) +- +- # Parent +- os.close(errpipe_write) +- if p2cread and p2cwrite: +- os.close(p2cread) +- if c2pwrite and c2pread: +- os.close(c2pwrite) +- if errwrite and errread: +- os.close(errwrite) +- +- # Wait for exec to fail or succeed; possibly raising exception +- data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB +- os.close(errpipe_read) +- if data != "": +- os.waitpid(self.pid, 0) +- child_exception = pickle.loads(data) +- raise child_exception +- +- +- def _handle_exitstatus(self, sts): +- if os.WIFSIGNALED(sts): +- self.returncode = -os.WTERMSIG(sts) +- elif os.WIFEXITED(sts): +- self.returncode = os.WEXITSTATUS(sts) +- else: +- # Should never happen +- raise RuntimeError("Unknown child exit status!") +- +- _active.remove(self) +- +- +- def poll(self): +- """Check if child process has terminated. Returns returncode +- attribute.""" +- if self.returncode == None: +- try: +- pid, sts = os.waitpid(self.pid, os.WNOHANG) +- if pid == self.pid: +- self._handle_exitstatus(sts) +- except os.error: +- pass +- return self.returncode +- +- +- def wait(self): +- """Wait for child process to terminate. Returns returncode +- attribute.""" +- if self.returncode == None: +- pid, sts = os.waitpid(self.pid, 0) +- self._handle_exitstatus(sts) +- return self.returncode +- +- +- def communicate(self, input=None): +- """Interact with process: Send data to stdin. Read data from +- stdout and stderr, until end-of-file is reached. Wait for +- process to terminate. The optional input argument should be a +- string to be sent to the child process, or None, if no data +- should be sent to the child. +- +- communicate() returns a tuple (stdout, stderr).""" +- read_set = [] +- write_set = [] +- stdout = None # Return +- stderr = None # Return +- +- if self.stdin: +- # Flush stdio buffer. This might block, if the user has +- # been writing to .stdin in an uncontrolled fashion. +- self.stdin.flush() +- if input: +- write_set.append(self.stdin) +- else: +- self.stdin.close() +- if self.stdout: +- read_set.append(self.stdout) +- stdout = [] +- if self.stderr: +- read_set.append(self.stderr) +- stderr = [] +- +- while read_set or write_set: +- rlist, wlist, xlist = select.select(read_set, write_set, []) +- +- if self.stdin in wlist: +- # When select has indicated that the file is writable, +- # we can write up to PIPE_BUF bytes without risk +- # blocking. POSIX defines PIPE_BUF >= 512 +- bytes_written = os.write(self.stdin.fileno(), input[:512]) +- input = input[bytes_written:] +- if not input: +- self.stdin.close() +- write_set.remove(self.stdin) +- +- if self.stdout in rlist: +- data = os.read(self.stdout.fileno(), 1024) +- if data == "": +- self.stdout.close() +- read_set.remove(self.stdout) +- stdout.append(data) +- +- if self.stderr in rlist: +- data = os.read(self.stderr.fileno(), 1024) +- if data == "": +- self.stderr.close() +- read_set.remove(self.stderr) +- stderr.append(data) +- +- # All data exchanged. Translate lists into strings. +- if stdout != None: +- stdout = ''.join(stdout) +- if stderr != None: +- stderr = ''.join(stderr) +- +- # Translate newlines, if requested. We cannot let the file +- # object do the translation: It is based on stdio, which is +- # impossible to combine with select (unless forcing no +- # buffering). +- if self.universal_newlines and hasattr(open, 'newlines'): +- if stdout: +- stdout = self._translate_newlines(stdout) +- if stderr: +- stderr = self._translate_newlines(stderr) +- +- self.wait() +- return (stdout, stderr) +- +- +-def _demo_posix(): +- # +- # Example 1: Simple redirection: Get process list +- # +- plist = Popen(["ps"], stdout=PIPE).communicate()[0] +- print "Process list:" +- print plist +- +- # +- # Example 2: Change uid before executing child +- # +- if os.getuid() == 0: +- p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) +- p.wait() +- +- # +- # Example 3: Connecting several subprocesses +- # +- print "Looking for 'hda'..." +- p1 = Popen(["dmesg"], stdout=PIPE) +- p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) +- print repr(p2.communicate()[0]) +- +- # +- # Example 4: Catch execution error +- # +- print +- print "Trying a weird file..." +- try: +- print Popen(["/this/path/does/not/exist"]).communicate() +- except OSError, e: +- if e.errno == errno.ENOENT: +- print "The file didn't exist. I thought so..." +- print "Child traceback:" +- print e.child_traceback +- else: +- print "Error", e.errno +- else: +- print >>sys.stderr, "Gosh. No error." +- +- +-def _demo_windows(): +- # +- # Example 1: Connecting several subprocesses +- # +- print "Looking for 'PROMPT' in set output..." +- p1 = Popen("set", stdout=PIPE, shell=True) +- p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) +- print repr(p2.communicate()[0]) +- +- # +- # Example 2: Simple execution of program +- # +- print "Executing calc..." +- p = Popen("calc") +- p.wait() +- +- +-if __name__ == "__main__": +- if mswindows: +- _demo_windows() +- else: +- _demo_posix() +diff --git a/koan/text_wrap.py b/koan/text_wrap.py +deleted file mode 100644 +index 2035463..0000000 +--- a/koan/text_wrap.py ++++ /dev/null +@@ -1,354 +0,0 @@ +-"""Text wrapping and filling. +-(note: repackaged in koan because it's not present in RHEL3) +-""" +- +-# Copyright (C) 1999-2001 Gregory P. Ward. +-# Copyright (C) 2002, 2003 Python Software Foundation. +-# Written by Greg Ward +- +-__revision__ = "$Id: textwrap.py,v 1.32.8.2 2004/05/13 01:48:15 gward Exp $" +- +-import string, re +- +-# Do the right thing with boolean values for all known Python versions +-# (so this module can be copied to projects that don't depend on Python +-# 2.3, e.g. Optik and Docutils). +-try: +- True, False +-except NameError: +- (True, False) = (1, 0) +- +-__all__ = ['TextWrapper', 'wrap', 'fill'] +- +-# Hardcode the recognized whitespace characters to the US-ASCII +-# whitespace characters. The main reason for doing this is that in +-# ISO-8859-1, 0xa0 is non-breaking whitespace, so in certain locales +-# that character winds up in string.whitespace. Respecting +-# string.whitespace in those cases would 1) make textwrap treat 0xa0 the +-# same as any other whitespace char, which is clearly wrong (it's a +-# *non-breaking* space), 2) possibly cause problems with Unicode, +-# since 0xa0 is not in range(128). +-_whitespace = '\t\n\x0b\x0c\r ' +- +-class TextWrapper: +- """ +- Object for wrapping/filling text. The public interface consists of +- the wrap() and fill() methods; the other methods are just there for +- subclasses to override in order to tweak the default behaviour. +- If you want to completely replace the main wrapping algorithm, +- you'll probably have to override _wrap_chunks(). +- +- Several instance attributes control various aspects of wrapping: +- width (default: 70) +- the maximum width of wrapped lines (unless break_long_words +- is false) +- initial_indent (default: "") +- string that will be prepended to the first line of wrapped +- output. Counts towards the line's width. +- subsequent_indent (default: "") +- string that will be prepended to all lines save the first +- of wrapped output; also counts towards each line's width. +- expand_tabs (default: true) +- Expand tabs in input text to spaces before further processing. +- Each tab will become 1 .. 8 spaces, depending on its position in +- its line. If false, each tab is treated as a single character. +- replace_whitespace (default: true) +- Replace all whitespace characters in the input text by spaces +- after tab expansion. Note that if expand_tabs is false and +- replace_whitespace is true, every tab will be converted to a +- single space! +- fix_sentence_endings (default: false) +- Ensure that sentence-ending punctuation is always followed +- by two spaces. Off by default because the algorithm is +- (unavoidably) imperfect. +- break_long_words (default: true) +- Break words longer than 'width'. If false, those words will not +- be broken, and some lines might be longer than 'width'. +- """ +- +- whitespace_trans = string.maketrans(_whitespace, ' ' * len(_whitespace)) +- +- unicode_whitespace_trans = {} +- uspace = ord(u' ') +- for x in map(ord, _whitespace): +- unicode_whitespace_trans[x] = uspace +- +- # This funky little regex is just the trick for splitting +- # text up into word-wrappable chunks. E.g. +- # "Hello there -- you goof-ball, use the -b option!" +- # splits into +- # Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option! +- # (after stripping out empty strings). +- wordsep_re = re.compile(r'(\s+|' # any whitespace +- r'-*\w{2,}-(?=\w{2,})|' # hyphenated words +- r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash +- +- # XXX will there be a locale-or-charset-aware version of +- # string.lowercase in 2.3? +- sentence_end_re = re.compile(r'[%s]' # lowercase letter +- r'[\.\!\?]' # sentence-ending punct. +- r'[\"\']?' # optional end-of-quote +- % string.lowercase) +- +- +- def __init__(self, +- width=70, +- initial_indent="", +- subsequent_indent="", +- expand_tabs=True, +- replace_whitespace=True, +- fix_sentence_endings=False, +- break_long_words=True): +- self.width = width +- self.initial_indent = initial_indent +- self.subsequent_indent = subsequent_indent +- self.expand_tabs = expand_tabs +- self.replace_whitespace = replace_whitespace +- self.fix_sentence_endings = fix_sentence_endings +- self.break_long_words = break_long_words +- +- +- # -- Private methods ----------------------------------------------- +- # (possibly useful for subclasses to override) +- +- def _munge_whitespace(self, text): +- """_munge_whitespace(text : string) -> string +- +- Munge whitespace in text: expand tabs and convert all other +- whitespace characters to spaces. Eg. " foo\tbar\n\nbaz" +- becomes " foo bar baz". +- """ +- if self.expand_tabs: +- text = text.expandtabs() +- if self.replace_whitespace: +- if isinstance(text, str): +- text = text.translate(self.whitespace_trans) +- elif isinstance(text, unicode): +- text = text.translate(self.unicode_whitespace_trans) +- return text +- +- +- def _split(self, text): +- """_split(text : string) -> [string] +- +- Split the text to wrap into indivisible chunks. Chunks are +- not quite the same as words; see wrap_chunks() for full +- details. As an example, the text +- Look, goof-ball -- use the -b option! +- breaks into the following chunks: +- 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', +- 'use', ' ', 'the', ' ', '-b', ' ', 'option!' +- """ +- chunks = self.wordsep_re.split(text) +- chunks = filter(None, chunks) +- return chunks +- +- def _fix_sentence_endings(self, chunks): +- """_fix_sentence_endings(chunks : [string]) +- +- Correct for sentence endings buried in 'chunks'. Eg. when the +- original text contains "... foo.\nBar ...", munge_whitespace() +- and split() will convert that to [..., "foo.", " ", "Bar", ...] +- which has one too few spaces; this method simply changes the one +- space to two. +- """ +- i = 0 +- pat = self.sentence_end_re +- while i < len(chunks)-1: +- if chunks[i+1] == " " and pat.search(chunks[i]): +- chunks[i+1] = " " +- i += 2 +- else: +- i += 1 +- +- def _handle_long_word(self, chunks, cur_line, cur_len, width): +- """_handle_long_word(chunks : [string], +- cur_line : [string], +- cur_len : int, width : int) +- +- Handle a chunk of text (most likely a word, not whitespace) that +- is too long to fit in any line. +- """ +- space_left = max(width - cur_len, 1) +- +- # If we're allowed to break long words, then do so: put as much +- # of the next chunk onto the current line as will fit. +- if self.break_long_words: +- cur_line.append(chunks[0][0:space_left]) +- chunks[0] = chunks[0][space_left:] +- +- # Otherwise, we have to preserve the long word intact. Only add +- # it to the current line if there's nothing already there -- +- # that minimizes how much we violate the width constraint. +- elif not cur_line: +- cur_line.append(chunks.pop(0)) +- +- # If we're not allowed to break long words, and there's already +- # text on the current line, do nothing. Next time through the +- # main loop of _wrap_chunks(), we'll wind up here again, but +- # cur_len will be zero, so the next line will be entirely +- # devoted to the long word that we can't handle right now. +- +- def _wrap_chunks(self, chunks): +- """_wrap_chunks(chunks : [string]) -> [string] +- +- Wrap a sequence of text chunks and return a list of lines of +- length 'self.width' or less. (If 'break_long_words' is false, +- some lines may be longer than this.) Chunks correspond roughly +- to words and the whitespace between them: each chunk is +- indivisible (modulo 'break_long_words'), but a line break can +- come between any two chunks. Chunks should not have internal +- whitespace; ie. a chunk is either all whitespace or a "word". +- Whitespace chunks will be removed from the beginning and end of +- lines, but apart from that whitespace is preserved. +- """ +- lines = [] +- if self.width <= 0: +- raise ValueError("invalid width %r (must be > 0)" % self.width) +- +- while chunks: +- +- # Start the list of chunks that will make up the current line. +- # cur_len is just the length of all the chunks in cur_line. +- cur_line = [] +- cur_len = 0 +- +- # Figure out which static string will prefix this line. +- if lines: +- indent = self.subsequent_indent +- else: +- indent = self.initial_indent +- +- # Maximum width for this line. +- width = self.width - len(indent) +- +- # First chunk on line is whitespace -- drop it, unless this +- # is the very beginning of the text (ie. no lines started yet). +- if chunks[0].strip() == '' and lines: +- del chunks[0] +- +- while chunks: +- l = len(chunks[0]) +- +- # Can at least squeeze this chunk onto the current line. +- if cur_len + l <= width: +- cur_line.append(chunks.pop(0)) +- cur_len += l +- +- # Nope, this line is full. +- else: +- break +- +- # The current line is full, and the next chunk is too big to +- # fit on *any* line (not just this one). +- if chunks and len(chunks[0]) > width: +- self._handle_long_word(chunks, cur_line, cur_len, width) +- +- # If the last chunk on this line is all whitespace, drop it. +- if cur_line and cur_line[-1].strip() == '': +- del cur_line[-1] +- +- # Convert current line back to a string and store it in list +- # of all lines (return value). +- if cur_line: +- lines.append(indent + ''.join(cur_line)) +- +- return lines +- +- +- # -- Public interface ---------------------------------------------- +- +- def wrap(self, text): +- """wrap(text : string) -> [string] +- +- Reformat the single paragraph in 'text' so it fits in lines of +- no more than 'self.width' columns, and return a list of wrapped +- lines. Tabs in 'text' are expanded with string.expandtabs(), +- and all other whitespace characters (including newline) are +- converted to space. +- """ +- text = self._munge_whitespace(text) +- indent = self.initial_indent +- chunks = self._split(text) +- if self.fix_sentence_endings: +- self._fix_sentence_endings(chunks) +- return self._wrap_chunks(chunks) +- +- def fill(self, text): +- """fill(text : string) -> string +- +- Reformat the single paragraph in 'text' to fit in lines of no +- more than 'self.width' columns, and return a new string +- containing the entire wrapped paragraph. +- """ +- return "\n".join(self.wrap(text)) +- +- +-# -- Convenience interface --------------------------------------------- +- +-def wrap(text, width=70, **kwargs): +- """Wrap a single paragraph of text, returning a list of wrapped lines. +- +- Reformat the single paragraph in 'text' so it fits in lines of no +- more than 'width' columns, and return a list of wrapped lines. By +- default, tabs in 'text' are expanded with string.expandtabs(), and +- all other whitespace characters (including newline) are converted to +- space. See TextWrapper class for available keyword args to customize +- wrapping behaviour. +- """ +- w = TextWrapper(width=width, **kwargs) +- return w.wrap(text) +- +-def fill(text, width=70, **kwargs): +- """Fill a single paragraph of text, returning a new string. +- +- Reformat the single paragraph in 'text' to fit in lines of no more +- than 'width' columns, and return a new string containing the entire +- wrapped paragraph. As with wrap(), tabs are expanded and other +- whitespace characters converted to space. See TextWrapper class for +- available keyword args to customize wrapping behaviour. +- """ +- w = TextWrapper(width=width, **kwargs) +- return w.fill(text) +- +- +-# -- Loosely related functionality ------------------------------------- +- +-def dedent(text): +- """dedent(text : string) -> string +- +- Remove any whitespace than can be uniformly removed from the left +- of every line in `text`. +- +- This can be used e.g. to make triple-quoted strings line up with +- the left edge of screen/whatever, while still presenting it in the +- source code in indented form. +- +- For example: +- +- def test(): +- # end first line with \ to avoid the empty line! +- s = '''\ +- hello +- world +- ''' +- print repr(s) # prints ' hello\n world\n ' +- print repr(dedent(s)) # prints 'hello\n world\n' +- """ +- lines = text.expandtabs().split('\n') +- margin = None +- for line in lines: +- content = line.lstrip() +- if not content: +- continue +- indent = len(line) - len(content) +- if margin is None: +- margin = indent +- else: +- margin = min(margin, indent) +- +- if margin is not None and margin > 0: +- for i in range(len(lines)): +- lines[i] = lines[i][margin:] +- +- return '\n'.join(lines) +diff --git a/koan/utils.py b/koan/utils.py +index 475bcd0..4a3f547 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -21,29 +21,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + """ + +-import random + import os + import traceback +-import tempfile +-ANCIENT_PYTHON = 0 +-try: +- try: +- import subprocess as sub_process +- except: +- import sub_process +- import urllib2 +-except: +- ANCIENT_PYTHON = 1 +-import time +-import shutil +-import errno +-import re + import sys ++import subprocess as sub_process ++import urllib2 + import xmlrpclib + import string +-import re +-import glob +-import socket + import shutil + import tempfile + import urlgrabber +@@ -131,13 +115,6 @@ def urlread(url): + fd.close() + return data + except: +- if ANCIENT_PYTHON: +- # this logic is to support python 1.5 and EL 2 +- import urllib +- fd = urllib.urlopen(url) +- data = fd.read() +- fd.close() +- return data + traceback.print_exc() + raise InfoException, "Couldn't download: %s" % url + elif url[0:4] == "file": +@@ -167,12 +144,7 @@ def subprocess_call(cmd,ignore_rc=0): + Wrapper around subprocess.call(...) + """ + print "- %s" % cmd +- if not ANCIENT_PYTHON: +- rc = sub_process.call(cmd) +- else: +- cmd = string.join(cmd, " ") +- print "cmdstr=(%s)" % cmd +- rc = os.system(cmd) ++ rc = sub_process.call(cmd) + if rc != 0 and not ignore_rc: + raise InfoException, "command failed (%s)" % rc + return rc +@@ -184,15 +156,10 @@ def subprocess_get_response(cmd, ignore_rc=False): + print "- %s" % cmd + rc = 0 + result = "" +- if not ANCIENT_PYTHON: +- p = sub_process.Popen(cmd, stdout=sub_process.PIPE, stderr=sub_process.STDOUT) +- result, stderr = p.communicate() +- result = result.strip() +- rc = p.poll() +- else: +- cmd = string.join(cmd, " ") +- print "cmdstr=(%s)" % cmd +- rc = os.system(cmd) ++ p = sub_process.Popen(cmd, stdout=sub_process.PIPE, stderr=sub_process.STDOUT) ++ result, stderr = p.communicate() ++ result = result.strip() ++ rc = p.poll() + if not ignore_rc and rc != 0: + raise InfoException, "command failed (%s)" % rc + return rc, result +@@ -347,9 +314,6 @@ def os_release(): + This code is borrowed from Cobbler and really shouldn't be repeated. + """ + +- if ANCIENT_PYTHON: +- return ("unknown", 0) +- + if check_dist() == "redhat": + fh = open("/etc/redhat-release") + data = fh.read().lower() +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 1bde891..3fda926 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -21,12 +21,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + """ + + +-import os, sys, time, stat +-import tempfile ++import os + import random +-from optparse import OptionParser +-import errno +-import re + import virtinst + + IMAGE_DIR = "/var/lib/vmware/images" +-- +2.5.5 + diff --git a/SOURCES/0003-fixing-xmlrpclib-urllib2-and-local-imports-in-Python.patch b/SOURCES/0003-fixing-xmlrpclib-urllib2-and-local-imports-in-Python.patch new file mode 100644 index 0000000..87db6b7 --- /dev/null +++ b/SOURCES/0003-fixing-xmlrpclib-urllib2-and-local-imports-in-Python.patch @@ -0,0 +1,195 @@ +From bad2b8a68909f7fba0252b2d61bbd98489fc4996 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:10:46 +0100 +Subject: [PATCH 03/17] fixing xmlrpclib, urllib2 and local imports in Python 3 + +--- + koan/app.py | 23 +++++++++++++---------- + koan/imagecreate.py | 4 ++-- + koan/qcreate.py | 4 ++-- + koan/register.py | 7 +++++-- + koan/utils.py | 8 ++++++-- + koan/virtinstall.py | 4 ++-- + koan/vmwcreate.py | 1 - + koan/xencreate.py | 4 ++-- + 8 files changed, 32 insertions(+), 23 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index 801fedd..e4e8a6d 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -36,10 +36,13 @@ import time + import shutil + import errno + import sys +-import xmlrpclib ++try: #python2 ++ import xmlrpclib ++except ImportError: #python3 ++ import xmlrpc.client as xmlrpclib + import string + import re +-import utils ++from . import utils + + COBBLER_REQUIRED = 1.300 + +@@ -1124,9 +1127,9 @@ class Koan: + """ + pd = profile_data + # importing can't throw exceptions any more, don't put it in a sub-method +- import xencreate +- import qcreate +- import imagecreate ++ from . import xencreate ++ from . import qcreate ++ from . import imagecreate + + arch = self.safe_load(pd,'arch','x86') + kextra = self.calc_kernel_args(pd) +@@ -1211,11 +1214,11 @@ class Koan: + if (self.image is not None) and (pd["image_type"] == "virt-clone"): + fullvirt = True + uuid = None +- import imagecreate ++ from . import imagecreate + creator = imagecreate.start_install + elif self.virt_type in [ "xenpv", "xenfv" ]: + uuid = self.get_uuid(self.calc_virt_uuid(pd)) +- import xencreate ++ from . import xencreate + creator = xencreate.start_install + if self.virt_type == "xenfv": + fullvirt = True +@@ -1223,15 +1226,15 @@ class Koan: + elif self.virt_type == "qemu": + fullvirt = True + uuid = None +- import qcreate ++ from . import qcreate + creator = qcreate.start_install + can_poll = "qemu" + elif self.virt_type == "vmware": +- import vmwcreate ++ from . import vmwcreate + uuid = None + creator = vmwcreate.start_install + elif self.virt_type == "vmwarew": +- import vmwwcreate ++ from . import vmwwcreate + uuid = None + creator = vmwwcreate.start_install + else: +diff --git a/koan/imagecreate.py b/koan/imagecreate.py +index 6bd9d9b..d70d519 100644 +--- a/koan/imagecreate.py ++++ b/koan/imagecreate.py +@@ -23,8 +23,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + """ + +-import utils +-import virtinstall ++from . import utils ++from . import virtinstall + + def start_install(*args, **kwargs): + cmd = virtinstall.build_commandline("import", *args, **kwargs) +diff --git a/koan/qcreate.py b/koan/qcreate.py +index 019a266..1db3d97 100755 +--- a/koan/qcreate.py ++++ b/koan/qcreate.py +@@ -23,8 +23,8 @@ module for creating fullvirt guests via KVM/kqemu/qemu + requires python-virtinst-0.200 (or virt-install in later distros). + """ + +-import utils +-import virtinstall ++from . import utils ++from . import virtinstall + from xml.dom.minidom import parseString + + def start_install(*args, **kwargs): +diff --git a/koan/register.py b/koan/register.py +index 871b8ac..cabd4a6 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -25,9 +25,12 @@ import traceback + from optparse import OptionParser + import time + import sys +-import xmlrpclib ++try: #python2 ++ import xmlrpclib ++except ImportError: #python3 ++ import xmlrpc.client as xmlrpclib + import socket +-import utils ++from . import utils + import string + + # usage: cobbler-register [--server=server] [--hostname=hostname] --profile=foo +diff --git a/koan/utils.py b/koan/utils.py +index 4a3f547..b8247e2 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -25,8 +25,12 @@ import os + import traceback + import sys + import subprocess as sub_process +-import urllib2 +-import xmlrpclib ++try: #python2 ++ import urllib2 ++ import xmlrpclib ++except ImportError: #python3 ++ import urllib.request as urllib2 ++ import xmlrpc.client as xmlrpclib + import string + import shutil + import tempfile +diff --git a/koan/virtinstall.py b/koan/virtinstall.py +index 4ef7874..ca11e04 100644 +--- a/koan/virtinstall.py ++++ b/koan/virtinstall.py +@@ -30,8 +30,8 @@ import os + import re + import shlex + +-import app as koan +-import utils ++from . import app as koan ++from . import utils + + # The virtinst module will no longer be availabe to import in some + # distros. We need to get all the info we need from the virt-install +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 3fda926..372173a 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -23,7 +23,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + + import os + import random +-import virtinst + + IMAGE_DIR = "/var/lib/vmware/images" + VMX_DIR = "/var/lib/vmware/vmx" +diff --git a/koan/xencreate.py b/koan/xencreate.py +index c3492ed..7eda3e6 100755 +--- a/koan/xencreate.py ++++ b/koan/xencreate.py +@@ -26,8 +26,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + """ + +-import utils +-import virtinstall ++from . import utils ++from . import virtinstall + + def start_install(*args, **kwargs): + cmd = virtinstall.build_commandline("xen:///", *args, **kwargs) +-- +2.5.5 + diff --git a/SOURCES/0004-Python-3-compatible-prints.patch b/SOURCES/0004-Python-3-compatible-prints.patch new file mode 100644 index 0000000..8eea735 --- /dev/null +++ b/SOURCES/0004-Python-3-compatible-prints.patch @@ -0,0 +1,715 @@ +From d045ccd1730c6211252cd68cd69d387895df9641 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:15:58 +0100 +Subject: [PATCH 04/17] Python 3 compatible prints + +--- + koan/app.py | 106 ++++++++++++++++++++++++++-------------------------- + koan/register.py | 24 ++++++------ + koan/utils.py | 36 +++++++++--------- + koan/virtinstall.py | 12 +++--- + koan/vmwcreate.py | 22 +++++------ + 5 files changed, 100 insertions(+), 100 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index e4e8a6d..ce8c8c3 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -217,11 +217,11 @@ def main(): + (xa, xb, tb) = sys.exc_info() + try: + getattr(e,"from_koan") +- print str(e)[1:-1] # nice exception, no traceback needed ++ print(str(e)[1:-1]) # nice exception, no traceback needed + except: +- print xa +- print xb +- print string.join(traceback.format_list(traceback.extract_tb(tb))) ++ print(xa) ++ print(xb) ++ print(string.join(traceback.format_list(traceback.extract_tb(tb)))) + return 1 + + return 0 +@@ -307,9 +307,9 @@ class Koan: + + if not os.getuid() == 0: + if self.is_virt: +- print "warning: running as non root" ++ print("warning: running as non root") + else: +- print "this operation requires root access" ++ print("this operation requires root access") + return 3 + + # if both --profile and --system were ommitted, autodiscover +@@ -372,20 +372,20 @@ class Koan: + try: + available_profiles = self.xmlrpc_server.get_profiles() + except: +- traceback.print_exc() ++ traceback.print(_exc()) + self.connect_fail() + +- print "\n- which profile to install?\n" ++ print("\n- which profile to install?\n") + + for x in available_profiles: +- print "%s" % x["name"] ++ print("%s" % x["name"]) + + sys.stdout.write("\n?>") + + data = sys.stdin.readline().strip() + + for x in available_profiles: +- print "comp (%s,%s)" % (x["name"],data) ++ print("comp (%s,%s)" % (x["name"],data)) + if x["name"] == data: + return data + return None +@@ -432,7 +432,7 @@ class Koan: + else: + return None + elif len(detected_systems) == 1: +- print "- Auto detected: %s" % detected_systems[0] ++ print("- Auto detected: %s" % detected_systems[0]) + return detected_systems[0] + + #--------------------------------------------------- +@@ -509,7 +509,7 @@ class Koan: + raise InfoException("xmlfile based installations are not supported") + + elif profile_data.has_key("file"): +- print "- ISO or Image based installation, always uses --virt-type=qemu" ++ print("- ISO or Image based installation, always uses --virt-type=qemu") + self.virt_type = "qemu" + + else: +@@ -525,7 +525,7 @@ class Koan: + # assume Xen, we'll check to see if virt-type is really usable later. + raise InfoException, "Not running a Xen kernel and qemu is not installed" + +- print "- no virt-type specified, auto-selecting %s" % self.virt_type ++ print("- no virt-type specified, auto-selecting %s" % self.virt_type) + + # now that we've figured out our virt-type, let's see if it is really usable + # rather than showing obscure error messages from Xen to the user :) +@@ -628,9 +628,9 @@ class Koan: + break + + if self.safe_load(profile_data,"install_tree"): +- print "install_tree:", profile_data["install_tree"] ++ print("install_tree:", profile_data["install_tree"]) + else: +- print "warning: kickstart found but no install_tree found" ++ print("warning: kickstart found but no install_tree found") + + except: + # unstable to download the kickstart, however this might not +@@ -646,7 +646,7 @@ class Koan: + data = self.get_data(what) + for x in data: + if x.has_key("name"): +- print x["name"] ++ print(x["name"]) + return True + + #--------------------------------------------------- +@@ -658,7 +658,7 @@ class Koan: + value = profile_data[x] + if x == 'kernel_options': + value = self.calc_kernel_args(profile_data) +- print "%20s : %s" % (x, value) ++ print("%20s : %s" % (x, value)) + return self.net_install(after_download) + + #--------------------------------------------------- +@@ -702,9 +702,9 @@ class Koan: + template_files = utils.input_string_or_hash(template_files) + template_keys = template_files.keys() + +- print "- template map: %s" % template_files ++ print("- template map: %s" % template_files) + +- print "- processing for files to download..." ++ print("- processing for files to download...") + for src in template_keys: + dest = template_files[src] + save_as = dest +@@ -713,7 +713,7 @@ class Koan: + if not save_as.startswith("/"): + # this is a file in the template system that is not to be downloaded + continue +- print "- file: %s" % save_as ++ print("- file: %s" % save_as) + + pattern = "http://%s/cblr/svc/op/template/%s/%s/path/%s" + if profile_data.has_key("interfaces"): +@@ -780,7 +780,7 @@ class Koan: + '--command-line=%s' % (k_args,), + self.safe_load(profile_data,'kernel_local') + ]) +- print "Kernel loaded; run 'kexec -e' to execute" ++ print("Kernel loaded; run 'kexec -e' to execute") + return self.net_install(after_download) + + +@@ -900,25 +900,25 @@ class Koan: + # Any post-grubby processing required (e.g. ybin, zipl, lilo)? + if arch.startswith("ppc") and "grub2" not in probe_output: + # FIXME - CHRP hardware uses a 'PPC PReP Boot' partition and doesn't require running ybin +- print "- applying ybin changes" ++ print("- applying ybin changes") + cmd = [ "/sbin/ybin" ] + utils.subprocess_call(cmd) + elif arch.startswith("s390"): +- print "- applying zipl changes" ++ print("- applying zipl changes") + cmd = [ "/sbin/zipl" ] + utils.subprocess_call(cmd) + else: + # if grubby --bootloader-probe returns lilo, + # apply lilo changes + if boot_probe_ret_code == 0 and string.find(probe_output, "lilo") != -1: +- print "- applying lilo changes" ++ print("- applying lilo changes") + cmd = [ "/sbin/lilo" ] + utils.subprocess_call(cmd) + + if not self.add_reinstall_entry: +- print "- reboot to apply changes" ++ print("- reboot to apply changes") + else: +- print "- reinstallation entry added" ++ print("- reinstallation entry added") + + return self.net_install(after_download) + +@@ -986,7 +986,7 @@ class Koan: + else: + data = getattr(self.xmlrpc_server, "get_%s_for_koan" % what)(name) + except: +- traceback.print_exc() ++ traceback.print(_exc()) + self.connect_fail() + if data == {}: + raise InfoException("No entry/entries found") +@@ -1049,15 +1049,15 @@ class Koan: + initrd = "http://%s/cobbler/images/%s/%s" % (self.server, distro, initrd_short) + + try: +- print "downloading initrd %s to %s" % (initrd_short, initrd_save) +- print "url=%s" % initrd ++ print("downloading initrd %s to %s" % (initrd_short, initrd_save)) ++ print("url=%s" % initrd) + utils.urlgrab(initrd,initrd_save) + +- print "downloading kernel %s to %s" % (kernel_short, kernel_save) +- print "url=%s" % kernel ++ print("downloading kernel %s to %s" % (kernel_short, kernel_save)) ++ print("url=%s" % kernel) + utils.urlgrab(kernel,kernel_save) + except: +- traceback.print_exc() ++ traceback.print(_exc()) + raise InfoException, "error downloading files" + profile_data['kernel_local'] = kernel_save + profile_data['initrd_local'] = initrd_save +@@ -1166,11 +1166,11 @@ class Koan: + noreboot = self.virtinstall_noreboot, + ) + +- print results ++ print(results) + + if can_poll is not None and self.should_poll: + import libvirt +- print "- polling for virt completion" ++ print("- polling for virt completion") + conn = None + if can_poll == "xen": + conn = libvirt.open(None) +@@ -1183,14 +1183,14 @@ class Koan: + time.sleep(3) + state = utils.get_vm_state(conn, virtname) + if state == "running": +- print "- install is still running, sleeping for 1 minute (%s)" % ct ++ print("- install is still running, sleeping for 1 minute (%s)" % ct) + ct = ct + 1 + time.sleep(60) + elif state == "crashed": +- print "- the install seems to have crashed." ++ print("- the install seems to have crashed.") + return "failed" + elif state == "shutdown": +- print "- shutdown VM detected, is the install done? Restarting!" ++ print("- shutdown VM detected, is the install done? Restarting!") + utils.find_vm(conn, virtname).create() + return results + else: +@@ -1202,7 +1202,7 @@ class Koan: + elif self.virt_type == "qemu": + utils.libvirt_enable_autostart(virtname) + else: +- print "- warning: don't know how to autoboot this virt type yet" ++ print("- warning: don't know how to autoboot this virt type yet") + # else... + return results + +@@ -1255,8 +1255,8 @@ class Koan: + disks.append([path,size]) + counter = counter + 1 + if len(disks) == 0: +- print "paths: ", paths +- print "sizes: ", sizes ++ print("paths: ", paths) ++ print("sizes: ", sizes) + raise InfoException, "Disk configuration not resolvable!" + return disks + +@@ -1320,7 +1320,7 @@ class Koan: + if size is None or size == '': + err = True + if err: +- print "invalid file size specified, using defaults" ++ print("invalid file size specified, using defaults") + return default_filesize + return int(size) + +@@ -1339,7 +1339,7 @@ class Koan: + if size is None or size == '' or int(size) < default_ram: + err = True + if err: +- print "invalid RAM size specified, using defaults." ++ print("invalid RAM size specified, using defaults.") + return default_ram + return int(size) + +@@ -1353,7 +1353,7 @@ class Koan: + try: + isize = int(size) + except: +- traceback.print_exc() ++ traceback.print(_exc()) + return default_cpus + return isize + +@@ -1386,7 +1386,7 @@ class Koan: + if my_id is None or my_id == '' or not uuid_re.match(id): + err = True + if err and my_id is not None: +- print "invalid UUID specified. randomizing..." ++ print("invalid UUID specified. randomizing...") + return None + return my_id + +@@ -1415,7 +1415,7 @@ class Koan: + else: + prefix = "/var/lib/vmware/images/" + if not os.path.exists(prefix): +- print "- creating: %s" % prefix ++ print("- creating: %s" % prefix) + os.makedirs(prefix) + return [ "%s/%s-disk0" % (prefix, name) ] + +@@ -1456,7 +1456,7 @@ class Koan: + elif not os.path.exists(location) and os.path.isdir(os.path.dirname(location)): + return location + elif not os.path.exists(os.path.dirname(location)): +- print "- creating: %s" % os.path.dirname(location) ++ print("- creating: %s" % os.path.dirname(location)) + os.makedirs(os.path.dirname(location)) + return location + else: +@@ -1470,21 +1470,21 @@ class Koan: + else: + # it's a volume group, verify that it exists + args = "vgs -o vg_name" +- print "%s" % args ++ print("%s" % args) + vgnames = sub_process.Popen(args, shell=True, stdout=sub_process.PIPE).communicate()[0] +- print vgnames ++ print(vgnames) + + if vgnames.find(location) == -1: + raise InfoException, "The volume group [%s] does not exist." % location + + # check free space + args = "LANG=C vgs --noheadings -o vg_free --units g %s" % location +- print args ++ print(args) + cmd = sub_process.Popen(args, stdout=sub_process.PIPE, shell=True) + freespace_str = cmd.communicate()[0] + freespace_str = freespace_str.split("\n")[0].strip() + freespace_str = freespace_str.lower().replace("g","") # remove gigabytes +- print "(%s)" % freespace_str ++ print("(%s)" % freespace_str) + freespace = int(float(freespace_str)) + + virt_size = self.calc_virt_filesize(pd) +@@ -1498,16 +1498,16 @@ class Koan: + + # look for LVM partition named foo, create if doesn't exist + args = "lvs -o lv_name %s" % location +- print "%s" % args ++ print("%s" % args) + lvs_str=sub_process.Popen(args, stdout=sub_process.PIPE, shell=True).communicate()[0] +- print lvs_str ++ print(lvs_str) + + name = "%s-disk%s" % (name,offset) + + # have to create it? + if lvs_str.find(name) == -1: + args = "lvcreate -L %sG -n %s %s" % (virt_size, name, location) +- print "%s" % args ++ print("%s" % args) + lv_create = sub_process.call(args, shell=True) + if lv_create != 0: + raise InfoException, "LVM creation failed" +diff --git a/koan/register.py b/koan/register.py +index cabd4a6..1157e5c 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -65,7 +65,7 @@ def main(): + + (options, args) = p.parse_args() + #if not os.getuid() == 0: +- # print "koan requires root access" ++ # print("koan requires root access") + # return 3 + + try: +@@ -80,11 +80,11 @@ def main(): + (xa, xb, tb) = sys.exc_info() + try: + getattr(e,"from_koan") +- print str(e)[1:-1] # nice exception, no traceback needed ++ print(str(e)[1:-1]) # nice exception, no traceback needed + except: +- print xa +- print xb +- print string.join(traceback.format_list(traceback.extract_tb(tb))) ++ print(xa) ++ print(xb) ++ print(string.join(traceback.format_list(traceback.extract_tb(tb)))) + return 1 + + return 0 +@@ -127,13 +127,13 @@ class Register: + if os.getuid() != 0: + raise InfoException("root access is required to register") + +- print "- preparing to koan home" ++ print("- preparing to koan home") + self.conn = utils.connect_to_server(self.server, self.port) + reg_info = {} +- print "- gathering network info" ++ print("- gathering network info") + netinfo = utils.get_network_info() + reg_info["interfaces"] = netinfo +- print "- checking hostname" ++ print("- checking hostname") + sysname = "" + if self.hostname != "" and self.hostname != "*AUTO*": + hostname = self.hostname +@@ -171,14 +171,14 @@ class Register: + + if not self.batch: + self.conn.register_new_system(reg_info) +- print "- registration successful, new system name: %s" % sysname ++ print("- registration successful, new system name: %s" % sysname) + else: + try: + self.conn.register_new_system(reg_info) +- print "- registration successful, new system name: %s" % sysname ++ print("- registration successful, new system name: %s" % sysname) + except: +- traceback.print_exc() +- print "- registration failed, ignoring because of --batch" ++ traceback.print(_exc()) ++ print("- registration failed, ignoring because of --batch") + + return + +diff --git a/koan/utils.py b/koan/utils.py +index b8247e2..77d53b2 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -90,7 +90,7 @@ def urlread(url): + parts of urlread and urlgrab from urlgrabber, in ways that + are less cool and less efficient. + """ +- print "- reading URL: %s" % url ++ print("- reading URL: %s" % url) + if url is None or url == "": + raise InfoException, "invalid URL: %s" % url + +@@ -109,7 +109,7 @@ def urlread(url): + subprocess_call(cmd) + return data + except: +- traceback.print_exc() ++ traceback.print(_exc()) + raise InfoException, "Couldn't mount and read URL: %s" % url + + elif url[0:4] == "http": +@@ -119,7 +119,7 @@ def urlread(url): + fd.close() + return data + except: +- traceback.print_exc() ++ traceback.print(_exc()) + raise InfoException, "Couldn't download: %s" % url + elif url[0:4] == "file": + try: +@@ -147,7 +147,7 @@ def subprocess_call(cmd,ignore_rc=0): + """ + Wrapper around subprocess.call(...) + """ +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = sub_process.call(cmd) + if rc != 0 and not ignore_rc: + raise InfoException, "command failed (%s)" % rc +@@ -157,7 +157,7 @@ def subprocess_get_response(cmd, ignore_rc=False): + """ + Wrapper around subprocess.check_output(...) + """ +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = 0 + result = "" + p = sub_process.Popen(cmd, stdout=sub_process.PIPE, stderr=sub_process.STDOUT) +@@ -219,7 +219,7 @@ def input_string_or_hash(options,delim=None,allow_multiples=True): + + def hash_to_string(hash): + """ +- Convert a hash to a printable string. ++ Convert a hash to a print(able string.) + used primarily in the kernel options string + and for some legacy stuff where koan expects strings + (though this last part should be changed to hashes) +@@ -232,7 +232,7 @@ def hash_to_string(hash): + if value is None: + buffer = buffer + str(key) + " " + elif type(value) == list: +- # this value is an array, so we print out every ++ # this value is an array, so we print(out every) + # key=value + for item in value: + buffer = buffer + str(key) + "=" + str(item) + " " +@@ -251,14 +251,14 @@ def nfsmount(input_path): + mount_cmd = [ + "/bin/mount", "-t", "nfs", "-o", "ro", dirpath, tempdir + ] +- print "- running: %s" % mount_cmd ++ print("- running: %s" % mount_cmd) + rc = sub_process.call(mount_cmd) + if not rc == 0: + shutil.rmtree(tempdir, ignore_errors=True) + raise koan.InfoException("nfs mount failed: %s" % dirpath) + # NOTE: option for a blocking install might be nice, so we could do this + # automatically, if supported by virt-install +- print "after install completes, you may unmount and delete %s" % tempdir ++ print("after install completes, you may unmount and delete %s" % tempdir) + return (tempdir, filename) + + +@@ -411,7 +411,7 @@ def get_network_info(): + "module" : module + } + +- # print interfaces ++ # print(interfaces) + return interfaces + + def connect_to_server(server=None,port=None): +@@ -431,7 +431,7 @@ def connect_to_server(server=None,port=None): + "https://%s/cobbler_api" % (server), + ] + for url in try_urls: +- print "- looking for Cobbler at %s" % url ++ print("- looking for Cobbler at %s" % url) + server = __try_connect(url) + if server is not None: + return server +@@ -467,18 +467,18 @@ def libvirt_enable_autostart(domain_name): + def make_floppy(kickstart): + + (fd, floppy_path) = tempfile.mkstemp(suffix='.floppy', prefix='tmp', dir="/tmp") +- print "- creating floppy image at %s" % floppy_path ++ print("- creating floppy image at %s" % floppy_path) + + # create the floppy image file + cmd = "dd if=/dev/zero of=%s bs=1440 count=1024" % floppy_path +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: + raise InfoException("dd failed") + + # vfatify + cmd = "mkdosfs %s" % floppy_path +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: + raise InfoException("mkdosfs failed") +@@ -486,19 +486,19 @@ def make_floppy(kickstart): + # mount the floppy + mount_path = tempfile.mkdtemp(suffix=".mnt", prefix='tmp', dir="/tmp") + cmd = "mount -o loop -t vfat %s %s" % (floppy_path, mount_path) +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: + raise InfoException("mount failed") + + # download the kickstart file onto the mounted floppy +- print "- downloading %s" % kickstart ++ print("- downloading %s" % kickstart) + save_file = os.path.join(mount_path, "unattended.txt") + urlgrabber.urlgrab(kickstart,filename=save_file) + + # umount + cmd = "umount %s" % mount_path +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: + raise InfoException("umount failed") +@@ -522,7 +522,7 @@ def __try_connect(url): + xmlrpc_server.ping() + return xmlrpc_server + except: +- traceback.print_exc() ++ traceback.print(_exc()) + return None + + +diff --git a/koan/virtinstall.py b/koan/virtinstall.py +index ca11e04..2d1f3df 100644 +--- a/koan/virtinstall.py ++++ b/koan/virtinstall.py +@@ -191,7 +191,7 @@ def build_commandline(uri, + oldstyle_accelerate = False + + if not virtinst_version: +- print ("- warning: old virt-install detected, a lot of features will be disabled") ++ print(("- warning: old virt-install detected, a lot of features will be disabled")) + disable_autostart = True + disable_boot_opt = True + disable_virt_type = True +@@ -239,7 +239,7 @@ def build_commandline(uri, + + # this is an image based installation + input_path = profile_data["file"] +- print "- using image location %s" % input_path ++ print("- using image location %s" % input_path) + if input_path.find(":") == -1: + # this is not an NFS path + cdrom = input_path +@@ -251,7 +251,7 @@ def build_commandline(uri, + if kickstart != "": + # we have a (windows?) answer file we have to provide + # to the ISO. +- print "I want to make a floppy for %s" % kickstart ++ print("I want to make a floppy for %s" % kickstart) + floppy = utils.make_floppy(kickstart) + elif is_qemu or is_xen: + # images don't need to source this +@@ -378,7 +378,7 @@ def build_commandline(uri, + # compatibility in virt-install grumble grumble. + cmd += "--os-variant %s" % os_version + ".0 " + else: +- print ("- warning: virt-install doesn't know this os_version, defaulting to generic26") ++ print(("- warning: virt-install doesn't know this os_version, defaulting to generic26")) + cmd += "--os-variant generic26 " + else: + distro = "unix" +@@ -394,8 +394,8 @@ def build_commandline(uri, + cmd += "--disk path=%s " % importpath + + for path, size, driver_type in disks: +- print ("- adding disk: %s of size %s (driver type=%s)" % +- (path, size, driver_type)) ++ print(("- adding disk: %s of size %s (driver type=%s)" % ++ (path, size, driver_type))) + cmd += "--disk path=%s" % (path) + if str(size) != "0": + cmd += ",size=%s" % size +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 372173a..97e0ba2 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -77,7 +77,7 @@ def random_mac(): + + def make_disk(disksize,image): + cmd = "vmware-vdiskmanager -c -a lsilogic -s %sGb -t 0 %s" % (disksize, image) +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if rc != 0: + raise VirtCreateException("command failed") +@@ -96,7 +96,7 @@ def make_vmx(path,vmdk_image,image_name,mac_address,memory): + + def register_vmx(vmx_file): + cmd = "vmware-cmd -s register %s" % vmx_file +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if rc!=0: + raise VirtCreateException("vmware registration failed") +@@ -104,7 +104,7 @@ def register_vmx(vmx_file): + def start_vm(vmx_file): + os.chmod(vmx_file,0755) + cmd = "vmware-cmd %s start" % vmx_file +- print "- %s" % cmd ++ print("- %s" % cmd) + rc = os.system(cmd) + if rc != 0: + raise VirtCreateException("vm start failed") +@@ -129,19 +129,19 @@ def start_install(name=None, + + mac = None + if not profile_data.has_key("interfaces"): +- print "- vmware installation requires a system, not a profile" ++ print("- vmware installation requires a system, not a profile") + return 1 + for iname in profile_data["interfaces"]: + intf = profile_data["interfaces"][iname] + mac = intf["mac_address"] + if mac is None: +- print "- no MAC information available in this record, cannot install" ++ print("- no MAC information available in this record, cannot install") + return 1 + +- print "DEBUG: name=%s" % name +- print "DEBUG: ram=%s" % ram +- print "DEBUG: mac=%s" % mac +- print "DEBUG: disks=%s" % disks ++ print("DEBUG: name=%s" % name) ++ print("DEBUG: ram=%s" % ram) ++ print("DEBUG: mac=%s" % mac) ++ print("DEBUG: disks=%s" % disks) + # starts vmware using PXE. disk/mem info come from Cobbler + # rest of the data comes from PXE which is also intended + # to be managed by Cobbler. +@@ -158,10 +158,10 @@ def start_install(name=None, + disksize = disks[0][1] + + image = "%s/%s" % (IMAGE_DIR, name) +- print "- saving virt disk image as %s" % image ++ print("- saving virt disk image as %s" % image) + make_disk(disksize,image) + vmx = "%s/%s" % (VMX_DIR, name) +- print "- saving vmx file as %s" % vmx ++ print("- saving vmx file as %s" % vmx) + make_vmx(vmx,image,name,mac,ram) + register_vmx(vmx) + start_vm(vmx) +-- +2.5.5 + diff --git a/SOURCES/0005-Python-3-compatible-exceptions.patch b/SOURCES/0005-Python-3-compatible-exceptions.patch new file mode 100644 index 0000000..ffd4418 --- /dev/null +++ b/SOURCES/0005-Python-3-compatible-exceptions.patch @@ -0,0 +1,448 @@ +From af3fa33d008de48edc76edafbae034512481cc28 Mon Sep 17 00:00:00 2001 +From: Jan Dobes +Date: Wed, 11 Oct 2017 16:53:07 +0200 +Subject: [PATCH 05/17] Python 3 compatible exceptions + +(cherry picked from commit 1d94e4941822ecb18f8d67672aace41f029141d4) + +Conflicts: + koan/app.py + koan/utils.py +--- + koan/app.py | 91 +++++++++++++++++++++++++++++++++---------------------- + koan/qcreate.py | 1 + + koan/register.py | 4 +-- + koan/utils.py | 24 +++++++-------- + koan/vmwcreate.py | 2 ++ + 5 files changed, 72 insertions(+), 50 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index ce8c8c3..c1d79c2 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -213,21 +213,38 @@ def main(): + k.port = options.port + k.run() + +- except Exception, e: ++ except Exception as e: + (xa, xb, tb) = sys.exc_info() + try: +- getattr(e,"from_koan") +- print(str(e)[1:-1]) # nice exception, no traceback needed ++ getattr(e, "from_koan") ++ print(str(e)[1:-1]) # nice exception, no traceback needed + except: + print(xa) + print(xb) +- print(string.join(traceback.format_list(traceback.extract_tb(tb)))) ++ print("".join(traceback.format_list(traceback.extract_tb(tb)))) + return 1 + + return 0 + + #======================================================= + ++ ++class KoanException(Exception): ++ ++ def __init__(self, value, *args): ++ self.value = value % args ++ # this is a hack to work around some odd exception handling ++ # in older pythons ++ self.from_koan = 1 ++ ++ def __str__(self): ++ return repr(self.value) ++ ++ ++class KX(KoanException): ++ pass ++ ++ + class InfoException(Exception): + """ + Custom exception for tracking of fatal errors. +@@ -283,7 +300,7 @@ class Koan: + # we can get the info we need from either the cobbler server + # or a kickstart file + if self.server is None: +- raise InfoException, "no server specified" ++ raise InfoException("no server specified") + + # check to see that exclusive arguments weren't used together + found = 0 +@@ -291,13 +308,13 @@ class Koan: + if x: + found = found+1 + if found != 1: +- raise InfoException, "choose: --virt, --replace-self, --update-files, --list=what, or --display" ++ raise InfoException("choose: --virt, --replace-self, --update-files, --list=what, or --display") + + + # This set of options are only valid with --server + if not self.server or self.server == "": + if self.list_items or self.profile or self.system or self.port: +- raise InfoException, "--server is required" ++ raise InfoException("--server is required") + + self.xmlrpc_server = utils.connect_to_server(server=self.server, port=self.port) + +@@ -315,7 +332,7 @@ class Koan: + # if both --profile and --system were ommitted, autodiscover + if self.is_virt: + if (self.profile is None and self.system is None and self.image is None): +- raise InfoException, "must specify --profile, --system, or --image" ++ raise InfoException("must specify --profile, --system, or --image") + else: + if (self.profile is None and self.system is None and self.image is None): + self.system = self.autodetect_system(allow_interactive=self.live_cd) +@@ -330,13 +347,13 @@ class Koan: + if self.virt_type not in [ "qemu", "xenpv", "xenfv", "xen", "vmware", "vmwarew", "auto" ]: + if self.virt_type == "xen": + self.virt_type = "xenpv" +- raise InfoException, "--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, or auto" ++ raise InfoException("--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, or auto") + + # if --qemu-disk-type was called without --virt-type=qemu, then fail + if (self.qemu_disk_type is not None): + self.qemu_disk_type = self.qemu_disk_type.lower() + if self.virt_type not in [ "qemu", "auto", "kvm" ]: +- raise InfoException, "--qemu-disk-type must use with --virt-type=qemu" ++ raise(InfoException, "--qemu-disk-type must use with --virt-type=qemu") + + # if --qemu-net-type was called without --virt-type=qemu, then fail + if (self.qemu_net_type is not None): +@@ -346,7 +363,7 @@ class Koan: + + # if --static-interface and --profile was called together, then fail + if self.static_interface is not None and self.profile is not None: +- raise InfoException, "--static-interface option is incompatible with --profile option use --system instead" ++ raise InfoException("--static-interface option is incompatible with --profile option use --system instead") + + # perform one of three key operations + if self.is_virt: +@@ -372,7 +389,7 @@ class Koan: + try: + available_profiles = self.xmlrpc_server.get_profiles() + except: +- traceback.print(_exc()) ++ traceback.print_exc() + self.connect_fail() + + print("\n- which profile to install?\n") +@@ -423,12 +440,12 @@ class Koan: + detected_systems = utils.uniqify(detected_systems) + + if len(detected_systems) > 1: +- raise InfoException, "Error: Multiple systems matched" ++ raise InfoException("Error: Multiple systems matched") + elif len(detected_systems) == 0: + if not allow_interactive: + mac_criteria = utils.uniqify(mac_criteria, purge="?") + ip_criteria = utils.uniqify(ip_criteria, purge="?") +- raise InfoException, "Error: Could not find a matching system with MACs: %s or IPs: %s" % (",".join(mac_criteria), ",".join(ip_criteria)) ++ raise InfoException("Error: Could not find a matching system with MACs: %s or IPs: %s" % (",".join(mac_criteria), ",".join(ip_criteria))) + else: + return None + elif len(detected_systems) == 1: +@@ -523,7 +540,7 @@ class Koan: + self.virt_type = "qemu" + else: + # assume Xen, we'll check to see if virt-type is really usable later. +- raise InfoException, "Not running a Xen kernel and qemu is not installed" ++ raise InfoException("Not running a Xen kernel and qemu is not installed") + + print("- no virt-type specified, auto-selecting %s" % self.virt_type) + +@@ -766,12 +783,12 @@ class Koan: + # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 + if arch.startswith("ppc") or arch.startswith("ia64"): + if len(k_args) > 511: +- raise InfoException, "Kernel options are too long, 512 chars exceeded: %s" % k_args ++ raise InfoException("Kernel options are too long, 512 chars exceeded: %s" % k_args) + elif arch.startswith("s390"): + if len(k_args) > 895: +- raise InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args ++ raise(InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args) + elif len(k_args) > 2047: +- raise InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args ++ raise(InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args) + + utils.subprocess_call([ + 'kexec', +@@ -800,19 +817,21 @@ class Koan: + """ + try: + shutil.rmtree("/var/spool/koan") +- except OSError, (err, msg): ++ except OSError as xxx_todo_changeme: ++ (err, msg) = xxx_todo_changeme.args + if err != errno.ENOENT: + raise + try: + os.makedirs("/var/spool/koan") +- except OSError, (err, msg): ++ except OSError as xxx_todo_changeme: ++ (err, msg) = xxx_todo_changeme.args + if err != errno.EEXIST: + raise + + + def after_download(self, profile_data): + if not os.path.exists("/sbin/grubby"): +- raise InfoException, "grubby is not installed" ++ raise InfoException("grubby is not installed") + k_args = self.calc_kernel_args(profile_data,replace_self=1) + + kickstart = self.safe_load(profile_data,'kickstart') +@@ -844,12 +863,12 @@ class Koan: + # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 + if arch.startswith("ppc") or arch.startswith("ia64"): + if len(k_args) > 511: +- raise InfoException, "Kernel options are too long, 512 chars exceeded: %s" % k_args ++ raise InfoException("Kernel options are too long, 512 chars exceeded: %s" % k_args) + elif arch.startswith("s390"): + if len(k_args) > 895: +- raise InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args ++ raise(InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args) + elif len(k_args) > 2047: +- raise InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args ++ raise(InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args) + + cmd = [ "/sbin/grubby", + "--add-kernel", self.safe_load(profile_data,'kernel_local'), +@@ -975,7 +994,7 @@ class Koan: + #--------------------------------------------------- + + def connect_fail(self): +- raise InfoException, "Could not communicate with %s:%s" % (self.server, self.port) ++ raise InfoException("Could not communicate with %s:%s" % (self.server, self.port)) + + #--------------------------------------------------- + +@@ -986,7 +1005,7 @@ class Koan: + else: + data = getattr(self.xmlrpc_server, "get_%s_for_koan" % what)(name) + except: +- traceback.print(_exc()) ++ traceback.print_exc() + self.connect_fail() + if data == {}: + raise InfoException("No entry/entries found") +@@ -1057,8 +1076,8 @@ class Koan: + print("url=%s" % kernel) + utils.urlgrab(kernel,kernel_save) + except: +- traceback.print(_exc()) +- raise InfoException, "error downloading files" ++ traceback.print_exc() ++ raise(InfoException, "error downloading files") + profile_data['kernel_local'] = kernel_save + profile_data['initrd_local'] = initrd_save + +@@ -1238,7 +1257,7 @@ class Koan: + uuid = None + creator = vmwwcreate.start_install + else: +- raise InfoException, "Unspecified virt type: %s" % self.virt_type ++ raise InfoException("Unspecified virt type: %s" % self.virt_type) + return (uuid, creator, fullvirt, can_poll) + + #--------------------------------------------------- +@@ -1257,7 +1276,7 @@ class Koan: + if len(disks) == 0: + print("paths: ", paths) + print("sizes: ", sizes) +- raise InfoException, "Disk configuration not resolvable!" ++ raise InfoException("Disk configuration not resolvable!") + return disks + + #--------------------------------------------------- +@@ -1353,7 +1372,7 @@ class Koan: + try: + isize = int(size) + except: +- traceback.print(_exc()) ++ traceback.print_exc() + return default_cpus + return isize + +@@ -1460,13 +1479,13 @@ class Koan: + os.makedirs(os.path.dirname(location)) + return location + else: +- raise InfoException, "invalid location: %s" % location ++ raise(InfoException, "invalid location: %s" % location) + elif location.startswith("/dev/"): + # partition + if os.path.exists(location): + return location + else: +- raise InfoException, "virt path is not a valid block device" ++ raise InfoException("virt path is not a valid block device") + else: + # it's a volume group, verify that it exists + args = "vgs -o vg_name" +@@ -1475,7 +1494,7 @@ class Koan: + print(vgnames) + + if vgnames.find(location) == -1: +- raise InfoException, "The volume group [%s] does not exist." % location ++ raise InfoException("The volume group [%s] does not exist." % location) + + # check free space + args = "LANG=C vgs --noheadings -o vg_free --units g %s" % location +@@ -1510,13 +1529,13 @@ class Koan: + print("%s" % args) + lv_create = sub_process.call(args, shell=True) + if lv_create != 0: +- raise InfoException, "LVM creation failed" ++ raise InfoException("LVM creation failed") + + # return partition location + return "/dev/%s/%s" % (location,name) + + else: +- raise InfoException, "volume group needs %s GB free space." % virt_size ++ raise InfoException("volume group needs %s GB free space." % virt_size) + + + def randomUUID(self): +diff --git a/koan/qcreate.py b/koan/qcreate.py +index 1db3d97..d0bafae 100755 +--- a/koan/qcreate.py ++++ b/koan/qcreate.py +@@ -26,6 +26,7 @@ requires python-virtinst-0.200 (or virt-install in later distros). + from . import utils + from . import virtinstall + from xml.dom.minidom import parseString ++from . import app as koan + + def start_install(*args, **kwargs): + # See http://post-office.corp.redhat.com/archives/satellite-dept-list/2013-December/msg00039.html for discussion on this hack. +diff --git a/koan/register.py b/koan/register.py +index 1157e5c..5d28acd 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -76,7 +76,7 @@ def main(): + k.hostname = options.hostname + k.batch = options.batch + k.run() +- except Exception, e: ++ except Exception as e: + (xa, xb, tb) = sys.exc_info() + try: + getattr(e,"from_koan") +@@ -177,7 +177,7 @@ class Register: + self.conn.register_new_system(reg_info) + print("- registration successful, new system name: %s" % sysname) + except: +- traceback.print(_exc()) ++ traceback.print_exc() + print("- registration failed, ignoring because of --batch") + + return +diff --git a/koan/utils.py b/koan/utils.py +index 77d53b2..1aee405 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -92,7 +92,7 @@ def urlread(url): + """ + print("- reading URL: %s" % url) + if url is None or url == "": +- raise InfoException, "invalid URL: %s" % url ++ raise InfoException("invalid URL: %s" % url) + + elif url[0:3] == "nfs": + try: +@@ -109,8 +109,8 @@ def urlread(url): + subprocess_call(cmd) + return data + except: +- traceback.print(_exc()) +- raise InfoException, "Couldn't mount and read URL: %s" % url ++ traceback.print_exc() ++ raise(InfoException, "Couldn't mount and read URL: %s" % url) + + elif url[0:4] == "http": + try: +@@ -119,8 +119,8 @@ def urlread(url): + fd.close() + return data + except: +- traceback.print(_exc()) +- raise InfoException, "Couldn't download: %s" % url ++ traceback.print_exc() ++ raise(InfoException, "Couldn't download: %s" % url) + elif url[0:4] == "file": + try: + fd = open(url[5:]) +@@ -128,10 +128,10 @@ def urlread(url): + fd.close() + return data + except: +- raise InfoException, "Couldn't read file from URL: %s" % url ++ raise InfoException("Couldn't read file from URL: %s" % url) + + else: +- raise InfoException, "Unhandled URL protocol: %s" % url ++ raise InfoException("Unhandled URL protocol: %s" % url) + + def urlgrab(url,saveto): + """ +@@ -150,7 +150,7 @@ def subprocess_call(cmd,ignore_rc=0): + print("- %s" % cmd) + rc = sub_process.call(cmd) + if rc != 0 and not ignore_rc: +- raise InfoException, "command failed (%s)" % rc ++ raise InfoException("command failed (%s)" % rc) + return rc + + def subprocess_get_response(cmd, ignore_rc=False): +@@ -165,7 +165,7 @@ def subprocess_get_response(cmd, ignore_rc=False): + result = result.strip() + rc = p.poll() + if not ignore_rc and rc != 0: +- raise InfoException, "command failed (%s)" % rc ++ raise InfoException("command failed (%s)" % rc) + return rc, result + + def input_string_or_hash(options,delim=None,allow_multiples=True): +@@ -333,9 +333,9 @@ def os_release(): + for t in tokens: + try: + return (make,float(t)) +- except ValueError, ve: ++ except ValueError: + pass +- raise CX("failed to detect local OS version from /etc/redhat-release") ++ raise koan.KX("failed to detect local OS version from /etc/redhat-release") + + elif check_dist() == "debian": + fd = open("/etc/debian_version") +@@ -522,7 +522,7 @@ def __try_connect(url): + xmlrpc_server.ping() + return xmlrpc_server + except: +- traceback.print(_exc()) ++ traceback.print_exc() + return None + + +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 97e0ba2..c77c018 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + import os + import random + ++from . import app as koan ++ + IMAGE_DIR = "/var/lib/vmware/images" + VMX_DIR = "/var/lib/vmware/vmx" + +-- +2.5.5 + diff --git a/SOURCES/0006-octal-number-Python-3-fix.patch b/SOURCES/0006-octal-number-Python-3-fix.patch new file mode 100644 index 0000000..5039f87 --- /dev/null +++ b/SOURCES/0006-octal-number-Python-3-fix.patch @@ -0,0 +1,25 @@ +From 9a06847b9b9b8ff0b2da837a5123d1a1178336d7 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:28:43 +0100 +Subject: [PATCH 06/17] octal number Python 3 fix + +--- + koan/vmwcreate.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index c77c018..82bfa4a 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -104,7 +104,7 @@ def register_vmx(vmx_file): + raise VirtCreateException("vmware registration failed") + + def start_vm(vmx_file): +- os.chmod(vmx_file,0755) ++ os.chmod(vmx_file, 0o755) + cmd = "vmware-cmd %s start" % vmx_file + print("- %s" % cmd) + rc = os.system(cmd) +-- +2.5.5 + diff --git a/SOURCES/0007-Python-3-compatible-string-operations.patch b/SOURCES/0007-Python-3-compatible-string-operations.patch new file mode 100644 index 0000000..90ffae1 --- /dev/null +++ b/SOURCES/0007-Python-3-compatible-string-operations.patch @@ -0,0 +1,111 @@ +From 79136996ad2cf9d5ea51cc0a053cfe5815785c41 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:34:57 +0100 +Subject: [PATCH 07/17] Python 3 compatible string operations + +--- + koan/app.py | 9 ++++----- + koan/register.py | 3 +-- + koan/utils.py | 7 +++---- + 3 files changed, 8 insertions(+), 11 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index c1d79c2..e727932 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -40,7 +40,6 @@ try: #python2 + import xmlrpclib + except ImportError: #python3 + import xmlrpc.client as xmlrpclib +-import string + import re + from . import utils + +@@ -880,7 +879,7 @@ class Koan: + cmd.append("--copy-default") + + boot_probe_ret_code, probe_output = self.get_boot_loader_info() +- if boot_probe_ret_code == 0 and string.find(probe_output, "lilo") >= 0: ++ if boot_probe_ret_code == 0 and probe_output.find("lilo") >= 0: + cmd.append("--lilo") + + if self.add_reinstall_entry: +@@ -929,7 +928,7 @@ class Koan: + else: + # if grubby --bootloader-probe returns lilo, + # apply lilo changes +- if boot_probe_ret_code == 0 and string.find(probe_output, "lilo") != -1: ++ if boot_probe_ret_code == 0 and probe_output.find("lilo") != -1: + print("- applying lilo changes") + cmd = [ "/sbin/lilo" ] + utils.subprocess_call(cmd) +@@ -1132,10 +1131,10 @@ class Koan: + hash2 = utils.input_string_or_hash(self.kopts_override) + hashv.update(hash2) + options = utils.hash_to_string(hashv) +- options = string.replace(options, "lang ","lang= ") ++ options = options.replace("lang ","lang= ") + # if using ksdevice=bootif that only works for PXE so replace + # it with something that will work +- options = string.replace(options, "ksdevice=bootif","ksdevice=link") ++ options = options.replace("ksdevice=bootif","ksdevice=link") + return options + + #--------------------------------------------------- +diff --git a/koan/register.py b/koan/register.py +index 5d28acd..a69f2d1 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -31,7 +31,6 @@ except ImportError: #python3 + import xmlrpc.client as xmlrpclib + import socket + from . import utils +-import string + + # usage: cobbler-register [--server=server] [--hostname=hostname] --profile=foo + +@@ -84,7 +83,7 @@ def main(): + except: + print(xa) + print(xb) +- print(string.join(traceback.format_list(traceback.extract_tb(tb)))) ++ print("".join(traceback.format_list(traceback.extract_tb(tb)))) + return 1 + + return 0 +diff --git a/koan/utils.py b/koan/utils.py +index 1aee405..f2f2692 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -31,7 +31,6 @@ try: #python2 + except ImportError: #python3 + import urllib.request as urllib2 + import xmlrpc.client as xmlrpclib +-import string + import shutil + import tempfile + import urlgrabber +@@ -181,9 +180,9 @@ def input_string_or_hash(options,delim=None,allow_multiples=True): + raise InfoException("No idea what to do with list: %s" % options) + elif type(options) == type(""): + new_dict = {} +- tokens = string.split(options, delim) ++ tokens = options.split(delim) + for t in tokens: +- tokens2 = string.split(t,"=",1) ++ tokens2 = t.split("=",1) + if len(tokens2) == 1: + # this is a singleton option, no value + key = tokens2[0] +@@ -246,7 +245,7 @@ def nfsmount(input_path): + # FIXME: move this function to util.py so other modules can use it + # we have to mount it first + filename = input_path.split("/")[-1] +- dirpath = string.join(input_path.split("/")[:-1],"/") ++ dirpath = "/".join(input_path.split("/")[:-1]) + tempdir = tempfile.mkdtemp(suffix='.mnt', prefix='koan_', dir='/tmp') + mount_cmd = [ + "/bin/mount", "-t", "nfs", "-o", "ro", dirpath, tempdir +-- +2.5.5 + diff --git a/SOURCES/0008-do-not-require-urlgrabber.patch b/SOURCES/0008-do-not-require-urlgrabber.patch new file mode 100644 index 0000000..93c9723 --- /dev/null +++ b/SOURCES/0008-do-not-require-urlgrabber.patch @@ -0,0 +1,33 @@ +From dc08d632d0f27e194da5cec05a1f367237ae4e2c Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:35:29 +0100 +Subject: [PATCH 08/17] do not require urlgrabber + +--- + koan/utils.py | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/koan/utils.py b/koan/utils.py +index f2f2692..b24fa0f 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -33,7 +33,6 @@ except ImportError: #python3 + import xmlrpc.client as xmlrpclib + import shutil + import tempfile +-import urlgrabber + + VIRT_STATE_NAME_MAP = { + 0 : "running", +@@ -493,7 +492,7 @@ def make_floppy(kickstart): + # download the kickstart file onto the mounted floppy + print("- downloading %s" % kickstart) + save_file = os.path.join(mount_path, "unattended.txt") +- urlgrabber.urlgrab(kickstart,filename=save_file) ++ urlgrab(kickstart,filename=save_file) + + # umount + cmd = "umount %s" % mount_path +-- +2.5.5 + diff --git a/SOURCES/0009-replace-iteritems-with-items.patch b/SOURCES/0009-replace-iteritems-with-items.patch new file mode 100644 index 0000000..cd04974 --- /dev/null +++ b/SOURCES/0009-replace-iteritems-with-items.patch @@ -0,0 +1,25 @@ +From a2dba086cd78ac54b0366d328a64b6e48e9dc422 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:35:59 +0100 +Subject: [PATCH 09/17] replace iteritems with items + +--- + koan/app.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/koan/app.py b/koan/app.py +index e727932..ac6b0df 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -426,7 +426,7 @@ class Koan: + systems = self.get_data("systems") + for system in systems: + obj_name = system["name"] +- for (obj_iname, obj_interface) in system['interfaces'].iteritems(): ++ for (obj_iname, obj_interface) in system['interfaces'].items(): + mac = obj_interface["mac_address"].upper() + ip = obj_interface["ip_address"].upper() + for my_mac in mac_criteria: +-- +2.5.5 + diff --git a/SOURCES/0010-open-target-file-in-binary-mode.patch b/SOURCES/0010-open-target-file-in-binary-mode.patch new file mode 100644 index 0000000..cbcdaa7 --- /dev/null +++ b/SOURCES/0010-open-target-file-in-binary-mode.patch @@ -0,0 +1,25 @@ +From 41cc091c9680935c072b44b4aaa49261d66f6dc4 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:36:30 +0100 +Subject: [PATCH 10/17] open target file in binary mode + +--- + koan/utils.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/koan/utils.py b/koan/utils.py +index b24fa0f..28c8af1 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -137,7 +137,7 @@ def urlgrab(url,saveto): + see comments for urlread as to why it's this way. + """ + data = urlread(url) +- fd = open(saveto, "w+") ++ fd = open(saveto, "w+b") + fd.write(data) + fd.close() + +-- +2.5.5 + diff --git a/SOURCES/0011-make-sure-it-s-a-string.patch b/SOURCES/0011-make-sure-it-s-a-string.patch new file mode 100644 index 0000000..16ab620 --- /dev/null +++ b/SOURCES/0011-make-sure-it-s-a-string.patch @@ -0,0 +1,34 @@ +From 31321dc6d1dd14166601ffc59b9e98c02aaf25f0 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:37:07 +0100 +Subject: [PATCH 11/17] make sure it's a string + +--- + koan/app.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index ac6b0df..f251c77 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -805,7 +805,7 @@ class Koan: + def get_boot_loader_info(self): + cmd = [ "/sbin/grubby", "--bootloader-probe" ] + probe_process = sub_process.Popen(cmd, stdout=sub_process.PIPE) +- which_loader = probe_process.communicate()[0] ++ which_loader = probe_process.communicate()[0].decode() + return probe_process.returncode, which_loader + + def replace(self): +@@ -851,7 +851,7 @@ class Koan: + ) + + arch_cmd = sub_process.Popen("/bin/uname -m", stdout=sub_process.PIPE, shell=True) +- arch = arch_cmd.communicate()[0] ++ arch = arch_cmd.communicate()[0].decode() + + # Validate kernel argument length (limit depends on architecture -- + # see asm-*/setup.h). For example: +-- +2.5.5 + diff --git a/SOURCES/0012-make-sure-list-is-returned.patch b/SOURCES/0012-make-sure-list-is-returned.patch new file mode 100644 index 0000000..168e6cd --- /dev/null +++ b/SOURCES/0012-make-sure-list-is-returned.patch @@ -0,0 +1,25 @@ +From cd2fc155f84655ca689e33cc176fc2d9ab08c936 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:37:38 +0100 +Subject: [PATCH 12/17] make sure list is returned + +--- + koan/utils.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/koan/utils.py b/koan/utils.py +index 28c8af1..e202bd5 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -364,7 +364,7 @@ def uniqify(lst, purge=None): + if x != purge: + temp2[x] = 1 + temp = temp2 +- return temp.keys() ++ return list(temp.keys()) + + def get_network_info(): + try: +-- +2.5.5 + diff --git a/SOURCES/0013-Python-3-ethtool-and-indentation-fixes.patch b/SOURCES/0013-Python-3-ethtool-and-indentation-fixes.patch new file mode 100644 index 0000000..422379c --- /dev/null +++ b/SOURCES/0013-Python-3-ethtool-and-indentation-fixes.patch @@ -0,0 +1,121 @@ +From 4a23a3cc1b9d1ab1238aadbd7945aa93c21f7638 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:39:34 +0100 +Subject: [PATCH 13/17] Python 3 ethtool and indentation fixes + +--- + koan/utils.py | 98 ++++++++++++++++++++++++++++++++--------------------------- + 1 file changed, 54 insertions(+), 44 deletions(-) + +diff --git a/koan/utils.py b/koan/utils.py +index e202bd5..0cd48ea 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -367,50 +367,60 @@ def uniqify(lst, purge=None): + return list(temp.keys()) + + def get_network_info(): +- try: +- import ethtool +- except: +- try: +- import rhpl.ethtool +- ethtool = rhpl.ethtool +- except: +- raise InfoException("the rhpl or ethtool module is required to use this feature (is your OS>=EL3?)") +- +- interfaces = {} +- # get names +- inames = ethtool.get_devices() +- +- for iname in inames: +- mac = ethtool.get_hwaddr(iname) +- +- if mac == "00:00:00:00:00:00": +- mac = "?" +- +- try: +- ip = ethtool.get_ipaddr(iname) +- if ip == "127.0.0.1": +- ip = "?" +- except: +- ip = "?" +- +- bridge = 0 +- module = "" +- +- try: +- nm = ethtool.get_netmask(iname) +- except: +- nm = "?" +- +- interfaces[iname] = { +- "ip_address" : ip, +- "mac_address" : mac, +- "netmask" : nm, +- "bridge" : bridge, +- "module" : module +- } +- +- # print(interfaces) +- return interfaces ++ try: # python 2 ++ import ethtool ++ ethtool_available = True ++ except ImportError: # python 3 ++ import netifaces ++ ethtool_available = False ++ ++ interfaces = {} ++ # get names ++ if ethtool_available: ++ inames = ethtool.get_devices() ++ else: ++ inames = netifaces.interfaces() ++ ++ for iname in inames: ++ if ethtool_available: ++ mac = ethtool.get_hwaddr(iname) ++ else: ++ mac = netifaces.ifaddresses(iname)[netifaces.AF_LINK][0]['addr'] ++ ++ if mac == "00:00:00:00:00:00": ++ mac = "?" ++ ++ try: ++ if ethtool_available: ++ ip = ethtool.get_ipaddr(iname) ++ else: ++ ip = netifaces.ifaddresses(iname)[netifaces.AF_INET][0]['addr'] ++ if ip == "127.0.0.1": ++ ip = "?" ++ except: ++ ip = "?" ++ ++ bridge = 0 ++ module = "" ++ ++ try: ++ if ethtool_available: ++ nm = ethtool.get_netmask(iname) ++ else: ++ nm = netifaces.ifaddresses(iname)[netifaces.AF_INET][0]['netmask'] ++ except: ++ nm = "?" ++ ++ interfaces[iname] = { ++ "ip_address" : ip, ++ "mac_address" : mac, ++ "netmask" : nm, ++ "bridge" : bridge, ++ "module" : module ++ } ++ ++ # print(interfaces) ++ return interfaces + + def connect_to_server(server=None,port=None): + +-- +2.5.5 + diff --git a/SOURCES/0014-has_key-is-not-in-Python-3.patch b/SOURCES/0014-has_key-is-not-in-Python-3.patch new file mode 100644 index 0000000..29987eb --- /dev/null +++ b/SOURCES/0014-has_key-is-not-in-Python-3.patch @@ -0,0 +1,129 @@ +From 769722facf088ea59ab010b363fc44d18b70d670 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:43:20 +0100 +Subject: [PATCH 14/17] has_key is not in Python 3 + +--- + koan/app.py | 14 +++++++------- + koan/utils.py | 2 +- + koan/virtinstall.py | 4 ++-- + koan/vmwcreate.py | 4 ++-- + 4 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index f251c77..4ae4c90 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -454,9 +454,9 @@ class Koan: + #--------------------------------------------------- + + def safe_load(self,hashv,primary_key,alternate_key=None,default=None): +- if hashv.has_key(primary_key): ++ if primary_key in hashv: + return hashv[primary_key] +- elif alternate_key is not None and hashv.has_key(alternate_key): ++ elif alternate_key is not None and alternate_key in hashv: + return hashv[alternate_key] + else: + return default +@@ -524,7 +524,7 @@ class Koan: + if profile_data.get("xml_file","") != "": + raise InfoException("xmlfile based installations are not supported") + +- elif profile_data.has_key("file"): ++ elif "file" in profile_data: + print("- ISO or Image based installation, always uses --virt-type=qemu") + self.virt_type = "qemu" + +@@ -661,7 +661,7 @@ class Koan: + raise InfoException("koan does not know how to list that") + data = self.get_data(what) + for x in data: +- if x.has_key("name"): ++ if "name" in x: + print(x["name"]) + return True + +@@ -670,7 +670,7 @@ class Koan: + def display(self): + def after_download(self, profile_data): + for x in DISPLAY_PARAMS: +- if profile_data.has_key(x): ++ if x in profile_data: + value = profile_data[x] + if x == 'kernel_options': + value = self.calc_kernel_args(profile_data) +@@ -732,7 +732,7 @@ class Koan: + print("- file: %s" % save_as) + + pattern = "http://%s/cblr/svc/op/template/%s/%s/path/%s" +- if profile_data.has_key("interfaces"): ++ if "interfaces" in profile_data: + url = pattern % (profile_data["http_server"],"system",profile_data["name"],dest) + else: + url = pattern % (profile_data["http_server"],"profile",profile_data["name"],dest) +@@ -1284,7 +1284,7 @@ class Koan: + if self.virt_name is not None: + # explicit override + name = self.virt_name +- elif profile_data.has_key("interfaces"): ++ elif "interfaces" in profile_data: + # this is a system object, just use the name + name = profile_data["name"] + else: +diff --git a/koan/utils.py b/koan/utils.py +index 0cd48ea..12ec718 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -206,7 +206,7 @@ def input_string_or_hash(options,delim=None,allow_multiples=True): + new_dict[key] = value + + # dict.pop is not avail in 2.2 +- if new_dict.has_key(""): ++ if "" in new_dict: + del new_dict[""] + return new_dict + elif type(options) == type({}): +diff --git a/koan/virtinstall.py b/koan/virtinstall.py +index 2d1f3df..5359b2a 100644 +--- a/koan/virtinstall.py ++++ b/koan/virtinstall.py +@@ -233,7 +233,7 @@ def build_commandline(uri, + raise koan.InfoException("Profile 'file' required for image " + "install") + +- elif profile_data.has_key("file"): ++ elif "file" in profile_data: + if is_xen: + raise koan.InfoException("Xen does not work with --image yet") + +@@ -255,7 +255,7 @@ def build_commandline(uri, + floppy = utils.make_floppy(kickstart) + elif is_qemu or is_xen: + # images don't need to source this +- if not profile_data.has_key("install_tree"): ++ if not "install_tree" in profile_data: + raise koan.InfoException("Cannot find install source in kickstart file, aborting.") + + if not profile_data["install_tree"].endswith("/"): +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 82bfa4a..33c5819 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -126,11 +126,11 @@ def start_install(name=None, + virt_type=None, + virt_auto_boot=False): + +- if profile_data.has_key("file"): ++ if "file" in profile_data: + raise koan.InfoException("vmware does not work with --image yet") + + mac = None +- if not profile_data.has_key("interfaces"): ++ if not "interfaces" in profile_data: + print("- vmware installation requires a system, not a profile") + return 1 + for iname in profile_data["interfaces"]: +-- +2.5.5 + diff --git a/SOURCES/0015-relative-imports-don-t-work-on-both-Python-2-and-3.patch b/SOURCES/0015-relative-imports-don-t-work-on-both-Python-2-and-3.patch new file mode 100644 index 0000000..7e3a93b --- /dev/null +++ b/SOURCES/0015-relative-imports-don-t-work-on-both-Python-2-and-3.patch @@ -0,0 +1,394 @@ +From f565382b5291409ac1ab4a96494c566cd9a4dfd4 Mon Sep 17 00:00:00 2001 +From: Jan Dobes +Date: Wed, 18 Oct 2017 13:18:24 +0200 +Subject: [PATCH 15/17] relative imports don't work on both Python 2 and 3 + +(cherry picked from commit a2339d3a544c0cfbec6cfc5437942a79abe4bd30) + +Conflicts: + koan/app.py + koan/utils.py + koan/virtinstall.py +--- + koan/app.py | 34 +++++++++------------------------- + koan/imagecreate.py | 7 ++++--- + koan/qcreate.py | 11 ++++++----- + koan/register.py | 6 +++--- + koan/utils.py | 6 ++++-- + koan/virtinstall.py | 27 ++++++++++++++------------- + koan/vmwcreate.py | 4 ++-- + koan/xencreate.py | 7 ++++--- + scripts/cobbler-register | 2 +- + scripts/koan | 2 +- + 10 files changed, 48 insertions(+), 58 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index 4ae4c90..f5af5e9 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -41,7 +41,7 @@ try: #python2 + except ImportError: #python3 + import xmlrpc.client as xmlrpclib + import re +-from . import utils ++from koan import utils + + COBBLER_REQUIRED = 1.300 + +@@ -228,22 +228,6 @@ def main(): + #======================================================= + + +-class KoanException(Exception): +- +- def __init__(self, value, *args): +- self.value = value % args +- # this is a hack to work around some odd exception handling +- # in older pythons +- self.from_koan = 1 +- +- def __str__(self): +- return repr(self.value) +- +- +-class KX(KoanException): +- pass +- +- + class InfoException(Exception): + """ + Custom exception for tracking of fatal errors. +@@ -1145,9 +1129,9 @@ class Koan: + """ + pd = profile_data + # importing can't throw exceptions any more, don't put it in a sub-method +- from . import xencreate +- from . import qcreate +- from . import imagecreate ++ from koan import xencreate ++ from koan import qcreate ++ from koan import imagecreate + + arch = self.safe_load(pd,'arch','x86') + kextra = self.calc_kernel_args(pd) +@@ -1232,11 +1216,11 @@ class Koan: + if (self.image is not None) and (pd["image_type"] == "virt-clone"): + fullvirt = True + uuid = None +- from . import imagecreate ++ from koan import imagecreate + creator = imagecreate.start_install + elif self.virt_type in [ "xenpv", "xenfv" ]: + uuid = self.get_uuid(self.calc_virt_uuid(pd)) +- from . import xencreate ++ from koan import xencreate + creator = xencreate.start_install + if self.virt_type == "xenfv": + fullvirt = True +@@ -1244,15 +1228,15 @@ class Koan: + elif self.virt_type == "qemu": + fullvirt = True + uuid = None +- from . import qcreate ++ from koan import qcreate + creator = qcreate.start_install + can_poll = "qemu" + elif self.virt_type == "vmware": +- from . import vmwcreate ++ from koan import vmwcreate + uuid = None + creator = vmwcreate.start_install + elif self.virt_type == "vmwarew": +- from . import vmwwcreate ++ from koan import vmwwcreate + uuid = None + creator = vmwwcreate.start_install + else: +diff --git a/koan/imagecreate.py b/koan/imagecreate.py +index d70d519..9b98f74 100644 +--- a/koan/imagecreate.py ++++ b/koan/imagecreate.py +@@ -23,9 +23,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + """ + +-from . import utils +-from . import virtinstall ++from koan.utils import subprocess_call ++from koan import virtinstall ++ + + def start_install(*args, **kwargs): + cmd = virtinstall.build_commandline("import", *args, **kwargs) +- utils.subprocess_call(cmd) ++ subprocess_call(cmd) +diff --git a/koan/qcreate.py b/koan/qcreate.py +index d0bafae..73b6abb 100755 +--- a/koan/qcreate.py ++++ b/koan/qcreate.py +@@ -23,10 +23,11 @@ module for creating fullvirt guests via KVM/kqemu/qemu + requires python-virtinst-0.200 (or virt-install in later distros). + """ + +-from . import utils +-from . import virtinstall + from xml.dom.minidom import parseString +-from . import app as koan ++from koan.utils import subprocess_call ++from koan import virtinstall ++from koan import app ++ + + def start_install(*args, **kwargs): + # See http://post-office.corp.redhat.com/archives/satellite-dept-list/2013-December/msg00039.html for discussion on this hack. +@@ -37,7 +38,7 @@ def start_install(*args, **kwargs): + try: + import libvirt + except: +- raise koan.InfoException("package libvirt is required for installing virtual guests") ++ raise app.InfoException("package libvirt is required for installing virtual guests") + conn = libvirt.openReadOnly(None) + # See http://libvirt.org/formatcaps.html + capabilities = parseString(conn.getCapabilities()) +@@ -49,4 +50,4 @@ def start_install(*args, **kwargs): + + virtinstall.create_image_file(*args, **kwargs) + cmd = virtinstall.build_commandline("qemu:///system", *args, **kwargs) +- utils.subprocess_call(cmd) ++ subprocess_call(cmd) +diff --git a/koan/register.py b/koan/register.py +index a69f2d1..8ce7db3 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -30,7 +30,7 @@ try: #python2 + except ImportError: #python3 + import xmlrpc.client as xmlrpclib + import socket +-from . import utils ++from koan.utils import connect_to_server, get_network_info + + # usage: cobbler-register [--server=server] [--hostname=hostname] --profile=foo + +@@ -127,10 +127,10 @@ class Register: + raise InfoException("root access is required to register") + + print("- preparing to koan home") +- self.conn = utils.connect_to_server(self.server, self.port) ++ self.conn = connect_to_server(self.server, self.port) + reg_info = {} + print("- gathering network info") +- netinfo = utils.get_network_info() ++ netinfo = get_network_info() + reg_info["interfaces"] = netinfo + print("- checking hostname") + sysname = "" +diff --git a/koan/utils.py b/koan/utils.py +index 12ec718..607db1f 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -44,6 +44,7 @@ VIRT_STATE_NAME_MAP = { + 6 : "crashed" + } + ++ + class InfoException(Exception): + """ + Custom exception for tracking of fatal errors. +@@ -54,6 +55,7 @@ class InfoException(Exception): + def __str__(self): + return repr(self.value) + ++ + def setupLogging(appname): + """ + set up logging ... code borrowed/adapted from virt-manager +@@ -253,7 +255,7 @@ def nfsmount(input_path): + rc = sub_process.call(mount_cmd) + if not rc == 0: + shutil.rmtree(tempdir, ignore_errors=True) +- raise koan.InfoException("nfs mount failed: %s" % dirpath) ++ raise InfoException("nfs mount failed: %s" % dirpath) + # NOTE: option for a blocking install might be nice, so we could do this + # automatically, if supported by virt-install + print("after install completes, you may unmount and delete %s" % tempdir) +@@ -333,7 +335,7 @@ def os_release(): + return (make,float(t)) + except ValueError: + pass +- raise koan.KX("failed to detect local OS version from /etc/redhat-release") ++ raise InfoException("failed to detect local OS version from /etc/redhat-release") + + elif check_dist() == "debian": + fd = open("/etc/debian_version") +diff --git a/koan/virtinstall.py b/koan/virtinstall.py +index 5359b2a..ce6c425 100644 +--- a/koan/virtinstall.py ++++ b/koan/virtinstall.py +@@ -30,15 +30,15 @@ import os + import re + import shlex + +-from . import app as koan +-from . import utils ++from koan import app ++from koan.utils import subprocess_get_response, nfsmount, make_floppy + + # The virtinst module will no longer be availabe to import in some + # distros. We need to get all the info we need from the virt-install + # command line tool. This should work on both old and new variants, + # as the virt-install command line tool has always been provided by + # python-virtinst (and now the new virt-install rpm). +-rc, response = utils.subprocess_get_response( ++rc, response = subprocess_get_response( + shlex.split('virt-install --version'), True) + if rc == 0: + virtinst_version = response +@@ -63,7 +63,7 @@ try: + supported_variants.add(variant) + except: + try: +- rc, response = utils.subprocess_get_response( ++ rc, response = subprocess_get_response( + shlex.split('virt-install --os-variant list')) + variants = response.split('\n') + for variant in variants: +@@ -90,7 +90,7 @@ def _sanitize_disks(disks): + if d[1] != 0 or d[0].startswith("/dev"): + ret.append((d[0], d[1], driver_type)) + else: +- raise koan.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") ++ raise app.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") + + return ret + +@@ -128,7 +128,7 @@ def _sanitize_nics(nics, bridge, profile_bridge, network_count): + intf_bridge = intf["virt_bridge"] + if intf_bridge == "": + if profile_bridge == "": +- raise koan.InfoException("virt-bridge setting is not defined in cobbler") ++ raise app.InfoException("virt-bridge setting is not defined in cobbler") + intf_bridge = profile_bridge + + else: +@@ -151,7 +151,8 @@ def create_image_file(disks=None, **kwargs): + continue + if str(size) == "0": + continue +- utils.create_qemu_image_file(path, size, driver_type) ++ # This method doesn't exist and this functionality is probably broken ++ # create_qemu_image_file(path, size, driver_type) + + def build_commandline(uri, + name=None, +@@ -230,12 +231,12 @@ def build_commandline(uri, + if is_import: + importpath = profile_data.get("file") + if not importpath: +- raise koan.InfoException("Profile 'file' required for image " ++ raise app.InfoException("Profile 'file' required for image " + "install") + + elif "file" in profile_data: + if is_xen: +- raise koan.InfoException("Xen does not work with --image yet") ++ raise app.InfoException("Xen does not work with --image yet") + + # this is an image based installation + input_path = profile_data["file"] +@@ -244,7 +245,7 @@ def build_commandline(uri, + # this is not an NFS path + cdrom = input_path + else: +- (tempdir, filename) = utils.nfsmount(input_path) ++ (tempdir, filename) = nfsmount(input_path) + cdrom = os.path.join(tempdir, filename) + + kickstart = profile_data.get("kickstart","") +@@ -252,11 +253,11 @@ def build_commandline(uri, + # we have a (windows?) answer file we have to provide + # to the ISO. + print("I want to make a floppy for %s" % kickstart) +- floppy = utils.make_floppy(kickstart) ++ floppy = make_floppy(kickstart) + elif is_qemu or is_xen: + # images don't need to source this + if not "install_tree" in profile_data: +- raise koan.InfoException("Cannot find install source in kickstart file, aborting.") ++ raise app.InfoException("Cannot find install source in kickstart file, aborting.") + + if not profile_data["install_tree"].endswith("/"): + profile_data["install_tree"] = profile_data["install_tree"] + "/" +@@ -277,7 +278,7 @@ def build_commandline(uri, + bridge = profile_data["virt_bridge"] + + if bridge == "": +- raise koan.InfoException("virt-bridge setting is not defined in cobbler") ++ raise app.InfoException("virt-bridge setting is not defined in cobbler") + nics = [(bridge, None)] + + +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 33c5819..1b90a27 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + import os + import random + +-from . import app as koan ++from koan import app + + IMAGE_DIR = "/var/lib/vmware/images" + VMX_DIR = "/var/lib/vmware/vmx" +@@ -127,7 +127,7 @@ def start_install(name=None, + virt_auto_boot=False): + + if "file" in profile_data: +- raise koan.InfoException("vmware does not work with --image yet") ++ raise app.InfoException("vmware does not work with --image yet") + + mac = None + if not "interfaces" in profile_data: +diff --git a/koan/xencreate.py b/koan/xencreate.py +index 7eda3e6..18b2969 100755 +--- a/koan/xencreate.py ++++ b/koan/xencreate.py +@@ -26,9 +26,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + """ + +-from . import utils +-from . import virtinstall ++from koan.utils import subprocess_call ++from koan import virtinstall ++ + + def start_install(*args, **kwargs): + cmd = virtinstall.build_commandline("xen:///", *args, **kwargs) +- utils.subprocess_call(cmd) ++ subprocess_call(cmd) +diff --git a/scripts/cobbler-register b/scripts/cobbler-register +index ed97cfc..3f2ffb1 100755 +--- a/scripts/cobbler-register ++++ b/scripts/cobbler-register +@@ -15,5 +15,5 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + """ + + import sys +-import koan.register as register ++from koan import register + sys.exit(register.main() or 0) +diff --git a/scripts/koan b/scripts/koan +index aeccb0e..9c3f445 100755 +--- a/scripts/koan ++++ b/scripts/koan +@@ -15,5 +15,5 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + """ + + import sys +-import koan.app as app ++from koan import app + sys.exit(app.main() or 0) +-- +2.5.5 + diff --git a/SOURCES/0016-keys-and-sort-doesn-t-work-on-Python-3.patch b/SOURCES/0016-keys-and-sort-doesn-t-work-on-Python-3.patch new file mode 100644 index 0000000..ddd367f --- /dev/null +++ b/SOURCES/0016-keys-and-sort-doesn-t-work-on-Python-3.patch @@ -0,0 +1,26 @@ +From c1c3e1d9f42861a756f26706765cb51b409926c3 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 11:47:37 +0100 +Subject: [PATCH 16/17] keys() and sort() doesn't work on Python 3 + +--- + koan/virtinstall.py | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/koan/virtinstall.py b/koan/virtinstall.py +index ce6c425..de2a670 100644 +--- a/koan/virtinstall.py ++++ b/koan/virtinstall.py +@@ -110,8 +110,7 @@ def _sanitize_nics(nics, bridge, profile_bridge, network_count): + if not nics: + return ret + +- interfaces = nics.keys() +- interfaces.sort() ++ interfaces = sorted(nics) + counter = -1 + vlanpattern = re.compile("[a-zA-Z0-9]+\.[0-9]+") + +-- +2.5.5 + diff --git a/SOURCES/0017-raise-is-a-function-call-in-python3.patch b/SOURCES/0017-raise-is-a-function-call-in-python3.patch new file mode 100644 index 0000000..23a102d --- /dev/null +++ b/SOURCES/0017-raise-is-a-function-call-in-python3.patch @@ -0,0 +1,671 @@ +From 650f96a40ab858904ae4ea17481a59a75ade7def Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Mon, 5 Mar 2018 12:02:02 +0100 +Subject: [PATCH 17/17] raise is a function call in python3 + +--- + koan/app.py | 82 ++++++++++++++++++++++++++--------------------------- + koan/qcreate.py | 2 +- + koan/register.py | 8 +++--- + koan/utils.py | 42 +++++++++++++-------------- + koan/virtinstall.py | 14 ++++----- + koan/vmwcreate.py | 10 +++---- + 6 files changed, 79 insertions(+), 79 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index f5af5e9..ed81fe4 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -283,7 +283,7 @@ class Koan: + # we can get the info we need from either the cobbler server + # or a kickstart file + if self.server is None: +- raise InfoException("no server specified") ++ raise(InfoException("no server specified")) + + # check to see that exclusive arguments weren't used together + found = 0 +@@ -291,13 +291,13 @@ class Koan: + if x: + found = found+1 + if found != 1: +- raise InfoException("choose: --virt, --replace-self, --update-files, --list=what, or --display") ++ raise(InfoException("choose: --virt, --replace-self, --update-files, --list=what, or --display")) + + + # This set of options are only valid with --server + if not self.server or self.server == "": + if self.list_items or self.profile or self.system or self.port: +- raise InfoException("--server is required") ++ raise(InfoException("--server is required")) + + self.xmlrpc_server = utils.connect_to_server(server=self.server, port=self.port) + +@@ -315,7 +315,7 @@ class Koan: + # if both --profile and --system were ommitted, autodiscover + if self.is_virt: + if (self.profile is None and self.system is None and self.image is None): +- raise InfoException("must specify --profile, --system, or --image") ++ raise(InfoException("must specify --profile, --system, or --image")) + else: + if (self.profile is None and self.system is None and self.image is None): + self.system = self.autodetect_system(allow_interactive=self.live_cd) +@@ -330,23 +330,23 @@ class Koan: + if self.virt_type not in [ "qemu", "xenpv", "xenfv", "xen", "vmware", "vmwarew", "auto" ]: + if self.virt_type == "xen": + self.virt_type = "xenpv" +- raise InfoException("--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, or auto") ++ raise(InfoException("--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, or auto")) + + # if --qemu-disk-type was called without --virt-type=qemu, then fail + if (self.qemu_disk_type is not None): + self.qemu_disk_type = self.qemu_disk_type.lower() + if self.virt_type not in [ "qemu", "auto", "kvm" ]: +- raise(InfoException, "--qemu-disk-type must use with --virt-type=qemu") ++ raise((InfoException, "--qemu-disk-type must use with --virt-type=qemu")) + + # if --qemu-net-type was called without --virt-type=qemu, then fail + if (self.qemu_net_type is not None): + self.qemu_net_type = self.qemu_net_type.lower() + if self.virt_type not in [ "qemu", "auto", "kvm" ]: +- raise InfoException, "--qemu-net-type must use with --virt-type=qemu" ++ raise(InfoException, "--qemu-net-type must use with --virt-type=qemu") + + # if --static-interface and --profile was called together, then fail + if self.static_interface is not None and self.profile is not None: +- raise InfoException("--static-interface option is incompatible with --profile option use --system instead") ++ raise(InfoException("--static-interface option is incompatible with --profile option use --system instead")) + + # perform one of three key operations + if self.is_virt: +@@ -423,12 +423,12 @@ class Koan: + detected_systems = utils.uniqify(detected_systems) + + if len(detected_systems) > 1: +- raise InfoException("Error: Multiple systems matched") ++ raise(InfoException("Error: Multiple systems matched")) + elif len(detected_systems) == 0: + if not allow_interactive: + mac_criteria = utils.uniqify(mac_criteria, purge="?") + ip_criteria = utils.uniqify(ip_criteria, purge="?") +- raise InfoException("Error: Could not find a matching system with MACs: %s or IPs: %s" % (",".join(mac_criteria), ",".join(ip_criteria))) ++ raise(InfoException("Error: Could not find a matching system with MACs: %s or IPs: %s" % (",".join(mac_criteria), ",".join(ip_criteria)))) + else: + return None + elif len(detected_systems) == 1: +@@ -506,7 +506,7 @@ class Koan: + if self.virt_type == "auto": + + if profile_data.get("xml_file","") != "": +- raise InfoException("xmlfile based installations are not supported") ++ raise(InfoException("xmlfile based installations are not supported")) + + elif "file" in profile_data: + print("- ISO or Image based installation, always uses --virt-type=qemu") +@@ -523,7 +523,7 @@ class Koan: + self.virt_type = "qemu" + else: + # assume Xen, we'll check to see if virt-type is really usable later. +- raise InfoException("Not running a Xen kernel and qemu is not installed") ++ raise(InfoException("Not running a Xen kernel and qemu is not installed")) + + print("- no virt-type specified, auto-selecting %s" % self.virt_type) + +@@ -535,20 +535,20 @@ class Koan: + uname_str = cmd.communicate()[0] + # correct kernel on dom0? + if uname_str.find("xen") == -1: +- raise InfoException("kernel-xen needs to be in use") ++ raise(InfoException("kernel-xen needs to be in use")) + # xend installed? + if not os.path.exists("/usr/sbin/xend"): +- raise InfoException("xen package needs to be installed") ++ raise(InfoException("xen package needs to be installed")) + # xend running? + rc = sub_process.call("/usr/sbin/xend status", stderr=None, stdout=None, shell=True) + if rc != 0: +- raise InfoException("xend needs to be started") ++ raise(InfoException("xend needs to be started")) + + # for qemu + if self.virt_type == "qemu": + # qemu package installed? + if not os.path.exists("/usr/bin/qemu-img"): +- raise InfoException("qemu package needs to be installed") ++ raise(InfoException("qemu package needs to be installed")) + # is libvirt new enough? + # Note: in some newer distros (like Fedora 19) the python-virtinst package has been + # subsumed into virt-install. If we don't have one check to see if we have the other. +@@ -556,7 +556,7 @@ class Koan: + if rc != 0: + rc, version_str = utils.subprocess_get_response(shlex.split('rpm -q python-virtinst'), True) + if rc != 0 or version_str.find("virtinst-0.1") != -1 or version_str.find("virtinst-0.0") != -1: +- raise InfoException("need python-virtinst >= 0.2 or virt-install package to do installs for qemu/kvm (depending on your OS)") ++ raise(InfoException("need python-virtinst >= 0.2 or virt-install package to do installs for qemu/kvm (depending on your OS)")) + + # for vmware + if self.virt_type == "vmware" or self.virt_type == "vmwarew": +@@ -565,14 +565,14 @@ class Koan: + + if self.virt_type == "virt-image": + if not os.path.exists("/usr/bin/virt-image"): +- raise InfoException("virt-image not present, downlevel virt-install package?") ++ raise(InfoException("virt-image not present, downlevel virt-install package?")) + + # for both virt types + if os.path.exists("/etc/rc.d/init.d/libvirtd"): + rc = sub_process.call("/sbin/service libvirtd status", stdout=None, shell=True) + if rc != 0: + # libvirt running? +- raise InfoException("libvirtd needs to be running") ++ raise(InfoException("libvirtd needs to be running")) + + + if self.virt_type in [ "xenpv" ]: +@@ -642,7 +642,7 @@ class Koan: + + def list(self,what): + if what not in [ "images", "profiles", "systems", "distros", "repos" ]: +- raise InfoException("koan does not know how to list that") ++ raise(InfoException("koan does not know how to list that")) + data = self.get_data(what) + for x in data: + if "name" in x: +@@ -766,12 +766,12 @@ class Koan: + # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 + if arch.startswith("ppc") or arch.startswith("ia64"): + if len(k_args) > 511: +- raise InfoException("Kernel options are too long, 512 chars exceeded: %s" % k_args) ++ raise(InfoException("Kernel options are too long, 512 chars exceeded: %s" % k_args)) + elif arch.startswith("s390"): + if len(k_args) > 895: +- raise(InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args) ++ raise((InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args)) + elif len(k_args) > 2047: +- raise(InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args) ++ raise((InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args)) + + utils.subprocess_call([ + 'kexec', +@@ -803,18 +803,18 @@ class Koan: + except OSError as xxx_todo_changeme: + (err, msg) = xxx_todo_changeme.args + if err != errno.ENOENT: +- raise ++ raise() + try: + os.makedirs("/var/spool/koan") + except OSError as xxx_todo_changeme: + (err, msg) = xxx_todo_changeme.args + if err != errno.EEXIST: +- raise ++ raise() + + + def after_download(self, profile_data): + if not os.path.exists("/sbin/grubby"): +- raise InfoException("grubby is not installed") ++ raise(InfoException("grubby is not installed")) + k_args = self.calc_kernel_args(profile_data,replace_self=1) + + kickstart = self.safe_load(profile_data,'kickstart') +@@ -846,12 +846,12 @@ class Koan: + # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 + if arch.startswith("ppc") or arch.startswith("ia64"): + if len(k_args) > 511: +- raise InfoException("Kernel options are too long, 512 chars exceeded: %s" % k_args) ++ raise(InfoException("Kernel options are too long, 512 chars exceeded: %s" % k_args)) + elif arch.startswith("s390"): + if len(k_args) > 895: +- raise(InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args) ++ raise((InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args)) + elif len(k_args) > 2047: +- raise(InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args) ++ raise((InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args)) + + cmd = [ "/sbin/grubby", + "--add-kernel", self.safe_load(profile_data,'kernel_local'), +@@ -977,7 +977,7 @@ class Koan: + #--------------------------------------------------- + + def connect_fail(self): +- raise InfoException("Could not communicate with %s:%s" % (self.server, self.port)) ++ raise(InfoException("Could not communicate with %s:%s" % (self.server, self.port))) + + #--------------------------------------------------- + +@@ -991,7 +991,7 @@ class Koan: + traceback.print_exc() + self.connect_fail() + if data == {}: +- raise InfoException("No entry/entries found") ++ raise(InfoException("No entry/entries found")) + return data + + #--------------------------------------------------- +@@ -1060,7 +1060,7 @@ class Koan: + utils.urlgrab(kernel,kernel_save) + except: + traceback.print_exc() +- raise(InfoException, "error downloading files") ++ raise((InfoException, "error downloading files")) + profile_data['kernel_local'] = kernel_save + profile_data['initrd_local'] = initrd_save + +@@ -1179,7 +1179,7 @@ class Koan: + elif can_poll == "qemu": + conn = libvirt.open("qemu:///system") + else: +- raise InfoException("Don't know how to poll this virt-type") ++ raise(InfoException("Don't know how to poll this virt-type")) + ct = 0 + while True: + time.sleep(3) +@@ -1196,7 +1196,7 @@ class Koan: + utils.find_vm(conn, virtname).create() + return results + else: +- raise InfoException("internal error, bad virt state") ++ raise(InfoException("internal error, bad virt state")) + + if virt_auto_boot: + if self.virt_type in [ "xenpv", "xenfv" ]: +@@ -1240,7 +1240,7 @@ class Koan: + uuid = None + creator = vmwwcreate.start_install + else: +- raise InfoException("Unspecified virt type: %s" % self.virt_type) ++ raise(InfoException("Unspecified virt type: %s" % self.virt_type)) + return (uuid, creator, fullvirt, can_poll) + + #--------------------------------------------------- +@@ -1259,7 +1259,7 @@ class Koan: + if len(disks) == 0: + print("paths: ", paths) + print("sizes: ", sizes) +- raise InfoException("Disk configuration not resolvable!") ++ raise(InfoException("Disk configuration not resolvable!")) + return disks + + #--------------------------------------------------- +@@ -1462,13 +1462,13 @@ class Koan: + os.makedirs(os.path.dirname(location)) + return location + else: +- raise(InfoException, "invalid location: %s" % location) ++ raise((InfoException, "invalid location: %s" % location)) + elif location.startswith("/dev/"): + # partition + if os.path.exists(location): + return location + else: +- raise InfoException("virt path is not a valid block device") ++ raise(InfoException("virt path is not a valid block device")) + else: + # it's a volume group, verify that it exists + args = "vgs -o vg_name" +@@ -1477,7 +1477,7 @@ class Koan: + print(vgnames) + + if vgnames.find(location) == -1: +- raise InfoException("The volume group [%s] does not exist." % location) ++ raise(InfoException("The volume group [%s] does not exist." % location)) + + # check free space + args = "LANG=C vgs --noheadings -o vg_free --units g %s" % location +@@ -1512,13 +1512,13 @@ class Koan: + print("%s" % args) + lv_create = sub_process.call(args, shell=True) + if lv_create != 0: +- raise InfoException("LVM creation failed") ++ raise(InfoException("LVM creation failed")) + + # return partition location + return "/dev/%s/%s" % (location,name) + + else: +- raise InfoException("volume group needs %s GB free space." % virt_size) ++ raise(InfoException("volume group needs %s GB free space." % virt_size)) + + + def randomUUID(self): +diff --git a/koan/qcreate.py b/koan/qcreate.py +index 73b6abb..e41a532 100755 +--- a/koan/qcreate.py ++++ b/koan/qcreate.py +@@ -38,7 +38,7 @@ def start_install(*args, **kwargs): + try: + import libvirt + except: +- raise app.InfoException("package libvirt is required for installing virtual guests") ++ raise(app.InfoException("package libvirt is required for installing virtual guests")) + conn = libvirt.openReadOnly(None) + # See http://libvirt.org/formatcaps.html + capabilities = parseString(conn.getCapabilities()) +diff --git a/koan/register.py b/koan/register.py +index 8ce7db3..0a066f1 100755 +--- a/koan/register.py ++++ b/koan/register.py +@@ -124,7 +124,7 @@ class Register: + # not really required, but probably best that ordinary users don't try + # to run this not knowing what it does. + if os.getuid() != 0: +- raise InfoException("root access is required to register") ++ raise(InfoException("root access is required to register")) + + print("- preparing to koan home") + self.conn = connect_to_server(self.server, self.port) +@@ -144,12 +144,12 @@ class Register: + hostname = "" + sysname = str(time.time()) + else: +- raise InfoException("must specify --fqdn, could not discover") ++ raise(InfoException("must specify --fqdn, could not discover")) + if sysname == "": + sysname = hostname + + if self.profile == "": +- raise InfoException("must specify --profile") ++ raise(InfoException("must specify --profile")) + + # we'll do a profile check here just to avoid some log noise on the remote end. + # network duplication checks and profile checks also happen on the remote end. +@@ -166,7 +166,7 @@ class Register: + reg_info['hostname'] = hostname + + if not matched_profile: +- raise InfoException("no such remote profile, see 'koan --list-profiles'") ++ raise(InfoException("no such remote profile, see 'koan --list-profiles'") ) + + if not self.batch: + self.conn.register_new_system(reg_info) +diff --git a/koan/utils.py b/koan/utils.py +index 607db1f..b88ba94 100644 +--- a/koan/utils.py ++++ b/koan/utils.py +@@ -92,7 +92,7 @@ def urlread(url): + """ + print("- reading URL: %s" % url) + if url is None or url == "": +- raise InfoException("invalid URL: %s" % url) ++ raise(InfoException("invalid URL: %s" % url)) + + elif url[0:3] == "nfs": + try: +@@ -110,7 +110,7 @@ def urlread(url): + return data + except: + traceback.print_exc() +- raise(InfoException, "Couldn't mount and read URL: %s" % url) ++ raise((InfoException, "Couldn't mount and read URL: %s" % url)) + + elif url[0:4] == "http": + try: +@@ -120,7 +120,7 @@ def urlread(url): + return data + except: + traceback.print_exc() +- raise(InfoException, "Couldn't download: %s" % url) ++ raise((InfoException, "Couldn't download: %s" % url)) + elif url[0:4] == "file": + try: + fd = open(url[5:]) +@@ -128,10 +128,10 @@ def urlread(url): + fd.close() + return data + except: +- raise InfoException("Couldn't read file from URL: %s" % url) ++ raise(InfoException("Couldn't read file from URL: %s" % url)) + + else: +- raise InfoException("Unhandled URL protocol: %s" % url) ++ raise(InfoException("Unhandled URL protocol: %s" % url)) + + def urlgrab(url,saveto): + """ +@@ -150,7 +150,7 @@ def subprocess_call(cmd,ignore_rc=0): + print("- %s" % cmd) + rc = sub_process.call(cmd) + if rc != 0 and not ignore_rc: +- raise InfoException("command failed (%s)" % rc) ++ raise(InfoException("command failed (%s)" % rc)) + return rc + + def subprocess_get_response(cmd, ignore_rc=False): +@@ -165,7 +165,7 @@ def subprocess_get_response(cmd, ignore_rc=False): + result = result.strip() + rc = p.poll() + if not ignore_rc and rc != 0: +- raise InfoException("command failed (%s)" % rc) ++ raise(InfoException("command failed (%s)" % rc)) + return rc, result + + def input_string_or_hash(options,delim=None,allow_multiples=True): +@@ -178,7 +178,7 @@ def input_string_or_hash(options,delim=None,allow_multiples=True): + if options is None: + return {} + elif type(options) == list: +- raise InfoException("No idea what to do with list: %s" % options) ++ raise(InfoException("No idea what to do with list: %s" % options)) + elif type(options) == type(""): + new_dict = {} + tokens = options.split(delim) +@@ -215,7 +215,7 @@ def input_string_or_hash(options,delim=None,allow_multiples=True): + options.pop('',None) + return options + else: +- raise InfoException("invalid input type: %s" % type(options)) ++ raise(InfoException("invalid input type: %s" % type(options))) + + def hash_to_string(hash): + """ +@@ -255,7 +255,7 @@ def nfsmount(input_path): + rc = sub_process.call(mount_cmd) + if not rc == 0: + shutil.rmtree(tempdir, ignore_errors=True) +- raise InfoException("nfs mount failed: %s" % dirpath) ++ raise(InfoException("nfs mount failed: %s" % dirpath)) + # NOTE: option for a blocking install might be nice, so we could do this + # automatically, if supported by virt-install + print("after install completes, you may unmount and delete %s" % tempdir) +@@ -290,7 +290,7 @@ def find_vm(conn, vmid): + if vm.name() == vmid: + return vm + +- raise InfoException("koan could not find the VM to watch: %s" % vmid) ++ raise(InfoException("koan could not find the VM to watch: %s" % vmid)) + + def get_vm_state(conn, vmid): + """ +@@ -335,7 +335,7 @@ def os_release(): + return (make,float(t)) + except ValueError: + pass +- raise InfoException("failed to detect local OS version from /etc/redhat-release") ++ raise(InfoException("failed to detect local OS version from /etc/redhat-release")) + + elif check_dist() == "debian": + fd = open("/etc/debian_version") +@@ -429,7 +429,7 @@ def connect_to_server(server=None,port=None): + if server is None: + server = os.environ.get("COBBLER_SERVER","") + if server == "": +- raise InfoException("--server must be specified") ++ raise(InfoException("--server must be specified")) + + if port is None: + port = 25151 +@@ -445,7 +445,7 @@ def connect_to_server(server=None,port=None): + server = __try_connect(url) + if server is not None: + return server +- raise InfoException ("Could not find Cobbler.") ++ raise(InfoException ("Could not find Cobbler.")) + + def create_xendomains_symlink(name): + """ +@@ -459,7 +459,7 @@ def create_xendomains_symlink(name): + if os.path.exists(src) and os.access(os.path.dirname(dst), os.W_OK): + os.symlink(src, dst) + else: +- raise InfoException("Could not create /etc/xen/auto/%s symlink. Please check write permissions and ownership" % name) ++ raise(InfoException("Could not create /etc/xen/auto/%s symlink. Please check write permissions and ownership" % name)) + + def libvirt_enable_autostart(domain_name): + import libvirt +@@ -469,10 +469,10 @@ def libvirt_enable_autostart(domain_name): + domain = conn.lookupByName(domain_name) + domain.setAutostart(1) + except: +- raise InfoException("libvirt could not find domain %s" % domain_name) ++ raise(InfoException("libvirt could not find domain %s" % domain_name)) + + if not domain.autostart: +- raise InfoException("Could not enable autostart on domain %s." % domain_name) ++ raise(InfoException("Could not enable autostart on domain %s." % domain_name)) + + def make_floppy(kickstart): + +@@ -484,14 +484,14 @@ def make_floppy(kickstart): + print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: +- raise InfoException("dd failed") ++ raise(InfoException("dd failed")) + + # vfatify + cmd = "mkdosfs %s" % floppy_path + print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: +- raise InfoException("mkdosfs failed") ++ raise(InfoException("mkdosfs failed")) + + # mount the floppy + mount_path = tempfile.mkdtemp(suffix=".mnt", prefix='tmp', dir="/tmp") +@@ -499,7 +499,7 @@ def make_floppy(kickstart): + print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: +- raise InfoException("mount failed") ++ raise(InfoException("mount failed")) + + # download the kickstart file onto the mounted floppy + print("- downloading %s" % kickstart) +@@ -511,7 +511,7 @@ def make_floppy(kickstart): + print("- %s" % cmd) + rc = os.system(cmd) + if not rc == 0: +- raise InfoException("umount failed") ++ raise(InfoException("umount failed")) + + # return the path to the completed disk image to pass to virt-install + return floppy_path +diff --git a/koan/virtinstall.py b/koan/virtinstall.py +index de2a670..fbafb43 100644 +--- a/koan/virtinstall.py ++++ b/koan/virtinstall.py +@@ -90,7 +90,7 @@ def _sanitize_disks(disks): + if d[1] != 0 or d[0].startswith("/dev"): + ret.append((d[0], d[1], driver_type)) + else: +- raise app.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") ++ raise(app.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero")) + + return ret + +@@ -127,7 +127,7 @@ def _sanitize_nics(nics, bridge, profile_bridge, network_count): + intf_bridge = intf["virt_bridge"] + if intf_bridge == "": + if profile_bridge == "": +- raise app.InfoException("virt-bridge setting is not defined in cobbler") ++ raise(app.InfoException("virt-bridge setting is not defined in cobbler")) + intf_bridge = profile_bridge + + else: +@@ -230,12 +230,12 @@ def build_commandline(uri, + if is_import: + importpath = profile_data.get("file") + if not importpath: +- raise app.InfoException("Profile 'file' required for image " +- "install") ++ raise(app.InfoException("Profile 'file' required for image " ++ "install")) + + elif "file" in profile_data: + if is_xen: +- raise app.InfoException("Xen does not work with --image yet") ++ raise(app.InfoException("Xen does not work with --image yet")) + + # this is an image based installation + input_path = profile_data["file"] +@@ -256,7 +256,7 @@ def build_commandline(uri, + elif is_qemu or is_xen: + # images don't need to source this + if not "install_tree" in profile_data: +- raise app.InfoException("Cannot find install source in kickstart file, aborting.") ++ raise(app.InfoException("Cannot find install source in kickstart file, aborting.")) + + if not profile_data["install_tree"].endswith("/"): + profile_data["install_tree"] = profile_data["install_tree"] + "/" +@@ -277,7 +277,7 @@ def build_commandline(uri, + bridge = profile_data["virt_bridge"] + + if bridge == "": +- raise app.InfoException("virt-bridge setting is not defined in cobbler") ++ raise(app.InfoException("virt-bridge setting is not defined in cobbler")) + nics = [(bridge, None)] + + +diff --git a/koan/vmwcreate.py b/koan/vmwcreate.py +index 1b90a27..3e94e1c 100755 +--- a/koan/vmwcreate.py ++++ b/koan/vmwcreate.py +@@ -82,7 +82,7 @@ def make_disk(disksize,image): + print("- %s" % cmd) + rc = os.system(cmd) + if rc != 0: +- raise VirtCreateException("command failed") ++ raise(VirtCreateException("command failed")) + + def make_vmx(path,vmdk_image,image_name,mac_address,memory): + template_params = { +@@ -101,7 +101,7 @@ def register_vmx(vmx_file): + print("- %s" % cmd) + rc = os.system(cmd) + if rc!=0: +- raise VirtCreateException("vmware registration failed") ++ raise(VirtCreateException("vmware registration failed")) + + def start_vm(vmx_file): + os.chmod(vmx_file, 0o755) +@@ -109,7 +109,7 @@ def start_vm(vmx_file): + print("- %s" % cmd) + rc = os.system(cmd) + if rc != 0: +- raise VirtCreateException("vm start failed") ++ raise(VirtCreateException("vm start failed")) + + def start_install(name=None, + ram=None, +@@ -127,7 +127,7 @@ def start_install(name=None, + virt_auto_boot=False): + + if "file" in profile_data: +- raise app.InfoException("vmware does not work with --image yet") ++ raise(app.InfoException("vmware does not work with --image yet")) + + mac = None + if not "interfaces" in profile_data: +@@ -154,7 +154,7 @@ def start_install(name=None, + os.makedirs(VMX_DIR) + + if len(disks) != 1: +- raise VirtCreateException("vmware support is limited to 1 virtual disk") ++ raise(VirtCreateException("vmware support is limited to 1 virtual disk")) + + diskname = disks[0][0] + disksize = disks[0][1] +-- +2.5.5 + diff --git a/SOURCES/0018-adapt-setup.py-for-both-py2-and-py3.patch b/SOURCES/0018-adapt-setup.py-for-both-py2-and-py3.patch new file mode 100644 index 0000000..c3cf464 --- /dev/null +++ b/SOURCES/0018-adapt-setup.py-for-both-py2-and-py3.patch @@ -0,0 +1,518 @@ +From 9d02d75f09b8e6612acb22eb0a9777df63397730 Mon Sep 17 00:00:00 2001 +From: Jan Dobes +Date: Thu, 12 Oct 2017 10:20:54 +0200 +Subject: [PATCH 18/18] adapt setup.py for both py2 and py3 + +(cherry picked from commit 84ef184850f29638f07d2540a9ec35b4fb6c5095) + +Conflicts: + setup.py +--- + setup.py | 376 ++++++++++++++++++++++++++++++++++----------------------------- + 1 file changed, 203 insertions(+), 173 deletions(-) + +diff --git a/setup.py b/setup.py +index 045abd7..38ee962 100644 +--- a/setup.py ++++ b/setup.py +@@ -4,12 +4,13 @@ import sys + import os.path + from distutils.core import setup, Extension + import string +-import yaml # PyYAML ++if sys.version_info[0] == 2: ++ import yaml # PyYAML ++ import Cheetah.Template as Template + try: + import subprocess + except: + import cobbler.sub_process as subprocess +-import Cheetah.Template as Template + import time + + VERSION = "2.0.7" +@@ -67,63 +68,64 @@ def gen_config(): + templatify(MODULES_TEMPLATE, defaults, os.path.join(OUTPUT_DIR, "modules.conf")) + templatify(SETTINGS_TEMPLATE, defaults, os.path.join(OUTPUT_DIR, "settings")) + +-if __name__ == "__main__": ++ ++def py2_setup(): + gen_build_version() + gen_config() + + # etc configs +- etcpath = "/etc/cobbler" +- initpath = "/etc/init.d" +- rotpath = "/etc/logrotate.d" +- powerpath = etcpath + "/power" +- pxepath = etcpath + "/pxe" +- reppath = etcpath + "/reporting" +- zonepath = etcpath + "/zone_templates" ++ etcpath = "/etc/cobbler" ++ initpath = "/etc/init.d" ++ rotpath = "/etc/logrotate.d" ++ powerpath = etcpath + "/power" ++ pxepath = etcpath + "/pxe" ++ reppath = etcpath + "/reporting" ++ zonepath = etcpath + "/zone_templates" + + # lib paths +- libpath = "/var/lib/cobbler" +- backpath = libpath + "/backup" +- trigpath = libpath + "/triggers" ++ libpath = "/var/lib/cobbler" ++ backpath = libpath + "/backup" ++ trigpath = libpath + "/triggers" + snippetpath = libpath + "/snippets" +- kickpath = libpath + "/kickstarts" +- dbpath = libpath + "/config" +- loadpath = libpath + "/loaders" ++ kickpath = libpath + "/kickstarts" ++ dbpath = libpath + "/config" ++ loadpath = libpath + "/loaders" + + # share paths +- sharepath = "/usr/share/cobbler" +- itemplates = sharepath + "/installer_templates" +- wwwtmpl = sharepath + "/webui_templates" +- manpath = "share/man/man1" +- spool_koan = "/var/spool/koan" ++ sharepath = "/usr/share/cobbler" ++ itemplates = sharepath + "/installer_templates" ++ wwwtmpl = sharepath + "/webui_templates" ++ manpath = "share/man/man1" ++ spool_koan = "/var/spool/koan" + + # www paths +- wwwpath = "/var/www/cobbler" ++ wwwpath = "/var/www/cobbler" + if os.path.exists("/etc/SuSE-release"): +- wwwconf = "/etc/apache2/conf.d" ++ wwwconf = "/etc/apache2/conf.d" + elif os.path.exists("/etc/debian_version"): +- wwwconf = "/etc/apache2/conf.d" ++ wwwconf = "/etc/apache2/conf.d" + else: +- wwwconf = "/etc/httpd/conf.d" ++ wwwconf = "/etc/httpd/conf.d" + +- wwwcon = "/var/www/cobbler_webui_content" ++ wwwcon = "/var/www/cobbler_webui_content" + vw_localmirror = wwwpath + "/localmirror" +- vw_kickstarts = wwwpath + "/kickstarts" +- vw_kickstarts_sys = wwwpath + "/kickstarts_sys" ++ vw_kickstarts = wwwpath + "/kickstarts" ++ vw_kickstarts_sys = wwwpath + "/kickstarts_sys" + vw_repomirror = wwwpath + "/repo_mirror" +- vw_ksmirror = wwwpath + "/ks_mirror" +- vw_ksmirrorc = wwwpath + "/ks_mirror/config" +- vw_images = wwwpath + "/images" +- vw_distros = wwwpath + "/distros" +- vw_systems = wwwpath + "/systems" +- vw_profiles = wwwpath + "/profiles" +- vw_links = wwwpath + "/links" +- vw_aux = wwwpath + "/aux" +- modpython = wwwpath + "/web" +- modwsgisvc = wwwpath + "/svc" +- modpythonsvc = modwsgisvc ++ vw_ksmirror = wwwpath + "/ks_mirror" ++ vw_ksmirrorc = wwwpath + "/ks_mirror/config" ++ vw_images = wwwpath + "/images" ++ vw_distros = wwwpath + "/distros" ++ vw_systems = wwwpath + "/systems" ++ vw_profiles = wwwpath + "/profiles" ++ vw_links = wwwpath + "/links" ++ vw_aux = wwwpath + "/aux" ++ modpython = wwwpath + "/web" ++ modwsgisvc = wwwpath + "/svc" ++ modpythonsvc = modwsgisvc + + # log paths +- logpath = "/var/log/cobbler" ++ logpath = "/var/log/cobbler" + logpath2 = logpath + "/kicklog" + logpath3 = logpath + "/syslog" + logpath4 = "/var/log/httpd/cobbler" +@@ -132,123 +134,123 @@ if __name__ == "__main__": + logpath7 = logpath + "/tasks" + + # django content +- dj_config = "/etc/httpd/conf.d/" ++ dj_config = "/etc/httpd/conf.d/" + dj_templates = "/usr/share/cobbler/web/cobbler_web/templates" +- dj_webui = "/usr/share/cobbler/web/cobbler_web" +- dj_webui2 = "/usr/share/cobbler/web/cobbler_web/templatetags" +- dj_webui_proj= "/usr/share/cobbler/web" +- dj_sessions = "/var/lib/cobbler/webui_sessions" +- dj_js = "/var/www/cobbler_webui_content/" ++ dj_webui = "/usr/share/cobbler/web/cobbler_web" ++ dj_webui2 = "/usr/share/cobbler/web/cobbler_web/templatetags" ++ dj_webui_proj = "/usr/share/cobbler/web" ++ dj_sessions = "/var/lib/cobbler/webui_sessions" ++ dj_js = "/var/www/cobbler_webui_content/" + + setup( + name="cobbler", +- version = VERSION, +- author = "Michael DeHaan", +- author_email = "mdehaan@redhat.com", +- url = "http://fedorahosted.org/cobbler/", +- license = "GPL", +- packages = [ ++ version=VERSION, ++ author="Michael DeHaan", ++ author_email="mdehaan@redhat.com", ++ url="http://fedorahosted.org/cobbler/", ++ license="GPL", ++ packages= [ + "cobbler", + "cobbler/modules", + "koan" + ], +- scripts = [ ++ scripts=[ + "scripts/cobbler", + "scripts/cobblerd", + "scripts/cobbler-ext-nodes", + "scripts/koan", + "scripts/cobbler-register" + ], +- data_files = [ ++ data_files=[ + (modpythonsvc, ['scripts/services.py']), +- (modwsgisvc, ['scripts/services.wsgi']), ++ (modwsgisvc, ['scripts/services.wsgi']), + + # miscellaneous config files +- (rotpath, ['config/cobblerd_rotate']), +- (wwwconf, ['config/cobbler.conf']), +- (wwwconf, ['config/cobbler_wsgi.conf']), +- (libpath, ['config/cobbler_hosts']), +- (etcpath, ['config/modules.conf']), +- (etcpath, ['config/users.digest']), +- (etcpath, ['config/rsync.exclude']), +- (etcpath, ['config/users.conf']), +- (etcpath, ['config/cheetah_macros']), ++ (rotpath, ['config/cobblerd_rotate']), ++ (wwwconf, ['config/cobbler.conf']), ++ (wwwconf, ['config/cobbler_wsgi.conf']), ++ (libpath, ['config/cobbler_hosts']), ++ (etcpath, ['config/modules.conf']), ++ (etcpath, ['config/users.digest']), ++ (etcpath, ['config/rsync.exclude']), ++ (etcpath, ['config/users.conf']), ++ (etcpath, ['config/cheetah_macros']), + (initpath, ['config/cobblerd']), +- (etcpath, ['config/settings']), ++ (etcpath, ['config/settings']), + + # django webui content +- (dj_config, [ 'config/cobbler_web.conf' ]), +- (dj_templates, [ 'web/cobbler_web/templates/blank.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/empty.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/enoaccess.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/error_page.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/header.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/index.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/item.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/ksfile_edit.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/ksfile_list.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/snippet_edit.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/snippet_list.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/master.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/message.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/paginate.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/settings.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/generic_edit.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/generic_list.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/generic_delete.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/generic_rename.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/events.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/eventlog.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/import.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/task_created.tmpl' ]), +- (dj_templates, [ 'web/cobbler_web/templates/check.tmpl' ]), ++ (dj_config, ['config/cobbler_web.conf']), ++ (dj_templates, ['web/cobbler_web/templates/blank.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/empty.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/enoaccess.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/error_page.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/header.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/index.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/item.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/ksfile_edit.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/ksfile_list.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/snippet_edit.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/snippet_list.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/master.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/message.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/paginate.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/settings.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/generic_edit.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/generic_list.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/generic_delete.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/generic_rename.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/events.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/eventlog.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/import.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/task_created.tmpl']), ++ (dj_templates, ['web/cobbler_web/templates/check.tmpl']), + + # django code, private to cobbler-web application +- (dj_webui, [ 'web/cobbler_web/__init__.py' ]), +- (dj_webui_proj, [ 'web/__init__.py' ]), +- (dj_webui_proj, [ 'web/urls.py' ]), +- (dj_webui_proj, [ 'web/manage.py' ]), +- (dj_webui_proj, [ 'web/settings.py' ]), +- (dj_webui, [ 'web/cobbler_web/urls.py' ]), +- (dj_webui, [ 'web/cobbler_web/views.py' ]), +- (dj_webui2, [ 'web/cobbler_web/templatetags/site.py' ]), +- (dj_webui2, [ 'web/cobbler_web/templatetags/__init__.py' ]), +- (dj_sessions, []), ++ (dj_webui, ['web/cobbler_web/__init__.py']), ++ (dj_webui_proj, ['web/__init__.py']), ++ (dj_webui_proj, ['web/urls.py']), ++ (dj_webui_proj, ['web/manage.py']), ++ (dj_webui_proj, ['web/settings.py']), ++ (dj_webui, ['web/cobbler_web/urls.py']), ++ (dj_webui, ['web/cobbler_web/views.py']), ++ (dj_webui2, ['web/cobbler_web/templatetags/site.py']), ++ (dj_webui2, ['web/cobbler_web/templatetags/__init__.py']), ++ (dj_sessions, []), + + # backups for upgrades + (backpath, []), + + # for --version support across distros +- (libpath, ['config/version']), ++ (libpath, ['config/version']), + + # bootloaders and syslinux support files + # we only package zpxe.rexx because it's source + # user supplies the others +- (loadpath, ['scripts/zpxe.rexx']), ++ (loadpath, ['scripts/zpxe.rexx']), + + # database/serializer +- (dbpath + "/distros.d", []), ++ (dbpath + "/distros.d", []), + (dbpath + "/profiles.d", []), +- (dbpath + "/systems.d", []), +- (dbpath + "/repos.d", []), +- (dbpath + "/images.d", []), ++ (dbpath + "/systems.d", []), ++ (dbpath + "/repos.d", []), ++ (dbpath + "/images.d", []), + + # sample kickstart files +- (kickpath, ['kickstarts/legacy.ks']), +- (kickpath, ['kickstarts/sample.ks']), +- (kickpath, ['kickstarts/sample_end.ks']), +- (kickpath, ['kickstarts/default.ks']), +- (kickpath, ['kickstarts/pxerescue.ks']), ++ (kickpath, ['kickstarts/legacy.ks']), ++ (kickpath, ['kickstarts/sample.ks']), ++ (kickpath, ['kickstarts/sample_end.ks']), ++ (kickpath, ['kickstarts/default.ks']), ++ (kickpath, ['kickstarts/pxerescue.ks']), + + # seed files for debian +- (kickpath, ['kickstarts/sample.seed']), ++ (kickpath, ['kickstarts/sample.seed']), + + # templates for DHCP, DNS, TFTP, RSYNC +- (etcpath, ['templates/dhcp.template']), +- (etcpath, ['templates/dnsmasq.template']), +- (etcpath, ['templates/named.template']), +- (etcpath, ['templates/zone.template']), +- (etcpath, ['templates/rsync.template']), ++ (etcpath, ['templates/dhcp.template']), ++ (etcpath, ['templates/dnsmasq.template']), ++ (etcpath, ['templates/named.template']), ++ (etcpath, ['templates/zone.template']), ++ (etcpath, ['templates/rsync.template']), + + # templates for netboot configs + (pxepath, ['templates/pxedefault.template']), +@@ -283,7 +285,7 @@ if __name__ == "__main__": + (powerpath, ['templates/power_virsh.template']), + + # templates for reporting +- (reppath, ['templates/build_report_email.template']), ++ (reppath, ['templates/build_report_email.template']), + + # templates for setup + (itemplates, ['installer_templates/modules.conf.template']), +@@ -313,12 +315,12 @@ if __name__ == "__main__": + (snippetpath, ['snippets/log_ks_post']), + + # documentation +- (manpath, ['docs/cobbler.1.gz']), +- (manpath, ['docs/koan.1.gz']), +- (manpath, ['docs/cobbler-register.1.gz']), ++ (manpath, ['docs/cobbler.1.gz']), ++ (manpath, ['docs/koan.1.gz']), ++ (manpath, ['docs/cobbler-register.1.gz']), + + # logfiles +- (logpath, []), ++ (logpath, []), + (logpath2, []), + (logpath3, []), + (logpath4, []), +@@ -330,72 +332,100 @@ if __name__ == "__main__": + (spool_koan, []), + + # web page directories that we own +- (vw_localmirror, []), +- (vw_kickstarts, []), ++ (vw_localmirror, []), ++ (vw_kickstarts, []), + (vw_kickstarts_sys, []), +- (vw_repomirror, []), +- (vw_ksmirror, []), +- (vw_ksmirrorc, []), +- (vw_distros, []), +- (vw_images, []), +- (vw_systems, []), +- (vw_profiles, []), +- (vw_links, []), +- (vw_aux, []), ++ (vw_repomirror, []), ++ (vw_ksmirror, []), ++ (vw_ksmirrorc, []), ++ (vw_distros, []), ++ (vw_images, []), ++ (vw_systems, []), ++ (vw_profiles, []), ++ (vw_links, []), ++ (vw_aux, []), + + # zone-specific templates directory +- (zonepath, []), ++ (zonepath, []), + + # Web UI templates for object viewing & modification + # FIXME: other templates to add as they are created. + # slurp in whole directory? + + # Web UI support files +- (wwwcon, ['web/content/style.css']), +- (wwwcon, ['web/content/logo-cobbler.png']), +- (modpython, ['web/content/index.html']), +- (wwwpath + "/pub", []), +- (dj_js, ['web/content/cobbler.js']), ++ (wwwcon, ['web/content/style.css']), ++ (wwwcon, ['web/content/logo-cobbler.png']), ++ (modpython, ['web/content/index.html']), ++ (wwwpath + "/pub", []), ++ (dj_js, ['web/content/cobbler.js']), + # FIXME: someday Fedora/EPEL will package these and then we should not embed them then. +- (dj_js, ['web/content/jquery-1.3.2.js']), +- (dj_js, ['web/content/jquery-1.3.2.min.js']), +- (dj_js, ['web/content/jsGrowl_jquery.js']), +- (dj_js, ['web/content/jsGrowl.js']), +- (dj_js, ['web/content/jsgrowl_close.png']), +- (dj_js, ['web/content/jsgrowl_corners.png']), +- (dj_js, ['web/content/jsgrowl_middle_hover.png']), +- (dj_js, ['web/content/jsgrowl_corners_hover.png']), +- (dj_js, ['web/content/jsgrowl_side_hover.png']), +- (dj_js, ['web/content/jsGrowl.css']), ++ (dj_js, ['web/content/jquery-1.3.2.js']), ++ (dj_js, ['web/content/jquery-1.3.2.min.js']), ++ (dj_js, ['web/content/jsGrowl_jquery.js']), ++ (dj_js, ['web/content/jsGrowl.js']), ++ (dj_js, ['web/content/jsgrowl_close.png']), ++ (dj_js, ['web/content/jsgrowl_corners.png']), ++ (dj_js, ['web/content/jsgrowl_middle_hover.png']), ++ (dj_js, ['web/content/jsgrowl_corners_hover.png']), ++ (dj_js, ['web/content/jsgrowl_side_hover.png']), ++ (dj_js, ['web/content/jsGrowl.css']), + + # Anamon script +- (vw_aux, ['aux/anamon', 'aux/anamon.init']), ++ (vw_aux, ['aux/anamon', 'aux/anamon.init']), + + # Directories to hold cobbler triggers +- ("%s/add/distro/pre" % trigpath, []), +- ("%s/add/distro/post" % trigpath, []), +- ("%s/add/profile/pre" % trigpath, []), +- ("%s/add/profile/post" % trigpath, []), +- ("%s/add/system/pre" % trigpath, []), +- ("%s/add/system/post" % trigpath, []), +- ("%s/add/repo/pre" % trigpath, []), +- ("%s/add/repo/post" % trigpath, []), +- ("%s/delete/distro/pre" % trigpath, []), +- ("%s/delete/distro/post" % trigpath, []), +- ("%s/delete/profile/pre" % trigpath, []), ++ ("%s/add/distro/pre" % trigpath, []), ++ ("%s/add/distro/post" % trigpath, []), ++ ("%s/add/profile/pre" % trigpath, []), ++ ("%s/add/profile/post" % trigpath, []), ++ ("%s/add/system/pre" % trigpath, []), ++ ("%s/add/system/post" % trigpath, []), ++ ("%s/add/repo/pre" % trigpath, []), ++ ("%s/add/repo/post" % trigpath, []), ++ ("%s/delete/distro/pre" % trigpath, []), ++ ("%s/delete/distro/post" % trigpath, []), ++ ("%s/delete/profile/pre" % trigpath, []), + ("%s/delete/profile/post" % trigpath, []), +- ("%s/delete/system/pre" % trigpath, []), +- ("%s/delete/system/post" % trigpath, []), +- ("%s/delete/repo/pre" % trigpath, []), +- ("%s/delete/repo/post" % trigpath, []), +- ("%s/delete/repo/post" % trigpath, []), +- ("%s/install/pre" % trigpath, []), +- ("%s/install/post" % trigpath, []), +- ("%s/sync/pre" % trigpath, []), +- ("%s/sync/post" % trigpath, []), +- ("%s/change" % trigpath, []) ++ ("%s/delete/system/pre" % trigpath, []), ++ ("%s/delete/system/post" % trigpath, []), ++ ("%s/delete/repo/pre" % trigpath, []), ++ ("%s/delete/repo/post" % trigpath, []), ++ ("%s/delete/repo/post" % trigpath, []), ++ ("%s/install/pre" % trigpath, []), ++ ("%s/install/post" % trigpath, []), ++ ("%s/sync/pre" % trigpath, []), ++ ("%s/sync/post" % trigpath, []), ++ ("%s/change" % trigpath, []) + ], +- description = SHORT_DESC, +- long_description = LONG_DESC ++ description=SHORT_DESC, ++ long_description=LONG_DESC + ) + ++ ++def py3_setup(): ++ # Only koan is ready for Python 3 ++ setup( ++ name='koan', ++ version=VERSION, ++ description=SHORT_DESC, ++ long_description=LONG_DESC, ++ author='Jan Dobes', ++ author_email='jdobes@redhat.com', ++ url='http://www.github.com/spacewalkproject', ++ packages=['koan'], ++ license='GPLv2', ++ scripts=[ ++ "scripts/koan", ++ "scripts/cobbler-register" ++ ], ++ ) ++ ++if __name__ == "__main__": ++ if sys.version_info[0] == 3: ++ py3_setup() ++ else: ++ py2_setup() ++ ++ ++ ++ +-- +2.5.5 + diff --git a/SOURCES/buildiso-boot-options.patch b/SOURCES/buildiso-boot-options.patch new file mode 100644 index 0000000..4ceb66d --- /dev/null +++ b/SOURCES/buildiso-boot-options.patch @@ -0,0 +1,19 @@ +diff -rupN cobbler-2.0.11.old/cobbler/action_buildiso.py cobbler-2.0.11/cobbler/action_buildiso.py +--- cobbler-2.0.11.old/cobbler/action_buildiso.py 2015-06-11 18:48:59.000000000 +0200 ++++ cobbler-2.0.11/cobbler/action_buildiso.py.new 2015-06-17 13:33:13.392123548 +0200 +@@ -253,7 +253,14 @@ class BuildIso: + append_line = append_line + " gateway=%s" % data["gateway"] + + if not exclude_dns and data.has_key("name_servers") and data["name_servers"]: +- append_line = append_line + " dns=%s\n" % ",".join(data["name_servers"]) ++ version = dist.os_version ++ if dist.breed == "redhat" and ( ++ (version.startswith("rhel") and version >= "rhel7") or ++ (version.startswith("fedora") and version >= "fedora17") ++ ): ++ append_line = append_line + " nameserver=%s\n" % " nameserver=".join(data["name_servers"]) ++ else: ++ append_line = append_line + " dns=%s\n" % ",".join(data["name_servers"]) + + length=len(append_line) + if length > 254: diff --git a/SOURCES/buildiso-no-local-hdd.patch b/SOURCES/buildiso-no-local-hdd.patch new file mode 100644 index 0000000..533486e --- /dev/null +++ b/SOURCES/buildiso-no-local-hdd.patch @@ -0,0 +1,135 @@ +diff -rupN cobbler-2.0.7-orig/cobbler/action_buildiso.py cobbler-2.0.7/cobbler/action_buildiso.py +--- cobbler-2.0.7-orig/cobbler/action_buildiso.py 2015-09-07 08:32:49.197852179 -0400 ++++ cobbler-2.0.7/cobbler/action_buildiso.py 2015-09-07 08:33:42.301458113 -0400 +@@ -45,6 +45,9 @@ TIMEOUT 200 + TOTALTIMEOUT 6000 + ONTIMEOUT local + ++""" ++ ++LOCAL_HDD_HEADER = HEADER + """ + LABEL local + MENU LABEL (local) + MENU DEFAULT +@@ -86,7 +89,7 @@ class BuildIso: + return str(self.distctr) + + +- def generate_netboot_iso(self,imagesdir,isolinuxdir,profiles=None,systems=None,exclude_dns=None,force_server=None): ++ def generate_netboot_iso(self,imagesdir,isolinuxdir,profiles=None,systems=None,exclude_dns=None,force_server=None,no_local_hdd=None): + self.logger.info("copying kernels and initrds for profiles") + # copy all images in included profiles to images dir + for profile in self.api.profiles(): +@@ -131,7 +134,11 @@ class BuildIso: + self.logger.info("generating a isolinux.cfg") + isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg") + cfg = open(isolinuxcfg, "w+") +- cfg.write(HEADER) # fixme, use template ++ header_written = True ++ if not no_local_hdd: ++ cfg.write(LOCAL_HDD_HEADER) # fixme, use template ++ else: ++ header_written = False + + self.logger.info("generating profile list") + #sort the profiles +@@ -156,6 +163,10 @@ class BuildIso: + data["server"] = force_server + distname = self.make_shorter(dist.name) + ++ if not header_written: ++ cfg.write(HEADER.replace('local', profile.name)) ++ header_written = True ++ + cfg.write("\n") + cfg.write("LABEL %s\n" % profile.name) + cfg.write(" MENU LABEL %s\n" % profile.name) +@@ -197,7 +197,8 @@ class BuildIso: + if systems is not None: + self.logger.info("generating system list") + +- cfg.write("\nMENU SEPARATOR\n") ++ if header_written: ++ cfg.write("\nMENU SEPARATOR\n") + + #sort the systems + system_list = [system for system in self.systems] +@@ -222,6 +223,10 @@ class BuildIso: + data["server"] = force_server + distname = self.make_shorter(dist.name) + ++ if not header_written: ++ cfg.write(HEADER.replace('local', system.name)) ++ header_written = True ++ + cfg.write("\n") + cfg.write("LABEL %s\n" % system.name) + cfg.write(" MENU LABEL %s\n" % system.name) +@@ -295,6 +300,9 @@ class BuildIso: + + cfg.write(append_line) + ++ if not header_written: ++ cfg.write(HEADER) ++ + self.logger.info("done writing config") + cfg.write("\n") + cfg.write("MENU END\n") +@@ -375,7 +386,7 @@ class BuildIso: + return + + +- def run(self,iso=None,tempdir=None,profiles=None,systems=None,distro=None,standalone=None,source=None,exclude_dns=None,force_server=None): ++ def run(self,iso=None,tempdir=None,profiles=None,systems=None,distro=None,standalone=None,source=None,exclude_dns=None,force_server=None,no_local_hdd=None): + + # the distro option is for stand-alone builds only + if not standalone and distro is not None: +@@ -446,7 +457,7 @@ class BuildIso: + if standalone: + self.generate_standalone_iso(imagesdir,isolinuxdir,distro,source) + else: +- self.generate_netboot_iso(imagesdir,isolinuxdir,profiles,systems,exclude_dns,force_server) ++ self.generate_netboot_iso(imagesdir,isolinuxdir,profiles,systems,exclude_dns,force_server,no_local_hdd) + + # removed --quiet + cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso +diff -rupN cobbler-2.0.7-orig/cobbler/api.py cobbler-2.0.7/cobbler/api.py +--- cobbler-2.0.7-orig/cobbler/api.py 2015-09-07 08:32:49.197852179 -0400 ++++ cobbler-2.0.7/cobbler/api.py 2015-09-07 08:34:50.453735488 -0400 +@@ -748,10 +748,10 @@ class BootAPI: + + # ========================================================================== + +- def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None,distro=None,standalone=None,source=None, exclude_dns=None, logger=None, force_server=None): ++ def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None,distro=None,standalone=None,source=None, exclude_dns=None, logger=None, force_server=None, no_local_hdd=None): + builder = action_buildiso.BuildIso(self._config, logger=logger) + return builder.run( +- iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source, exclude_dns=exclude_dns, force_server=force_server ++ iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source, exclude_dns=exclude_dns, force_server=force_server,no_local_hdd=no_local_hdd + ) + + # ========================================================================== +diff -rupN cobbler-2.0.7-orig/cobbler/cli.py cobbler-2.0.7/cobbler/cli.py +--- cobbler-2.0.7-orig/cobbler/cli.py 2015-09-07 08:32:49.198852228 -0400 ++++ cobbler-2.0.7/cobbler/cli.py 2015-09-07 08:35:19.491150220 -0400 +@@ -323,6 +323,7 @@ class BootCLI: + self.parser.add_option("--source", dest="source", help="(OPTIONAL) used with --standalone to specify a source for the distribution files") + self.parser.add_option("--exclude-dns", dest="exclude_dns", action="store_true", help="(OPTIONAL) prevents addition of name server addresses to the kernel boot options") + self.parser.add_option("--force-server", dest="force_server", help="(OPTIONAL) when kickstarting get required files from the given server instead of the default (may be given as IP Address or FQDN of the server). Useful when kickstarting through a proxy.") ++ self.parser.add_option("--no-local-hdd", dest="no_local_hdd", action="store_true", help="(OPTIONAL) removes option to boot from local hdd, keeps only kickstart profiles in boot menu") + + (options, args) = self.parser.parse_args() + task_id = self.start_task("buildiso",options) +diff -rupN cobbler-2.0.7-orig/cobbler/remote.py cobbler-2.0.7/cobbler/remote.py +--- cobbler-2.0.7-orig/cobbler/remote.py 2015-09-07 08:32:49.198852228 -0400 ++++ cobbler-2.0.7/cobbler/remote.py 2015-09-07 08:35:49.765636882 -0400 +@@ -153,7 +153,8 @@ class CobblerXMLRPCInterface: + self.options.get("source",None), + self.options.get("exclude_dns",False), + self.logger, +- self.options.get("force_server",None) ++ self.options.get("force_server",None), ++ self.options.get("no_local_hdd",False) + ) + def on_done(self): + if self.options.get("iso","") == "/var/www/cobbler/pub/generated.iso": diff --git a/SOURCES/cobbler-bootproto-post-install.patch b/SOURCES/cobbler-bootproto-post-install.patch new file mode 100644 index 0000000..6543880 --- /dev/null +++ b/SOURCES/cobbler-bootproto-post-install.patch @@ -0,0 +1,17 @@ +--- ./snippets/post_install_network_config 2013-05-13 16:18:57.855827751 +0200 ++++ ./snippets/post_install_network_config 2013-05-13 16:19:20.707940757 +0200 +@@ -204,13 +204,8 @@ echo "IPV6_DEFAULTGW=$ipv6_default_gatew + #end if + #end if + #else +- #if $ip == "" +- ## this interface has no IPv4 address set +-echo "BOOTPROTO=none" >> $devfile +- #else +- ## this is a DHCP interface, much less work to do ++ ## this is a DHCP interface, much less work to do + echo "BOOTPROTO=dhcp" >> $devfile +- #end if + #end if + #if $mtu != "" + echo "MTU=$mtu" >> $devfile diff --git a/SOURCES/cobbler-buildiso.patch b/SOURCES/cobbler-buildiso.patch new file mode 100644 index 0000000..078470c --- /dev/null +++ b/SOURCES/cobbler-buildiso.patch @@ -0,0 +1,222 @@ +diff -rupN cobbler-2.0.7-orig/cobbler/action_buildiso.py cobbler-2.0.7-new/cobbler/action_buildiso.py +--- cobbler-2.0.7-orig/cobbler/action_buildiso.py 2013-05-22 15:00:28.213867934 -0400 ++++ cobbler-2.0.7-new/cobbler/action_buildiso.py 2013-07-11 15:35:59.637378396 -0400 +@@ -86,7 +86,7 @@ class BuildIso: + return str(self.distctr) + + +- def generate_netboot_iso(self,imagesdir,isolinuxdir,profiles=None,systems=None,exclude_dns=None): ++ def generate_netboot_iso(self,imagesdir,isolinuxdir,profiles=None,systems=None,exclude_dns=None,force_server=None): + self.logger.info("copying kernels and initrds for profiles") + # copy all images in included profiles to images dir + for profile in self.api.profiles(): +@@ -152,6 +152,8 @@ class BuildIso: + if dist.name.find("-xen") != -1: + continue + data = utils.blender(self.api, True, profile) ++ if force_server: ++ data["server"] = force_server + distname = self.make_shorter(dist.name) + + cfg.write("\n") +@@ -164,6 +166,12 @@ class BuildIso: + data["server"], + profile.name + ) ++ else: ++ if force_server: ++ # replace configured hostname with the forced one ++ data["kickstart"] = re.sub(r'://.*?/', ++ '://' + data["server"] + '/', ++ data["kickstart"]) + + append_line = " append initrd=%s.img" % distname + append_line = append_line + " ks=%s " % data["kickstart"] +@@ -199,6 +207,8 @@ class BuildIso: + if dist.name.find("-xen") != -1: + continue + data = utils.blender(self.api, True, system) ++ if force_server: ++ data["server"] = force_server + distname = self.make_shorter(dist.name) + + cfg.write("\n") +@@ -211,6 +221,12 @@ class BuildIso: + data["server"], + system.name + ) ++ else: ++ if force_server: ++ # replace configured hostname with the forced one ++ data["kickstart"] = re.sub(r'://.*?/', ++ '://' + data["server"] + '/', ++ data["kickstart"]) + + append_line = " append initrd=%s.img" % distname + append_line = append_line + " ks=%s" % data["kickstart"] +@@ -352,7 +368,7 @@ class BuildIso: + return + + +- def run(self,iso=None,tempdir=None,profiles=None,systems=None,distro=None,standalone=None,source=None,exclude_dns=None): ++ def run(self,iso=None,tempdir=None,profiles=None,systems=None,distro=None,standalone=None,source=None,exclude_dns=None,force_server=None): + + # the distro option is for stand-alone builds only + if not standalone and distro is not None: +@@ -372,7 +388,7 @@ class BuildIso: + iso = "kickstart.iso" + + if tempdir is None: +- tempdir = os.path.join(os.getcwd(), "buildiso") ++ tempdir = self.config.settings().buildisodir + else: + if not os.path.isdir(tempdir): + utils.die(self.logger,"The --tempdir specified is not a directory") +@@ -415,14 +431,15 @@ class BuildIso: + files = [ isolinuxbin, menu, chain ] + for f in files: + if not os.path.exists(f): +- utils.die(self.logger,"Required file not found: %s" % f) ++ self.logger.error("Required file not found: %s. Try 'yum install cobbler-loaders'." % f) ++ return False + else: + utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f)), self.api) + + if standalone: + self.generate_standalone_iso(imagesdir,isolinuxdir,distro,source) + else: +- self.generate_netboot_iso(imagesdir,isolinuxdir,profiles,systems,exclude_dns) ++ self.generate_netboot_iso(imagesdir,isolinuxdir,profiles,systems,exclude_dns,force_server) + + # removed --quiet + cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso +diff -rupN cobbler-2.0.7-orig/cobbler/action_check.py cobbler-2.0.7-new/cobbler/action_check.py +--- cobbler-2.0.7-orig/cobbler/action_check.py 2013-05-22 15:00:28.210867913 -0400 ++++ cobbler-2.0.7-new/cobbler/action_check.py 2013-07-11 14:41:26.976177891 -0400 +@@ -325,7 +325,7 @@ class BootCheck: + missing = True + + if missing: +- status.append("some network boot-loaders are missing from /var/lib/cobbler/loaders, you may run 'cobbler get-loaders' to download them, or, if you only want to handle x86/x86_64 netbooting, you may ensure that you have installed a *recent* version of the syslinux package installed and can ignore this message entirely. Files in this directory, should you want to support all architectures, should include pxelinux.0, menu.c32, elilo.efi, and yaboot. The 'cobbler get-loaders' command is the easiest way to resolve these requirements.") ++ status.append("some network boot-loaders are missing from /var/lib/cobbler/loaders, you may run 'yum install cobbler-loaders' to download them, or, if you only want to handle x86/x86_64 netbooting, you may ensure that you have installed a *recent* version of the syslinux package installed and can ignore this message entirely. Files in this directory, should you want to support all architectures, should include pxelinux.0, menu.c32, elilo.efi, and yaboot. The 'yum install cobbler-loaders' command is the easiest way to resolve these requirements.") + + def check_tftpd_bin(self,status): + """ +diff -rupN cobbler-2.0.7-orig/cobbler/action_dlcontent.py cobbler-2.0.7-new/cobbler/action_dlcontent.py +--- cobbler-2.0.7-orig/cobbler/action_dlcontent.py 2013-05-22 15:00:28.209867906 -0400 ++++ cobbler-2.0.7-new/cobbler/action_dlcontent.py 2013-07-11 14:41:26.976177891 -0400 +@@ -39,36 +39,11 @@ class ContentDownloader: + + def run(self,force=False): + """ +- Download bootloader content for all of the latest bootloaders, since the user +- has chosen to not supply their own. You may ask "why not get this from yum", though +- Fedora has no IA64 repo, for instance, and we also want this to be able to work on Debian and +- further do not want folks to have to install a cross compiler. For those that don't like this approach +- they can still source their cross-arch bootloader content manually. ++ This action used to download the bootloaders from fedorapeople.org, ++ however these files are now available from yum in the cobbler-loaders ++ package so you should use that instead. + """ + +- content_server = "http://mdehaan.fedorapeople.org/loaders" +- dest = "/var/lib/cobbler/loaders" +- +- files = ( +- ( "%s/README" % content_server, "%s/README" % dest ), +- ( "%s/COPYING.elilo" % content_server, "%s/COPYING.elilo" % dest ), +- ( "%s/COPYING.yaboot" % content_server, "%s/COPYING.yaboot" % dest), +- ( "%s/COPYING.syslinux" % content_server, "%s/COPYING.syslinux" % dest), +- ( "%s/elilo-3.8-ia64.efi" % content_server, "%s/elilo-ia64.efi" % dest ), +- ( "%s/yaboot-1.3.14-12" % content_server, "%s/yaboot" % dest), +- ( "%s/pxelinux.0-3.61" % content_server, "%s/pxelinux.0" % dest), +- ( "%s/menu.c32-3.61" % content_server, "%s/menu.c32" % dest), +- ) +- +- self.logger.info("downloading content required to netboot all arches") +- for f in files: +- src = f[0] +- dst = f[1] +- if os.path.exists(dst) and not force: +- self.logger.info("path %s already exists, not overwriting existing content, use --force if you wish to update" % dst) +- continue +- self.logger.info("downloading %s to %s" % (src,dst)) +- urlgrabber.urlgrab(src,dst) +- +- return True ++ self.logger.info("The 'cobbler get-loaders' command has been obsoleted with 'yum install cobbler-loaders' in this version of cobbler. Please use 'yum install cobbler-loaders' instead.") ++ return False + +diff -rupN cobbler-2.0.7-orig/cobbler/api.py cobbler-2.0.7-new/cobbler/api.py +--- cobbler-2.0.7-orig/cobbler/api.py 2013-05-22 15:00:28.214867942 -0400 ++++ cobbler-2.0.7-new/cobbler/api.py 2013-07-11 14:41:26.977177897 -0400 +@@ -750,10 +750,10 @@ class BootAPI: + + # ========================================================================== + +- def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None,distro=None,standalone=None,source=None, exclude_dns=None, logger=None): ++ def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None,distro=None,standalone=None,source=None, exclude_dns=None, logger=None, force_server=None): + builder = action_buildiso.BuildIso(self._config, logger=logger) + return builder.run( +- iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source, exclude_dns=exclude_dns ++ iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source, exclude_dns=exclude_dns, force_server=force_server + ) + + # ========================================================================== +diff -rupN cobbler-2.0.7-orig/cobbler/cli.py cobbler-2.0.7-new/cobbler/cli.py +--- cobbler-2.0.7-orig/cobbler/cli.py 2013-05-22 15:00:28.212867927 -0400 ++++ cobbler-2.0.7-new/cobbler/cli.py 2013-07-12 15:18:14.024502192 -0400 +@@ -29,6 +29,7 @@ import time + import os + + import utils ++import config + import module_loader + import item_distro + import item_profile +@@ -311,16 +312,17 @@ class BootCLI: + task_id = -1 # if assigned, we must tail the logfile + + if action_name == "buildiso": +- +- defaultiso = os.path.join(os.getcwd(), "generated.iso") +- self.parser.add_option("--iso", dest="iso", default=defaultiso, help="(OPTIONAL) output ISO to this path") ++ buildisodir = config.Config(self).settings().buildisodir ++ defaultiso = os.path.join(buildisodir, "generated.iso") ++ self.parser.add_option("--iso", dest="iso", default=defaultiso, help="(OPTIONAL) output ISO to this path (must be writable by cobblerd)") + self.parser.add_option("--profiles", dest="profiles", help="(OPTIONAL) use these profiles only") + self.parser.add_option("--systems", dest="systems", help="(OPTIONAL) use these systems only") +- self.parser.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) working directory") ++ self.parser.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) temporary working directory (must be writable by cobblerd)") + self.parser.add_option("--distro", dest="distro", help="(OPTIONAL) used with --standalone to create a distro-based ISO including all associated profiles/systems") + self.parser.add_option("--standalone", dest="standalone", action="store_true", help="(OPTIONAL) creates a standalone ISO with all required distro files on it") + self.parser.add_option("--source", dest="source", help="(OPTIONAL) used with --standalone to specify a source for the distribution files") + self.parser.add_option("--exclude-dns", dest="exclude_dns", action="store_true", help="(OPTIONAL) prevents addition of name server addresses to the kernel boot options") ++ self.parser.add_option("--force-server", dest="force_server", help="(OPTIONAL) when kickstarting get required files from the given server instead of the default (may be given as IP Address or FQDN of the server). Useful when kickstarting through a proxy.") + + (options, args) = self.parser.parse_args() + task_id = self.start_task("buildiso",options) +diff -rupN cobbler-2.0.7-orig/cobbler/remote.py cobbler-2.0.7-new/cobbler/remote.py +--- cobbler-2.0.7-orig/cobbler/remote.py 2013-05-22 15:00:28.212867927 -0400 ++++ cobbler-2.0.7-new/cobbler/remote.py 2013-07-11 14:41:26.978177903 -0400 +@@ -152,7 +152,8 @@ class CobblerXMLRPCInterface: + self.options.get("standalone",False), + self.options.get("source",None), + self.options.get("exclude_dns",False), +- self.logger ++ self.logger, ++ self.options.get("force_server",None) + ) + def on_done(self): + if self.options.get("iso","") == "/var/www/cobbler/pub/generated.iso": +diff -rupN cobbler-2.0.7-orig/cobbler/settings.py cobbler-2.0.7-new/cobbler/settings.py +--- cobbler-2.0.7-orig/cobbler/settings.py 2013-05-22 15:00:28.212867927 -0400 ++++ cobbler-2.0.7-new/cobbler/settings.py 2013-07-11 16:31:33.375946740 -0400 +@@ -96,6 +96,7 @@ DEFAULTS = { + "template_remote_kickstarts" : 0, + "virt_auto_boot" : 0, + "webdir" : "/var/www/cobbler", ++ "buildisodir" : "/tmp/cobbler/buildiso", + "xmlrpc_port" : 25151, + "yum_post_install_mirror" : 1, + "createrepo_flags" : "-c cache -s sha", diff --git a/SOURCES/cobbler-bz-253274.patch b/SOURCES/cobbler-bz-253274.patch new file mode 100644 index 0000000..c39d0d4 --- /dev/null +++ b/SOURCES/cobbler-bz-253274.patch @@ -0,0 +1,92 @@ +From 51f0baf6c7e6ef042ce6fa7543b37b0d2d723ae0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Miroslav=20Such=C3=BD?= +Date: Fri, 23 Sep 2011 13:56:07 +0200 +Subject: [PATCH] if hostname is not resolvable do not fail and use that + hostname + +in commit 588756aa7aefc122310847d007becf3112647944 cobbler start using IP address in kickstart path due s390x +but in BZ 253274 we have example where hostname is not resolvable from the location where cobbler run. +But it is resolvable from location where client runs. +So if cobbler is unable to resolve hostname, do not fail and revert to behaviour prior 588756aa7aefc122310847d007becf3112647944. + +Addressing: +Fri Sep 23 04:51:12 2011 - INFO | Exception occured: +Fri Sep 23 04:51:12 2011 - INFO | Exception value: [Errno -2] Name or service +not known +Fri Sep 23 04:51:12 2011 - INFO | Exception Info: + File "/usr/lib/python2.6/site-packages/cobbler/remote.py", line 1759, in +_dispatch + return method_handle(*params) + File "/usr/lib/python2.6/site-packages/cobbler/remote.py", line 874, in +save_system + return self.save_item("system",object_id,token,editmode=editmode) + File "/usr/lib/python2.6/site-packages/cobbler/remote.py", line 866, in +save_item + rc = self.api.add_item(what,obj) + File "/usr/lib/python2.6/site-packages/cobbler/api.py", line 394, in +add_item + return +self.get_items(what).add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save,logger=logger) + File "/usr/lib/python2.6/site-packages/cobbler/collection.py", line 284, in +add + self.lite_sync.add_single_system(ref.name) + File "/usr/lib/python2.6/site-packages/cobbler/action_litesync.py", line +142, in add_single_system + self.sync.pxegen.write_all_system_files(system) + File "/usr/lib/python2.6/site-packages/cobbler/pxegen.py", line 295, in +write_all_system_files + self.write_pxe_file(f2,system,profile,distro,working_arch) + File "/usr/lib/python2.6/site-packages/cobbler/pxegen.py", line 566, in +write_pxe_file + ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] +--- + cobbler/pxegen.py | 19 ++++++++++++++----- + 1 files changed, 14 insertions(+), 5 deletions(-) + +diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py +index 889f363..32c8f01 100644 +--- a/cobbler/pxegen.py ++++ b/cobbler/pxegen.py +@@ -209,8 +209,11 @@ class PXEGen: + blended = utils.blender(self.api, True, system) + self.templar.render(template_cf, blended, cf) + # FIXME: profiles also need this data! +- # FIXME: the _conf and _parm files are limited to 80 characters in length +- ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] ++ # FIXME: the _conf and _parm files are limited to 80 characters in length ++ try: ++ ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] ++ except socket.gaierror: ++ ipaddress = blended["http_server"] + kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (ipaddress, system.name) + # gather default kernel_options and default kernel_options_s390x + kopts = blended.get("kernel_options","") +@@ -321,8 +324,11 @@ class PXEGen: + blended = utils.blender(self.api, True, profile) + self.templar.render(template_cf, blended, cf) + # FIXME: profiles also need this data! +- # FIXME: the _conf and _parm files are limited to 80 characters in length +- ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] ++ # FIXME: the _conf and _parm files are limited to 80 characters in length ++ try: ++ ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] ++ except socket.gaierror: ++ ipaddress = blended["http_server"] + kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (ipaddress, profile.name) + # gather default kernel_options and default kernel_options_s390x + kopts = blended.get("kernel_options","") +@@ -666,7 +672,10 @@ class PXEGen: + + # FIXME: need to make shorter rewrite rules for these URLs + +- ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] ++ try: ++ ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0] ++ except socket.gaierror: ++ ipaddress = blended["http_server"] + if system is not None and kickstart_path.startswith("/"): + kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (ipaddress, system.name) + elif kickstart_path.startswith("/"): +-- +1.7.6.2 + diff --git a/SOURCES/cobbler-bz1052857.patch b/SOURCES/cobbler-bz1052857.patch new file mode 100644 index 0000000..b9706a0 --- /dev/null +++ b/SOURCES/cobbler-bz1052857.patch @@ -0,0 +1,53 @@ +diff -rupN cobbler-2.0.7.old/cobbler/api.py cobbler-2.0.7/cobbler/api.py +--- cobbler-2.0.7.old/cobbler/api.py 2015-05-06 14:47:57.860206615 -0400 ++++ cobbler-2.0.7/cobbler/api.py 2015-05-06 15:09:40.455772584 -0400 +@@ -179,11 +179,9 @@ class BootAPI: + API instance, regardless of the serializer type. + """ + if not os.path.exists("/var/lib/cobbler/.mtime"): +- old = os.umask(0x777) +- fd = open("/var/lib/cobbler/.mtime","w") +- fd.write("0") +- fd.close() +- os.umask(old) ++ fd = os.open("/var/lib/cobbler/.mtime", os.O_CREAT|os.O_RDWR, 0200) ++ os.write(fd, "0") ++ os.close(fd) + return 0 + fd = open("/var/lib/cobbler/.mtime") + data = fd.read().strip() +diff -rupN cobbler-2.0.7.old/cobbler/cobblerd.py cobbler-2.0.7/cobbler/cobblerd.py +--- cobbler-2.0.7.old/cobbler/cobblerd.py 2015-05-06 14:47:57.856206586 -0400 ++++ cobbler-2.0.7/cobbler/cobblerd.py 2015-05-06 15:23:17.605662935 -0400 +@@ -58,10 +58,9 @@ def regen_ss_file(): + fd = open("/dev/urandom") + data = fd.read(512) + fd.close() +- fd = open("/var/lib/cobbler/web.ss","w+") +- fd.write(binascii.hexlify(data)) +- fd.close() +- utils.os_system("chmod 700 /var/lib/cobbler/web.ss") ++ fd = os.open("/var/lib/cobbler/web.ss", os.O_CREAT|os.O_RDWR, 0600) ++ os.write(fd, binascii.hexlify(data)) ++ os.close(fd) + utils.os_system("chown apache /var/lib/cobbler/web.ss") + return 1 + +diff -rupN cobbler-2.0.7.old/cobbler/serializer.py cobbler-2.0.7/cobbler/serializer.py +--- cobbler-2.0.7.old/cobbler/serializer.py 2015-05-06 14:47:57.858206601 -0400 ++++ cobbler-2.0.7/cobbler/serializer.py 2015-05-06 15:24:31.318192455 -0400 +@@ -64,11 +64,9 @@ def __release_lock(with_changes=False): + # this file is used to know when the last config change + # was made -- allowing the API to work more smoothly without + # a lot of unneccessary reloads. +- old = os.umask(0x777) +- fd = open("/var/lib/cobbler/.mtime","w") +- fd.write("%f" % time.time()) +- fd.close() +- os.umask(old) ++ fd = os.open("/var/lib/cobbler/.mtime", os.O_CREAT|os.O_RDWR, 0200) ++ os.write(fd, "%f" % time.time()) ++ os.close(fd) + if LOCK_ENABLED: + LOCK_HANDLE = open("/var/lib/cobbler/lock","r") + fcntl.flock(LOCK_HANDLE.fileno(), fcntl.LOCK_UN) diff --git a/SOURCES/cobbler-catch-cheetah-exception.patch b/SOURCES/cobbler-catch-cheetah-exception.patch new file mode 100644 index 0000000..8014529 --- /dev/null +++ b/SOURCES/cobbler-catch-cheetah-exception.patch @@ -0,0 +1,68 @@ +diff -ur cobbler.orig/scripts/services.py cobbler/scripts/services.py +--- cobbler.orig/scripts/services.py 2012-11-09 17:31:33.000000000 +0100 ++++ cobbler/scripts/services.py 2012-11-09 17:31:51.000000000 +0100 +@@ -86,7 +86,12 @@ + mode = form.get('op','index') + + func = getattr( cw, mode ) +- content = func( **form ) ++ try: ++ content = func( **form ) ++ except xmlrpclib.Fault, (err): ++ req.status = apache.HTTP_INTERNAL_SERVER_ERROR ++ req.write(err.faultString) ++ return apache.OK + + # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form))) + req.content_type = "text/plain;charset=utf-8" +diff -ur cobbler.orig/scripts/services.wsgi cobbler/scripts/services.wsgi +--- cobbler.orig/scripts/services.wsgi 2012-11-09 17:31:33.000000000 +0100 ++++ cobbler/scripts/services.wsgi 2012-11-09 17:31:58.000000000 +0100 +@@ -21,6 +21,7 @@ + """ + import yaml + import os ++import xmlrpclib + + from cobbler.services import CobblerSvc + import cobbler.utils as utils +@@ -75,22 +76,26 @@ + + # Execute corresponding operation on the CobblerSvc object: + func = getattr( cw, mode ) +- content = func( **form ) ++ try: ++ content = func( **form ) + +- content = unicode(content).encode('utf-8') +- status = '200 OK' ++ content = unicode(content).encode('utf-8') ++ status = '200 OK' ++ ++ if content.find("# *** ERROR ***") != -1: ++ status = '500 SERVER ERROR' ++ print("possible cheetah template error") + +- if content.find("# *** ERROR ***") != -1: ++ # TODO: Not sure these strings are the right ones to look for... ++ elif content.find("# profile not found") != -1 or \ ++ content.find("# system not found") != -1 or \ ++ content.find("# object not found") != -1: ++ print("content not found: %s" % my_uri) ++ status = "404 NOT FOUND" ++ except xmlrpclib.Fault, (err): + status = '500 SERVER ERROR' +- print("possible cheetah template error") +- +- # TODO: Not sure these strings are the right ones to look for... +- elif content.find("# profile not found") != -1 or \ +- content.find("# system not found") != -1 or \ +- content.find("# object not found") != -1: +- print("content not found: %s" % my_uri) +- status = "404 NOT FOUND" +- ++ content = err.faultString ++ + # req.content_type = "text/plain;charset=utf-8" + response_headers = [('Content-type', 'text/plain;charset=utf-8'), + ('Content-Length', str(len(content)))] diff --git a/SOURCES/cobbler-concurrency.patch b/SOURCES/cobbler-concurrency.patch new file mode 100644 index 0000000..c72c9f3 --- /dev/null +++ b/SOURCES/cobbler-concurrency.patch @@ -0,0 +1,141 @@ +diff -rupN cobbler-2.0.7-orig/cobbler/collection_distros.py cobbler-2.0.7/cobbler/collection_distros.py +--- cobbler-2.0.7-orig/cobbler/collection_distros.py 2013-10-21 14:11:29.327459462 -0400 ++++ cobbler-2.0.7/cobbler/collection_distros.py 2013-10-22 08:22:48.727270516 -0400 +@@ -68,7 +68,11 @@ class Distros(collection.Collection): + if with_sync: + lite_sync = action_litesync.BootLiteSync(self.config, logger=logger) + lite_sync.remove_single_distro(name) +- del self.listing[name] ++ self.lock.acquire() ++ try: ++ del self.listing[name] ++ finally: ++ self.lock.release() + + self.config.serialize_delete(self, obj) + +diff -rupN cobbler-2.0.7-orig/cobbler/collection_images.py cobbler-2.0.7/cobbler/collection_images.py +--- cobbler-2.0.7-orig/cobbler/collection_images.py 2013-10-21 14:11:29.327459462 -0400 ++++ cobbler-2.0.7/cobbler/collection_images.py 2013-10-22 08:23:16.008462783 -0400 +@@ -65,7 +65,11 @@ class Images(collection.Collection): + lite_sync = action_litesync.BootLiteSync(self.config, logger=logger) + lite_sync.remove_single_image(name) + +- del self.listing[name] ++ self.lock.acquire() ++ try: ++ del self.listing[name] ++ finally: ++ self.lock.release() + self.config.serialize_delete(self, obj) + + if with_delete: +diff -rupN cobbler-2.0.7-orig/cobbler/collection_profiles.py cobbler-2.0.7/cobbler/collection_profiles.py +--- cobbler-2.0.7-orig/cobbler/collection_profiles.py 2013-10-21 14:11:29.326459455 -0400 ++++ cobbler-2.0.7/cobbler/collection_profiles.py 2013-10-22 08:35:28.586626217 -0400 +@@ -68,7 +68,11 @@ class Profiles(collection.Collection): + if with_delete: + if with_triggers: + utils.run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/profile/pre/*", [], logger) +- del self.listing[name] ++ self.lock.acquire() ++ try: ++ del self.listing[name] ++ finally: ++ self.lock.release() + self.config.serialize_delete(self, obj) + if with_delete: + if with_triggers: +diff -rupN cobbler-2.0.7-orig/cobbler/collection.py cobbler-2.0.7/cobbler/collection.py +--- cobbler-2.0.7-orig/cobbler/collection.py 2013-10-21 14:11:29.331459490 -0400 ++++ cobbler-2.0.7/cobbler/collection.py 2013-10-22 08:32:46.137481152 -0400 +@@ -26,6 +26,7 @@ import utils + import glob + import time + import random ++from threading import Lock + + import action_litesync + import item_system +@@ -45,6 +46,7 @@ class Collection: + self.clear() + self.api = self.config.api + self.lite_sync = None ++ self.lock = Lock() + + def factory_produce(self,config,seed_data): + """ +@@ -89,9 +91,13 @@ class Collection: + if len(kargs) == 1 and kargs.has_key("name") and not return_list: + return self.listing.get(kargs["name"].lower(), None) + +- for (name, obj) in self.listing.iteritems(): +- if obj.find_match(kargs, no_errors=no_errors): +- matches.append(obj) ++ self.lock.acquire() ++ try: ++ for (name, obj) in self.listing.iteritems(): ++ if obj.find_match(kargs, no_errors=no_errors): ++ matches.append(obj) ++ finally: ++ self.lock.release() + + if not return_list: + if len(matches) == 0: +@@ -266,14 +272,22 @@ class Collection: + + if not save: + # don't need to run triggers, so add it already ... +- self.listing[ref.name.lower()] = ref ++ self.lock.acquire() ++ try: ++ self.listing[ref.name.lower()] = ref ++ finally: ++ self.lock.release() + + # perform filesystem operations + if save: + # failure of a pre trigger will prevent the object from being added + if with_triggers: + utils.run_triggers(self.api, ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type(), [], logger) +- self.listing[ref.name.lower()] = ref ++ self.lock.acquire() ++ try: ++ self.listing[ref.name.lower()] = ref ++ finally: ++ self.lock.release() + + # save just this item if possible, if not, save + # the whole collection +diff -rupN cobbler-2.0.7-orig/cobbler/collection_repos.py cobbler-2.0.7/cobbler/collection_repos.py +--- cobbler-2.0.7-orig/cobbler/collection_repos.py 2013-10-21 14:11:29.324459441 -0400 ++++ cobbler-2.0.7/cobbler/collection_repos.py 2013-10-22 08:24:23.448938085 -0400 +@@ -58,7 +58,11 @@ class Repos(collection.Collection): + if with_triggers: + utils.run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/repo/pre/*", [], logger) + +- del self.listing[name] ++ self.lock.acquire() ++ try: ++ del self.listing[name] ++ finally: ++ self.lock.release() + self.config.serialize_delete(self, obj) + + if with_delete: +diff -rupN cobbler-2.0.7-orig/cobbler/collection_systems.py cobbler-2.0.7/cobbler/collection_systems.py +--- cobbler-2.0.7-orig/cobbler/collection_systems.py 2013-10-21 14:11:29.324459441 -0400 ++++ cobbler-2.0.7/cobbler/collection_systems.py 2013-10-22 08:24:36.502030081 -0400 +@@ -56,7 +56,11 @@ class Systems(collection.Collection): + if with_sync: + lite_sync = action_litesync.BootLiteSync(self.config, logger=logger) + lite_sync.remove_single_system(name) +- del self.listing[name] ++ self.lock.acquire() ++ try: ++ del self.listing[name] ++ finally: ++ self.lock.release() + self.config.serialize_delete(self, obj) + if with_delete: + if with_triggers: diff --git a/SOURCES/cobbler-daemon.patch b/SOURCES/cobbler-daemon.patch new file mode 100644 index 0000000..15399d8 --- /dev/null +++ b/SOURCES/cobbler-daemon.patch @@ -0,0 +1,17 @@ +--- scripts/cobblerd.orig 2013-06-18 14:12:00.328726666 +0200 ++++ scripts/cobblerd 2013-06-18 14:12:19.784627599 +0200 +@@ -50,10 +50,10 @@ + print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + +- #dev_null = file('/dev/null','rw') +- #os.dup2(dev_null.fileno(), sys.stdin.fileno()) +- #os.dup2(dev_null.fileno(), sys.stdout.fileno()) +- #os.dup2(dev_null.fileno(), sys.stderr.fileno()) ++ dev_null = file('/dev/null','rw') ++ os.dup2(dev_null.fileno(), sys.stdin.fileno()) ++ os.dup2(dev_null.fileno(), sys.stdout.fileno()) ++ os.dup2(dev_null.fileno(), sys.stderr.fileno()) + + def main(): + op = optparse.OptionParser() diff --git a/SOURCES/cobbler-disable-check-selinux-bz706857.patch b/SOURCES/cobbler-disable-check-selinux-bz706857.patch new file mode 100644 index 0000000..a61bb6b --- /dev/null +++ b/SOURCES/cobbler-disable-check-selinux-bz706857.patch @@ -0,0 +1,12 @@ +diff -up cobbler/action_check.py.orig cobbler-2.0.7/cobbler/action_check.py +--- cobbler/action_check.py 2010-07-28 17:48:48.000000000 +0200 ++++ cobbler/action_check.py 2011-05-25 11:26:10.000000000 +0200 +@@ -51,7 +51,7 @@ class BootCheck: + status = [] + self.checked_dist = utils.check_dist() + self.check_name(status) +- self.check_selinux(status) ++ # self.check_selinux(status) + if self.settings.manage_dhcp: + mode = self.config.api.get_sync().dhcp.what() + if mode == "isc": diff --git a/SOURCES/cobbler-disable-hardlinks-bz568801.patch b/SOURCES/cobbler-disable-hardlinks-bz568801.patch new file mode 100644 index 0000000..eb36d22 --- /dev/null +++ b/SOURCES/cobbler-disable-hardlinks-bz568801.patch @@ -0,0 +1,26 @@ +--- cobbler/utils.py 2010-10-07 20:12:03.000000000 +0200 ++++ cobbler/utils.py 2011-05-25 15:53:03.000000000 +0200 +@@ -996,21 +996,8 @@ + return True + + def is_safe_to_hardlink(src,dst,api): +- (dev1, path1) = get_file_device_path(src) +- (dev2, path2) = get_file_device_path(dst) +- if dev1 != dev2: +- return False +- if dev1.find(":") != -1: +- # is remoted +- return False +- # note: this is very cobbler implementation specific! +- if not api.is_selinux_enabled(): +- return True +- if _re_initrd.match(os.path.basename(path1)): +- return True +- if _re_kernel.match(os.path.basename(path1)): +- return True +- # we're dealing with SELinux and files that are not safe to chcon ++ # 568801: hardlinks ruin SELinux contexts because multiple ++ # paths match, avoid hardlinks + return False + + def hashfile(fn, lcache=None, logger=None): diff --git a/SOURCES/cobbler-findks.patch b/SOURCES/cobbler-findks.patch new file mode 100644 index 0000000..6827c8e --- /dev/null +++ b/SOURCES/cobbler-findks.patch @@ -0,0 +1,76 @@ +diff -rupN cobbler-2.0.7.old/cobbler/services.py cobbler-2.0.7/cobbler/services.py +--- cobbler-2.0.7.old/cobbler/services.py 2014-09-11 11:51:46.494164060 -0400 ++++ cobbler-2.0.7/cobbler/services.py 2014-09-11 11:52:33.163499576 -0400 +@@ -217,25 +217,18 @@ class CobblerSvc(object): + def findks(self,system=None,profile=None,**rest): + self.__xmlrpc_setup() + +- serverseg = self.server.replace("http://","") +- serverseg = self.server.replace("/cobbler_api","") +- + name = "?" +- type = "system" + if system is not None: +- url = "%s/cblr/svc/op/ks/system/%s" % (serverseg, name) ++ url = "/cblr/svc/op/ks/system/%s" % (system.name) + elif profile is not None: +- url = "%s/cblr/svc/op/ks/profile/%s" % (serverseg, name) ++ url = "/cblr/svc/op/ks/profile/%s" % (profile.name) + else: + name = self.autodetect(**rest) + if name.startswith("FAILED"): + return "# autodetection %s" % name +- url = "%s/cblr/svc/op/ks/system/%s" % (serverseg, name) ++ url = "/cblr/svc/op/ks/system/%s" % (name) + +- try: +- return urlgrabber.urlread(url) +- except: +- return "# kickstart retrieval failed (%s)" % url ++ return url + "# redirect" + + def puppet(self,hostname=None,**rest): + self.__xmlrpc_setup() +diff -rupN cobbler-2.0.7.old/scripts/services.wsgi cobbler-2.0.7/scripts/services.wsgi +--- cobbler-2.0.7.old/scripts/services.wsgi 2014-09-11 11:51:46.500164103 -0400 ++++ cobbler-2.0.7/scripts/services.wsgi 2014-09-11 11:52:33.163499576 -0400 +@@ -56,7 +56,11 @@ def application(environ, start_response) + # This MAC header is set by anaconda during a kickstart booted with the + # kssendmac kernel option. The field will appear here as something + # like: eth0 XX:XX:XX:XX:XX:XX +- form["REMOTE_MAC"] = form.get("HTTP_X_RHN_PROVISIONING_MAC_0", None) ++ form["REMOTE_MAC"] = environ.get("HTTP_X_RHN_PROVISIONING_MAC_0", None) ++ ++ # REMOTE_ADDR isn't a required wsgi attribute so it may be naive to assume ++ # it's always present in this context. ++ form["REMOTE_ADDR"] = environ.get("REMOTE_ADDR", None) + + # Save client remote address for ks/triggers + form["REMOTE_ADDR"] = environ['REMOTE_ADDR'] +@@ -80,6 +84,7 @@ def application(environ, start_response) + + # Execute corresponding operation on the CobblerSvc object: + func = getattr( cw, mode ) ++ response_headers = [('Content-type', 'text/plain;charset=utf-8')] + try: + content = func( **form ) + +@@ -96,13 +101,16 @@ def application(environ, start_response) + content.find("# object not found") != -1: + print("content not found: %s" % my_uri) + status = "404 NOT FOUND" ++ elif content.find("# redirect") != -1: ++ status = "302 Found" ++ response_headers.append(('Location', content[:content.find("# redirect")])) ++ content = "" + except xmlrpclib.Fault, (err): + status = '500 SERVER ERROR' + content = err.faultString + + # req.content_type = "text/plain;charset=utf-8" +- response_headers = [('Content-type', 'text/plain;charset=utf-8'), +- ('Content-Length', str(len(content)))] ++ response_headers.append(('Content-Length', str(len(content)))) + start_response(status, response_headers) + + return [content] diff --git a/SOURCES/cobbler-ipv6-snippet.patch b/SOURCES/cobbler-ipv6-snippet.patch new file mode 100644 index 0000000..1f6fcee --- /dev/null +++ b/SOURCES/cobbler-ipv6-snippet.patch @@ -0,0 +1,392 @@ +--- ./cobbler/field_info.py 2010-07-28 17:48:48.000000000 +0200 ++++ ./cobbler/field_info.py 2012-01-26 13:43:04.000000000 +0100 +@@ -58,6 +58,7 @@ USES_CHECKBOX = [ + "*netboot_enabled", + "netboot_enabled", + "*static", ++ "ipv6_autoconfiguration", + "keep_updated", + "mirror_locally", + "virt_auto_boot" +@@ -101,6 +102,7 @@ BLOCK_MAPPINGS = { + "broadcast" : "Networking", # .. + "reserved" : "Networking", # .. + "*mac_address" : "Networking", ++ "*mtu" : "Networking", + "*ip_address" : "Networking", + "*dhcp_tag" : "Networking", + "*static" : "Networking", +@@ -110,17 +112,24 @@ BLOCK_MAPPINGS = { + "*dns_name" : "Networking", + "*static_routes" : "Networking", + "*subnet" : "Networking", +- "hostname" : "Networking (Global)", +- "gateway" : "Networking (Global)", +- "name_servers" : "Networking (Global)", +- "name_servers_search" : "Networking (Global)", +- "repos" : "General", +- "dhcp_tag" : "Advanced", +- "mgmt_classes" : "Management", +- "template_files" : "Management", +- "network_widget_a" : "Networking", +- "network_widget_b" : "Networking", +- "server" : "Advanced", ++ "*ipv6_address" : "Networking", ++ "*ipv6_secondaries" : "Networking", ++ "*ipv6_mtu" : "Networking", ++ "*ipv6_static_routes" : "Networking", ++ "*ipv6_default_gateway" : "Networking", ++ "hostname" : "Networking (Global)", ++ "gateway" : "Networking (Global)", ++ "name_servers" : "Networking (Global)", ++ "name_servers_search" : "Networking (Global)", ++ "ipv6_default_device" : "Networking (Global)", ++ "ipv6_autoconfiguration" : "Networking (Global)", ++ "repos" : "General", ++ "dhcp_tag" : "Advanced", ++ "mgmt_classes" : "Management", ++ "template_files" : "Management", ++ "network_widget_a" : "Networking", ++ "network_widget_b" : "Networking", ++ "server" : "Advanced", + "redhat_management_key" : "Management", + "redhat_management_server" : "Management", + "createrepo_flags" : "Advanced", +--- ./cobbler/item_system.py 2010-07-28 17:48:48.000000000 +0200 ++++ ./cobbler/item_system.py 2012-01-26 13:43:04.000000000 +0100 +@@ -59,9 +59,12 @@ FIELDS = [ + ["gateway","",0,"Gateway",True,"",0,"str"], + ["name_servers",[],0,"Name Servers",True,"space delimited",0,"list"], + ["name_servers_search",[],0,"Name Servers Search Path",True,"space delimited",0,"list"], ++ ["ipv6_default_device","",0,"IPv6 Default Device",True,"",0,"str"], ++ ["ipv6_autoconfiguration",False,0,"IPv6 Autoconfiguration",True,"",0,"bool"], + ["network_widget_a","",0,"Add Interface",True,"",0,"str"], # not a real field, a marker for the web app + ["network_widget_b","",0,"Edit Interface",True,"",0,"str"], # not a real field, a marker for the web app + ["*mac_address","",0,"MAC Address",True,"(Place \"random\" in this field for a random MAC Address.)",0,"str"], ++ ["*mtu","",0,"MTU",True,"",0,"str"], + ["*ip_address","",0,"IP Address",True,"",0,"str"], + ["*bonding","na",0,"Bonding Mode",True,"",["na","master","slave"],"str"], + ["*bonding_master","",0,"Bonding Master",True,"",0,"str"], +@@ -72,6 +75,11 @@ FIELDS = [ + ["*dns_name","",0,"DNS Name",True,"",0,"str"], + ["*static_routes",[],0,"Static Routes",True,"",0,"list"], + ["*virt_bridge","",0,"Virt Bridge",True,"",0,"str"], ++ ["*ipv6_address","",0,"IPv6 Address",True,"",0,"str"], ++ ["*ipv6_secondaries",[],0,"IPv6 Secondaries",True,"space delimited",0,"list"], ++ ["*ipv6_mtu","",0,"IPv6 MTU",True,"",0,"str"], ++ ["*ipv6_static_routes",[],0,"IPv6 Static Routes",True,"",0,"list"], ++ ["*ipv6_default_gateway","",0,"IPv6 Default Gateway",True,"",0,"str"], + ["mgmt_classes",[],0,"Management Classes",True,"For external config management",0,"list"], + ["template_files",{},0,"Template Files",True,"File mappings for built-in configuration management",0,"dict"], + ["redhat_management_key","<>",0,"Red Hat Management Key",True,"Registration key for RHN, Satellite, or Spacewalk",0,"str"], +@@ -114,6 +122,7 @@ class System(item.Item): + if not self.interfaces.has_key(name): + self.interfaces[name] = { + "mac_address" : "", ++ "mtu" : "", + "ip_address" : "", + "dhcp_tag" : "", + "subnet" : "", +@@ -124,6 +133,11 @@ class System(item.Item): + "bonding_opts" : "", + "dns_name" : "", + "static_routes" : [], ++ "ipv6_address" : "", ++ "ipv6_secondaries" : [], ++ "ipv6_mtu" : "", ++ "ipv6_static_routes" : [], ++ "ipv6_default_gateway" : "", + } + + return self.interfaces[name] +@@ -364,6 +378,65 @@ class System(item.Item): + intf["bonding_opts"] = bonding_opts + return True + ++ def set_ipv6_autoconfiguration(self,truthiness): ++ self.ipv6_autoconfiguration = utils.input_boolean(truthiness) ++ return True ++ ++ def set_ipv6_default_device(self,interface_name): ++ if interface_name is None: ++ interface_name = "" ++ self.ipv6_default_device = interface_name ++ return True ++ ++ def set_ipv6_address(self,address,interface): ++ """ ++ Assign a IP or hostname in DHCP when this MAC boots. ++ Only works if manage_dhcp is set in /etc/cobbler/settings ++ """ ++ intf = self.__get_interface(interface) ++ if address == "" or utils.is_ip(address): ++ intf["ipv6_address"] = address.strip() ++ return True ++ raise CX(_("invalid format for IPv6 IP address (%s)") % address) ++ ++ def set_ipv6_secondaries(self,addresses,interface): ++ intf = self.__get_interface(interface) ++ data = utils.input_string_or_list(addresses) ++ secondaries = [] ++ for address in data: ++ if address == "" or utils.is_ip(address): ++ secondaries.append(address) ++ else: ++ raise CX(_("invalid format for IPv6 IP address (%s)") % address) ++ ++ intf["ipv6_secondaries"] = secondaries ++ return True ++ ++ def set_ipv6_default_gateway(self,address,interface): ++ intf = self.__get_interface(interface) ++ if address == "" or utils.is_ip(address): ++ intf["ipv6_default_gateway"] = address.strip() ++ return True ++ raise CX(_("invalid format for IPv6 IP address (%s)") % address) ++ ++ def set_ipv6_static_routes(self,routes,interface): ++ intf = self.__get_interface(interface) ++ data = utils.input_string_or_list(routes) ++ intf["ipv6_static_routes"] = data ++ return True ++ ++ def set_ipv6_mtu(self,mtu,interface): ++ intf = self.__get_interface(interface) ++ intf["ipv6_mtu"] = mtu ++ return True ++ ++ def set_mtu(self,mtu,interface): ++ intf = self.__get_interface(interface) ++ intf["mtu"] = mtu ++ return True ++ ++ ++ + def set_profile(self,profile_name): + """ + Set the system to use a certain named profile. The profile +@@ -503,17 +576,23 @@ class System(item.Item): + for (key,value) in hash.iteritems(): + (field,interface) = key.split("-") + field = field.replace("_","").replace("-","") +- if field == "macaddress" : self.set_mac_address(value, interface) +- if field == "ipaddress" : self.set_ip_address(value, interface) +- if field == "dnsname" : self.set_dns_name(value, interface) +- if field == "static" : self.set_static(value, interface) +- if field == "dhcptag" : self.set_dhcp_tag(value, interface) +- if field == "subnet" : self.set_subnet(value, interface) +- if field == "virtbridge" : self.set_virt_bridge(value, interface) +- if field == "bonding" : self.set_bonding(value, interface) +- if field == "bondingmaster" : self.set_bonding_master(value, interface) +- if field == "bondingopts" : self.set_bonding_opts(value, interface) +- if field == "staticroutes" : self.set_static_routes(value, interface) ++ if field == "macaddress" : self.set_mac_address(value, interface) ++ if field == "mtu" : self.set_mtu(value, interface) ++ if field == "ipaddress" : self.set_ip_address(value, interface) ++ if field == "dnsname" : self.set_dns_name(value, interface) ++ if field == "static" : self.set_static(value, interface) ++ if field == "dhcptag" : self.set_dhcp_tag(value, interface) ++ if field == "subnet" : self.set_subnet(value, interface) ++ if field == "virtbridge" : self.set_virt_bridge(value, interface) ++ if field == "bonding" : self.set_bonding(value, interface) ++ if field == "bondingmaster" : self.set_bonding_master(value, interface) ++ if field == "bondingopts" : self.set_bonding_opts(value, interface) ++ if field == "staticroutes" : self.set_static_routes(value, interface) ++ if field == "ipv6address" : self.set_ipv6_address(value, interface) ++ if field == "ipv6secondaries" : self.set_ipv6_secondaries(value, interface) ++ if field == "ipv6mtu" : self.set_ipv6_mtu(value, interface) ++ if field == "ipv6staticroutes" : self.set_ipv6_static_routes(value, interface) ++ if field == "ipv6defaultgateway" : self.set_ipv6_default_gateway(value, interface) + return True + + def check_if_valid(self): +--- ./snippets/post_install_network_config 2010-07-28 17:48:48.000000000 +0200 ++++ ./snippets/post_install_network_config 2012-01-26 15:53:46.000000000 +0100 +@@ -14,6 +14,7 @@ + # + #set $configbymac = True + #set $numbondingdevs = 0 ++ #set $enableipv6 = False + ## ============================================================================= + #for $iname in $ikeys + ## look at the interface hash data for the specific interface +@@ -29,6 +30,12 @@ + #if $idata["bonding"].lower() == "master" + #set $numbondingdevs += 1 + #end if ++ ++ ## enable IPv6 networking if we set an ipv6 address or turn on autoconfiguration ++ #if $idata["ipv6_address"] != "" or $ipv6_autoconfiguration == True ++ #set $enableipv6 = True ++ #end if ++ + #end for + ## end looping through the interfaces to see which ones we need to configure. + ## ============================================================================= +@@ -64,22 +71,56 @@ mv /etc/sysconfig/network.cobbler /etc/s + # connecting to Puppet before a reboot). + /bin/hostname $hostname + #end if ++ ++ #if $enableipv6 == True ++grep -v NETWORKING_IPV6 /etc/sysconfig/network > /etc/sysconfig/network.cobbler ++echo "NETWORKING_IPV6=yes" >> /etc/sysconfig/network.cobbler ++rm -f /etc/sysconfig/network ++mv /etc/sysconfig/network.cobbler /etc/sysconfig/network ++ ++ #if $ipv6_autoconfiguration != "" ++grep -v IPV6_AUTOCONF /etc/sysconfig/network > /etc/sysconfig/network.cobbler ++ #if $ipv6_autoconfiguration == True ++echo "IPV6_AUTOCONF=yes" >> /etc/sysconfig/network.cobbler ++ #else ++echo "IPV6_AUTOCONF=no" >> /etc/sysconfig/network.cobbler ++ #end if ++rm -f /etc/sysconfig/network ++mv /etc/sysconfig/network.cobbler /etc/sysconfig/network ++ #end if ++ ++ #if $ipv6_default_device != "" ++grep -v IPV6_DEFAULTDEV /etc/sysconfig/network > /etc/sysconfig/network.cobbler ++echo "IPV6_DEFAULTDEV=$ipv6_default_device" >> /etc/sysconfig/network.cobbler ++rm -f /etc/sysconfig/network ++mv /etc/sysconfig/network.cobbler /etc/sysconfig/network ++ #end if ++ ++ #end if ++ + ## ============================================================================= + ## now create the config file for each interface + #for $iname in $ikeys + # Start configuration for $iname + ## create lots of variables to use later +- #set $idata = $interfaces[$iname] +- #set $mac = $idata["mac_address"].upper() +- #set $static = $idata["static"] +- #set $ip = $idata["ip_address"] +- #set $netmask = $idata["subnet"] +- #set $static_routes = $idata["static_routes"] +- #set $bonding = $idata["bonding"] +- #set $bonding_master = $idata["bonding_master"] +- #set $bonding_opts = $idata["bonding_opts"] +- #set $devfile = "/etc/sysconfig/network-scripts/cobbler/ifcfg-" + $iname +- #set $routesfile = "/etc/sysconfig/network-scripts/cobbler/route-" + $iname ++ #set $idata = $interfaces[$iname] ++ #set $mac = $idata["mac_address"].upper() ++ #set $mtu = $idata["mtu"] ++ #set $static = $idata["static"] ++ #set $ip = $idata["ip_address"] ++ #set $netmask = $idata["subnet"] ++ #set $static_routes = $idata["static_routes"] ++ #set $bonding = $idata["bonding"] ++ #set $bonding_master = $idata["bonding_master"] ++ #set $bonding_opts = $idata["bonding_opts"] ++ #set $ipv6_address = $idata["ipv6_address"] ++ #set $ipv6_secondaries = $idata["ipv6_secondaries"] ++ #set $ipv6_mtu = $idata["ipv6_mtu"] ++ #set $ipv6_default_gateway = $idata["ipv6_default_gateway"] ++ #set $ipv6_static_routes = $idata["ipv6_static_routes"] ++ #set $devfile = "/etc/sysconfig/network-scripts/cobbler/ifcfg-" + $iname ++ #set $routesfile = "/etc/sysconfig/network-scripts/cobbler/route-" + $iname ++ #set $ipv6_routesfile = "/etc/sysconfig/network-scripts/cobbler/route6-" + $iname + ## determine if this interface is for a VLAN + #if $vlanpattern.match($iname) + ## If this is a VLAN interface, skip it, anaconda doesn't know +@@ -140,9 +181,37 @@ echo "NETMASK=$netmask" >> $devfile + ## we don't have enough info for static configuration + echo "BOOTPROTO=none" >> $devfile + #end if ++ #if $enableipv6 == True and $ipv6_address == "" and $ipv6_secondaries == "" ++echo "IPV6INIT=no" >> $devfile ++ #end if ++ #if $enableipv6 == True and $ipv6_autoconfiguration == False ++ #if $ipv6_address != "" ++echo "IPV6INIT=yes" >> $devfile ++echo "IPV6ADDR=$ipv6_address" >> $devfile ++ #end if ++ #if $ipv6_secondaries != "" ++ #set ipv6_secondaries = ' '.join(ipv6_secondaries) ++ ## The quotes around the ipv6 ip's need to be here ++echo 'IPV6ADDR_SECONDARIES="$ipv6_secondaries"' >> $devfile ++ #end if ++ #if $ipv6_mtu != "" ++echo "IPV6MTU=$ipv6_mtu" >> $devfile ++ #end if ++ #if $ipv6_default_gateway != "" ++echo "IPV6_DEFAULTGW=$ipv6_default_gateway" >> $devfile ++ #end if ++ #end if + #else ++ #if $ip == "" ++ ## this interface has no IPv4 address set ++echo "BOOTPROTO=none" >> $devfile ++ #else + ## this is a DHCP interface, much less work to do + echo "BOOTPROTO=dhcp" >> $devfile ++ #end if ++ #end if ++ #if $mtu != "" ++echo "MTU=$mtu" >> $devfile + #end if + #else if $is_vlan == "true" or $bonding.lower() == "master" + ## Handle non-physical interfaces with special care. :) +@@ -178,6 +247,9 @@ echo "BOOTPROTO=none" >> $devfile + #else + echo "BOOTPROTO=dhcp" >> $devfile + #end if ++ #if $mtu != "" ++echo "MTU=$mtu" >> $devfile ++ #end if + #else if $configbymac == False + ## We'll end up here when not all physical interfaces present for + ## this system have MAC-addresses configured for them. We don't +@@ -214,6 +286,9 @@ echo "BOOTPROTO=none" >> $devfile + ## this is a DHCP interface, much less work to do + echo "BOOTPROTO=dhcp" >> $devfile + #end if ++ #if $mtu != "" ++echo "MTU=$mtu" >> $devfile ++ #end if + #else + # If you end up here, please mail the list... This shouldn't + # happen. ;-) -- jcapel +@@ -225,19 +300,32 @@ echo "BOOTPROTO=dhcp" >> $devfile + #set $nct = $nct + 1 + echo "DNS$nct=$nameserver" >> $devfile + #end for +- #end if +- #for $route in $static_routes +- #set routepattern = $re.compile("[0-9/.]+:[0-9.]+") +- #if $routepattern.match($route) +- #set $routebits = $route.split(":") +- #set [$network, $router] = $route.split(":") ++ #end if ++ #for $route in $static_routes ++ #set routepattern = $re.compile("[0-9/.]+:[0-9.]+") ++ #if $routepattern.match($route) ++ #set $routebits = $route.split(":") ++ #set [$network, $router] = $route.split(":") + echo "$network via $router" >> $routesfile +- #else ++ #else ++ # Warning: invalid route "$route" ++ #end if ++ #end for ++ #if $enableipv6 == True ++ #for $route in $ipv6_static_routes ++ #set routepattern = $re.compile("[0-9a-fA-F:/]+,[0-9a-fA-F:]+") ++ #if $routepattern.match($route) ++ #set $routebits = $route.split(",") ++ #set [$network, $router] = $route.split(",") ++echo "$network via $router dev $iname" >> $ipv6_routesfile ++ #else + # Warning: invalid route "$route" + #end if + #end for ++ #end if + #set $i = $i + 1 + # End configuration for $iname ++ + #end for + ## ============================================================================= + ## Configure name server search path in /etc/resolv.conf + diff --git a/SOURCES/cobbler-ipv6-xmlrpc.patch b/SOURCES/cobbler-ipv6-xmlrpc.patch new file mode 100644 index 0000000..65888b1 --- /dev/null +++ b/SOURCES/cobbler-ipv6-xmlrpc.patch @@ -0,0 +1,134 @@ +--- ./cobbler/cobblerd.py 2010-07-28 17:48:48.000000000 +0200 ++++ ./cobbler/cobblerd.py 2012-01-26 11:26:05.000000000 +0100 +@@ -25,6 +25,7 @@ import socket + import time + import os + import SimpleXMLRPCServer ++import SocketServer + import glob + from utils import _ + import xmlrpclib +@@ -104,7 +105,27 @@ def log(logger,msg): + def do_xmlrpc_rw(bootapi,settings,port): + + xinterface = remote.ProxiedXMLRPCInterface(bootapi,remote.CobblerXMLRPCInterface) +- server = remote.CobblerXMLRPCServer(('127.0.0.1', port)) ++ ++ bound = False ++ for res in socket.getaddrinfo('localhost', port, socket.AF_UNSPEC, ++ socket.SOCK_STREAM, 0, socket.AI_PASSIVE): ++ (af, sa) = (res[0], res[4]) ++ try: ++ SocketServer.TCPServer.address_family = af ++ server = remote.CobblerXMLRPCServer((sa[0], sa[1])) ++ except Exception, msg: ++ if af == socket.AF_INET: ++ message = "Could not bind to %s:%s: %s" % (sa[0], sa[1], msg) ++ elif af == socket.AF_INET6: ++ message = "Could not bind to [%s]:%s: %s" % (sa[0], sa[1], msg) ++ xinterface.logger.debug(message) ++ else: ++ bound = True ++ break ++ ++ if not bound: ++ raise socket.error("Could not bind to localhost") ++ + server.logRequests = 0 # don't print stuff + xinterface.logger.debug("XMLRPC running on %s" % port) + server.register_instance(xinterface) +--- ./cobbler/modules/authn_spacewalk.py 2010-07-28 17:48:48.000000000 +0200 ++++ ./cobbler/modules/authn_spacewalk.py 2012-01-26 11:26:05.000000000 +0100 +@@ -77,7 +77,7 @@ def authenticate(api_handle,username,pas + if server == "xmlrpc.rhn.redhat.com": + return False # emergency fail, don't bother RHN! + +- spacewalk_url = "https://%s/rpc/api" % server ++ spacewalk_url = "http://%s/rpc/api" % server + + client = xmlrpclib.Server(spacewalk_url, verbose=0) + +--- ./cobbler/utils.py 2012-01-26 11:26:05.000000000 +0100 ++++ ./cobbler/utils.py 2012-01-26 11:28:22.000000000 +0100 +@@ -1862,6 +1862,30 @@ def get_shared_secret(): + return -1 + return str(data).strip() + ++def get_localhost_addr(port): ++ (sock, sa, af) = (None, None, None) ++ for res in socket.getaddrinfo('localhost', port, socket.AF_UNSPEC, socket.SOCK_STREAM): ++ af, socktype, proto, canonname, sa = res ++ try: ++ sock = socket.socket(af, socktype, proto) ++ except socket.error: ++ sock = None ++ continue ++ ++ try: ++ sock.connect((sa[0], sa[1])) ++ except socket.error: ++ sock.close() ++ sock = None ++ continue ++ break ++ ++ if sock is None: ++ return (None, None) ++ ++ sock.close() ++ return (sa[0], af) ++ + def local_get_cobbler_api_url(): + # Load server and http port + try: +@@ -1882,7 +1906,18 @@ def local_get_cobbler_xmlrpc_url(): + except: + traceback.print_exc() + raise CX("/etc/cobbler/settings is not a valid YAML file") +- return "http://%s:%s" % ("127.0.0.1",data.get("xmlrpc_port","25151")) ++ port = data.get("xmlrpc_port","25151") ++ addr = get_localhost_addr(port) ++ ++ if addr[1] == socket.AF_INET: ++ return "http://%s:%s" % (addr[0], port) ++ if addr[1] == socket.AF_INET6: ++ return "http://[%s]:%s" % (addr[0], port) ++ ++ if os.path.exists('/proc/net/if_inet6'): ++ return "http://[::1]:%s" % port ++ else: ++ return "http://127.0.0.1:%s" % port + + def strip_none(data, omit_none=False): + """ +--- ./scripts/services.py 2010-07-28 17:48:48.000000000 +0200 ++++ ./scripts/services.py 2012-01-26 11:26:05.000000000 +0100 +@@ -78,7 +78,7 @@ def handler(req): + # instantiate a CobblerWeb object + cw = CobblerSvc( + apache = apache, +- server = "http://127.0.0.1:%s" % remote_port ++ server = utils.local_get_cobbler_xmlrpc_url() + ) + + # check for a valid path/mode +--- ./scripts/services.wsgi 2010-10-07 20:12:03.000000000 +0200 ++++ ./scripts/services.wsgi 2012-01-26 11:26:05.000000000 +0100 +@@ -23,6 +23,7 @@ import yaml + import os + + from cobbler.services import CobblerSvc ++import cobbler.utils as utils + + def application(environ, start_response): + +@@ -64,7 +65,7 @@ def application(environ, start_response) + remote_port = ydata.get("xmlrpc_port",25151) + + # instantiate a CobblerWeb object +- cw = CobblerSvc(server = "http://127.0.0.1:%s" % remote_port) ++ cw = CobblerSvc(server = utils.local_get_cobbler_xmlrpc_url()) + + # check for a valid path/mode + # handle invalid paths gracefully + diff --git a/SOURCES/cobbler-keep-ssh-snippet.patch b/SOURCES/cobbler-keep-ssh-snippet.patch new file mode 100644 index 0000000..b64a626 --- /dev/null +++ b/SOURCES/cobbler-keep-ssh-snippet.patch @@ -0,0 +1,14 @@ +--- ./snippets/keep_ssh_host_keys 2010-07-28 17:48:48.000000000 +0200 ++++ ./snippets/keep_ssh_host_keys 2011-08-18 13:56:47.083867519 +0200 +@@ -89,10 +89,9 @@ if [ "$keys_found" = "yes" ]; then + sleep 10 + if [ -d /mnt/sysimage$SEARCHDIR ] ; then + cp -af /tmp/$TEMPDIR/${PATTERN}* /mnt/sysimage$SEARCHDIR +- logger "keys copied to newly installed system" + break + fi +- done & ++ done >/dev/null 2>/dev/null & + fi + #end raw + diff --git a/SOURCES/cobbler-koan-rhpl.patch b/SOURCES/cobbler-koan-rhpl.patch new file mode 100644 index 0000000..6a30244 --- /dev/null +++ b/SOURCES/cobbler-koan-rhpl.patch @@ -0,0 +1,56 @@ +--- ./koan/app.py 2010-10-18 15:40:34.000000000 +0200 ++++ ./koan/app.py 2011-01-07 11:47:23.173337611 +0100 +@@ -382,10 +382,6 @@ class Koan: + Determine the name of the cobbler system record that + matches this MAC address. + """ +- try: +- import rhpl +- except: +- raise CX("the rhpl module is required to autodetect a system. Your OS does not have this, please manually specify --profile or --system") + systems = self.get_data("systems") + my_netinfo = utils.get_network_info() + my_interfaces = my_netinfo.keys() +--- ./koan/utils.py 2010-10-18 15:40:34.000000000 +0200 ++++ ./koan/utils.py 2011-01-07 11:47:23.173337611 +0100 +@@ -383,22 +383,26 @@ def uniqify(lst, purge=None): + + def get_network_info(): + try: +- import rhpl.ethtool ++ import ethtool + except: +- raise InfoException("the rhpl module is required to use this feature (is your OS>=EL3?)") ++ try: ++ import rhpl.ethtool ++ ethtool = rhpl.ethtool ++ except: ++ raise InfoException("the rhpl or ethtool module is required to use this feature (is your OS>=EL3?)") + + interfaces = {} + # get names +- inames = rhpl.ethtool.get_devices() ++ inames = ethtool.get_devices() + + for iname in inames: +- mac = rhpl.ethtool.get_hwaddr(iname) ++ mac = ethtool.get_hwaddr(iname) + + if mac == "00:00:00:00:00:00": + mac = "?" + + try: +- ip = rhpl.ethtool.get_ipaddr(iname) ++ ip = ethtool.get_ipaddr(iname) + if ip == "127.0.0.1": + ip = "?" + except: +@@ -408,7 +412,7 @@ def get_network_info(): + module = "" + + try: +- nm = rhpl.ethtool.get_netmask(iname) ++ nm = ethtool.get_netmask(iname) + except: + nm = "?" + diff --git a/SOURCES/cobbler-lvm-installation.patch b/SOURCES/cobbler-lvm-installation.patch new file mode 100644 index 0000000..48e244c --- /dev/null +++ b/SOURCES/cobbler-lvm-installation.patch @@ -0,0 +1,11 @@ +--- ./koan/app.py 2011-08-23 17:47:53.135881905 +0200 ++++ ./koan/app.py 2011-08-23 17:48:18.715879926 +0200 +@@ -1460,7 +1460,7 @@ class Koan: + cmd = sub_process.Popen(args, stdout=sub_process.PIPE, shell=True) + freespace_str = cmd.communicate()[0] + freespace_str = freespace_str.split("\n")[0].strip() +- freespace_str = freespace_str.replace("G","") # remove gigabytes ++ freespace_str = freespace_str.lower().replace("g","") # remove gigabytes + print "(%s)" % freespace_str + freespace = int(float(freespace_str)) + diff --git a/SOURCES/cobbler-lvm-selinux.patch b/SOURCES/cobbler-lvm-selinux.patch new file mode 100644 index 0000000..87e20eb --- /dev/null +++ b/SOURCES/cobbler-lvm-selinux.patch @@ -0,0 +1,12 @@ +diff -ur cobbler-2.0.7.old/koan/app.py cobbler-2.0.7.new/koan/app.py +--- cobbler-2.0.7.old/koan/app.py 2013-03-27 14:24:46.002915215 -0400 ++++ cobbler-2.0.7.new/koan/app.py 2013-03-27 15:32:44.987651207 -0400 +@@ -1492,7 +1492,7 @@ + raise InfoException, "LVM creation failed" + + # partition location +- partition_location = "/dev/mapper/%s-%s" % (location,name.replace('-','--')) ++ partition_location = "/dev/%s/%s" % (location,name) + + # check whether we have SELinux enabled system + args = "/usr/sbin/selinuxenabled" diff --git a/SOURCES/cobbler-modprobe-d.patch b/SOURCES/cobbler-modprobe-d.patch new file mode 100644 index 0000000..07b7c6a --- /dev/null +++ b/SOURCES/cobbler-modprobe-d.patch @@ -0,0 +1,67 @@ +--- a/snippets/post_install_network_config 2013-11-11 10:46:45.464341394 +0100 ++++ b/snippets/post_install_network_config 2013-11-12 12:13:34.578812084 +0100 +@@ -43,9 +43,13 @@ + ## setup bonding if we have to + ## if using bonding, must have an /ect/modpobe.conf file + #if $numbondingdevs > 0 +-touch "/etc/modprobe.conf" +-if [ -f "/etc/modprobe.conf" ]; then +- echo "options bonding max_bonds=$numbondingdevs" >> /etc/modprobe.conf ++MODPROBE_CONF="/etc/modprobe.conf" ++if [ -d "/etc/modprobe.d" ]; then ++ MODPROBE_CONF="/etc/modprobe.d/post_install_network_config.conf" ++fi ++touch "\$MODPROBE_CONF" ++if [ -f "\$MODPROBE_CONF" ]; then ++ echo "options bonding max_bonds=$numbondingdevs" >> \$MODPROBE_CONF + fi + #end if + ## ============================================================================= +@@ -134,13 +138,13 @@ + ## if this is a bonded interface, configure it in modprobe.conf + #if $bonding.lower() == "master" + #if $osversion == "rhel4" +-if [ -f "/etc/modprobe.conf" ]; then +- echo "install $iname /sbin/modprobe bonding -o $iname $bonding_opts" >> /etc/modprobe.conf.cobbler ++if [ -f "\$MODPROBE_CONF" ]; then ++ echo "install $iname /sbin/modprobe bonding -o $iname $bonding_opts" >> \$MODPROBE_CONF.cobbler + fi + #else + ## Add required entry to modprobe.conf +-if [ -f "/etc/modprobe.conf" ]; then +- echo "alias $iname bonding" >> /etc/modprobe.conf.cobbler ++if [ -f "\$MODPROBE_CONF" ]; then ++ echo "alias $iname bonding" >> \$MODPROBE_CONF.cobbler + fi + #end if + #end if +@@ -150,11 +154,11 @@ + IFNAME=\$(ip -o link | grep -i '$mac' | sed -e 's/^[0-9]*: //' -e 's/:.*//') + ## Rename this interface in modprobe.conf + ## FIXME: if both interfaces startwith eth this is wrong +-if [ -f "/etc/modprobe.conf" ] && [ \$IFNAME ]; then +- grep \$IFNAME /etc/modprobe.conf | sed "s/\$IFNAME/$iname/" >> /etc/modprobe.conf.cobbler +- grep -v \$IFNAME /etc/modprobe.conf >> /etc/modprobe.conf.new +- rm -f /etc/modprobe.conf +- mv /etc/modprobe.conf.new /etc/modprobe.conf ++if [ -f "\$MODPROBE_CONF" ] && [ \$IFNAME ]; then ++ grep \$IFNAME \$MODPROBE_CONF | sed "s/\$IFNAME/$iname/" >> \$MODPROBE_CONF.cobbler ++ grep -v \$IFNAME \$MODPROBE_CONF >> \$MODPROBE_CONF.new ++ rm -f \$MODPROBE_CONF ++ mv \$MODPROBE_CONF.new \$MODPROBE_CONF + fi + echo "DEVICE=$iname" > $devfile + echo "HWADDR=$mac" >> $devfile +@@ -348,9 +352,9 @@ + rm -f /etc/sysconfig/network-scripts/ifcfg-* + mv /etc/sysconfig/network-scripts/cobbler/* /etc/sysconfig/network-scripts/ + rm -r /etc/sysconfig/network-scripts/cobbler +-if [ -f "/etc/modprobe.conf" ]; then +-cat /etc/modprobe.conf.cobbler >> /etc/modprobe.conf +-rm -f /etc/modprobe.conf.cobbler ++if [ -f "\$MODPROBE_CONF" ]; then ++cat \$MODPROBE_CONF.cobbler >> \$MODPROBE_CONF ++rm -f \$MODPROBE_CONF.cobbler + fi + #end if + # End post_install_network_config generated code diff --git a/SOURCES/cobbler-netaddr.patch b/SOURCES/cobbler-netaddr.patch new file mode 100644 index 0000000..51fa1b3 --- /dev/null +++ b/SOURCES/cobbler-netaddr.patch @@ -0,0 +1,23 @@ +--- ./cobbler/utils.py 2011-08-23 17:19:24.442680062 +0200 ++++ ./cobbler/utils.py 2011-08-23 17:19:37.466012392 +0200 +@@ -186,8 +186,9 @@ def get_host_ip(ip, shorten=True): + ip = netaddr.IP(ip) + cidr = ip.cidr() + else: +- ip = netaddr.ip.IPAddress(ip) +- cidr = netaddr.ip.IPNetwork(ip) ++ ipnet = netaddr.IPNetwork(ip) ++ ip = ipnet.ip ++ cidr = ipnet.cidr + + if len(cidr) == 1: # Just an IP, e.g. a /32 + return pretty_hex(ip) +@@ -210,7 +211,7 @@ def _IP(ip): + if NETADDR_PRE_0_7: + ip_class = netaddr.IP + else: +- ip_class = netaddr.ip.IPAddress ++ ip_class = netaddr.ip.IPNetwork + if isinstance(ip, ip_class) or ip == "": + return ip + else: diff --git a/SOURCES/cobbler-nic-dash.patch b/SOURCES/cobbler-nic-dash.patch new file mode 100644 index 0000000..48a30c3 --- /dev/null +++ b/SOURCES/cobbler-nic-dash.patch @@ -0,0 +1,12 @@ +--- ./cobbler/item_system.py 2012-02-02 11:47:38.977405017 +0100 ++++ ./cobbler/item_system.py 2012-02-09 17:21:33.318228733 +0100 +@@ -574,7 +574,7 @@ class System(item.Item): + Used by the WUI to modify an interface more-efficiently + """ + for (key,value) in hash.iteritems(): +- (field,interface) = key.split("-") ++ (field,interface) = key.split("-", 1) + field = field.replace("_","").replace("-","") + if field == "macaddress" : self.set_mac_address(value, interface) + if field == "mtu" : self.set_mtu(value, interface) + diff --git a/SOURCES/cobbler-no-remove-pub-bz707215.patch b/SOURCES/cobbler-no-remove-pub-bz707215.patch new file mode 100644 index 0000000..65f8557 --- /dev/null +++ b/SOURCES/cobbler-no-remove-pub-bz707215.patch @@ -0,0 +1,11 @@ +--- cobbler/action_sync.py 2011-05-27 16:05:40.000000000 +0200 ++++ cobbler/action_sync.py 2011-05-27 16:05:53.000000000 +0200 +@@ -182,7 +182,7 @@ + if not x.endswith(".py"): + utils.rmfile(path,logger=self.logger) + if os.path.isdir(path): +- if not x in ["aux", "web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc","rendered",".link_cache"] : ++ if not x in ["aux", "web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc","rendered",".link_cache", "pub"] : + # delete directories that shouldn't exist + utils.rmtree(path,logger=self.logger) + if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system","rendered"]: diff --git a/SOURCES/cobbler-post-install-network-defaults.patch b/SOURCES/cobbler-post-install-network-defaults.patch new file mode 100644 index 0000000..2cc2005 --- /dev/null +++ b/SOURCES/cobbler-post-install-network-defaults.patch @@ -0,0 +1,59 @@ +--- cobbler-2.0.7/snippets/post_install_network_config.org 2017-10-31 11:57:38.783714163 +0100 ++++ cobbler-2.0.7/snippets/post_install_network_config 2017-10-31 12:11:05.663448156 +0100 +@@ -21,18 +21,18 @@ + #set $idata = $interfaces[$iname] + ## do not configure by mac address if we don't have one AND it's not for bonding/vlans + ## as opposed to a "real" physical interface +- #if $idata["mac_address"] == "" and not $vlanpattern.match($iname) and not $idata["bonding"].lower() == "master": ++ #if $idata.get("mac_address", "") == "" and not $vlanpattern.match($iname) and not $idata.get("bonding", "").lower() == "master": + ## we have to globally turn off the config by mac feature as we can't + ## use it now + #set $configbymac = False + #end if + ## count the number of bonding devices we have. +- #if $idata["bonding"].lower() == "master" ++ #if $idata.get("bonding", "").lower() == "master" + #set $numbondingdevs += 1 + #end if + + ## enable IPv6 networking if we set an ipv6 address or turn on autoconfiguration +- #if $idata["ipv6_address"] != "" or $ipv6_autoconfiguration == True ++ #if $idata.get("ipv6_address", "") != "" or $ipv6_autoconfiguration == True + #set $enableipv6 = True + #end if + +@@ -110,20 +110,20 @@ mv /etc/sysconfig/network.cobbler /etc/s + # Start configuration for $iname + ## create lots of variables to use later + #set $idata = $interfaces[$iname] +- #set $mac = $idata["mac_address"].upper() +- #set $mtu = $idata["mtu"] +- #set $static = $idata["static"] +- #set $ip = $idata["ip_address"] +- #set $netmask = $idata["subnet"] +- #set $static_routes = $idata["static_routes"] +- #set $bonding = $idata["bonding"] +- #set $bonding_master = $idata["bonding_master"] +- #set $bonding_opts = $idata["bonding_opts"] +- #set $ipv6_address = $idata["ipv6_address"] +- #set $ipv6_secondaries = $idata["ipv6_secondaries"] +- #set $ipv6_mtu = $idata["ipv6_mtu"] +- #set $ipv6_default_gateway = $idata["ipv6_default_gateway"] +- #set $ipv6_static_routes = $idata["ipv6_static_routes"] ++ #set $mac = $idata.get("mac_address", "").upper() ++ #set $mtu = $idata.get("mtu", "") ++ #set $static = $idata.get("static", "") ++ #set $ip = $idata.get("ip_address", "") ++ #set $netmask = $idata.get("subnet", "") ++ #set $static_routes = $idata.get("static_routes", "") ++ #set $bonding = $idata.get("bonding", "") ++ #set $bonding_master = $idata.get("bonding_master", "") ++ #set $bonding_opts = $idata.get("bonding_opts", "") ++ #set $ipv6_address = $idata.get("ipv6_address", "") ++ #set $ipv6_secondaries = $idata.get("ipv6_secondaries", "") ++ #set $ipv6_mtu = $idata.get("ipv6_mtu", "") ++ #set $ipv6_default_gateway = $idata.get("ipv6_default_gateway", "") ++ #set $ipv6_static_routes = $idata.get("ipv6_static_routes", "") + #set $devfile = "/etc/sysconfig/network-scripts/cobbler/ifcfg-" + $iname + #set $routesfile = "/etc/sysconfig/network-scripts/cobbler/route-" + $iname + #set $ipv6_routesfile = "/etc/sysconfig/network-scripts/cobbler/route6-" + $iname diff --git a/SOURCES/cobbler-power-status.patch b/SOURCES/cobbler-power-status.patch new file mode 100644 index 0000000..c92bad0 --- /dev/null +++ b/SOURCES/cobbler-power-status.patch @@ -0,0 +1,108 @@ +diff -rupN cobbler-2.0.7.old/cobbler/action_power.py cobbler-2.0.7/cobbler/action_power.py +--- cobbler-2.0.7.old/cobbler/action_power.py 2014-11-10 15:07:00.578732736 -0500 ++++ cobbler-2.0.7/cobbler/action_power.py 2014-11-10 15:11:19.692536590 -0500 +@@ -30,6 +30,7 @@ import os + import os.path + import traceback + import time ++import re + + import utils + import func_utils +@@ -102,8 +103,22 @@ class PowerTool: + # Try the power command 5 times before giving up. + # Some power switches are flakey + for x in range(0,5): +- rc = utils.subprocess_call(self.logger, template_command, shell=False) ++ output, rc = utils.subprocess_sp(self.logger, template_command, shell=False) + if rc == 0: ++ # If the desired state is actually a query for the status ++ # return different information than command return code ++ if desired_state == 'status': ++ match = re.match('(^Status:\s)(ON|OFF)', output) ++ if not match: ++ match = re.match('(.*Chassis power\s*(=|is)\s*)(On|Off).*', output, re.IGNORECASE) ++ if match: ++ power_status = match.groups()[2] ++ if power_status.upper() == 'ON': ++ return True ++ else: ++ return False ++ utils.die(self.logger,"command succeeded (rc=%s), but output ('%s') was not understood" % (rc, output)) ++ return None + break + else: + time.sleep(2) +diff -rupN cobbler-2.0.7.old/cobbler/api.py cobbler-2.0.7/cobbler/api.py +--- cobbler-2.0.7.old/cobbler/api.py 2014-11-10 15:07:00.577732729 -0500 ++++ cobbler-2.0.7/cobbler/api.py 2014-11-10 15:07:27.941923229 -0500 +@@ -818,6 +818,15 @@ class BootAPI: + time.sleep(5) + return self.power_on(system, user, password, logger=logger) + ++ def power_status(self, system, user=None, password=None, logger=None): ++ """ ++ Returns the power status for a system that has power management configured. ++ ++ @return: 0 the system is powered on, False if it's not or None on error ++ """ ++ return action_power.PowerTool(self._config, system, self, user, password, logger = logger).power("status") ++ ++ + # ========================================================================== + + def clear_logs(self, system, logger=None): +diff -rupN cobbler-2.0.7.old/cobbler/cli.py cobbler-2.0.7/cobbler/cli.py +--- cobbler-2.0.7.old/cobbler/cli.py 2014-11-10 15:07:00.575732715 -0500 ++++ cobbler-2.0.7/cobbler/cli.py 2014-11-10 15:07:27.941923229 -0500 +@@ -40,7 +40,7 @@ import item_image + OBJECT_ACTIONS = { + "distro" : "add copy edit find list remove rename report".split(" "), + "profile" : "add copy dumpvars edit find getks list remove rename report".split(" "), +- "system" : "add copy dumpvars edit find getks list remove rename report poweron poweroff reboot".split(" "), ++ "system" : "add copy dumpvars edit find getks list remove rename report poweron poweroff powerstatus reboot".split(" "), + "image" : "add copy edit find list remove rename report".split(" "), + "repo" : "add copy edit find list remove rename report".split(" ") + } +@@ -278,7 +278,7 @@ class BootCLI: + keys.sort() + for x in keys: + print "%s : %s" % (x, data[x]) +- elif object_action in [ "poweron", "poweroff", "reboot" ]: ++ elif object_action in [ "poweron", "poweroff", "powerstatus", "reboot" ]: + power={} + power["power"] = object_action.replace("power","") + power["systems"] = [options.name] +diff -rupN cobbler-2.0.7.old/cobbler/remote.py cobbler-2.0.7/cobbler/remote.py +--- cobbler-2.0.7.old/cobbler/remote.py 2014-11-10 15:07:00.575732715 -0500 ++++ cobbler-2.0.7/cobbler/remote.py 2014-11-10 15:07:27.942923236 -0500 +@@ -1706,7 +1706,7 @@ class CobblerXMLRPCInterface: + """ + Internal implementation used by background_power, do not call + directly if possible. +- Allows poweron/poweroff/reboot of a system specified by object_id. ++ Allows poweron/poweroff/powerstatus/reboot of a system specified by object_id. + """ + obj = self.__get_object(object_id) + self.check_access(token, "power_system", obj) +@@ -1714,10 +1714,12 @@ class CobblerXMLRPCInterface: + rc=self.api.power_on(obj, user=None, password=None, logger=logger) + elif power=="off": + rc=self.api.power_off(obj, user=None, password=None, logger=logger) ++ elif power=="status": ++ rc=self.api.power_status(obj, user=None, password=None, logger=logger) + elif power=="reboot": + rc=self.api.reboot(obj, user=None, password=None, logger=logger) + else: +- utils.die(self.logger, "invalid power mode '%s', expected on/off/reboot" % power) ++ utils.die(self.logger, "invalid power mode '%s', expected on/off/status/reboot" % power) + return rc + + def clear_system_logs(self, object_id, token=None, logger=None): +diff -rupN cobbler-2.0.7.old/templates/power_ipmilan.template cobbler-2.0.7/templates/power_ipmilan.template +--- cobbler-2.0.7.old/templates/power_ipmilan.template 2014-11-10 15:07:00.581732757 -0500 ++++ cobbler-2.0.7/templates/power_ipmilan.template 2014-11-10 15:07:27.943923243 -0500 +@@ -1,2 +1 @@ +-#use power_id to pass in additional options like -P, Use Lanplus +-fence_ipmilan -i "$power_address" $power_id -l "$power_user" -p "$power_pass" -o "$power_mode" ++fence_ipmilan -a "$power_address" $power_id -l "$power_user" -p "$power_pass" -o "$power_mode" diff --git a/SOURCES/cobbler-power-vulnerability.patch b/SOURCES/cobbler-power-vulnerability.patch new file mode 100644 index 0000000..0def94a --- /dev/null +++ b/SOURCES/cobbler-power-vulnerability.patch @@ -0,0 +1,127 @@ +diff -ru cobbler-2.0.7/cobbler/action_power.py cobbler-2.0.7-new/cobbler/action_power.py +--- cobbler-2.0.7/cobbler/action_power.py 2010-07-28 17:48:48.000000000 +0200 ++++ cobbler-2.0.7-new/cobbler/action_power.py 2012-06-11 11:16:06.409111259 +0200 +@@ -36,6 +36,7 @@ + from cexceptions import * + import templar + import clogger ++import shlex + + class PowerTool: + """ +@@ -68,8 +69,9 @@ + interested in maximum security should take that route. + """ + +- template = self.get_command_template() +- template_file = open(template, "r") ++ power_command = utils.get_power(self.system.power_type) ++ if not power_command: ++ utils.die(self.logger,"no power type set for system") + + meta = utils.blender(self.api, False, self.system) + meta["power_mode"] = desired_state +@@ -80,35 +82,27 @@ + if self.force_pass is not None: + meta["power_pass"] = self.force_pass + +- tmp = templar.Templar(self.api._config) +- cmd = tmp.render(template_file, meta, None, self.system) +- template_file.close() +- +- cmd = cmd.strip() +- + self.logger.info("cobbler power configuration is:") +- + self.logger.info(" type : %s" % self.system.power_type) + self.logger.info(" address: %s" % self.system.power_address) + self.logger.info(" user : %s" % self.system.power_user) + self.logger.info(" id : %s" % self.system.power_id) + + # if no username/password data, check the environment +- + if meta.get("power_user","") == "": + meta["power_user"] = os.environ.get("COBBLER_POWER_USER","") + if meta.get("power_pass","") == "": + meta["power_pass"] = os.environ.get("COBBLER_POWER_PASS","") + +- self.logger.info("- %s" % cmd) +- +- # use shell so we can have mutliple power commands chained together +- cmd = ['/bin/sh','-c', cmd] ++ template = utils.get_power_template(self.system.power_type) ++ tmp = templar.Templar(self.api._config) ++ template_data = tmp.render(template, meta, None, self.system) ++ template_command = shlex.split(str(template_data)) + + # Try the power command 5 times before giving up. + # Some power switches are flakey + for x in range(0,5): +- rc = utils.subprocess_call(self.logger, cmd, shell=False) ++ rc = utils.subprocess_call(self.logger, template_command, shell=False) + if rc == 0: + break + else: +@@ -119,19 +113,3 @@ + + return rc + +- def get_command_template(self): +- +- """ +- In case the user wants to customize the power management commands, +- we source the code for each command from /etc/cobbler and run +- them through Cheetah. +- """ +- +- if self.system.power_type in [ "", "none" ]: +- utils.die(self.logger,"Power management is not enabled for this system") +- +- result = utils.get_power(self.system.power_type) +- if not result: +- utils.die(self.logger, "Invalid power management type for this system (%s, %s)" % (self.system.power_type, self.system.name)) +- return result +- +diff -ru cobbler-2.0.7/cobbler/item_system.py cobbler-2.0.7-new/cobbler/item_system.py +--- cobbler-2.0.7/cobbler/item_system.py 2010-07-28 17:48:48.000000000 +0200 ++++ cobbler-2.0.7-new/cobbler/item_system.py 2012-06-11 11:16:06.410111268 +0200 +@@ -50,11 +50,11 @@ + ["virt_auto_boot","<>",0,"Virt Auto Boot",True,"Auto boot this VM?",0,"bool"], + ["ctime",0,0,"",False,"",0,"float"], + ["mtime",0,0,"",False,"",0,"float"], +- ["power_type","SETTINGS:power_management_default_type",0,"Power Management Type",True,"",utils.get_power_types(),"str"], ++ ["power_type","SETTINGS:power_management_default_type",0,"Power Management Type",True,"Power management script to use",utils.get_power_types(),"str"], + ["power_address","",0,"Power Management Address",True,"Ex: power-device.example.org",0,"str"], +- ["power_user","",0,"Power Username ",True,"",0,"str"], +- ["power_pass","",0,"Power Password",True,"",0,"str"], +- ["power_id","",0,"Power ID",True,"Usually a plug number or blade name, if power type requires it",0,"str"], ++ ["power_user","",0,"Power Management Username ",True,"",0,"str"], ++ ["power_pass","",0,"Power Management Password",True,"",0,"str"], ++ ["power_id","",0,"Power Management ID",True,"Usually a plug number or blade name, if power type requires it",0,"str"], + ["hostname","",0,"Hostname",True,"",0,"str"], + ["gateway","",0,"Gateway",True,"",0,"str"], + ["name_servers",[],0,"Name Servers",True,"space delimited",0,"list"], +diff -ru cobbler-2.0.7/cobbler/utils.py cobbler-2.0.7-new/cobbler/utils.py +--- cobbler-2.0.7/cobbler/utils.py 2012-06-11 11:19:10.469232289 +0200 ++++ cobbler-2.0.7-new/cobbler/utils.py 2012-06-11 11:18:39.660541633 +0200 +@@ -1846,6 +1846,20 @@ + return powerpath + return None + ++def get_power_template(powertype=None): ++ """ ++ Return power template for type ++ """ ++ if powertype: ++ powertemplate = "/etc/cobbler/power/power_%s.template" % powertype ++ if os.path.isfile(powertemplate): ++ f = open(powertemplate) ++ template = f.read() ++ f.close() ++ return template ++ # return a generic template if a specific one wasn't found ++ return "action=$power_mode\nlogin=$power_user\npasswd=$power_pass\nipaddr=$power_address\nport=$power_id" ++ + def get_shared_secret(): + """ + The 'web.ss' file is regenerated each time cobblerd restarts and is diff --git a/SOURCES/cobbler-pxelinux-s390x-bz580072.patch b/SOURCES/cobbler-pxelinux-s390x-bz580072.patch new file mode 100644 index 0000000..bff36ce --- /dev/null +++ b/SOURCES/cobbler-pxelinux-s390x-bz580072.patch @@ -0,0 +1,14 @@ +--- ./cobbler/pxegen.py.orig 2010-07-28 17:48:48.000000000 +0200 ++++ ./cobbler/pxegen.py 2010-11-24 10:48:30.000000000 +0100 +@@ -91,8 +91,9 @@ + utils.copyfile_pattern('/usr/share/syslinux/menu.c32', dst, api=self.api, logger=self.logger) + + except: +- utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0', dst, api=self.api, logger=self.logger) +- utils.copyfile_pattern('/usr/lib/syslinux/menu.c32', dst, api=self.api, logger=self.logger) ++ if os.path.exists('/usr/lib/syslinux'): ++ utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0', dst, api=self.api, logger=self.logger) ++ utils.copyfile_pattern('/usr/lib/syslinux/menu.c32', dst, api=self.api, logger=self.logger) + + # copy memtest only if we find it + utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api, logger=self.logger) diff --git a/SOURCES/cobbler-remote-addr.patch b/SOURCES/cobbler-remote-addr.patch new file mode 100644 index 0000000..d3822ea --- /dev/null +++ b/SOURCES/cobbler-remote-addr.patch @@ -0,0 +1,13 @@ +diff -rupN cobbler-2.0.7-old/scripts/services.wsgi cobbler-2.0.7/scripts/services.wsgi +--- cobbler-2.0.7-old/scripts/services.wsgi 2014-06-02 10:33:45.037218183 -0400 ++++ cobbler-2.0.7/scripts/services.wsgi 2014-06-02 10:17:09.122312563 -0400 +@@ -58,6 +58,9 @@ def application(environ, start_response) + # like: eth0 XX:XX:XX:XX:XX:XX + form["REMOTE_MAC"] = form.get("HTTP_X_RHN_PROVISIONING_MAC_0", None) + ++ # Save client remote address for ks/triggers ++ form["REMOTE_ADDR"] = environ['REMOTE_ADDR'] ++ + # Read config for the XMLRPC port to connect to: + fd = open("/etc/cobbler/settings") + data = fd.read() diff --git a/SOURCES/cobbler-rhel6-bonding.patch b/SOURCES/cobbler-rhel6-bonding.patch new file mode 100644 index 0000000..e25ca09 --- /dev/null +++ b/SOURCES/cobbler-rhel6-bonding.patch @@ -0,0 +1,13 @@ +diff -rupN cobbler-old/snippets/post_install_network_config cobbler-new/snippets/post_install_network_config +--- cobbler-old/snippets/post_install_network_config 2012-08-24 15:20:57.543943513 -0400 ++++ cobbler-new/snippets/post_install_network_config 2012-08-24 15:28:14.298918151 -0400 +@@ -41,7 +41,9 @@ + ## ============================================================================= + #set $i = 0 + ## setup bonding if we have to ++ ## if using bonding, must have an /ect/modpobe.conf file + #if $numbondingdevs > 0 ++touch "/etc/modprobe.conf" + if [ -f "/etc/modprobe.conf" ]; then + echo "options bonding max_bonds=$numbondingdevs" >> /etc/modprobe.conf + fi diff --git a/SOURCES/cobbler-rhel7-distros.patch b/SOURCES/cobbler-rhel7-distros.patch new file mode 100644 index 0000000..d562fc6 --- /dev/null +++ b/SOURCES/cobbler-rhel7-distros.patch @@ -0,0 +1,12 @@ +diff -rupN cobbler-2.0.7-old/cobbler/codes.py cobbler-2.0.7-new/cobbler/codes.py +--- cobbler-2.0.7-old/cobbler/codes.py 2014-04-09 15:09:16.712074725 -0400 ++++ cobbler-2.0.7-new/cobbler/codes.py 2014-04-09 15:20:32.775951259 -0400 +@@ -41,7 +41,7 @@ VALID_OS_BREEDS = [ + ] + + VALID_OS_VERSIONS = { +- "redhat" : [ "rhel2.1", "rhel3", "rhel4", "rhel5", "rhel6", "fedora5", "fedora6", "fedora7", "fedora8", "fedora9", "fedora10", "fedora11", "fedora12", "fedora13", "fedora14", "fedora15", "generic24", "generic26", "virtio26", "other" ], ++ "redhat" : [ "rhel2.1", "rhel3", "rhel4", "rhel5", "rhel5.4", "rhel6", "rhel7", "fedora5", "fedora6", "fedora7", "fedora8", "fedora9", "fedora10", "fedora11", "fedora12", "fedora13", "fedora14", "fedora15", "fedora16", "fedora17", "fedora18", "fedora19", "fedora20", "generic24", "generic26", "virtio26", "other" ], + "suse" : [ "sles10", "generic24", "generic26", "virtio26", "other" ], + "debian" : [ "etch", "lenny", "generic24", "generic26", "other" ], + "ubuntu" : [ "dapper", "hardy", "intrepid", "jaunty" ], diff --git a/SOURCES/cobbler-rhel7-snippets.patch b/SOURCES/cobbler-rhel7-snippets.patch new file mode 100644 index 0000000..f1754d5 --- /dev/null +++ b/SOURCES/cobbler-rhel7-snippets.patch @@ -0,0 +1,54 @@ +--- a/snippets/post_install_network_config ++++ a/snippets/post_install_network_config +@@ -147,7 +147,7 @@ fi + #if $configbymac and $is_vlan == "false" and $bonding.lower() != "master" + ## This is the code path physical interfaces will follow. + ## Get the current interface name +-IFNAME=\$(ifconfig -a | grep -i '$mac' | cut -d ' ' -f 1) ++IFNAME=\$(ip -o link | grep -i '$mac' | sed -e 's/^[0-9]*: //' -e 's/:.*//') + ## Rename this interface in modprobe.conf + ## FIXME: if both interfaces startwith eth this is wrong + if [ -f "/etc/modprobe.conf" ] && [ \$IFNAME ]; then +--- a/snippets/pre_install_network_config ++++ a/snippets/pre_install_network_config +@@ -1,5 +1,27 @@ + #if $getVar("system_name","") != "" + # Start pre_install_network_config generated code ++#raw ++# generic functions to be used later for discovering NICs ++mac_exists() { ++ if which ip &> /dev/null; then ++ ip -o link | grep -i "$1" &> /dev/null ++ return $? ++ else ++ ifconfig -a | grep -i "$1" &> /dev/null ++ return $? ++ fi ++} ++get_ifname() { ++ if which ip &> /dev/null; then ++ IFNAME=$(ip -o link | grep -i "$1" | sed -e 's/^[0-9]*: //' -e 's/:.*//') ++ else ++ IFNAME=$(ifconfig -a | grep -i "$1" | cut -d " " -f 1) ++ if [ -z $IFNAME ]; then ++ IFNAME=$(ifconfig -a | grep -i -B 2 "$1" | sed -n '/flags/s/:.*$//p') ++ fi ++ fi ++} ++#end raw + #set ikeys = $interfaces.keys() + #import re + #set $vlanpattern = $re.compile("[a-zA-Z0-9]+[\.:][0-9]+") +@@ -75,9 +97,9 @@ + #set $netinfo = "%s --hostname=%s" % ($netinfo, $hostname) + #end if + # Configuring $iname ($mac) +-if ifconfig -a | grep -i $mac ++if mac_exists $mac + then +- IFNAME=\$(ifconfig -a | grep -i '$mac' | cut -d " " -f 1) ++ get_ifname $mac + echo "network --device=\$IFNAME $netinfo" >> /tmp/pre_install_network_config + fi + #else + diff --git a/SOURCES/cobbler-s390-kernel-options.patch b/SOURCES/cobbler-s390-kernel-options.patch new file mode 100644 index 0000000..93c3d2f --- /dev/null +++ b/SOURCES/cobbler-s390-kernel-options.patch @@ -0,0 +1,21 @@ +diff -rupN cobbler-2.0.11.old/cobbler/utils.py cobbler-2.0.11/cobbler/utils.py +--- cobbler-2.0.11.old/cobbler/utils.py.org ++++ cobbler-2.0.11/cobbler/utils.py +@@ -615,9 +615,16 @@ def blender(api_handle,remove_hashes, ro + # hack -- s390 nodes get additional default kernel options + arch = results.get("arch","?") + if arch.startswith("s390"): ++ blacklist_options = [] ++ version = results.get("os_version") ++ if ((version.startswith("rhel") and version >= "rhel7") or ++ (version.startswith("fedora") and version >= "fedora17")): ++ # these options were removed from default kernel options ++ blacklist_options.append("ip") ++ blacklist_options.append("root") + keyz = settings.kernel_options_s390x.keys() + for k in keyz: +- if not results.has_key(k): ++ if not results.has_key(k) and k not in blacklist_options: + results["kernel_options"][k] = settings.kernel_options_s390x[k] + + # Get topmost object to determine which breed we're dealing with diff --git a/SOURCES/cobbler-token-validation.patch b/SOURCES/cobbler-token-validation.patch new file mode 100644 index 0000000..d9c8506 --- /dev/null +++ b/SOURCES/cobbler-token-validation.patch @@ -0,0 +1,25 @@ +diff -up ./cobbler/remote.py.org ./cobbler/remote.py +--- ./cobbler/remote.py 2010-10-07 20:12:03.000000000 +0200 ++++ ./cobbler/remote.py 2011-12-08 16:18:15.984887836 +0100 +@@ -1501,7 +1501,7 @@ class CobblerXMLRPCInterface: + return True + else: + self._log("invalid token",token=token) +- raise CX("invalid token: %s" % token) ++ return False + + def __name_to_object(self,resource,name): + if resource.find("distro") != -1: +@@ -1587,10 +1587,9 @@ class CobblerXMLRPCInterface: + + def token_check(self,token): + """ +- This is a demo function that does not return anything useful. ++ Checks to make sure a token is valid or not + """ +- self.__validate_token(token) +- return True ++ return self.__validate_token(token) + + def sync(self,token): + """ diff --git a/SOURCES/cobbler-triggers.patch b/SOURCES/cobbler-triggers.patch new file mode 100644 index 0000000..9a8829a --- /dev/null +++ b/SOURCES/cobbler-triggers.patch @@ -0,0 +1,23 @@ +diff -rupN cobbler-2.0.7-orig/cobbler/utils.py cobbler-2.0.7/cobbler/utils.py +--- cobbler-2.0.7-orig/cobbler/utils.py 2013-07-17 14:28:09.987051106 -0400 ++++ cobbler-2.0.7/cobbler/utils.py 2013-07-17 14:31:38.577514157 -0400 +@@ -854,7 +854,8 @@ def run_triggers(api,ref,globber,additio + if ref: + arglist.append(ref.name) + for x in additional: +- arglist.append(x) ++ if x: ++ arglist.append(x) + if logger is not None: + logger.debug("running python trigger %s" % m.__name__) + rc = m.run(api, arglist, logger) +@@ -878,7 +879,8 @@ def run_triggers(api,ref,globber,additio + if ref: + arglist.append(ref.name) + for x in additional: +- arglist.append(x) ++ if x: ++ arglist.append(x) + if logger is not None: + logger.debug("running shell trigger %s" % file) + rc = subprocess_call(logger, arglist, shell=False) # close_fds=True) diff --git a/SOURCES/cobbler-unicode-scripts.patch b/SOURCES/cobbler-unicode-scripts.patch new file mode 100644 index 0000000..fd1fb53 --- /dev/null +++ b/SOURCES/cobbler-unicode-scripts.patch @@ -0,0 +1,29 @@ +diff -rupN cobbler-2.0.7.old/cobbler/templar.py cobbler-2.0.7/cobbler/templar.py +--- cobbler-2.0.7.old/cobbler/templar.py 2015-05-06 14:47:57.858206601 -0400 ++++ cobbler-2.0.7/cobbler/templar.py 2015-05-06 14:54:56.002214099 -0400 +@@ -108,6 +108,9 @@ class Templar: + # tell Cheetah not to blow up if it can't find a symbol for something + raw_data = "#errorCatcher ListErrors\n" + raw_data + ++ # specify unicode encoding for Cheetah Compiler ++ raw_data = "#unicode UTF-8\n" + raw_data ++ + table_copy = search_table.copy() + + # for various reasons we may want to call a module inside a template and pass + +diff -rupN cobbler-2.0.7.old/cobbler/template_api.py cobbler-2.0.7/cobbler/template_api.py +--- cobbler-2.0.7.old/cobbler/template_api.py 2015-05-06 14:47:57.860206615 -0400 ++++ cobbler-2.0.7/cobbler/template_api.py 2015-05-06 14:59:50.250384146 -0400 +@@ -311,8 +311,9 @@ class Template(BuiltinTemplate, MacrosTe + pass + + try: +- return utils.read_file_contents('%s/%s' % (self.getVar('snippetsdir'), +- file), fetch_if_remote=True) ++ return "#unicode UTF-8\n" + utils.read_file_contents( ++ '%s/%s' % (self.getVar('snippetsdir'), file), ++ fetch_if_remote=True) + except FileNotFoundException: + return None + diff --git a/SOURCES/cobbler-updating-logrotate-config.patch b/SOURCES/cobbler-updating-logrotate-config.patch new file mode 100644 index 0000000..77e463d --- /dev/null +++ b/SOURCES/cobbler-updating-logrotate-config.patch @@ -0,0 +1,27 @@ +From 33b5fbd7ac2071aa5108c31b468504d8c9916ef8 Mon Sep 17 00:00:00 2001 +From: Tomas Kasparek +Date: Tue, 31 Oct 2017 11:07:14 +0100 +Subject: [PATCH] 1314379 - updating logrotate config to cobbler 2.8 state + +--- + config/cobblerd_rotate | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate +index 1a1fb2f..b529355 100644 +--- a/config/cobblerd_rotate ++++ b/config/cobblerd_rotate +@@ -4,9 +4,7 @@ + rotate 4 + weekly + postrotate +- if [ -f /var/lock/subsys/cobblerd ]; then +- /etc/init.d/cobblerd condrestart > /dev/null +- fi ++ /sbin/service cobblerd condrestart > /dev/null 2>&1 + endscript + } + +-- +2.5.5 + diff --git a/SOURCES/cobbler-uudecode.patch b/SOURCES/cobbler-uudecode.patch new file mode 100644 index 0000000..3b53b62 --- /dev/null +++ b/SOURCES/cobbler-uudecode.patch @@ -0,0 +1,39 @@ +diff -rupN cobbler-2.0.7.old/scripts/services.py cobbler-2.0.7/scripts/services.py +--- cobbler-2.0.7.old/scripts/services.py 2015-07-29 10:26:04.312785484 -0400 ++++ cobbler-2.0.7/scripts/services.py 2015-07-29 08:41:25.904423573 -0400 +@@ -22,6 +22,7 @@ import os + from cobbler.services import CobblerSvc + import yaml # PyYAML version + import cobbler.utils as utils ++import urllib + + #======================================= + +@@ -34,7 +35,7 @@ def handler(req): + but we still need to use the token for all remote requests. + """ + +- my_uri = req.uri ++ my_uri = urllib.unquote(req.uri) + + req.add_common_vars() + +diff -rupN cobbler-2.0.7.old/scripts/services.wsgi cobbler-2.0.7/scripts/services.wsgi +--- cobbler-2.0.7.old/scripts/services.wsgi 2015-07-29 10:26:04.312785484 -0400 ++++ cobbler-2.0.7/scripts/services.wsgi 2015-07-29 10:14:41.360193691 -0400 +@@ -22,13 +22,14 @@ Foundation, Inc., 51 Franklin Street, Fi + import yaml + import os + import xmlrpclib ++import urllib + + from cobbler.services import CobblerSvc + import cobbler.utils as utils + + def application(environ, start_response): + +- my_uri = environ['REQUEST_URI'] ++ my_uri = urllib.unquote(environ['REQUEST_URI']) + + form = {} + diff --git a/SOURCES/cobbler-xenpv-tap-driver.patch b/SOURCES/cobbler-xenpv-tap-driver.patch new file mode 100644 index 0000000..d53c811 --- /dev/null +++ b/SOURCES/cobbler-xenpv-tap-driver.patch @@ -0,0 +1,50 @@ +--- ./koan/xencreate.py 2010-07-28 17:48:48.000000000 +0200 ++++ ./koan/xencreate.py 2011-01-06 23:04:33.000000000 +0100 +@@ -36,11 +36,16 @@ import virtinst + import app as koan + + try: +- import virtinst.DistroManager as DistroManager +-except: +- # older virtinst, this is probably ok +- # but we know we can't do Xen fullvirt installs +- pass ++ from virtinst.DistroManager import PXEInstaller ++ pxe_installer = PXEInstaller() ++except ImportError: ++ try: ++ from virtinst import PXEInstaller ++ pxe_installer = PXEInstaller(os_type='hvm') ++ except: ++ # older virtinst, this is probably ok ++ # but we know we can't do Xen fullvirt installs ++ pass + import traceback + + def random_mac(): +@@ -77,7 +82,7 @@ def start_install(name=None, + + if fullvirt: + # FIXME: add error handling here to explain when it's not supported +- guest = virtinst.FullVirtGuest(installer=DistroManager.PXEInstaller()) ++ guest = virtinst.FullVirtGuest(installer=pxe_installer) + else: + guest = virtinst.ParaVirtGuest() + +@@ -123,7 +128,15 @@ def start_install(name=None, + + for d in disks: + if d[1] != 0 or d[0].startswith("/dev"): +- guest.disks.append(virtinst.XenDisk(d[0], size=d[1])) ++ virtdisk = virtinst.XenDisk(d[0], size=d[1]) ++ ++ # Set driver_name to tap for Xen PV guests ++ if guest.installer and guest.installer.os_type in ('xen', 'linux'): ++ if virtdisk.type == virtinst.XenDisk.TYPE_FILE and \ ++ virtinst._util.is_blktap_capable(): ++ virtdisk.driver_name = virtinst.XenDisk.DRIVER_TAP ++ ++ guest.disks.append(virtdisk) + else: + raise koan.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") + diff --git a/SOURCES/koan-bz1699743.patch b/SOURCES/koan-bz1699743.patch new file mode 100644 index 0000000..50ccdb5 --- /dev/null +++ b/SOURCES/koan-bz1699743.patch @@ -0,0 +1,63 @@ +diff '--exclude=CVS' '--exclude=.svn' -ur a/koan/app.py b/koan/app.py +--- a/koan/app.py 2019-03-21 11:35:58.000000000 +0100 ++++ b/koan/app.py 2019-04-15 15:21:12.954548508 +0200 +@@ -786,12 +786,6 @@ + + #--------------------------------------------------- + +- def get_boot_loader_info(self): +- cmd = [ "/sbin/grubby", "--bootloader-probe" ] +- probe_process = sub_process.Popen(cmd, stdout=sub_process.PIPE) +- which_loader = probe_process.communicate()[0].decode() +- return probe_process.returncode, which_loader +- + def replace(self): + """ + Handle morphing an existing system through downloading new +@@ -862,10 +856,6 @@ + if self.grubby_copy_default: + cmd.append("--copy-default") + +- boot_probe_ret_code, probe_output = self.get_boot_loader_info() +- if boot_probe_ret_code == 0 and probe_output.find("lilo") >= 0: +- cmd.append("--lilo") +- + if self.add_reinstall_entry: + cmd.append("--title=Reinstall") + else: +@@ -879,10 +869,7 @@ + + # Are we running on ppc? + if arch.startswith("ppc"): +- if "grub2" in probe_output: +- cmd.append("--grub2") +- else: +- cmd.append("--yaboot") ++ cmd.append("--grub2") + elif arch.startswith("s390"): + cmd.append("--zipl") + +@@ -900,22 +887,10 @@ + utils.subprocess_call(cmd) + + # Any post-grubby processing required (e.g. ybin, zipl, lilo)? +- if arch.startswith("ppc") and "grub2" not in probe_output: +- # FIXME - CHRP hardware uses a 'PPC PReP Boot' partition and doesn't require running ybin +- print("- applying ybin changes") +- cmd = [ "/sbin/ybin" ] +- utils.subprocess_call(cmd) +- elif arch.startswith("s390"): ++ if arch.startswith("s390"): + print("- applying zipl changes") + cmd = [ "/sbin/zipl" ] + utils.subprocess_call(cmd) +- else: +- # if grubby --bootloader-probe returns lilo, +- # apply lilo changes +- if boot_probe_ret_code == 0 and probe_output.find("lilo") != -1: +- print("- applying lilo changes") +- cmd = [ "/sbin/lilo" ] +- utils.subprocess_call(cmd) + + if not self.add_reinstall_entry: + print("- reboot to apply changes") diff --git a/SOURCES/koan-cmdline-length.patch b/SOURCES/koan-cmdline-length.patch new file mode 100644 index 0000000..fe87892 --- /dev/null +++ b/SOURCES/koan-cmdline-length.patch @@ -0,0 +1,51 @@ +--- ./koan/app.py 2011-08-24 11:54:37.985552147 +0200 ++++ ./koan/app.py 2011-08-24 11:55:39.448880750 +0200 +@@ -742,19 +742,19 @@ class Koan: + + # Validate kernel argument length (limit depends on architecture -- + # see asm-*/setup.h). For example: +- # asm-i386/setup.h:#define COMMAND_LINE_SIZE 256 ++ # asm-i386/setup.h:#define COMMAND_LINE_SIZE 2048 + # asm-ia64/setup.h:#define COMMAND_LINE_SIZE 512 + # asm-powerpc/setup.h:#define COMMAND_LINE_SIZE 512 + # asm-s390/setup.h:#define COMMAND_LINE_SIZE 896 +- # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 256 ++ # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 + if arch.startswith("ppc") or arch.startswith("ia64"): + if len(k_args) > 511: + raise InfoException, "Kernel options are too long, 512 chars exceeded: %s" % k_args + elif arch.startswith("s390"): + if len(k_args) > 895: + raise InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args +- elif len(k_args) > 255: +- raise InfoException, "Kernel options are too long, 255 chars exceeded: %s" % k_args ++ elif len(k_args) > 2047: ++ raise InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args + + utils.subprocess_call([ + 'kexec', +@@ -829,11 +829,11 @@ class Koan: + + # Validate kernel argument length (limit depends on architecture -- + # see asm-*/setup.h). For example: +- # asm-i386/setup.h:#define COMMAND_LINE_SIZE 256 ++ # asm-i386/setup.h:#define COMMAND_LINE_SIZE 2048 + # asm-ia64/setup.h:#define COMMAND_LINE_SIZE 512 + # asm-powerpc/setup.h:#define COMMAND_LINE_SIZE 512 + # asm-s390/setup.h:#define COMMAND_LINE_SIZE 896 +- # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 256 ++ # asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 2048 + if not ANCIENT_PYTHON: + if arch.startswith("ppc") or arch.startswith("ia64"): + if len(k_args) > 511: +@@ -841,8 +841,8 @@ class Koan: + elif arch.startswith("s390"): + if len(k_args) > 895: + raise InfoException, "Kernel options are too long, 896 chars exceeded: %s" % k_args +- elif len(k_args) > 255: +- raise InfoException, "Kernel options are too long, 255 chars exceeded: %s" % k_args ++ elif len(k_args) > 2047: ++ raise InfoException, "Kernel options are too long, 2047 chars exceeded: %s" % k_args + + cmd = [ "/sbin/grubby", + "--add-kernel", self.safe_load(profile_data,'kernel_local'), diff --git a/SOURCES/koan-el6-ks-embed.patch b/SOURCES/koan-el6-ks-embed.patch new file mode 100644 index 0000000..71bd275 --- /dev/null +++ b/SOURCES/koan-el6-ks-embed.patch @@ -0,0 +1,11 @@ +--- ./koan/app.py 2011-03-31 20:47:55.360598401 +0200 ++++ ./koan/app.py 2011-03-31 20:48:01.308598402 +0200 +@@ -808,7 +808,7 @@ class Koan: + + (make, version) = utils.os_release() + +- if (make == "centos" and version < 6) or (make == "redhat" and version < 6) or (make == "fedora" and version < 10): ++ if (make == "centos" and version < 6) or (make == "redhat" and version < 7) or (make == "fedora" and version < 10): + + # embed the initrd in the kickstart file because of libdhcp and/or pump + # needing the help due to some DHCP timeout potential in some certain diff --git a/SOURCES/koan-fix-TypeError.patch b/SOURCES/koan-fix-TypeError.patch new file mode 100644 index 0000000..f7f0dbd --- /dev/null +++ b/SOURCES/koan-fix-TypeError.patch @@ -0,0 +1,31 @@ +--- a/koan/app.py 2018-11-12 05:28:52.566167929 -0500 ++++ b/koan/app.py 2018-11-12 05:32:12.017191022 -0500 +@@ -614,7 +614,7 @@ class Koan: + nfs_parser.add_option("--server", dest="server") + + for line in lines: +- match = method_re.match(line) ++ match = method_re.match(line.decode('utf-8')) + if match: + cmd = match.group("urlcmd") + if cmd: +--- a/koan/virtinstall.py 2018-11-12 05:32:23.803251480 -0500 ++++ b/koan/virtinstall.py 2018-11-12 05:32:54.193407368 -0500 +@@ -65,7 +65,7 @@ except: + try: + rc, response = subprocess_get_response( + shlex.split('virt-install --os-variant list')) +- variants = response.split('\n') ++ variants = response.decode('utf-8').split('\n') + for variant in variants: + supported_variants.add(variant.split()[0]) + except: +@@ -73,7 +73,7 @@ except: + # maybe on newer os using osinfo-query? + rc, response = utils.subprocess_get_response( + shlex.split('osinfo-query os')) +- variants = response.split('\n') ++ variants = response.decode('utf-8').split('\n') + for variant in variants: + supported_variants.add(variant.split()[0]) + except: diff --git a/SOURCES/koan-grubby480.patch b/SOURCES/koan-grubby480.patch new file mode 100644 index 0000000..614ce13 --- /dev/null +++ b/SOURCES/koan-grubby480.patch @@ -0,0 +1,11 @@ +--- ./koan/app.py.orig 2018-10-18 09:57:07.010606609 -0400 ++++ ./koan/app.py 2018-10-18 09:57:52.062860372 -0400 +@@ -856,7 +856,7 @@ + cmd = [ "/sbin/grubby", + "--add-kernel", self.safe_load(profile_data,'kernel_local'), + "--initrd", self.safe_load(profile_data,'initrd_local'), +- "--args", "\"%s\"" % k_args ++ "--args", k_args + ] + + if self.grubby_copy_default: diff --git a/SOURCES/koan-no-check_output.patch b/SOURCES/koan-no-check_output.patch new file mode 100644 index 0000000..0dbbda6 --- /dev/null +++ b/SOURCES/koan-no-check_output.patch @@ -0,0 +1,19 @@ +diff -rupN cobbler-2.0.7.old/koan/utils.py cobbler-2.0.7/koan/utils.py +--- cobbler-2.0.7.old/koan/utils.py 2015-01-12 13:33:54.809244930 -0500 ++++ cobbler-2.0.7/koan/utils.py 2015-01-12 14:00:17.264594111 -0500 +@@ -186,11 +186,10 @@ def subprocess_get_response(cmd, ignore_ + rc = 0 + result = "" + if not ANCIENT_PYTHON: +- try: +- result = sub_process.check_output(cmd).strip() +- except sub_process.CalledProcessError, e: +- rc = e.returncode +- result = e.output ++ p = sub_process.Popen(cmd, stdout=sub_process.PIPE, stderr=sub_process.STDOUT) ++ result, stderr = p.communicate() ++ result = result.strip() ++ rc = p.poll() + else: + cmd = string.join(cmd, " ") + print "cmdstr=(%s)" % cmd diff --git a/SOURCES/koan-remove-root-argument.patch b/SOURCES/koan-remove-root-argument.patch new file mode 100644 index 0000000..d2eecbe --- /dev/null +++ b/SOURCES/koan-remove-root-argument.patch @@ -0,0 +1,21 @@ +diff -rupN cobbler-2.0.7.old/koan/app.py cobbler-2.0.7/koan/app.py +--- cobbler-2.0.7.old/koan/app.py.org 2016-03-15 12:58:22.739224509 -0400 ++++ cobbler-2.0.7/koan/app.py 2016-03-15 12:58:49.898224509 -0400 +@@ -922,6 +922,17 @@ class Koan: + + utils.subprocess_call(cmd) + ++ # Need to remove the root= argument to prevent booting the current OS ++ cmd = [ ++ "/sbin/grubby", ++ "--update-kernel", ++ self.safe_load( ++ profile_data, ++ 'kernel_local'), ++ "--remove-args=root"] ++ ++ utils.subprocess_call(cmd) ++ + # Any post-grubby processing required (e.g. ybin, zipl, lilo)? + if not ANCIENT_PYTHON and arch.startswith("ppc") and "grub2" not in probe_output: + # FIXME - CHRP hardware uses a 'PPC PReP Boot' partition and doesn't require running ybin diff --git a/SOURCES/koan-rhel7-initramfs-embedding.patch b/SOURCES/koan-rhel7-initramfs-embedding.patch new file mode 100644 index 0000000..9ebd0cf --- /dev/null +++ b/SOURCES/koan-rhel7-initramfs-embedding.patch @@ -0,0 +1,21 @@ +--- a/koan/app.py ++++ b/koan/app.py +@@ -727,7 +727,7 @@ class Koan: + + (make, version) = utils.os_release() + +- if (make == "centos" and version < 6) or (make == "redhat" and version < 6) or (make == "fedora" and version < 10): ++ if (make == "centos" and version < 8) or (make == "redhat" and version < 8) or (make == "fedora" and version < 10): + + # embed the initrd in the kickstart file because of libdhcp and/or pump + # needing the help due to some DHCP timeout potential in some certain +@@ -808,7 +808,7 @@ class Koan: + + (make, version) = utils.os_release() + +- if (make == "centos" and version < 6) or (make == "redhat" and version < 7) or (make == "fedora" and version < 10): ++ if (make == "centos" and version < 8) or (make == "redhat" and version < 8) or (make == "fedora" and version < 10): + + # embed the initrd in the kickstart file because of libdhcp and/or pump + # needing the help due to some DHCP timeout potential in some certain + diff --git a/SOURCES/koan-rhel7-ppc.patch b/SOURCES/koan-rhel7-ppc.patch new file mode 100644 index 0000000..48d669b --- /dev/null +++ b/SOURCES/koan-rhel7-ppc.patch @@ -0,0 +1,23 @@ +diff -rupN cobbler-2.0.7-old/koan/app.py cobbler-2.0.7-new/koan/app.py +--- cobbler-2.0.7-old/koan/app.py 2014-03-18 13:53:31.916116417 -0400 ++++ cobbler-2.0.7-new/koan/app.py 2014-03-18 13:56:36.590423095 -0400 +@@ -875,14 +875,17 @@ class Koan: + # Are we running on ppc? + if not ANCIENT_PYTHON: + if arch.startswith("ppc"): +- cmd.append("--yaboot") ++ if "grub2" in probe_output: ++ cmd.append("--grub2") ++ else: ++ cmd.append("--yaboot") + elif arch.startswith("s390"): + cmd.append("--zipl") + + utils.subprocess_call(cmd) + + # Any post-grubby processing required (e.g. ybin, zipl, lilo)? +- if not ANCIENT_PYTHON and arch.startswith("ppc"): ++ if not ANCIENT_PYTHON and arch.startswith("ppc") and "grub2" not in probe_output: + # FIXME - CHRP hardware uses a 'PPC PReP Boot' partition and doesn't require running ybin + print "- applying ybin changes" + cmd = [ "/sbin/ybin" ] diff --git a/SOURCES/koan-rhel7-virtinst.patch b/SOURCES/koan-rhel7-virtinst.patch new file mode 100644 index 0000000..89eb507 --- /dev/null +++ b/SOURCES/koan-rhel7-virtinst.patch @@ -0,0 +1,1166 @@ +diff -rupN cobbler-2.0.7-old/koan/app.py cobbler-2.0.7-new/koan/app.py +--- cobbler-2.0.7-old/koan/app.py 2013-11-21 14:02:54.303559201 -0500 ++++ cobbler-2.0.7-new/koan/app.py 2013-11-22 17:54:23.691918000 -0500 +@@ -28,6 +28,7 @@ import random + import os + import traceback + import tempfile ++import shlex + + ANCIENT_PYTHON = 0 + try: +@@ -536,10 +537,13 @@ class Koan: + if not os.path.exists("/usr/bin/qemu-img"): + raise InfoException("qemu package needs to be installed") + # is libvirt new enough? +- cmd = sub_process.Popen("rpm -q python-virtinst", stdout=sub_process.PIPE, shell=True) +- version_str = cmd.communicate()[0] +- if version_str.find("virtinst-0.1") != -1 or version_str.find("virtinst-0.0") != -1: +- raise InfoException("need python-virtinst >= 0.2 to do installs for qemu/kvm") ++ # Note: in some newer distros (like Fedora 19) the python-virtinst package has been ++ # subsumed into virt-install. If we don't have one check to see if we have the other. ++ rc, version_str = utils.subprocess_get_response(shlex.split('rpm -q virt-install'), True) ++ if rc != 0: ++ rc, version_str = utils.subprocess_get_response(shlex.split('rpm -q python-virtinst'), True) ++ if rc != 0 or version_str.find("virtinst-0.1") != -1 or version_str.find("virtinst-0.0") != -1: ++ raise InfoException("need python-virtinst >= 0.2 or virt-install package to do installs for qemu/kvm (depending on your OS)") + + # for vmware + if self.virt_type == "vmware" or self.virt_type == "vmwarew": +@@ -1106,7 +1110,10 @@ class Koan: + Invoke virt guest-install (or tweaked copy thereof) + """ + pd = profile_data +- self.load_virt_modules() ++ # importing can't throw exceptions any more, don't put it in a sub-method ++ import xencreate ++ import qcreate ++ import imagecreate + + arch = self.safe_load(pd,'arch','x86') + kextra = self.calc_kernel_args(pd) +@@ -1180,17 +1187,6 @@ class Koan: + + #--------------------------------------------------- + +- def load_virt_modules(self): +- try: +- import xencreate +- import qcreate +- import imagecreate +- except: +- traceback.print_exc() +- raise InfoException("no virtualization support available, install python-virtinst?") +- +- #--------------------------------------------------- +- + def virt_choose(self, pd): + fullvirt = False + can_poll = None +@@ -1438,6 +1434,10 @@ class Koan: + return "%s/%s-disk%s" % (location, name, offset) + elif not os.path.exists(location) and os.path.isdir(os.path.dirname(location)): + return location ++ elif not os.path.exists(os.path.dirname(location)): ++ print "- creating: %s" % os.path.dirname(location) ++ os.makedirs(os.path.dirname(location)) ++ return location + else: + raise InfoException, "invalid location: %s" % location + elif location.startswith("/dev/"): +diff -rupN cobbler-2.0.7-old/koan/imagecreate.py cobbler-2.0.7-new/koan/imagecreate.py +--- cobbler-2.0.7-old/koan/imagecreate.py 2013-11-21 14:02:54.303559201 -0500 ++++ cobbler-2.0.7-new/koan/imagecreate.py 2013-11-21 14:08:24.214897902 -0500 +@@ -1,7 +1,7 @@ + """ + Virtualization installation functions for image based deployment + +-Copyright 2008 Red Hat, Inc. ++Copyright 2008 Red Hat, Inc and Others. + Bryan Kearney + + Original version based on virt-image +@@ -23,169 +23,9 @@ Foundation, Inc., 51 Franklin Street, Fi + 02110-1301 USA + """ + +-import os, sys, time, stat +-import shutil +-import random +-import exceptions +-import errno +-import virtinst +-try: +- from virtinst import ImageParser, Guest, CapabilitiesParser, VirtualNetworkInterface +-except: +- # if this fails, this is ok, the user just won't be able to use image objects... +- # keeping this dynamic allows this to work on older EL. +- pass +-import libvirt +- +-import app as koan +- +-#FIXME this was copied +-def random_mac(): +- """ +- from xend/server/netif.py +- Generate a random MAC address. +- Uses OUI 00-16-3E, allocated to +- Xensource, Inc. Last 3 fields are random. +- return: MAC address string +- """ +- mac = [ 0x00, 0x16, 0x3e, +- random.randint(0x00, 0x7f), +- random.randint(0x00, 0xff), +- random.randint(0x00, 0xff) ] +- return ':'.join(map(lambda x: "%02x" % x, mac)) +- +- +-def transform_arch(arch): +- if arch == "i386": +- return "i686" +- else: +- return arch +- +-def copy_image(original_file, new_location): +- shutil.copyfile(original_file, new_location) +- return new_location +- +- +-def process_disk(image, boot, file, location, target): +- image_location = copy_image(file, location) +- # Create the disk +- disk = ImageParser.Disk() +- disk.format = "raw" +- disk.file = image_location +- disk.use = "user" +- disk.id = image_location +- image.storage[disk.id] = disk +- +- #Create the drive +- drive = ImageParser.Drive() +- drive.id = image_location +- drive.target = target +- drive.disk = disk +- boot.disks.append(drive) +- #dev api +- #boot.drives.append(drive) +- +- +-def process_networks(domain, guest, profile_data, bridge): +- # Create a bridge or default network for every requested nic. If there are more +- # bridges then nics discard the last one. +- domain.interface = int(profile_data["network_count"]) +- bridges = [] +- #use the provided bridge first +- guest_bridge = bridge +- if guest_bridge is None: +- guest_bridge = profile_data["virt_bridge"] +- +- # Look for commas +- if (guest_bridge is not None) and (len(guest_bridge.strip()) > 0): +- if guest_bridge.find(",") == -1: +- bridges.append(guest_bridge) +- else: +- bridges == guest_bridge.split(",") +- +- for cnt in range(0,domain.interface): +- if cnt < len(bridges): +- nic = VirtualNetworkInterface(random_mac(), type="bridge", bridge = bridges[cnt]) +- #dev api +- #nic = VirtualNetworkInterface(random_mac(), type="bridge", bridge = bridge, conn=guest.conn) +- else: +- default_network = virtinst.util.default_network() +- #dev api +- #default_network = virtinst.util.default_network(guest.conn) +- nic = VirtualNetworkInterface(random_mac(), type=default_network[0], network=default_network[1]) +- guest.nics.append(nic) +- +-def start_install(name=None, +- ram=None, +- disks=None, +- uuid=None, +- extra=None, +- vcpus=None, +- profile_data=None, +- arch=None, +- no_gfx=False, +- fullvirt=False, +- bridge=None, +- virt_type=None, +- virt_auto_boot=None): +- +- #FIXME how to do a non-default connection +- #Can we drive off of virt-type? +- connection = None +- +- if (virt_type is None ) or (virt_type == "auto"): +- connection = virtinst.util.default_connection() +- elif virt_type.lower()[0:3] == "xen": +- connection = "xen" +- else: +- connection = "qemu:///system" +- +- connection = libvirt.open(connection) +- capabilities = virtinst.CapabilitiesParser.parse(connection.getCapabilities()) +- image_arch = transform_arch(arch) +- +- image = ImageParser.Image() +- #dev api +- #image = ImageParser.Image(filename="") #FIXME, ImageParser should take in None +- image.name = name +- +- domain = ImageParser.Domain() +- domain.vcpu = vcpus +- domain.memory = ram +- image.domain = domain +- +- boot = ImageParser.Boot() +- boot.type = "hvm" #FIXME HARDCODED +- boot.loader = "hd" #FIXME HARDCODED +- boot.arch = image_arch +- domain.boots.append(boot) +- +- #FIXME Several issues. Single Disk, type is hardcoded +- #And there is no way to provision with access to "file" +- process_disk(image, boot, profile_data["file"], disks[0][0], "hda") +- +- #FIXME boot_index?? +- installer = virtinst.ImageInstaller(boot_index = 0, image=image, capabilities=capabilities) +- guest = virtinst.FullVirtGuest(connection = connection, installer=installer, arch=image_arch) +- +- extra = extra.replace("&","&") +- +- guest.extraargs = extra +- guest.set_name(name) +- guest.set_memory(ram) +- guest.set_vcpus(vcpus) +- +- if not no_gfx: +- guest.set_graphics("vnc") +- else: +- guest.set_graphics(False) +- +- if uuid is not None: +- guest.set_uuid(uuid) +- +- process_networks(domain, guest, profile_data, bridge) +- +- guest.start_install() +- +- return "use virt-manager or reconnect with virsh console %s" % name +- ++import utils ++import virtinstall ++ ++def start_install(*args, **kwargs): ++ cmd = virtinstall.build_commandline("import", *args, **kwargs) ++ utils.subprocess_call(cmd) +diff -rupN cobbler-2.0.7-old/koan/qcreate.py cobbler-2.0.7-new/koan/qcreate.py +--- cobbler-2.0.7-old/koan/qcreate.py 2013-11-21 14:02:54.304559209 -0500 ++++ cobbler-2.0.7-new/koan/qcreate.py 2013-12-05 18:49:06.355883703 -0500 +@@ -1,8 +1,8 @@ + """ +-Virtualization installation functions. ++Virtualization installation functions. + +-Copyright 2007-2008 Red Hat, Inc. +-Michael DeHaan ++Copyright 2007-2008 Red Hat, Inc and Others. ++Michael DeHaan + + 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 +@@ -20,192 +20,32 @@ Foundation, Inc., 51 Franklin Street, Fi + 02110-1301 USA + + module for creating fullvirt guests via KVM/kqemu/qemu +-requires python-virtinst-0.200. ++requires python-virtinst-0.200 (or virt-install in later distros). + """ + +-import os, sys, time, stat +-import tempfile +-import random +-from optparse import OptionParser +-import exceptions +-import errno +-import re +-import tempfile +-import shutil +-import virtinst +-import app as koan +-try: +- import subprocess +-except: +- import sub_process as subprocess + import utils ++import virtinstall ++from xml.dom.minidom import parseString + +-def random_mac(): +- """ +- from xend/server/netif.py +- Generate a random MAC address. +- Uses OUI 00-16-3E, allocated to +- Xensource, Inc. Last 3 fields are random. +- return: MAC address string +- """ +- mac = [ 0x00, 0x16, 0x3e, +- random.randint(0x00, 0x7f), +- random.randint(0x00, 0xff), +- random.randint(0x00, 0xff) ] +- return ':'.join(map(lambda x: "%02x" % x, mac)) +- +- +-def start_install(name=None, +- ram=None, +- disks=None, +- mac=None, +- uuid=None, +- extra=None, +- vcpus=None, +- profile_data=None, +- arch=None, +- no_gfx=False, +- fullvirt=True, +- bridge=None, +- virt_type=None, +- virt_auto_boot=False): +- +- vtype = "qemu" +- if virtinst.util.is_kvm_capable(): +- vtype = "kvm" +- arch = None # let virtinst.FullVirtGuest() default to the host arch +- elif virtinst.util.is_kqemu_capable(): +- vtype = "kqemu" +- print "- using qemu hypervisor, type=%s" % vtype +- +- if arch is not None and arch.lower() in ["x86","i386"]: +- arch = "i686" +- +- guest = virtinst.FullVirtGuest(hypervisorURI="qemu:///system",type=vtype, arch=arch) +- +- if not profile_data.has_key("file"): +- # images don't need to source this +- if not profile_data.has_key("install_tree"): +- raise koan.InfoException("Cannot find install source in kickstart file, aborting.") +- +- +- if not profile_data["install_tree"].endswith("/"): +- profile_data["install_tree"] = profile_data["install_tree"] + "/" +- +- # virt manager doesn't like nfs:// and just wants nfs: +- # (which cobbler should fix anyway) +- profile_data["install_tree"] = profile_data["install_tree"].replace("nfs://","nfs:") +- +- if profile_data.has_key("file"): +- # this is an image based installation +- input_path = profile_data["file"] +- print "- using image location %s" % input_path +- if input_path.find(":") == -1: +- # this is not an NFS path +- guest.cdrom = input_path +- else: +- (tempdir, filename) = utils.nfsmount(input_path) +- guest.cdrom = os.path.join(tempdir, filename) +- +- kickstart = profile_data.get("kickstart","") +- if kickstart != "": +- # we have a (windows?) answer file we have to provide +- # to the ISO. +- print "I want to make a floppy for %s" % kickstart +- floppy_path = utils.make_floppy(kickstart) +- guest.disks.append(virtinst.VirtualDisk(device=virtinst.VirtualDisk.DEVICE_FLOPPY, path=floppy_path)) +- +- +- else: +- guest.location = profile_data["install_tree"] +- +- extra = extra.replace("&","&") +- guest.extraargs = extra +- +- if profile_data.has_key("breed"): +- breed = profile_data["breed"] +- if breed != "other" and breed != "": +- if breed in [ "debian", "suse", "redhat" ]: +- guest.set_os_type("linux") +- elif breed in [ "windows" ]: +- guest.set_os_type("windows") +- else: +- guest.set_os_type("unix") +- if profile_data.has_key("os_version"): +- # FIXME: when os_version is not defined and it's linux, do we use generic24/generic26 ? +- version = profile_data["os_version"] +- if version != "other" and version != "": +- try: +- guest.set_os_variant(version) +- except: +- print "- virtinst library does not understand variant %s, treating as generic" % version +- pass +- +- guest.set_name(name) +- guest.set_memory(ram) +- guest.set_vcpus(vcpus) +- # for KVM, we actually can't disable this, since it's the only +- # console it has other than SDL +- guest.set_graphics("vnc") +- +- if uuid is not None: +- guest.set_uuid(uuid) +- +- for d in disks: +- print "- adding disk: %s of size %s" % (d[0], d[1]) +- if d[1] != 0 or d[0].startswith("/dev"): +- guest.disks.append(virtinst.VirtualDisk(d[0], size=d[1])) +- else: +- raise koan.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") +- +- if profile_data.has_key("interfaces"): +- +- counter = 0 +- interfaces = profile_data["interfaces"].keys() +- interfaces.sort() +- vlanpattern = re.compile("[a-zA-Z0-9]+\.[0-9]+") +- for iname in interfaces: +- intf = profile_data["interfaces"][iname] +- +- if intf["bonding"] == "master" or vlanpattern.match(iname) or iname.find(":") != -1: +- continue +- +- mac = intf["mac_address"] +- if mac == "": +- mac = random_mac() +- +- if bridge is None: +- profile_bridge = profile_data["virt_bridge"] +- +- intf_bridge = intf["virt_bridge"] +- if intf_bridge == "": +- if profile_bridge == "": +- raise koan.InfoException("virt-bridge setting is not defined in cobbler") +- intf_bridge = profile_bridge +- else: +- if bridge.find(",") == -1: +- intf_bridge = bridge +- else: +- bridges = bridge.split(",") +- intf_bridge = bridges[counter] +- nic_obj = virtinst.VirtualNetworkInterface(macaddr=mac, bridge=intf_bridge) +- guest.nics.append(nic_obj) +- counter = counter + 1 +- +- else: +- +- if bridge is not None: +- profile_bridge = bridge +- else: +- profile_bridge = profile_data["virt_bridge"] +- +- if profile_bridge == "": +- raise koan.InfoException("virt-bridge setting is not defined in cobbler") +- +- nic_obj = virtinst.VirtualNetworkInterface(macaddr=random_mac(), bridge=profile_bridge) +- guest.nics.append(nic_obj) +- +- guest.start_install() +- +- return "use virt-manager and connect to qemu to manage guest: %s" % name +- ++def start_install(*args, **kwargs): ++ # See http://post-office.corp.redhat.com/archives/satellite-dept-list/2013-December/msg00039.html for discussion on this hack. ++ if 'arch' in kwargs.keys(): ++ kwargs['arch'] = None # use host arch for kvm acceleration ++ ++ # Use kvm acceleration if available ++ try: ++ import libvirt ++ except: ++ raise koan.InfoException("package libvirt is required for installing virtual guests") ++ conn = libvirt.openReadOnly(None) ++ # See http://libvirt.org/formatcaps.html ++ capabilities = parseString(conn.getCapabilities()) ++ for domain in capabilities.getElementsByTagName("domain"): ++ attributes = dict(domain.attributes.items()) ++ if 'type' in attributes.keys() and attributes['type'] == 'kvm': ++ kwargs['virt_type'] = 'kvm' ++ break ++ ++ virtinstall.create_image_file(*args, **kwargs) ++ cmd = virtinstall.build_commandline("qemu:///system", *args, **kwargs) ++ utils.subprocess_call(cmd) +diff -rupN cobbler-2.0.7-old/koan/utils.py cobbler-2.0.7-new/koan/utils.py +--- cobbler-2.0.7-old/koan/utils.py 2013-11-21 14:02:54.303559201 -0500 ++++ cobbler-2.0.7-new/koan/utils.py 2013-12-03 16:14:43.732007939 -0500 +@@ -178,6 +178,27 @@ def subprocess_call(cmd,ignore_rc=0): + raise InfoException, "command failed (%s)" % rc + return rc + ++def subprocess_get_response(cmd, ignore_rc=False): ++ """ ++ Wrapper around subprocess.check_output(...) ++ """ ++ print "- %s" % cmd ++ rc = 0 ++ result = "" ++ if not ANCIENT_PYTHON: ++ try: ++ result = sub_process.check_output(cmd).strip() ++ except sub_process.CalledProcessError, e: ++ rc = e.returncode ++ result = e.output ++ else: ++ cmd = string.join(cmd, " ") ++ print "cmdstr=(%s)" % cmd ++ rc = os.system(cmd) ++ if not ignore_rc and rc != 0: ++ raise InfoException, "command failed (%s)" % rc ++ return rc, result ++ + def input_string_or_hash(options,delim=None,allow_multiples=True): + """ + Older cobbler files stored configurations in a flat way, such that all values for strings. +@@ -267,7 +288,7 @@ def nfsmount(input_path): + shutil.rmtree(tempdir, ignore_errors=True) + raise koan.InfoException("nfs mount failed: %s" % dirpath) + # NOTE: option for a blocking install might be nice, so we could do this +- # automatically, if supported by python-virtinst ++ # automatically, if supported by virt-install + print "after install completes, you may unmount and delete %s" % tempdir + return (tempdir, filename) + +@@ -516,7 +537,7 @@ def make_floppy(kickstart): + if not rc == 0: + raise InfoException("umount failed") + +- # return the path to the completed disk image to pass to virtinst ++ # return the path to the completed disk image to pass to virt-install + return floppy_path + + +diff -rupN cobbler-2.0.7-old/koan/virtinstall.py cobbler-2.0.7-new/koan/virtinstall.py +--- cobbler-2.0.7-old/koan/virtinstall.py 1969-12-31 19:00:00.000000000 -0500 ++++ cobbler-2.0.7-new/koan/virtinstall.py 2013-12-05 17:06:04.469953236 -0500 +@@ -0,0 +1,413 @@ ++""" ++Virtualization installation functions. ++Currently somewhat Xen/paravirt specific, will evolve later. ++ ++Copyright 2006-2008 Red Hat, Inc. ++Michael DeHaan ++ ++Original version based on virtguest-install ++Jeremy Katz ++Option handling added by Andrew Puch ++Simplified for use as library by koan, Michael DeHaan ++ ++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 2 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, write to the Free Software ++Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ++02110-1301 USA ++""" ++ ++import os ++import re ++import shlex ++ ++import app as koan ++import utils ++ ++# The virtinst module will no longer be availabe to import in some ++# distros. We need to get all the info we need from the virt-install ++# command line tool. This should work on both old and new variants, ++# as the virt-install command line tool has always been provided by ++# python-virtinst (and now the new virt-install rpm). ++rc, response = utils.subprocess_get_response( ++ shlex.split('virt-install --version'), True) ++if rc == 0: ++ virtinst_version = response ++else: ++ virtinst_version = None ++ ++# This one's trickier. We need a list of supported os varients, but ++# the man page explicitly says not to parse the result of this command. ++# But we need it, and there's no other way to get it. I spoke with the ++# virt-install maintainers and they said the point of that message ++# is that you can't absolutely depend on the output not changing, but ++# at the moment it's the only option for us. Long term plans are for ++# virt-install to switch to libosinfo for OS metadata tracking, which ++# provides a library and tools for querying valid OS values. Until ++# that's available and pervasive the best we can do is to use the ++# module if it's availabe and if not parse the command output. ++supported_variants = set() ++try: ++ from virtinst import osdict ++ for ostype in osdict.OS_TYPES.keys(): ++ for variant in osdict.OS_TYPES[ostype]["variants"].keys(): ++ supported_variants.add(variant) ++except: ++ try: ++ rc, response = utils.subprocess_get_response( ++ shlex.split('virt-install --os-variant list')) ++ variants = response.split('\n') ++ for variant in variants: ++ supported_variants.add(variant.split()[0]) ++ except: ++ pass # No problem, we'll just use generic ++ ++def _sanitize_disks(disks): ++ ret = [] ++ for d in disks: ++ driver_type = None ++ if len(d) > 2: ++ driver_type = d[2] ++ ++ if d[1] != 0 or d[0].startswith("/dev"): ++ ret.append((d[0], d[1], driver_type)) ++ else: ++ raise koan.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") ++ ++ return ret ++ ++def _sanitize_nics(nics, bridge, profile_bridge, network_count): ++ ret = [] ++ ++ if network_count is not None and not nics: ++ # Fill in some stub nics so we can take advantage of the loop logic ++ nics = {} ++ for i in range(int(network_count)): ++ nics["foo%s" % i] = { ++ "interface_type" : "na", ++ "mac_address": None, ++ "virt_bridge": None, ++ } ++ ++ if not nics: ++ return ret ++ ++ interfaces = nics.keys() ++ interfaces.sort() ++ counter = -1 ++ vlanpattern = re.compile("[a-zA-Z0-9]+\.[0-9]+") ++ ++ for iname in interfaces: ++ counter = counter + 1 ++ intf = nics[iname] ++ ++ if (intf["bonding"] == "master" or vlanpattern.match(iname) or iname.find(":") != -1): ++ continue ++ ++ mac = intf["mac_address"] ++ ++ if not bridge: ++ intf_bridge = intf["virt_bridge"] ++ if intf_bridge == "": ++ if profile_bridge == "": ++ raise koan.InfoException("virt-bridge setting is not defined in cobbler") ++ intf_bridge = profile_bridge ++ ++ else: ++ if bridge.find(",") == -1: ++ intf_bridge = bridge ++ else: ++ bridges = bridge.split(",") ++ intf_bridge = bridges[counter] ++ ++ ret.append((intf_bridge, mac)) ++ ++ return ret ++ ++def create_image_file(disks=None, **kwargs): ++ disks = _sanitize_disks(disks) ++ for path, size, driver_type in disks: ++ if driver_type is None: ++ continue ++ if os.path.isdir(path) or os.path.exists(path): ++ continue ++ if str(size) == "0": ++ continue ++ utils.create_qemu_image_file(path, size, driver_type) ++ ++def build_commandline(uri, ++ name=None, ++ ram=None, ++ disks=None, ++ uuid=None, ++ extra=None, ++ vcpus=None, ++ profile_data=None, ++ arch=None, ++ no_gfx=False, ++ fullvirt=False, ++ bridge=None, ++ virt_type=None, ++ virt_auto_boot=False, ++ virt_pxe_boot=False, ++ qemu_driver_type=None, ++ qemu_net_type=None, ++ qemu_machine_type=None, ++ wait=0, ++ noreboot=False, ++ osimport=False): ++ ++ # Set flags for CLI arguments based on the virtinst_version ++ # tuple above. Older versions of python-virtinst don't have ++ # a version easily accessible, so it will be None and we can ++ # easily disable features based on that (RHEL5 and older usually) ++ ++ disable_autostart = False ++ disable_virt_type = False ++ disable_boot_opt = False ++ disable_driver_type = False ++ disable_net_model = False ++ disable_machine_type = False ++ oldstyle_macs = False ++ oldstyle_accelerate = False ++ ++ if not virtinst_version: ++ print ("- warning: old virt-install detected, a lot of features will be disabled") ++ disable_autostart = True ++ disable_boot_opt = True ++ disable_virt_type = True ++ disable_driver_type = True ++ disable_net_model = True ++ disable_machine_type = True ++ oldstyle_macs = True ++ oldstyle_accelerate = True ++ ++ import_exists = False # avoid duplicating --import parameter ++ disable_extra = False # disable --extra-args on --import ++ if osimport: ++ disable_extra = True ++ ++ is_import = uri.startswith("import") ++ if is_import: ++ # We use the special value 'import' for imagecreate.py. Since ++ # it is connection agnostic, just let virt-install choose the ++ # best hypervisor. ++ uri = "" ++ fullvirt = None ++ ++ is_xen = uri.startswith("xen") ++ is_qemu = uri.startswith("qemu") ++ if is_qemu: ++ if virt_type != "kvm": ++ fullvirt = True ++ else: ++ fullvirt = None ++ ++ floppy = None ++ cdrom = None ++ location = None ++ importpath = None ++ ++ if is_import: ++ importpath = profile_data.get("file") ++ if not importpath: ++ raise koan.InfoException("Profile 'file' required for image " ++ "install") ++ ++ elif profile_data.has_key("file"): ++ if is_xen: ++ raise koan.InfoException("Xen does not work with --image yet") ++ ++ # this is an image based installation ++ input_path = profile_data["file"] ++ print "- using image location %s" % input_path ++ if input_path.find(":") == -1: ++ # this is not an NFS path ++ cdrom = input_path ++ else: ++ (tempdir, filename) = utils.nfsmount(input_path) ++ cdrom = os.path.join(tempdir, filename) ++ ++ kickstart = profile_data.get("kickstart","") ++ if kickstart != "": ++ # we have a (windows?) answer file we have to provide ++ # to the ISO. ++ print "I want to make a floppy for %s" % kickstart ++ floppy = utils.make_floppy(kickstart) ++ elif is_qemu or is_xen: ++ # images don't need to source this ++ if not profile_data.has_key("install_tree"): ++ raise koan.InfoException("Cannot find install source in kickstart file, aborting.") ++ ++ if not profile_data["install_tree"].endswith("/"): ++ profile_data["install_tree"] = profile_data["install_tree"] + "/" ++ ++ location = profile_data["install_tree"] ++ ++ ++ disks = _sanitize_disks(disks) ++ nics = _sanitize_nics(profile_data.get("interfaces"), ++ bridge, ++ profile_data.get("virt_bridge"), ++ profile_data.get("network_count")) ++ if not nics: ++ # for --profile you get one NIC, go define a system if you want more. ++ # FIXME: can mac still be sent on command line in this case? ++ ++ if bridge is None: ++ bridge = profile_data["virt_bridge"] ++ ++ if bridge == "": ++ raise koan.InfoException("virt-bridge setting is not defined in cobbler") ++ nics = [(bridge, None)] ++ ++ ++ kernel = profile_data.get("kernel_local") ++ initrd = profile_data.get("initrd_local") ++ breed = profile_data.get("breed") ++ os_version = profile_data.get("os_version") ++ if os_version and breed == "ubuntu": ++ os_version = "ubuntu%s" % os_version ++ if os_version and breed == "debian": ++ os_version = "debian%s" % os_version ++ ++ net_model = None ++ disk_bus = None ++ machine_type = None ++ ++ if is_qemu: ++ net_model = qemu_net_type ++ disk_bus = qemu_driver_type ++ machine_type = qemu_machine_type ++ ++ if machine_type is None: ++ machine_type = "pc" ++ ++ cmd = "virt-install " ++ if uri: ++ cmd += "--connect %s " % uri ++ ++ cmd += "--name %s " % name ++ cmd += "--ram %s " % ram ++ cmd += "--vcpus %s " % vcpus ++ ++ if uuid: ++ cmd += "--uuid %s " % uuid ++ ++ if virt_auto_boot and not disable_autostart: ++ cmd += "--autostart " ++ ++ if no_gfx: ++ cmd += "--nographics " ++ else: ++ cmd += "--vnc " ++ ++ if is_qemu and virt_type: ++ if not disable_virt_type: ++ cmd += "--virt-type %s " % virt_type ++ ++ if is_qemu and machine_type and not disable_machine_type: ++ cmd += "--machine %s " % machine_type ++ ++ if fullvirt or is_qemu or is_import: ++ if fullvirt is not None: ++ cmd += "--hvm " ++ elif oldstyle_accelerate: ++ cmd += "--accelerate " ++ ++ if is_qemu and extra and not(virt_pxe_boot) and not(disable_extra): ++ cmd += ("--extra-args=\"%s\" " % (extra)) ++ ++ if virt_pxe_boot or is_xen: ++ cmd += "--pxe " ++ elif cdrom: ++ cmd += "--cdrom %s " % cdrom ++ elif location: ++ cmd += "--location %s " % location ++ elif importpath: ++ cmd += "--import " ++ import_exists = True ++ ++ if arch: ++ cmd += "--arch %s " % arch ++ else: ++ cmd += "--paravirt " ++ if not disable_boot_opt: ++ cmd += ("--boot kernel=%s,initrd=%s,kernel_args=\"%s\" " % ++ (kernel, initrd, extra)) ++ else: ++ if location: ++ cmd += "--location %s " % location ++ if extra: ++ cmd += "--extra-args=\"%s\" " % extra ++ ++ if breed and breed != "other": ++ if os_version and os_version != "other": ++ if breed == "suse": ++ suse_version_re = re.compile("^(opensuse[0-9]+)\.([0-9]+)$") ++ if suse_version_re.match(os_version): ++ os_version = suse_version_re.match(os_version).groups()[0] ++ # make sure virt-install knows about our os_version, ++ # otherwise default it to generic26 ++ found = False ++ if os_version in supported_variants: ++ cmd += "--os-variant %s " % os_version ++ else: ++ print ("- warning: virt-install doesn't know this os_version, defaulting to generic26") ++ cmd += "--os-variant generic26 " ++ else: ++ distro = "unix" ++ if breed in [ "debian", "suse", "redhat" ]: ++ distro = "linux" ++ elif breed in [ "windows" ]: ++ distro = "windows" ++ ++ cmd += "--os-type %s " % distro ++ ++ if importpath: ++ # This needs to be the first disk for import to work ++ cmd += "--disk path=%s " % importpath ++ ++ for path, size, driver_type in disks: ++ print ("- adding disk: %s of size %s (driver type=%s)" % ++ (path, size, driver_type)) ++ cmd += "--disk path=%s" % (path) ++ if str(size) != "0": ++ cmd += ",size=%s" % size ++ if disk_bus: ++ cmd += ",bus=%s" % disk_bus ++ if driver_type and not disable_driver_type: ++ cmd += ",format=%s" % driver_type ++ cmd += " " ++ ++ if floppy: ++ cmd += "--disk path=%s,device=floppy " % floppy ++ ++ for bridge, mac in nics: ++ cmd += "--network bridge=%s" % bridge ++ if net_model and not disable_net_model: ++ cmd += ",model=%s" % net_model ++ if mac: ++ if oldstyle_macs: ++ cmd += " --mac=%s" % mac ++ else: ++ cmd += ",mac=%s" % mac ++ cmd += " " ++ ++ cmd += "--wait %d " % int(wait) ++ if noreboot: ++ cmd += "--noreboot " ++ if osimport and not(import_exists): ++ cmd += "--import " ++ cmd += "--noautoconsole " ++ ++ return shlex.split(cmd.strip()) +diff -rupN cobbler-2.0.7-old/koan/xencreate.py cobbler-2.0.7-new/koan/xencreate.py +--- cobbler-2.0.7-old/koan/xencreate.py 2013-11-21 14:02:54.304559209 -0500 ++++ cobbler-2.0.7-new/koan/xencreate.py 2013-11-21 14:05:56.006847361 -0500 +@@ -1,14 +1,14 @@ + """ +-Virtualization installation functions. ++Virtualization installation functions. + Currently somewhat Xen/paravirt specific, will evolve later. + +-Copyright 2006-2008 Red Hat, Inc. +-Michael DeHaan ++Copyright 2006-2008 Red Hat, Inc and Others. ++Michael DeHaan + + Original version based on virtguest-install + Jeremy Katz + Option handling added by Andrew Puch +-Simplified for use as library by koan, Michael DeHaan ++Simplified for use as library by koan, Michael DeHaan + + 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 +@@ -26,180 +26,9 @@ Foundation, Inc., 51 Franklin Street, Fi + 02110-1301 USA + """ + +-import os, sys, time, stat +-import tempfile +-import random +-import exceptions +-import errno +-import re +-import virtinst +-import app as koan +- +-try: +- from virtinst.DistroManager import PXEInstaller +- pxe_installer = PXEInstaller() +-except ImportError: +- try: +- from virtinst import PXEInstaller +- pxe_installer = PXEInstaller(os_type='hvm') +- except: +- # older virtinst, this is probably ok +- # but we know we can't do Xen fullvirt installs +- pass +-import traceback +- +-def random_mac(): +- """ +- from xend/server/netif.py +- Generate a random MAC address. +- Uses OUI 00-16-3E, allocated to +- Xensource, Inc. Last 3 fields are random. +- return: MAC address string +- """ +- mac = [ 0x00, 0x16, 0x3e, +- random.randint(0x00, 0x7f), +- random.randint(0x00, 0xff), +- random.randint(0x00, 0xff) ] +- return ':'.join(map(lambda x: "%02x" % x, mac)) +- +- +-def start_install(name=None, +- ram=None, +- disks=None, +- uuid=None, +- extra=None, +- vcpus=None, +- profile_data=None, +- arch=None, +- no_gfx=False, +- fullvirt=False, +- bridge=None, +- virt_type=None, +- virt_auto_boot=False): +- +- if profile_data.has_key("file"): +- raise koan.InfoException("Xen does not work with --image yet") +- +- if fullvirt: +- # FIXME: add error handling here to explain when it's not supported +- guest = virtinst.FullVirtGuest(installer=pxe_installer) +- else: +- guest = virtinst.ParaVirtGuest() +- +- extra = extra.replace("&","&") +- +- if not fullvirt: +- guest.set_boot((profile_data["kernel_local"], profile_data["initrd_local"])) +- # fullvirt OS's will get this from the PXE config (managed by Cobbler) +- guest.extraargs = extra +- else: +- print "- fullvirt mode" +- if profile_data.has_key("breed"): +- breed = profile_data["breed"] +- if breed != "other" and breed != "": +- if breed in [ "debian", "suse", "redhat" ]: +- guest.set_os_type("linux") +- elif breed in [ "windows" ]: +- guest.set_os_type("windows") +- else: +- guest.set_os_type("unix") +- if profile_data.has_key("os_version"): +- # FIXME: when os_version is not defined and it's linux, do we use generic24/generic26 ? +- version = profile_data["os_version"] +- if version != "other" and version != "": +- try: +- guest.set_os_variant(version) +- except: +- print "- virtinst library does not understand variant %s, treating as generic" % version +- pass +- +- +- guest.set_name(name) +- guest.set_memory(ram) +- guest.set_vcpus(vcpus) +- +- if not no_gfx: +- guest.set_graphics("vnc") +- else: +- guest.set_graphics(False) +- +- if uuid is not None: +- guest.set_uuid(uuid) +- +- for d in disks: +- if d[1] != 0 or d[0].startswith("/dev"): +- virtdisk = virtinst.XenDisk(d[0], size=d[1]) +- +- # Set driver_name to tap for Xen PV guests +- if guest.installer and guest.installer.os_type in ('xen', 'linux'): +- if virtdisk.type == virtinst.XenDisk.TYPE_FILE and \ +- virtinst._util.is_blktap_capable(): +- virtdisk.driver_name = virtinst.XenDisk.DRIVER_TAP +- +- guest.disks.append(virtdisk) +- else: +- raise koan.InfoException("this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero") +- +- counter = 0 +- +- if profile_data.has_key("interfaces"): +- +- interfaces = profile_data["interfaces"].keys() +- interfaces.sort() +- counter = -1 +- vlanpattern = re.compile("[a-zA-Z0-9]+\.[0-9]+") +- +- for iname in interfaces: +- counter = counter + 1 +- intf = profile_data["interfaces"][iname] +- +- if intf["bonding"] == "master" or vlanpattern.match(iname) or iname.find(":") != -1: +- continue +- +- mac = intf["mac_address"] +- if mac == "": +- mac = random_mac() +- +- if not bridge: +- profile_bridge = profile_data["virt_bridge"] +- +- intf_bridge = intf["virt_bridge"] +- if intf_bridge == "": +- if profile_bridge == "": +- raise koan.InfoException("virt-bridge setting is not defined in cobbler") +- intf_bridge = profile_bridge +- +- else: +- if bridge.find(",") == -1: +- intf_bridge = bridge +- else: +- bridges = bridge.split(",") +- intf_bridge = bridges[counter] +- +- +- nic_obj = virtinst.XenNetworkInterface(macaddr=mac, bridge=intf_bridge) +- guest.nics.append(nic_obj) +- counter = counter + 1 +- +- else: +- # for --profile you just get one NIC, go define a system if you want more. +- # FIXME: can mac still be sent on command line in this case? +- +- if bridge is None: +- profile_bridge = profile_data["virt_bridge"] +- else: +- profile_bridge = bridge +- +- if profile_bridge == "": +- raise koan.InfoException("virt-bridge setting is not defined in cobbler") +- +- nic_obj = virtinst.XenNetworkInterface(macaddr=random_mac(), bridge=profile_bridge) +- guest.nics.append(nic_obj) +- +- +- +- +- guest.start_install() +- +- return "use virt-manager or reconnect with virsh console %s" % name +- ++import utils ++import virtinstall ++ ++def start_install(*args, **kwargs): ++ cmd = virtinstall.build_commandline("xen:///", *args, **kwargs) ++ utils.subprocess_call(cmd) diff --git a/SOURCES/koan-rhel71.patch b/SOURCES/koan-rhel71.patch new file mode 100644 index 0000000..00f5510 --- /dev/null +++ b/SOURCES/koan-rhel71.patch @@ -0,0 +1,34 @@ +diff -rupN cobbler-2.0.7.old/koan/virtinstall.py cobbler-2.0.7/koan/virtinstall.py +--- cobbler-2.0.7.old/koan/virtinstall.py 2015-01-22 11:11:27.917649507 -0500 ++++ cobbler-2.0.7/koan/virtinstall.py 2015-01-22 11:30:07.849523465 -0500 +@@ -69,7 +69,16 @@ except: + for variant in variants: + supported_variants.add(variant.split()[0]) + except: +- pass # No problem, we'll just use generic ++ try: ++ # maybe on newer os using osinfo-query? ++ rc, response = utils.subprocess_get_response( ++ shlex.split('osinfo-query os')) ++ variants = response.split('\n') ++ for variant in variants: ++ supported_variants.add(variant.split()[0]) ++ except: ++ # okay, probably on old os and we'll just use generic26 ++ pass + + def _sanitize_disks(disks): + ret = [] +@@ -362,6 +371,12 @@ def build_commandline(uri, + found = False + if os_version in supported_variants: + cmd += "--os-variant %s " % os_version ++ elif os_version + ".0" in supported_variants: ++ # osinfo based virt-install only knows about major.minor ++ # variants, not just major variants like it used to. Default ++ # to major.0 variant in that case. Lack of backwards ++ # compatibility in virt-install grumble grumble. ++ cmd += "--os-variant %s" % os_version + ".0 " + else: + print ("- warning: virt-install doesn't know this os_version, defaulting to generic26") + cmd += "--os-variant generic26 " diff --git a/SOURCES/koan-s390-kernel-options-parse.patch b/SOURCES/koan-s390-kernel-options-parse.patch new file mode 100644 index 0000000..d335101 --- /dev/null +++ b/SOURCES/koan-s390-kernel-options-parse.patch @@ -0,0 +1,12 @@ +diff -rupN cobbler-2.0.7.org/koan/utils.py cobbler-2.0.7/koan/utils.py +--- cobbler-2.0.7.org/koan/utils.py 2015-10-09 08:27:48.306630477 -0400 ++++ cobbler-2.0.7/koan/utils.py 2015-10-09 08:27:11.026630477 -0400 +@@ -213,7 +213,7 @@ def input_string_or_hash(options,delim=N + new_dict = {} + tokens = string.split(options, delim) + for t in tokens: +- tokens2 = string.split(t,"=") ++ tokens2 = string.split(t,"=",1) + if len(tokens2) == 1: + # this is a singleton option, no value + key = tokens2[0] diff --git a/SOURCES/koan-support-kvm-type.patch b/SOURCES/koan-support-kvm-type.patch new file mode 100644 index 0000000..b4b3809 --- /dev/null +++ b/SOURCES/koan-support-kvm-type.patch @@ -0,0 +1,66 @@ +From 53b4932a705653c45228db287fc933a1288b5108 Mon Sep 17 00:00:00 2001 +From: Jan Dobes +Date: Tue, 17 Oct 2017 11:58:51 +0200 +Subject: [PATCH] support kvm type + +copy from Cobbler 2.8 +--- + koan/app.py | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/koan/app.py b/koan/app.py +index bacc135..b7dabb1 100755 +--- a/koan/app.py ++++ b/koan/app.py +@@ -327,10 +327,10 @@ + # if --virt-type was specified and invalid, then fail + if self.virt_type is not None: + self.virt_type = self.virt_type.lower() +- if self.virt_type not in [ "qemu", "xenpv", "xenfv", "xen", "vmware", "vmwarew", "auto" ]: ++ if self.virt_type not in [ "qemu", "xenpv", "xenfv", "xen", "vmware", "vmwarew", "auto", "kvm" ]: + if self.virt_type == "xen": + self.virt_type = "xenpv" +- raise(InfoException("--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, or auto")) ++ raise(InfoException("--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, kvm or auto")) + + # if --qemu-disk-type was called without --virt-type=qemu, then fail + if (self.qemu_disk_type is not None): +@@ -545,7 +545,7 @@ + raise(InfoException("xend needs to be started")) + + # for qemu +- if self.virt_type == "qemu": ++ if self.virt_type in [ "qemu", "kvm" ]: + # qemu package installed? + if not os.path.exists("/usr/bin/qemu-img"): + raise(InfoException("qemu package needs to be installed")) +@@ -1201,7 +1201,7 @@ + if virt_auto_boot: + if self.virt_type in [ "xenpv", "xenfv" ]: + utils.create_xendomains_symlink(virtname) +- elif self.virt_type == "qemu": ++ elif self.virt_type in [ "qemu", "kvm" ]: + utils.libvirt_enable_autostart(virtname) + else: + print("- warning: don't know how to autoboot this virt type yet") +@@ -1225,7 +1225,7 @@ + if self.virt_type == "xenfv": + fullvirt = True + can_poll = "xen" +- elif self.virt_type == "qemu": ++ elif self.virt_type in [ "qemu", "kvm" ]: + fullvirt = True + uuid = None + from koan import qcreate +@@ -1410,7 +1410,7 @@ + # not set in cobbler either? then assume reasonable defaults + if self.virt_type in [ "xenpv", "xenfv" ]: + prefix = "/var/lib/xen/images/" +- elif self.virt_type == "qemu": ++ elif self.virt_type in [ "qemu", "kvm" ]: + prefix = "/var/lib/libvirt/images/" + elif self.virt_type == "vmwarew": + prefix = "/var/lib/vmware/%s/" % name +-- +1.8.3.1 + diff --git a/SOURCES/koan-support-osinfo-query.patch b/SOURCES/koan-support-osinfo-query.patch new file mode 100644 index 0000000..9b12426 --- /dev/null +++ b/SOURCES/koan-support-osinfo-query.patch @@ -0,0 +1,51 @@ +--- a/koan/virtinstall.py 2018-11-12 05:32:23.803251480 -0500 ++++ b/koan/virtinstall.py 2018-11-12 06:40:17.454546012 -0500 +@@ -70,12 +70,13 @@ except: + supported_variants.add(variant.split()[0]) + except: + try: +- # maybe on newer os using osinfo-query? +- rc, response = utils.subprocess_get_response( +- shlex.split('osinfo-query os')) ++ rc, response = subprocess_get_response( ++ shlex.split('osinfo-query -f short-id os')) + variants = response.decode('utf-8').split('\n') + for variant in variants: +- supported_variants.add(variant.split()[0]) ++ supported_variants.add(variant.strip()) ++ # osinfo-query does not list virtio26, add it here for fallback ++ supported_variants.add('virtio26') + except: + # okay, probably on old os and we'll just use generic26 + pass +@@ -367,19 +368,24 @@ def build_commandline(uri, + if suse_version_re.match(os_version): + os_version = suse_version_re.match(os_version).groups()[0] + # make sure virt-install knows about our os_version, +- # otherwise default it to generic26 +- found = False ++ # otherwise default it to virtio26 or generic26 ++ # found = False + if os_version in supported_variants: +- cmd += "--os-variant %s " % os_version ++ pass # os_version is correct + elif os_version + ".0" in supported_variants: + # osinfo based virt-install only knows about major.minor + # variants, not just major variants like it used to. Default + # to major.0 variant in that case. Lack of backwards + # compatibility in virt-install grumble grumble. +- cmd += "--os-variant %s" % os_version + ".0 " ++ os_version = os_version + ".0" + else: +- print(("- warning: virt-install doesn't know this os_version, defaulting to generic26")) +- cmd += "--os-variant generic26 " ++ if "virtio26" in supported_variants: ++ os_version = "virtio26" ++ else: ++ os_version = "generic26" ++ print("- warning: virt-install doesn't know this os_version, " ++ "defaulting to %s" % os_version) ++ cmd += "--os-variant %s " % os_version + else: + distro = "unix" + if breed in [ "debian", "suse", "redhat" ]: diff --git a/SOURCES/koan-virt-install-options.patch b/SOURCES/koan-virt-install-options.patch new file mode 100644 index 0000000..f388050 --- /dev/null +++ b/SOURCES/koan-virt-install-options.patch @@ -0,0 +1,103 @@ +diff -rupN cobbler-2.0.7.old/koan/app.py cobbler-2.0.7.new/koan/app.py +--- cobbler-2.0.7.old/koan/app.py 2014-11-06 15:02:37.476914876 -0500 ++++ cobbler-2.0.7.new/koan/app.py 2014-11-06 15:14:29.830881393 -0500 +@@ -182,6 +182,22 @@ def main(): + dest="embed_kickstart", + action="store_true", + help="When used with --replace-self, embed the kickstart in the initrd to overcome potential DHCP timeout issues. (seldom needed)") ++ p.add_option("", "--qemu-disk-type", ++ dest="qemu_disk_type", ++ help="when used with --virt_type=qemu, add select of disk driver types: ide,scsi,virtio") ++ p.add_option("", "--qemu-net-type", ++ dest="qemu_net_type", ++ help="when used with --virt_type=qemu, select type of network device to use: e1000, ne2k_pci, pcnet, rtl8139, virtio") ++ p.add_option("", "--wait", ++ dest="wait", type='int', default=0, # default to 0 for koan backwards compatibility ++ help="pass the --wait= argument to virt-install") ++ p.add_option("", "--cpu", ++ dest="cpu", ++ help="pass the --cpu argument to virt-install") ++ p.add_option("", "--noreboot", ++ dest="noreboot", default=False, # default to False for koan backwards compatibility ++ action="store_true", ++ help="pass the --noreboot argument to virt-install") + + (options, args) = p.parse_args() + +@@ -209,6 +225,11 @@ def main(): + k.should_poll = options.should_poll + k.embed_kickstart = options.embed_kickstart + k.virt_auto_boot = options.virt_auto_boot ++ k.qemu_disk_type = options.qemu_disk_type ++ k.qemu_net_type = options.qemu_net_type ++ k.virtinstall_cpu = options.cpu ++ k.virtinstall_wait = options.wait ++ k.virtinstall_noreboot= options.noreboot + + if options.virt_name is not None: + k.virt_name = options.virt_name +@@ -264,6 +285,11 @@ class Koan: + self.virt_path = None + self.qemu_disk_type = None + self.virt_auto_boot = None ++ self.qemu_disk_type = None ++ self.qemu_net_type = None ++ self.virtinstall_cpu = None ++ self.virtinstall_wait = None ++ self.virtinstall_noreboot = None + + # This option adds the --copy-default argument to /sbin/grubby + # which uses the default boot entry in the grub.conf +@@ -330,6 +356,18 @@ class Koan: + self.virt_type = "xenpv" + raise InfoException, "--virt-type should be qemu, xenpv, xenfv, vmware, vmwarew, or auto" + ++ # if --qemu-disk-type was called without --virt-type=qemu, then fail ++ if (self.qemu_disk_type is not None): ++ self.qemu_disk_type = self.qemu_disk_type.lower() ++ if self.virt_type not in [ "qemu", "auto", "kvm" ]: ++ raise InfoException, "--qemu-disk-type must use with --virt-type=qemu" ++ ++ # if --qemu-net-type was called without --virt-type=qemu, then fail ++ if (self.qemu_net_type is not None): ++ self.qemu_net_type = self.qemu_net_type.lower() ++ if self.virt_type not in [ "qemu", "auto", "kvm" ]: ++ raise InfoException, "--qemu-net-type must use with --virt-type=qemu" ++ + # if --static-interface and --profile was called together, then fail + if self.static_interface is not None and self.profile is not None: + raise InfoException, "--static-interface option is incompatible with --profile option use --system instead" +@@ -1145,7 +1183,12 @@ class Koan: + fullvirt = fullvirt, + bridge = self.virt_bridge, + virt_type = self.virt_type, +- virt_auto_boot = virt_auto_boot ++ virt_auto_boot = virt_auto_boot, ++ qemu_driver_type = self.qemu_disk_type, ++ qemu_net_type = self.qemu_net_type, ++ cpu = self.virtinstall_cpu, ++ wait = self.virtinstall_wait, ++ noreboot = self.virtinstall_noreboot, + ) + + print results +diff -rupN cobbler-2.0.7.old/koan/virtinstall.py cobbler-2.0.7.new/koan/virtinstall.py +--- cobbler-2.0.7.old/koan/virtinstall.py 2014-11-06 15:02:37.476914876 -0500 ++++ cobbler-2.0.7.new/koan/virtinstall.py 2014-11-06 15:11:11.564499529 -0500 +@@ -162,6 +162,7 @@ def build_commandline(uri, + qemu_driver_type=None, + qemu_net_type=None, + qemu_machine_type=None, ++ cpu=None, + wait=0, + noreboot=False, + osimport=False): +@@ -404,6 +405,8 @@ def build_commandline(uri, + cmd += " " + + cmd += "--wait %d " % int(wait) ++ if cpu: ++ cmd += "--cpu %s " % cpu + if noreboot: + cmd += "--noreboot " + if osimport and not(import_exists): diff --git a/SOURCES/koan-xz-initrd.patch b/SOURCES/koan-xz-initrd.patch new file mode 100644 index 0000000..8cc9db1 --- /dev/null +++ b/SOURCES/koan-xz-initrd.patch @@ -0,0 +1,23 @@ +--- ./koan/app.py 2012-06-21 13:38:43.303738934 +0200 ++++ ./koan/app.py 2012-06-21 13:40:26.704251677 +0200 +@@ -912,7 +912,9 @@ class Koan: + return r""" + cd /var/spool/koan + mkdir initrd +- gzip -dc %s > initrd.tmp ++ if ! gzip -dc %s > initrd.tmp 2> /dev/null; then ++ xz -dc %s > initrd.tmp ++ fi + if mount -o loop -t ext2 initrd.tmp initrd >&/dev/null ; then + cp ks.cfg initrd/ + ln initrd/ks.cfg initrd/tmp/ks.cfg +@@ -927,7 +929,7 @@ class Koan: + find . | cpio -o -H newc | gzip -9 > ../initrd_final + echo "...done" + fi +- """ % initrd ++ """ % (initrd, initrd) + + #--------------------------------------------------- + + diff --git a/SOURCES/koan_no_selinux_set.patch b/SOURCES/koan_no_selinux_set.patch new file mode 100644 index 0000000..9b13178 --- /dev/null +++ b/SOURCES/koan_no_selinux_set.patch @@ -0,0 +1,37 @@ +diff -ru cobbler-2.0.7.orig/koan/app.py cobbler-2.0.7/koan/app.py +--- cobbler-2.0.7.orig/koan/app.py 2013-04-10 10:51:54.775900810 +0200 ++++ cobbler-2.0.7/koan/app.py 2013-04-10 10:52:36.816981814 +0200 +@@ -1491,32 +1491,8 @@ + if lv_create != 0: + raise InfoException, "LVM creation failed" + +- # partition location +- partition_location = "/dev/%s/%s" % (location,name) +- +- # check whether we have SELinux enabled system +- args = "/usr/sbin/selinuxenabled" +- selinuxenabled = sub_process.call(args) +- if selinuxenabled == 0: +- # required context type +- context_type = "virt_image_t" +- +- # change security context type to required one +- args = "/usr/bin/chcon -t %s %s" % (context_type, partition_location) +- print "%s" % args +- change_context = sub_process.call(args, close_fds=True, shell=True) +- +- # modify SELinux policy in order to preserve security context +- # between reboots +- args = "/usr/sbin/semanage fcontext -a -t %s %s" % (context_type, partition_location) +- print "%s" % args +- change_context |= sub_process.call(args, close_fds=True, shell=True) +- +- if change_context != 0: +- raise InfoException, "SELinux security context setting to LVM partition failed" +- + # return partition location +- return partition_location ++ return "/dev/%s/%s" % (location,name) + + else: + raise InfoException, "volume group needs %s GB free space." % virt_size diff --git a/SPECS/cobbler.spec b/SPECS/cobbler.spec new file mode 100644 index 0000000..5350025 --- /dev/null +++ b/SPECS/cobbler.spec @@ -0,0 +1,683 @@ +%if 0%{?fedora} || 0%{?rhel} >= 8 +%global build_py3 1 +%global default_py3 1 +%{!?python3_sitelib: %global python3_sitelib %(%{__python3} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +%endif + +%define pythonX %{?default_py3: python3}%{!?default_py3: python2} + +%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +%define _binaries_in_noarch_packages_terminate_build 0 +%global debug_package %{nil} +Summary: Boot server configurator +Name: cobbler +License: GPLv2+ +AutoReq: no +Version: 2.0.7.1 +Release: 6%{?dist} +Source0: cobbler-%{version}.tar.gz +Patch0: cobbler-pxelinux-s390x-bz580072.patch +Patch1: cobbler-xenpv-tap-driver.patch +Patch2: cobbler-koan-rhpl.patch +Patch3: koan-el6-ks-embed.patch +Patch4: cobbler-disable-check-selinux-bz706857.patch +Patch5: cobbler-disable-hardlinks-bz568801.patch +Patch6: cobbler-no-remove-pub-bz707215.patch +Patch7: cobbler-keep-ssh-snippet.patch +Patch8: cobbler-netaddr.patch +Patch9: cobbler-lvm-installation.patch +Patch10: koan-cmdline-length.patch +Patch11: cobbler-bz-253274.patch +Patch12: cobbler-token-validation.patch +Patch13: cobbler-ipv6-xmlrpc.patch +Patch14: cobbler-ipv6-snippet.patch +Patch15: koan-xz-initrd.patch +Patch16: cobbler-nic-dash.patch +Patch17: cobbler-power-vulnerability.patch +Patch18: cobbler-rhel6-bonding.patch +Patch19: cobbler-catch-cheetah-exception.patch +Patch20: cobbler-lvm-selinux.patch +Patch21: koan_no_selinux_set.patch +Patch22: cobbler-buildiso.patch +Patch23: cobbler-daemon.patch +Patch24: cobbler-rhel7-snippets.patch +Patch25: koan-rhel7-initramfs-embedding.patch +Patch26: cobbler-bootproto-post-install.patch +Patch27: cobbler-triggers.patch +Patch28: cobbler-concurrency.patch +Patch29: koan-rhel7-virtinst.patch +Patch30: koan-rhel7-ppc.patch +Patch31: cobbler-rhel7-distros.patch +Patch32: cobbler-remote-addr.patch +Patch34: cobbler-modprobe-d.patch +Patch35: cobbler-findks.patch +Patch36: koan-virt-install-options.patch +Patch37: cobbler-power-status.patch +Patch38: koan-no-check_output.patch +Patch39: koan-rhel71.patch +Patch40: cobbler-unicode-scripts.patch +Patch41: cobbler-bz1052857.patch +Patch42: buildiso-boot-options.patch +Patch43: cobbler-uudecode.patch +Patch44: buildiso-no-local-hdd.patch +Patch45: cobbler-s390-kernel-options.patch +Patch46: koan-s390-kernel-options-parse.patch +Patch47: koan-remove-root-argument.patch +Patch48: cobbler-updating-logrotate-config.patch +Patch49: cobbler-post-install-network-defaults.patch +Patch50: 0001-exceptions-module-doesn-t-have-to-be-imported.patch +Patch51: 0002-cleanup-ANCIENT_PYTHON-stuff-and-unused-imports.patch +Patch52: 0003-fixing-xmlrpclib-urllib2-and-local-imports-in-Python.patch +Patch53: 0004-Python-3-compatible-prints.patch +Patch54: 0005-Python-3-compatible-exceptions.patch +Patch55: 0006-octal-number-Python-3-fix.patch +Patch56: 0007-Python-3-compatible-string-operations.patch +Patch57: 0008-do-not-require-urlgrabber.patch +Patch58: 0009-replace-iteritems-with-items.patch +Patch59: 0010-open-target-file-in-binary-mode.patch +Patch60: 0011-make-sure-it-s-a-string.patch +Patch61: 0012-make-sure-list-is-returned.patch +Patch62: 0013-Python-3-ethtool-and-indentation-fixes.patch +Patch63: 0014-has_key-is-not-in-Python-3.patch +Patch64: 0015-relative-imports-don-t-work-on-both-Python-2-and-3.patch +Patch65: 0016-keys-and-sort-doesn-t-work-on-Python-3.patch +Patch66: 0017-raise-is-a-function-call-in-python3.patch +Patch67: 0018-adapt-setup.py-for-both-py2-and-py3.patch +Patch68: koan-grubby480.patch +Patch69: koan-fix-TypeError.patch +Patch70: koan-support-osinfo-query.patch +Patch71: koan-support-kvm-type.patch +Patch72: koan-bz1699743.patch + +Group: Applications/System + +%if 0%{?default_py3} +%if 0%{?fedora} && 0%{?fedora} < 21 +BuildRequires: python3-setuptools-devel +%else +BuildRequires: python3-setuptools +%endif +%if 0%{?suse_version} < 0 +BuildRequires: redhat-rpm-config +%endif +BuildRequires: python3-PyYAML +%endif + +Requires: python >= 2.3 +Provides: cobbler2 = %{version}-%{release} + +%if 0%{?suse_version} >= 1000 +Requires: apache2 +Requires: apache2-mod_python +Requires: tftp +%else +Requires: httpd +Requires: tftp-server +%endif + +%if 0%{?rhel} >= 6 +Requires: mod_wsgi +Requires: ipmitool +%else +Requires: mod_python +%endif + +Requires: createrepo +%if 0%{?fedora} >= 11 || 0%{?rhel} >= 6 +Requires: genisoimage +%else +Requires: mkisofs +%endif +Requires: libyaml +Requires: python-cheetah +Requires: python-devel +Requires: python-netaddr +Requires: python-simplejson +Requires: python-urlgrabber +Requires: PyYAML +Requires: rsync +%if 0%{?fedora} >= 6 || 0%{?rhel} >= 5 +Requires: yum-utils +%endif + +Requires(post): /sbin/chkconfig +Requires(preun): /sbin/chkconfig + +Requires(preun): /sbin/service +%if 0%{?fedora} >= 11 || 0%{?rhel} >= 6 +%{!?pyver: %define pyver %(%{__python} -c "import sys ; print sys.version[:3]" || echo 0)} +Requires: python(abi) >= %{pyver} +%endif + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +Url: http://fedorahosted.org/cobbler + +%description + +Cobbler is a network install server. Cobbler +supports PXE, virtualized installs, and +reinstalling existing Linux machines. The last two +modes use a helper tool, 'koan', that +integrates with cobbler. There is also a web interface +'cobbler-web'. Cobbler's advanced features +include importing distributions from DVDs and rsync +mirrors, kickstart templating, integrated yum +mirroring, and built-in DHCP/DNS Management. Cobbler has +a XMLRPC API for integration with other applications. + +%prep +%setup -q +%patch0 -p0 +%patch1 -p0 +%patch2 -p0 +%patch3 -p0 +%patch4 -p0 +%patch5 -p0 +%patch6 -p0 +%patch7 -p0 +%patch8 -p0 +%patch9 -p0 +%patch10 -p0 +%patch11 -p1 +%patch12 -p0 +%patch13 -p1 +%patch14 -p1 +%patch15 -p1 +%patch16 -p1 +%patch17 -p1 +%patch18 -p1 +%patch19 -p1 +%patch20 -p1 +%patch21 -p1 +%patch22 -p1 +%patch23 -p0 +%patch24 -p1 +%patch25 -p1 +%patch26 -p1 +%patch27 -p1 +%patch28 -p1 +%patch29 -p1 +%patch30 -p1 +%patch31 -p1 +%patch32 -p1 +%patch34 -p1 +%patch35 -p1 +%patch36 -p1 +%patch37 -p1 +%patch38 -p1 +%patch39 -p1 +%patch40 -p1 +%patch41 -p1 +%patch42 -p1 +%patch43 -p1 +%patch44 -p1 +%patch45 -p1 +%patch46 -p1 +%patch47 -p1 +%patch48 -p1 +%patch49 -p1 +%patch50 -p1 +%patch51 -p1 +%patch52 -p1 +%patch53 -p1 +%patch54 -p1 +%patch55 -p1 +%patch56 -p1 +%patch57 -p1 +%patch58 -p1 +%patch59 -p1 +%patch60 -p1 +%patch61 -p1 +%patch62 -p1 +%patch63 -p1 +%patch64 -p1 +%patch65 -p1 +%patch66 -p1 +%patch67 -p1 +%patch68 -p1 +%patch69 -p1 +%patch70 -p1 +%patch71 -p1 +%patch72 -p1 + +%build +%{pythonX} setup.py build + +%install +test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT +%if 0%{?suse_version} >= 1000 +PREFIX="--prefix=/usr" +%endif +%if 0%{?build_py3} +sed -i 's|#!/usr/bin/python|#!/usr/bin/python3|' {scripts/koan,scripts/cobbler-register} +%endif +%{pythonX} setup.py install --optimize=1 --root=$RPM_BUILD_ROOT $PREFIX +mkdir -p $RPM_BUILD_ROOT/var/log/koan +mkdir -p $RPM_BUILD_ROOT/var/spool/koan +mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/ +cp docs/cobbler-register.1.gz $RPM_BUILD_ROOT/%{_mandir}/man1/ +cp docs/koan.1.gz $RPM_BUILD_ROOT/%{_mandir}/man1/ +cp koan/*py $RPM_BUILD_ROOT/%{python3_sitelib}/koan/ + +%post +if [ "$1" = "1" ]; +then + # This happens upon initial install. Upgrades will follow the next else + /sbin/chkconfig --add cobblerd +elif [ "$1" -ge "2" ]; +then + # backup config + if [ -e /var/lib/cobbler/distros ]; then + cp /var/lib/cobbler/distros* /var/lib/cobbler/backup 2>/dev/null + cp /var/lib/cobbler/profiles* /var/lib/cobbler/backup 2>/dev/null + cp /var/lib/cobbler/systems* /var/lib/cobbler/backup 2>/dev/null + cp /var/lib/cobbler/repos* /var/lib/cobbler/backup 2>/dev/null + cp /var/lib/cobbler/networks* /var/lib/cobbler/backup 2>/dev/null + fi + if [ -e /var/lib/cobbler/config ]; then + cp -a /var/lib/cobbler/config /var/lib/cobbler/backup 2>/dev/null + fi + # upgrade older installs + # move power and pxe-templates from /etc/cobbler, backup new templates to *.rpmnew + for n in power pxe; do + rm -f /etc/cobbler/$n*.rpmnew + find /etc/cobbler -maxdepth 1 -name "$n*" -type f | while read f; do + newf=/etc/cobbler/$n/`basename $f` + [ -e $newf ] && mv $newf $newf.rpmnew + mv $f $newf + done + done + # upgrade older installs + # copy kickstarts from /etc/cobbler to /var/lib/cobbler/kickstarts + rm -f /etc/cobbler/*.ks.rpmnew + find /etc/cobbler -maxdepth 1 -name "*.ks" -type f | while read f; do + newf=/var/lib/cobbler/kickstarts/`basename $f` + [ -e $newf ] && mv $newf $newf.rpmnew + cp $f $newf + done + # reserialize and restart + # FIXIT: ????? + #/usr/bin/cobbler reserialize + /sbin/service cobblerd condrestart +fi + +%preun +if [ $1 = 0 ]; then + /sbin/service cobblerd stop >/dev/null 2>&1 || : + chkconfig --del cobblerd || : +fi + +%postun +if [ "$1" -ge "1" ]; then + /sbin/service cobblerd condrestart >/dev/null 2>&1 || : + /sbin/service httpd condrestart >/dev/null 2>&1 || : +fi + + +%clean +test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT + +%package -n koan + +Summary: Helper tool that performs cobbler orders on remote machines. +Group: Applications/System +%if 0%{?build_py3} +Requires: python3-koan +%else +Requires: python2-koan +%endif +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +Url: http://fedorahosted.org/cobbler/ + +%description -n koan + +Koan stands for kickstart-over-a-network and allows for both +network installation of new virtualized guests and reinstallation +of an existing system. For use with a boot-server configured with Cobbler + +%files -n koan +%defattr(644,root,root,755) +# FIXME: need to generate in setup.py +%dir /var/spool/koan +%{_mandir}/man1/koan.1.gz +%{_mandir}/man1/cobbler-register.1.gz +%dir /var/log/koan +%doc AUTHORS COPYING CHANGELOG README + +%if 0%{?build_py3} + +%package -n python3-koan + +Summary: Helper tool that performs cobbler orders on remote machines. +Group: Applications/System +%{?__python3:Requires: %{__python3}} +BuildRequires: python3-devel +%if 0%{?fedora} >= 11 || 0%{?rhel} >= 6 +%{!?pyver: %define pyver %(%{__python} -c "import sys ; print sys.version[:3]")} +Requires: python(abi) >= %{pyver} +%endif +%if 0%{?fedora} && 0%{?fedora} < 21 +BuildRequires: python3-setuptools-devel +%else +BuildRequires: python3-setuptools +%endif +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +Url: http://fedorahosted.org/cobbler/ + + +%description -n python3-koan + +Koan stands for kickstart-over-a-network and allows for both +network installation of new virtualized guests and reinstallation +of an existing system. For use with a boot-server configured with Cobbler + +%files -n python3-koan +%{_bindir}/koan +%{_bindir}/cobbler-register +%{python3_sitelib}/koan/ +%{python3_sitelib}/koan-*.egg-info + +%endif + +%changelog +* Tue Apr 23 2019 Michael Mraka 2.0.7.1-6 +- 1699743 - grubby --bootloader-probe has been deprecated + +* Thu Mar 21 2019 Michael Mraka 2.0.7.1-5 +- 1686794 - backported koan kvm support patch + +* Wed Mar 06 2019 Michael Mraka 2.0.7.1-4 +- removed patches which break RHEL8 + +* Tue Mar 05 2019 Michael Mraka 2.0.7.1-3 +- remove broken unused dependency + +* Mon Nov 12 2018 Tomas Kasparek 2.0.7.1-2 +- 1647351 - fix provisioning of RHEL8 virtual guests (tkasparek@redhat.com) + +* Wed Nov 07 2018 Tomas Kasparek 2.0.7.1-1 +- Resolves: rhbz#1647355 - RHEL8 version should be higher than RHEL7 + +* Fri Oct 19 2018 Tomas Kasparek 2.0.7-6 +- 1633713 - Require the Python interpreter directly instead of using the + package name (tkasparek@redhat.com) + +* Thu Oct 18 2018 Michael Mraka 2.0.7-5 +- 1640635 - don't quote kernel args (michael.mraka@redhat.com) +- use python3 dependencies (tkasparek@redhat.com) + +* Tue Jul 24 2018 Tomas Kasparek 2.0.7-4 +- require python3 packages in buildtime (tkasparek@redhat.com) + +* Tue Jul 24 2018 Tomas Kasparek 2.0.7-3 +- fix bogus changelog date (nils@redhat.com) +- remove subpackages we don't ship (nils@redhat.com) +- deactivate main package BRs if we don't build it (nils@redhat.com) +- concentrate main package BRs in one place (nils@redhat.com) +- add tito.props for cobbler (tkasparek@redhat.com) + +* Thu Mar 08 2018 Tomas Kasparek 2.0.7-2 +- reset package release (tkasparek@redhat.com) +- package koan properly (tkasparek@redhat.com) +- python-urlgrabber is no longer needed in koan (tkasparek@redhat.com) +- don't build cobbler subpackage at all (tkasparek@redhat.com) +- build python3 version of koan (tkasparek@redhat.com) +- add python3 patches (tkasparek@redhat.com) +- resue cobbler from Satellite git (tkasparek@redhat.com) + +* Tue Oct 31 2017 Tomas Kasparek 2.0.7-69 +- 1178515 - use empty string when key is not defined in + post_install_network_config snippet +- 1314379 - updating logrotate config to cobbler 2.8 state + +* Mon Apr 25 2016 Tomas Kasparek 2.0.7-68 +- 1208253 - remove the root= argument to prevent booting the current OS + +* Wed Jan 13 2016 Grant Gainey 2.0.7-67 +- 1279986 - Updated version for PPC64LE release + +* Wed Dec 09 2015 Tomas Kasparek 2.0.7-66 +- add system support to --no-local-hdd option without need of profiles + +* Thu Oct 29 2015 Jan Dobes 2.0.7-65 +- 1270676 - split only once for creating key-value pairs + +* Mon Oct 05 2015 Tomas Kasparek 2.0.7-64 +- 1078820 - timeout to 1st available profile with --no-local-hdd instead of + local hdd + +* Wed Sep 09 2015 Tomas Kasparek 2.0.7-63 +- 1199214 - removing kernel options for s390 systems +- add option to skip local harddrive as buildiso entry + +* Wed Jul 29 2015 Grant Gainey 2.0.7-62 +- 1245769 - Apply fix to remaining codepath + +* Tue Jul 28 2015 Grant Gainey 2.0.7-61 +- Having a patch isn't enough, the spec has to actually *apply* it + +* Wed Jul 22 2015 Grant Gainey 2.0.7-60 +- Let cobbler handle urlencoded URLs (see upstream commit + c9a51eec74a66d5034c47f08212177884642d70e for full explanation) + +* Mon Jun 29 2015 Jan Dobes 2.0.7-59 +- Revert "fix adding netmask kernel parameter into isolinux.cfg" + +* Thu Jun 25 2015 Tomas Kasparek 2.0.7-58 +- 1095198 - fixing multiple nameserver boot options on rhel7 and fedora +- fix adding netmask kernel parameter into isolinux.cfg + +* Tue Jun 02 2015 Jan Dobes 2.0.7-57 +- 1227340 - fixing order of cheetah keywords + +* Wed May 20 2015 Grant Gainey 2.0.7-56 +- 1052857 - fix typo in patch + +* Wed May 06 2015 Stephen Herr 2.0.7-55 +- 1052857 - remove python timing window for incorrect file permissions +- 1096263 - set Cheetah templates to use UTF-8 + +* Thu Jan 22 2015 Stephen Herr 2.0.7-54 +- 1184595 - update koan to be compatible with rhel 7.1 virt-install + +* Mon Jan 12 2015 Stephen Herr 2.0.7-53 +- 1181286 - check_output is only available on RHEL 7 + +* Fri Dec 05 2014 Tomas Lestach 2.0.7-52 +- 1169741 - accept more power status messages + +* Mon Dec 01 2014 Stephen Herr 2.0.7-51 +- 1162311 - remove comment from power template + +* Mon Nov 10 2014 Stephen Herr 2.0.7-50 +- 1162337 - install ipmitool by default so that power management will work +- 1162311 - add status power command support to cobbler + +* Thu Nov 06 2014 Stephen Herr 2.0.7-49 +- 1002467 - add optional virt-install options to koan +- cobbler (koan) is in RHN Tools which we build on all RHEL 5/6/7 + +* Fri Sep 26 2014 Stephen Herr 2.0.7-48 +- 1138710 - fixing arm-arch patch, cobbler-2.0.7 does not have cache arg + +* Thu Sep 11 2014 Stephen Herr 2.0.7-47 +- 979966 - auto-trialing-whitespace trim broke patch file +- 905129 - add support for cobbler findks operation + +* Thu Sep 11 2014 Michael Mraka 2.0.7-46 +- 979966 - support modprobe.d on RHEL6 + +* Fri Sep 05 2014 Stephen Herr 2.0.7-45 +- 1138710 - cobbler should support provisioning aarch64 systems + +* Mon Jun 02 2014 Stephen Herr 2.0.7-44 +- 1057785 - set REMOTE_ADDR for cobbler triggers + +* Wed Apr 09 2014 Stephen Herr 2.0.7-43 +- 1051160 - update available distros to include rhel7 + +* Tue Mar 25 2014 Stephen Herr 2.0.7-42 +- 1073822 - koan needs to be grub2 aware for ppc + +* Thu Dec 05 2013 Stephen Herr 2.0.7-41 +- 1029493 - fixing koan guest arch and kvm acceleration issue + +* Fri Nov 22 2013 Stephen Herr 2.0.7-40 +- 1029493 - provisioning virtual guests on rhel 7 fails + +* Thu Oct 24 2013 Stephen Herr 2.0.7-39 +- 1008967 - adding finally blocks to lock releases - cobbler concurrency + +* Mon Oct 21 2013 Stephen Herr 2.0.7-38 +- 1008967 - better concurrency in cobbler + +* Wed Jul 17 2013 Stephen Herr 2.0.7-37 +- 856944 - make cobbler triggers work + +* Fri Jul 12 2013 Stephen Herr 2.0.7-36 +- 506485 - spaces in the source file mean you have to keep spaces in the patch + +* Fri Jul 12 2013 Stephen Herr 2.0.7-35 +- 506485 - cobbler buildiso documentation updates + +* Fri Jul 12 2013 Milan Zazrivec 2.0.7-34 +- 895096 - correctly setup dhcp networking for new systems +- 886609 - Support for ks.cfg initramfs embedding on RHEL-7 +- 883885 - pre/post install_network_config: RHEL-7 support + +* Thu Jul 11 2013 Stephen Herr 2.0.7-33 +- 978601 - fixing cobbler buildiso selinux issue + +* Tue Jun 18 2013 Michael Mraka 2.0.7-32 +- 718238 - detach daemon from terminal + +* Wed May 22 2013 Stephen Herr 2.0.7-29 +- 506485 - Cobbler buildiso changes +- update dist-git branches for cobbler + +* Wed Apr 10 2013 Tomas Lestach 2.0.7-28 +- 768451 - do not set selinux context for patition locations + +* Wed Mar 27 2013 Stephen Herr 2.0.7-27 +- 768451 - bumping build number so the tag won't interfere with SATELLITE-5.5 +- 768451 - actually adding the patch is a good thing +- 768451 - /dev/mapper doesn't work with lvm if selinux is on + +* Tue Mar 19 2013 Michael Mraka 2.0.7-24 +- provide cobbler2 to satisfy deps in spacewalk 1.8+ packages + +* Tue Nov 20 2012 Tomas Lestach 2.0.7-23 +- fix patch to match --fuzz=0 option on rhel6 + +* Tue Nov 20 2012 Tomas Lestach 2.0.7-22 +- 866326 - catch cheetah exception in mod_pythod/mod_wsgi and forward it as 500 + SERVER ERROR + +* Thu Sep 06 2012 Milan Zazrivec 2.0.7-21 +- 784049 - support XZ packes ramdisk: correct bash syntax + +* Fri Aug 24 2012 Stephen Herr 2.0.7-20 +- 589318 - make sure modprobe.conf exists if we need to create a bond +- updating tito configs to move from Satellite-5.x* to Satellite-5.5* branches + +* Tue Jul 03 2012 Stephen Herr 2.0.7-19 +- 836545 - have to convert from unicode to string on RHEL 6 + +* Fri Jun 29 2012 Stephen Herr 2.0.7-18 +- 830662 - fixing power vulnerability patch so that templated commands will run + properly +- 830662 - fixing 'no power type set for system' errors + +* Thu Jun 21 2012 Milan Zazrivec 2.0.7-17 +- 784049 - correct support for xz packed ramdisk + +* Mon Jun 11 2012 Jan Pazdziora 2.0.7-16 +- CVE-2012-2395 - power vulnerability patch. (sherr@redhat.com) +- update build settings for cobbler (mzazrivec@redhat.com) + +* Wed Mar 14 2012 Milan Zazrivec 2.0.7-15 +- 789037 - handle nic with a dash correctly (mzazrivec@redhat.com) +- 784049 - support for XZ packed ramdisk (mzazrivec@redhat.com) +- 784912 - post_install_network snippet: IPv6 support (mzazrivec@redhat.com) +- 717884 - make cobblerd work in IPv6 environment (mzazrivec@redhat.com) + +* Thu Dec 08 2011 Tomas Lestach 2.0.7-14 +- 723060 - fix token validation (tlestach@redhat.com) + +* Fri Sep 23 2011 Miroslav Suchý 2.0.7-13 +- 253274 - if resolving to ip address fail, use hostname + +* Wed Aug 24 2011 Milan Zazrivec 2.0.7-12 +- 728268 - update allowed kernel command line parameter length +- 708347 - fix koan error when provisioning VM to use a logical volume +- 717344 - fix problem with CIDR network notation in RHEL-6 +- 723898 - fix keep_ssh_host_keys snippet + +* Fri May 27 2011 Jan Pazdziora 2.0.7-11 +- 707215 - cobbler should not remove pub during sync as the cobbler rpm owns + that directory. + +* Wed May 25 2011 Jan Pazdziora 2.0.7-10 +- 568801 - hardlinks ruin SELinux contexts because multiple paths match, avoid + hardlinks. + +* Wed May 25 2011 Jan Pazdziora 2.0.7-9 +- 706857 - disable the SELinux part of cobbler check. + +* Thu Mar 31 2011 Milan Zazrivec 2.0.7-8 +- 673388 - embed kickstart file into ramdisk for RHEL-6 and static networking + +* Mon Mar 28 2011 Tomas Lestach 2.0.7-7 +- remove fence-agents Require from cobbler (tlestach@redhat.com) +- We need to be building cobbler / koan for RHEL-4 as well + (mzazrivec@redhat.com) + +* Mon Jan 10 2011 Milan Zazrivec 2.0.7-6 +- 660673 - RHEL-6: replace rhpl with ethtool +- 610174 - use tap driver for Xen PV disks + +* Thu Dec 02 2010 Jan Pazdziora 2.0.7-5 +- 580072 - avoid copying pxelinux.0 on arches where it is not present (s390x). + +* Tue Oct 26 2010 Justin Sherrill 2.0.7-4 +- fixing previous dep for koan to not appear within an if statement + (jsherril@redhat.com) + +* Mon Oct 25 2010 Justin Sherrill 2.0.7-3 +- adding missing python-urlgrabber dep for koan (jsherril@redhat.com) + +* Mon Oct 18 2010 Shannon Hughes 2.0.7-2 +- combine patches into new version build (shughes@redhat.com) +- build.py.props for cobbler (mzazrivec@redhat.com) + +* Mon Oct 18 2010 Scott Henson - 2.0.7-1 +- Bug fix relase, see Changelog for details + +* Tue Jul 13 2010 Scott Henson - 2.0.5-1 +- Bug fix release, see Changelog for details + +* Tue Apr 27 2010 Scott Henson - 2.0.4-1 +- Bug fix release, see Changelog for details + +* Mon Mar 1 2010 Scott Henson - 2.0.3.1-3 +- Bump release because I forgot cobbler-web + +* Mon Mar 1 2010 Scott Henson - 2.0.3.1-2 +- Remove requires on mkinitrd as it is not used + +* Mon Feb 15 2010 Scott Henson - 2.0.3.1-1 +- Upstream Brown Paper Bag Release (see CHANGELOG) + +* Thu Feb 11 2010 Scott Henson - 2.0.3-1 +- Upstream changes (see CHANGELOG) + +* Mon Nov 23 2009 John Eckersberg - 2.0.2-1 +- Upstream changes (see CHANGELOG) + +* Tue Sep 15 2009 Michael DeHaan - 2.0.0-1 +- First release with unified spec files +