From 134f6113711e8ced404ac24f66b5e4e6aa6c56d4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 10 Mar 2008 11:40:33 -0400 Subject: Patch to allow overriding gpgcheck, +version bump --- AUTHORS | 1 + CHANGELOG | 3 ++- MANIFEST.in | 1 - cobbler.spec | 4 ++-- cobbler/action_reposync.py | 12 ++++++++++-- setup.py | 2 +- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index a73f8a0..9a0fe8b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Patches and other contributions from: Tru Huynh Matt Hyclak Mihai Ibanescu + Vito Laurenza Adrian Likins David Lutterkort Jim Meyering diff --git a/CHANGELOG b/CHANGELOG index c4d3bab..6cc350f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,9 @@ Cobbler CHANGELOG (all entries mdehaan@redhat.com unless noted otherwise) -* Fri Feb 22 2008 - 0.8.3 +- Mon Mar 10 2008 - 0.9.0 - Make createrepo get run for local cobbler reposync invocations as needed +- patch to allow yumopts to override gpgcheck * Fri Feb 22 2008 - 0.8.2 - fix to webui to allow repos to be edited there on profile page diff --git a/MANIFEST.in b/MANIFEST.in index 357cdcc..ed1d412 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -21,7 +21,6 @@ include scripts/index.py include scripts/cobblerd include scripts/findks.cgi include scripts/nopxe.cgi -include scripts/webui.cgi include scripts/gateway.py include scripts/post_install_trigger.cgi include scripts/cobbler_auth_help diff --git a/cobbler.spec b/cobbler.spec index e56d298..4b90755 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -2,7 +2,7 @@ Summary: Boot server configurator Name: cobbler AutoReq: no -Version: 0.8.2 +Version: 0.9.0 Release: 1%{?dist} Source0: %{name}-%{version}.tar.gz License: GPLv2+ @@ -190,7 +190,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -* Fri Mar 07 2008 Michael DeHaan - 0.8.2-1 +* Fri Mar 07 2008 Michael DeHaan - 0.9.0-1 - Upstream changes (see CHANGELOG) * Wed Feb 20 2008 Michael DeHaan - 0.8.1-1 diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index 32d38bd..c86e0be 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -235,6 +235,8 @@ class RepoSync: config_file = open(fname, "w+") config_file.write("[%s]\n" % repo.name) config_file.write("name=%s\n" % repo.name) + optenabled = False + optgpgcheck = False if output: line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name) config_file.write(line) @@ -242,15 +244,21 @@ class RepoSync: # add them to the file for x in repo.yumopts: config_file.write("%s=%s\n" % (x, repo.yumopts[x])) + if x == "enabled": + optenabled = True + if x == "gpgcheck": + optgpgcheck = True else: line = "baseurl=%s\n" % repo.mirror http_server = "%s:%s" % (self.settings.server, self.settings.http_port) line = line.replace("@@server@@",http_server) config_file.write(line) - config_file.write("enabled=1\n") + if not optenabled: + config_file.write("enabled=1\n") config_file.write("priority=%s\n" % repo.priority) # FIXME: potentially might want a way to turn this on/off on a per-repo basis - config_file.write("gpgcheck=0\n") + if not optgpgcheck: + config_file.write("gpgcheck=0\n") config_file.close() return fname diff --git a/setup.py b/setup.py index cc4eb12..39bba40 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys from distutils.core import setup, Extension import string -VERSION = "0.8.2" +VERSION = "0.9.0" SHORT_DESC = "Network Boot and Update Server" LONG_DESC = """ Cobbler is a network boot and update server. Cobbler supports PXE, provisioning virtualized images, and reinstalling existing Linux machines. The last two modes require a helper tool called 'koan' that integrates with cobbler. Cobbler's advanced features include importing distributions from DVDs and rsync mirrors, kickstart templating, integrated yum mirroring, and built-in DHCP Management. Cobbler has a Python API for integration with other GPL systems management applications. -- cgit From 73857cbd67b62fda81398ba95c81df3abb59f1b1 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 12 Mar 2008 15:33:35 -0400 Subject: Adding patch to send hostname --- AUTHORS | 1 + CHANGELOG | 1 + cobbler/action_sync.py | 2 ++ cobbler/settings.py | 1 + 4 files changed, 5 insertions(+) diff --git a/AUTHORS b/AUTHORS index 9a0fe8b..bcb8137 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ Patches and other contributions from: Máirín Duffy Tru Huynh Matt Hyclak + Pablo Iranzo Gómez Mihai Ibanescu Vito Laurenza Adrian Likins diff --git a/CHANGELOG b/CHANGELOG index 4b1a203..db1303e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ Cobbler CHANGELOG - Mon Mar 10 2008 - 0.9.0 - Make createrepo get run for local cobbler reposync invocations as needed - patch to allow yumopts to override gpgcheck +- applied patch to send hostname from ISC - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 139066e..d6ea178 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -163,6 +163,8 @@ class BootSync: # the label the entry after the hostname if possible if host is not None and host != "": systxt = "\nhost %s {\n" % host + if self.settings.isc_set_host_name: + systxt = systxt + " option host-name = %s;\n" % host else: systxt = "\nhost generic%d {\n" % counter diff --git a/cobbler/settings.py b/cobbler/settings.py index 581403c..e89f254 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -39,6 +39,7 @@ DEFAULTS = { "dnsmasq_conf" : "/etc/dnsmasq.conf", "httpd_bin" : "/usr/sbin/httpd", "http_port" : "80", + "isc_set_host_name" : 0, "kerberos_realm" : "example.org", "kernel_options" : { "lang" : " ", -- cgit From dcdeba285768a48b56121bbdde0f628ba6793dae Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 12 Mar 2008 15:36:59 -0400 Subject: Fix authors file. --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index bcb8137..2481c67 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,7 +12,7 @@ Patches and other contributions from: Máirín Duffy Tru Huynh Matt Hyclak - Pablo Iranzo Gómez + Pablo Iranzo Gómez Mihai Ibanescu Vito Laurenza Adrian Likins -- cgit From 0268b337a80d6e910166d444be49b9be328328d0 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 13 Mar 2008 14:11:17 -0400 Subject: Added patch to allow kopts/ksmeta to be cleared with --kopts=delete on the command line. --- AUTHORS | 1 + CHANGELOG | 1 + cobbler/utils.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 2481c67..b464d5b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,6 +7,7 @@ Cobbler is written & maintained by: Patches and other contributions from: + David Brown James Bowes C. Daniel Chase Máirín Duffy diff --git a/CHANGELOG b/CHANGELOG index db1303e..39e858c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ Cobbler CHANGELOG - Make createrepo get run for local cobbler reposync invocations as needed - patch to allow yumopts to override gpgcheck - applied patch to send hostname from ISC +- added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/cobbler/utils.py b/cobbler/utils.py index 069d440..c6a6154 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -240,7 +240,7 @@ def input_string_or_hash(options,delim=","): if options == "<>": options = {} - if options is None: + if options is None or options == "delete": return (True, {}) elif type(options) == list: raise CX(_("No idea what to do with list: %s") % options) -- cgit From 6d8f6541c02bfb83630b10795a33500fa96e30c4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 17 Mar 2008 16:59:19 -0400 Subject: Apply patch to make disabled repos (using new --yumopts feature) not be used during install. --- cobbler/action_sync.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index d6ea178..2596906 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -30,6 +30,7 @@ import errno import item_distro import item_profile +import item_repo import item_system from Cheetah.Template import Template @@ -430,13 +431,16 @@ class BootSync: buf = "" blended = utils.blender(self.api, False, obj, self.blend_cache) - configs = self.get_repo_filenames(obj,is_profile) + repos = self.repos + for c in configs: name = c.split("/")[-1].replace(".repo","") (is_core, baseurl) = self.analyze_repo_config(c) - buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl) - + for repo in repos: + if repo.name == name: + if not repo.yumopts.has_key('enabled') or repo.yumopts['enabled'] == '1': + buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl) return buf def analyze_repo_config(self, filename): -- cgit From 0ceedaa6657d4b31267a3a7224d3c9db3bd124aa Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 25 Mar 2008 15:55:18 -0400 Subject: tftpboot location discovery for F9 --- CHANGELOG | 1 + cobbler/action_check.py | 11 +++++------ cobbler/settings.py | 9 ++++++++- cobbler/utils.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ config/settings | 1 - 5 files changed, 62 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dcb8b96..28b45ca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ Cobbler CHANGELOG - patch to allow yumopts to override gpgcheck - applied patch to send hostname from ISC - added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete +- tftpboot location is now inferred from xinetd config (added for F9 compat) - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/cobbler/action_check.py b/cobbler/action_check.py index f7bc9d9..a4a09f6 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -17,6 +17,7 @@ import os import re import sub_process import action_sync +import utils from rhpl.translate import _, N_, textdomain, utf8 class BootCheck: @@ -152,17 +153,15 @@ class BootCheck: if os.path.exists(self.settings.tftpd_conf): f = open(self.settings.tftpd_conf) re_disable = re.compile(r'disable.*=.*yes') - found_bootdir = False for line in f.readlines(): if re_disable.search(line): status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : self.settings.tftpd_conf }) - if line.find("-s %s" % self.settings.tftpboot) != -1: - found_bootdir = True - if not found_bootdir: - status.append(_("change 'server_args' to '-s %(args)s' in %(file)s") % { "file" : "/etc/xinetd.d/tftp", "args" : self.settings.tftpboot }) - else: status.append(_("file %(file)s does not exist") % { "file" : self.settings.tftpd_conf }) + + bootloc = utils.tftpboot_location() + if not os.path.exists(bootloc): + status.append(_("directory needs to be created: %s" % bootloc)) def check_dhcpd_conf(self,status): diff --git a/cobbler/settings.py b/cobbler/settings.py index ccaf04d..cdbcabd 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -54,7 +54,7 @@ DEFAULTS = { "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "syslog_port" : 25150, - "tftpboot" : "/tftpboot", + "tftpboot" : -1, # special, see note below "tftpd_bin" : "/usr/sbin/in.tftpd", "tftpd_conf" : "/etc/xinetd.d/tftp", "webdir" : "/var/www/cobbler", @@ -102,7 +102,14 @@ class Settings(serializable.Serializable): if datastruct is None: print _("warning: not loading empty structure for %s") % self.filename() return + self._attributes = datastruct + + # this last attribute is special. In F9, the tftpboot location moves, so + # what we have in settings is not (neccessarily) correct. So instead + # of using settings we determine it by looking at the OS. + self._attributes["tftpboot"] = utils.tftpboot_location() + return self def __getattr__(self,name): diff --git a/cobbler/utils.py b/cobbler/utils.py index c6a6154..86ff41d 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -468,3 +468,51 @@ def fix_mod_python_select_submission(repos): repos = repos.lstrip().rstrip() return repos +def redhat_release(): + if not os.path.exists("/bin/rpm"): + return ("unknown", 0) + args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"] + cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE) + data = cmd.communicate()[0] + data = data.rstrip().lower() + make = "other" + if data.find("redhat") != -1: + make = "redhat" + elif data.find("centos") != -1: + make = "centos" + elif data.find("fedora") != -1: + make = "fedora" + version = data.split("release-")[-1] + rest = 0 + if version.find("-"): + parts = version.split("-") + version = parts[0] + rest = parts[1] + return (make, float(version), rest) + +def tftpboot_location(): + + # if possible, read from TFTP config file to get the location + if os.path.exists("/etc/xinetd.d/tftp"): + fd = open("/etc/xinetd.d/tftp") + lines = fd.read().split("\n") + for line in lines: + if line.find("server_args") != -1: + tokens = line.split(None) + mark = False + for t in tokens: + if t == "-s": + mark = True + elif mark: + return t + + # otherwise, guess based on the distro + (make,version,rest) = redhat_release() + if make == "fedora" and version >= 9: + return "/var/lib/tftpboot" + return "/tftpboot" + +if __name__ == "__main__": + # print redhat_release() + print tftpboot_location() + diff --git a/config/settings b/config/settings index ba709ae..19e8b6b 100644 --- a/config/settings +++ b/config/settings @@ -28,7 +28,6 @@ run_post_install_trigger: 0 server: '127.0.0.1' snippetsdir: /var/lib/cobbler/snippets syslog_port: 25150 -tftpboot: /tftpboot tftpd_bin: /usr/sbin/in.tftpd tftpd_conf: /etc/xinetd.d/tftp webdir: /var/www/cobbler -- cgit From 25a743c875c6af64e51769f7a78d028dd2594aac Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 25 Mar 2008 18:06:32 -0400 Subject: Preliminary support for authentication against LDAP --- CHANGELOG | 1 + cobbler/modules/authn_ldap.py | 105 ++++++++++++++++++++++++++++++++++++ cobbler/modules/authz_configfile.py | 42 +++++++++++++++ cobbler/settings.py | 6 ++- 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 cobbler/modules/authn_ldap.py create mode 100644 cobbler/modules/authz_configfile.py diff --git a/CHANGELOG b/CHANGELOG index 28b45ca..e0e7f05 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ Cobbler CHANGELOG - applied patch to send hostname from ISC - added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete - tftpboot location is now inferred from xinetd config (added for F9 compat) +- added authn_ldap and stub for authz_configfile - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py new file mode 100644 index 0000000..e228db3 --- /dev/null +++ b/cobbler/modules/authn_ldap.py @@ -0,0 +1,105 @@ +""" +Authentication module that uses ldap +Settings in /etc/cobbler/authn_ldap.conf +Choice of authentication module is in /etc/cobbler/modules.conf + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import distutils.sysconfig +#import ConfigParser +import sys +import os +from rhpl.translate import _, N_, textdomain, utf8 +import md5 +import traceback +import ldap +import traceback + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +import cexceptions +import utils +import api as cobbler_api + +#CONFIG_FILE='/etc/cobbler/auth_ldap.conf' + +def register(): + """ + The mandatory cobbler module registration hook. + """ + + return "authn" + +def authenticate(api_handle,username,password): + """ + Validate an ldap bind, returning True/False + """ + + server = api_handle.settings().ldap_server + basedn = api_handle.settings().ldap_base_dn + port = api_handle.settings().ldap_port + tls = api_handle.settings().ldap_tls + + # parse CONFIG_FILE + # server,basedn,port,tls = __parse_config() + + # form our ldap uri based on connection port + if port == '389': + uri = 'ldap://' + server + elif port == '636': + uri = 'ldaps://' + server + else: + uri = 'ldap://' + "%s:%s" % (server,port) + + # connect to LDAP host + dir = ldap.initialize(uri) + + # start_tls if tls is 'on', 'true' or 'yes' + # and we're not already using old-SSL + tls = str(tls).lower() + if port != '636': + if tls in [ "on", "true", "yes", "1" ]: + try: + dir.start_tls_s() + except: + traceback.print_exc() + return False + + # perform a subtree search in basedn to find the full dn of the user + # TODO: what if username is a CN? maybe it goes into the config file as well? + filter = "uid=" + username + result = dir.search_s(basedn, ldap.SCOPE_SUBTREE, filter, []) + if result: + for dn,entry in result: + # uid should be unique so we should only have one result + # ignore entry; we don't need it + pass + else: + print "FAIL 2" + return False + + try: + # attempt to bind as the user + dir.simple_bind_s(dn,password) + dir.unbind() + print "FAIL 1" + return True + except: + traceback.print_exc() + return False + # catch-all + return False + +if __name__ == "__main__": + api_handle = cobbler_api.BootAPI() + # print authenticate(api_handle, "mdehaan", "test1") + print authenticate(api_handle, "mdehaan", "dog8code") + diff --git a/cobbler/modules/authz_configfile.py b/cobbler/modules/authz_configfile.py new file mode 100644 index 0000000..0d41cce --- /dev/null +++ b/cobbler/modules/authz_configfile.py @@ -0,0 +1,42 @@ +""" +Authorization module that allow users listed in +the auth_ldap.conf config file + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import distutils.sysconfig +import ConfigParser +import sys +import os +from rhpl.translate import _, N_, textdomain, utf8 + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +import cexceptions +import utils + +CONFIG_FILE='/etc/cobbler/auth_ldap.conf' + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "authz" + +def authorize(api_handle,user,resource,arg1=None,arg2=None): + """ + Validate a user against a resource. + """ + + # FIXME: implement this, only users in /etc/cobbler/users.conf + # will return 1. Later we'll do authz_ownership.py + + return 0 diff --git a/cobbler/settings.py b/cobbler/settings.py index cdbcabd..495a4b5 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -40,7 +40,11 @@ DEFAULTS = { "httpd_bin" : "/usr/sbin/httpd", "http_port" : "80", "isc_set_host_name" : 0, - "kerberos_realm" : "example.org", + "ldap_server" : "grimlock.devel.redhat.com", + "ldap_base_dn" : "DC=devel,DC=redhat,DC=com", + "ldap_port" : 389, + "ldap_tls" : "on", + "kerberos_realm" : "EXAMPLE.COM", "kernel_options" : { "lang" : " ", "text" : None, -- cgit From b100b25105b45b196190cff6e89fce402c85e0dd Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 25 Mar 2008 18:08:40 -0400 Subject: Add LDAP settings to /var/lib/cobbler/settings --- config/settings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/settings b/config/settings index 19e8b6b..d9b1785 100644 --- a/config/settings +++ b/config/settings @@ -20,6 +20,10 @@ kernel_options: ksdevice: eth0 lang: ' ' text: ~ +ldap_server: "ldap.example.com", +ldap_base_dn: "DC=example,DC=com", +ldap_port: 389, +ldap_tls: 1, manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' -- cgit From 297805a2c498e57556348f3bb28e8f054c2556aa Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 25 Mar 2008 18:28:51 -0400 Subject: Remove test data from git --- cobbler/modules/authn_ldap.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index e228db3..4597f3c 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -100,6 +100,5 @@ def authenticate(api_handle,username,password): if __name__ == "__main__": api_handle = cobbler_api.BootAPI() - # print authenticate(api_handle, "mdehaan", "test1") - print authenticate(api_handle, "mdehaan", "dog8code") + print authenticate(api_handle, "guest", "guest") -- cgit From a6a82750ac3cab01fbafdd689a7ea1f5f6dc0bf7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 26 Mar 2008 12:49:35 -0400 Subject: Updated LDAP and authorization code, plus packaging --- CHANGELOG | 1 + MANIFEST.in | 1 + Makefile | 2 ++ cobbler.spec | 9 +++++---- cobbler/cobblerd.py | 4 ++-- cobbler/demo_connect.py | 16 ++++++++++++---- cobbler/modules/authn_ldap.py | 1 - cobbler/modules/authz_configfile.py | 32 +++++++++++++++++++++++++++----- cobbler/remote.py | 29 +++++++++++++++++------------ config/modules.conf | 2 +- setup.py | 1 + 11 files changed, 69 insertions(+), 29 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e0e7f05..804254a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ Cobbler CHANGELOG - added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete - tftpboot location is now inferred from xinetd config (added for F9 compat) - added authn_ldap and stub for authz_configfile +- authz_configfile allows filtering ldap/other users by config file - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/MANIFEST.in b/MANIFEST.in index ed1d412..4c8ed20 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,6 +10,7 @@ include config/modules.conf include config/auth.conf include config/settings include config/users.digest +include config/users.conf recursive-include templates *.template recursive-include kickstarts *.ks include docs/cobbler.1.gz diff --git a/Makefile b/Makefile index c40b4ed..e742e39 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,12 @@ install: clean manpage devinstall: cp /var/lib/cobbler/settings /tmp/cobbler_settings cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf + cp /etc/cobbler/users.conf /tmp/cobbler_users.conf -cp /etc/cobbler/users.digest /tmp/cobbler_users.digest make install cp /tmp/cobbler_settings /var/lib/cobbler/settings cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf + cp /tmp/cobbler_users.conf /etc/cobbler/users.conf -cp /tmp/cobbler_users.digest /etc/cobbler/users.digest find /var/lib/cobbler/triggers | xargs chmod +x chown -R apache /var/www/cobbler diff --git a/cobbler.spec b/cobbler.spec index aa61bd9..c4cc5a0 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -128,6 +128,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /etc/cobbler/rsync.exclude %config(noreplace) /etc/logrotate.d/cobblerd_rotate %config(noreplace) /etc/cobbler/modules.conf +%config(noreplace) /etc/cobbler/users.conf %dir %{python_sitelib}/cobbler %dir %{python_sitelib}/cobbler/yaml %dir %{python_sitelib}/cobbler/modules @@ -190,14 +191,14 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -<<<<<<< HEAD:cobbler.spec -* Fri Mar 07 2008 Michael DeHaan - 0.9.0-1 -======= +* Wed Mar 26 2008 Michael DeHaan - 0.9.0-1 +- Upstream changes (see CHANGELOG) +- packaged /etc/cobbler/users.conf + * Mon Mar 10 2008 Michael DeHaan - 0.8.3-1 - Upstream changes (see CHANGELOG) * Fri Mar 07 2008 Michael DeHaan - 0.8.2-1 ->>>>>>> master:cobbler.spec - Upstream changes (see CHANGELOG) * Wed Feb 20 2008 Michael DeHaan - 0.8.1-1 diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py index 3c06723..5640aec 100644 --- a/cobbler/cobblerd.py +++ b/cobbler/cobblerd.py @@ -108,7 +108,7 @@ def do_xmlrpc(bootapi, settings, port, logger): # This is the simple XMLRPC API we provide to koan and other # apps that do not need to manage Cobbler's config - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,True) + xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,False) server = remote.CobblerXMLRPCServer(('', port)) server.logRequests = 0 # don't print stuff @@ -124,7 +124,7 @@ def do_xmlrpc(bootapi, settings, port, logger): def do_xmlrpc_rw(bootapi,settings,port,logger): - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,False) + xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True) server = remote.CobblerReadWriteXMLRPCServer(('127.0.0.1', port)) server.logRequests = 0 # don't print stuff logger.debug("XMLRPC (read-write variant) running on %s" % port) diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py index 0fa058b..6397a6b 100644 --- a/cobbler/demo_connect.py +++ b/cobbler/demo_connect.py @@ -11,12 +11,20 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ -from server.xmlrpcclient import ServerProxy +from xmlrpclib import ServerProxy +import optparse if __name__ == "__main__": - sp = ServerProxy("httpu:///var/lib/cobbler/sock") - print sp.login("","") - + p = optparse.OptionParser() + p.add_option("-u","--user",dest="user",default="test") + p.add_option("-p","--pass",dest="password",default="test") + sp = ServerProxy("http://127.0.0.1/cobbler_api_rw") + (options, args) = p.parse_args() + print "- trying to login with user=%s" % options.user + token = sp.login(options.user,options.password) + print "- token: %s" % token + check = sp.check_access(token,"imaginary_method_name") + print "- access ok? %s" % check diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index 4597f3c..6d190bd 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -90,7 +90,6 @@ def authenticate(api_handle,username,password): # attempt to bind as the user dir.simple_bind_s(dn,password) dir.unbind() - print "FAIL 1" return True except: traceback.print_exc() diff --git a/cobbler/modules/authz_configfile.py b/cobbler/modules/authz_configfile.py index 0d41cce..c183721 100644 --- a/cobbler/modules/authz_configfile.py +++ b/cobbler/modules/authz_configfile.py @@ -1,6 +1,8 @@ """ Authorization module that allow users listed in -the auth_ldap.conf config file +/etc/cobbler/users.conf to be permitted to access resources. +For instance, when using authz_ldap, you want to use authn_configfile, +not authz_allowall, which will most likely NOT do what you want. This software may be freely redistributed under the terms of the GNU general public license. @@ -23,7 +25,7 @@ sys.path.insert(0, mod_path) import cexceptions import utils -CONFIG_FILE='/etc/cobbler/auth_ldap.conf' +CONFIG_FILE='/etc/cobbler/users.conf' def register(): """ @@ -31,12 +33,32 @@ def register(): """ return "authz" +def __parse_config(): + if not os.path.exists(CONFIG_FILE): + return [] + config = ConfigParser.SafeConfigParser() + config.read(CONFIG_FILE) + alldata = {} + groups = config.sections() + for g in groups: + alldata[str(g)] = {} + opts = config.options(g) + for o in opts: + alldata[g][o] = 1 + return alldata + + def authorize(api_handle,user,resource,arg1=None,arg2=None): """ Validate a user against a resource. + All users in the file are permitted by this module. """ - # FIXME: implement this, only users in /etc/cobbler/users.conf - # will return 1. Later we'll do authz_ownership.py - + data = __parse_config() + for g in data: + if user in data[g]: + return 1 return 0 + +if __name__ == "__main__": + print __parse_config() diff --git a/cobbler/remote.py b/cobbler/remote.py index 5131323..4b04fcb 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -561,10 +561,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): FIXME: currently looks for users in /etc/cobbler/auth.conf Would be very nice to allow for PAM and/or just Kerberos. """ - if not self.auth_enabled and input_user == "": - return True - if self.auth_enabled and input_user == "": - return False return self.api.authenticate(input_user,input_password) def __validate_token(self,token): @@ -579,11 +575,12 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): self.__invalidate_expired_tokens() self.__invalidate_expired_objects() - if not self.auth_enabled: - user = self.get_user_from_token(token) - if user == "": - self.token_cache[token] = (time.time(), user) # update to prevent timeout - return True + #if not self.auth_enabled: + # user = self.get_user_from_token(token) + # # old stuff, preserving for future usage + # # if user == "": + # # self.token_cache[token] = (time.time(), user) # update to prevent timeout + # # return True if self.token_cache.has_key(token): user = self.get_user_from_token(token) @@ -598,10 +595,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def check_access(self,token,resource,arg1=None,arg2=None): validated = self.__validate_token(token) + user = self.get_user_from_token(token) if not self.auth_enabled: + # for public read-only XMLRPC, permit access + self.log("permitting read-only access") return True - return self.__authorize(token,resource,arg1,arg2) - + rc = self.__authorize(token,resource,arg1,arg2) + self.log("authorization result: %s" % rc) + if not rc: + raise CX(_("authorization failure for user %s" % user)) + return rc def login(self,login_user,login_password): """ @@ -621,7 +624,9 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def __authorize(self,token,resource,arg1=None,arg2=None): user = self.get_user_from_token(token) - if self.api.authorize(user,resource,arg1,arg2): + self.log("calling authorize for resource %s" % resource, user=user) + rc = self.api.authorize(user,resource,arg1,arg2) + if rc: return True else: raise CX(_("user does not have access to resource: %s") % resource) diff --git a/config/modules.conf b/config/modules.conf index 2d60d21..2daf0e4 100644 --- a/config/modules.conf +++ b/config/modules.conf @@ -9,4 +9,4 @@ repo = serializer_yaml module = authn_configfile [authorization] -module = authn_allowall +module = authz_allowall diff --git a/setup.py b/setup.py index 39bba40..8253216 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ if __name__ == "__main__": (etcpath, ['config/modules.conf']), (etcpath, ['config/users.digest']), (etcpath, ['config/rsync.exclude']), + (etcpath, ['config/users.conf']), (initpath, ['config/cobblerd']), (cobpath, ['config/settings']), -- cgit From 602591cae12323012693f49de3ba59516b40c3f5 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 26 Mar 2008 16:02:48 -0400 Subject: Add a --owner to all the objects, plus associated API calls and backend stuff, for use with the (pending real soon now) authz_ownership module. Also updated docs. Incidentally, self.settings.tftpboot is now utils.tftpboot_location() -- which is required because tftpboot moves around. Previously this was masked to still look like a settings variable but I decided to remove the hack. All code using that location has been updated appropriately. --- cobbler/action_check.py | 5 +++-- cobbler/action_litesync.py | 8 +++++--- cobbler/action_sync.py | 23 ++++++++++++----------- cobbler/item.py | 12 +++++++++++- cobbler/item_distro.py | 9 +++++++-- cobbler/item_profile.py | 10 ++++++++-- cobbler/item_repo.py | 5 ++++- cobbler/item_system.py | 7 ++++++- cobbler/modules/cli_distro.py | 5 +++++ cobbler/modules/cli_profile.py | 5 +++++ cobbler/modules/cli_repo.py | 5 +++++ cobbler/modules/cli_system.py | 4 ++++ cobbler/settings.py | 6 ------ cobbler/utils.py | 16 +++++++++++++++- cobbler/webui/master.py | 4 ++-- docs/cobbler.pod | 4 ++++ tests/tests.py | 35 +++++++++++++++++++++++++++++++++++ 17 files changed, 131 insertions(+), 32 deletions(-) diff --git a/cobbler/action_check.py b/cobbler/action_check.py index a4a09f6..1fb4734 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -141,8 +141,9 @@ class BootCheck: """ Check if cobbler.conf's tftpboot directory exists """ - if not os.path.exists(self.settings.tftpboot): - status.append(_("please create directory: %(dirname)s") % { "dirname" : self.settings.tftpboot }) + bootloc = utils.tftpboot_location() + if not os.path.exists(bootloc): + status.append(_("please create directory: %(dirname)s") % { "dirname" : bootloc }) def check_tftpd_conf(self,status): diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index 457f3af..9200a3e 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -66,12 +66,13 @@ class BootLiteSync: self.add_single_profile(k.name) def remove_single_distro(self, name): + bootloc = utils.tftpboot_location() # delete distro YAML file in distros/$name in webdir self.sync.rmfile(os.path.join(self.settings.webdir, "distros", name)) # delete contents of images/$name directory in webdir self.sync.rmtree(os.path.join(self.settings.webdir, "images", name)) # delete contents of images/$name in tftpboot - self.sync.rmtree(os.path.join(self.settings.tftpboot, "images", name)) + self.sync.rmtree(os.path.join(bootloc, "images", name)) # delete potential symlink to tree in webdir/links self.sync.rmfile(os.path.join(self.settings.webdir, "links", name)) @@ -127,6 +128,7 @@ class BootLiteSync: self.sync.retemplate_yum_repos(system,False) def remove_single_system(self, name): + bootloc = utils.tftpboot_location() system_record = self.systems.find(name=name) # rebuild system_list file in webdir self.sync.write_listings() @@ -152,7 +154,7 @@ class BootLiteSync: if distro is not None and distro in [ "ia64", "IA64"]: itanic = True if not itanic: - self.sync.rmfile(os.path.join(self.settings.tftpboot, "pxelinux.cfg", filename)) + self.sync.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) else: - self.sync.rmfile(os.path.join(self.settings.tftpboot, filename)) + self.sync.rmfile(os.path.join(bootloc, filename)) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 2596906..8a0eadf 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -57,14 +57,15 @@ class BootSync: self.repos = config.repos() self.blend_cache = {} self.load_snippet_cache() + self.bootloc = utils.tftpboot_location() def run(self): """ Syncs the current configuration file with the config tree. Using the Check().run_ functions previously is recommended """ - if not os.path.exists(self.settings.tftpboot): - raise CX(_("cannot find directory: %s") % self.settings.tftpboot) + if not os.path.exists(self.bootloc): + raise CX(_("cannot find directory: %s") % self.bootloc) # run pre-triggers... utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*") @@ -104,9 +105,9 @@ class BootSync: for loader in self.settings.bootloaders.keys(): path = self.settings.bootloaders[loader] newname = os.path.basename(path) - destpath = os.path.join(self.settings.tftpboot, newname) + destpath = os.path.join(self.bootloc, newname) self.copyfile(path, destpath) - self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.settings.tftpboot, "menu.c32")) + self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) def write_dhcp_file(self): """ @@ -278,8 +279,8 @@ class BootSync: if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]: # clean out directory contents self.rmtree_contents(path) - self.rmtree_contents(os.path.join(self.settings.tftpboot, "pxelinux.cfg")) - self.rmtree_contents(os.path.join(self.settings.tftpboot, "images")) + self.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) + self.rmtree_contents(os.path.join(self.bootloc, "images")) def copy_distros(self): """ @@ -297,7 +298,7 @@ class BootSync: self.copy_single_distro_files(d) def copy_single_distro_files(self, d): - for dirtree in [self.settings.tftpboot, self.settings.webdir]: + for dirtree in [self.bootloc, self.settings.webdir]: distros = os.path.join(dirtree, "images") distro_dir = os.path.join(distros,d.name) self.mkdir(distro_dir) @@ -763,7 +764,7 @@ class BootSync: # for tftp only ... if distro.arch in [ "x86", "x86_64", "standard"]: # pxelinux wants a file named $name under pxelinux.cfg - f2 = os.path.join(self.settings.tftpboot, "pxelinux.cfg", f1) + f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1) if distro.arch == "ia64": # elilo expects files to be named "$name.conf" in the root # and can not do files based on the MAC address @@ -771,7 +772,7 @@ class BootSync: print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name filename = "%s.conf" % utils.get_config_filename(system,interface=name) - f2 = os.path.join(self.settings.tftpboot, filename) + f2 = os.path.join(self.bootloc, filename) f3 = os.path.join(self.settings.webdir, "systems", f1) @@ -798,7 +799,7 @@ class BootSync: if default is not None: return - fname = os.path.join(self.settings.tftpboot, "pxelinux.cfg", "default") + fname = os.path.join(self.bootloc, "pxelinux.cfg", "default") # read the default template file template_src = open("/etc/cobbler/pxedefault.template") @@ -820,7 +821,7 @@ class BootSync: # save the template. metadata = { "pxe_menu_items" : pxe_menu_items } - outfile = os.path.join(self.settings.tftpboot, "pxelinux.cfg", "default") + outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") self.apply_template(template_data, metadata, outfile) template_src.close() diff --git a/cobbler/item.py b/cobbler/item.py index dadcd23..f51f959 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -51,7 +51,7 @@ class Item(serializable.Serializable): self.clear(is_subobject) # reset behavior differs for inheritance cases self.parent = '' # all objects by default are not subobjects self.children = {} # caching for performance reasons, not serialized - + self.owners = [] self.log_func = self.config.api.log def clear(self): @@ -117,6 +117,16 @@ class Item(serializable.Serializable): self.name = name return True + def set_owners(self,data): + """ + The owners field is a comment unless using an authz module that pays attention to it, + like authz_ownership, which ships with Cobbler but is off by default. Consult the Wiki + docs for more info on CustomizableAuthorization. + """ + owners = utils.input_string_or_list(data) + self.owners = owners + return True + def set_kernel_options(self,options): """ Kernel options are a space delimited list, diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index f52ad0b..c40f002 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -32,6 +32,7 @@ class Distro(item.Item): Reset this object. """ self.name = None + self.owners = [] self.kernel = (None, '<>')[is_subobject] self.initrd = (None, '<>')[is_subobject] self.kernel_options = ({}, '<>')[is_subobject] @@ -60,6 +61,7 @@ class Distro(item.Item): """ self.parent = self.load_item(seed_data,'parent') self.name = self.load_item(seed_data,'name') + self.owners = self.load_item(seed_data,'owners',[]) self.kernel = self.load_item(seed_data,'kernel') self.initrd = self.load_item(seed_data,'initrd') self.kernel_options = self.load_item(seed_data,'kernel_options') @@ -165,7 +167,8 @@ class Distro(item.Item): 'breed' : self.breed, 'source_repos' : self.source_repos, 'parent' : self.parent, - 'depth' : self.depth + 'depth' : self.depth, + 'owners' : self.owners } def printable(self): @@ -181,6 +184,7 @@ class Distro(item.Item): buf = buf + _("architecture : %s\n") % self.arch buf = buf + _("ks metadata : %s\n") % self.ks_meta buf = buf + _("breed : %s\n") % self.breed + buf = buf + _("owners : %s\n") % self.owners return buf def remote_methods(self): @@ -191,7 +195,8 @@ class Distro(item.Item): 'kopts' : self.set_kernel_options, 'arch' : self.set_arch, 'ksmeta' : self.set_ksmeta, - 'breed' : self.set_breed + 'breed' : self.set_breed, + 'owners' : self.set_owners } diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index f229d4c..3145b82 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -34,6 +34,7 @@ class Profile(item.Item): Reset this object. """ self.name = None + self.owners = [] # should not be inheritable self.distro = (None, '<>')[is_subobject] self.kickstart = (self.settings.default_kickstart , '<>')[is_subobject] self.kernel_options = ({}, '<>')[is_subobject] @@ -57,6 +58,7 @@ class Profile(item.Item): self.parent = self.load_item(seed_data,'parent','') self.name = self.load_item(seed_data,'name') + self.owners = self.load_item(seed_data,'owners',[]) self.distro = self.load_item(seed_data,'distro') self.kickstart = self.load_item(seed_data,'kickstart') self.kernel_options = self.load_item(seed_data,'kernel_options') @@ -328,6 +330,7 @@ class Profile(item.Item): """ return { 'name' : self.name, + 'owners' : self.owners, 'distro' : self.distro, 'kickstart' : self.kickstart, 'kernel_options' : self.kernel_options, @@ -342,7 +345,8 @@ class Profile(item.Item): 'virt_type' : self.virt_type, 'virt_path' : self.virt_path, 'dhcp_tag' : self.dhcp_tag, - 'server' : self.server + 'server' : self.server, + } def printable(self): @@ -366,6 +370,7 @@ class Profile(item.Item): buf = buf + _("repos : %s\n") % self.repos buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag buf = buf + _("server : %s\n") % self.server + buf = buf + _("owners : %s\n") % self.owners return buf def remote_methods(self): @@ -385,6 +390,7 @@ class Profile(item.Item): 'virt-bridge' : self.set_virt_bridge, 'virt-cpus' : self.set_virt_cpus, 'dhcp-tag' : self.set_dhcp_tag, - 'server' : self.set_server + 'server' : self.set_server, + 'owners' : self.set_owners } diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index a7b1f3b..d65d09b 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -161,6 +161,7 @@ class Repo(item.Item): def to_datastruct(self): return { 'name' : self.name, + 'owners' : self.owners, 'mirror' : self.mirror, 'keep_updated' : self.keep_updated, 'priority' : self.priority, @@ -174,6 +175,7 @@ class Repo(item.Item): def printable(self): buf = _("repo : %s\n") % self.name + buf = buf + _("owners : %s\n") % self.owners buf = buf + _("mirror : %s\n") % self.mirror buf = buf + _("keep updated : %s\n") % self.keep_updated buf = buf + _("priority : %s\n") % self.priority @@ -210,6 +212,7 @@ class Repo(item.Item): 'priority' : self.set_priority, 'rpm-list' : self.set_rpm_list, 'createrepo-flags' : self.set_createrepo_flags, - 'yumopts' : self.set_yumopts + 'yumopts' : self.set_yumopts, + 'owners' : self.set_owners } diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 936f1dd..51dd063 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -30,6 +30,7 @@ class System(item.Item): def clear(self,is_subobject=False): self.name = None + self.owners = None self.profile = None self.kernel_options = {} self.ks_meta = {} @@ -80,6 +81,7 @@ class System(item.Item): self.parent = self.load_item(seed_data, 'parent') self.name = self.load_item(seed_data, 'name') + self.owners = self.load_item(seed_data, 'owners') self.profile = self.load_item(seed_data, 'profile') self.kernel_options = self.load_item(seed_data, 'kernel_options', {}) self.ks_meta = self.load_item(seed_data, 'ks_meta', {}) @@ -336,6 +338,7 @@ class System(item.Item): def to_datastruct(self): return { 'name' : self.name, + 'owners' : self.set_owners, 'profile' : self.profile, 'kernel_options' : self.kernel_options, 'ks_meta' : self.ks_meta, @@ -360,6 +363,7 @@ class System(item.Item): buf = buf + _("virt type : %s\n") % self.virt_type buf = buf + _("virt path : %s\n") % self.virt_path buf = buf + _("server : %s\n") % self.server + buf = buf + _("owners : %s\n") % self.owners counter = 0 for (name,x) in self.interfaces.iteritems(): @@ -405,6 +409,7 @@ class System(item.Item): 'virt-type' : self.set_virt_type, 'modify-interface' : self.modify_interface, 'delete-interface' : self.delete_interface, - 'server' : self.set_server + 'server' : self.set_server, + 'owners' : self.set_owners } diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py index 35f5a4b..9e0637f 100644 --- a/cobbler/modules/cli_distro.py +++ b/cobbler/modules/cli_distro.py @@ -47,12 +47,15 @@ class DistroFunction(commands.CobblerFunction): p.add_option("--name", dest="name", help="ex: 'RHEL-5-i386' (REQUIRED)") + if self.matches_args(args,["copy","rename"]): p.add_option("--newname", dest="newname", help="for copy/rename commands") if not self.matches_args(args,["remove","report","list"]): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") if not self.matches_args(args,["report","list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") + if not self.matches_args(args,["remove","report","list"]): + p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") if self.matches_args(args,["remove"]): p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects") @@ -73,6 +76,8 @@ class DistroFunction(commands.CobblerFunction): obj.set_ksmeta(self.options.ksmeta) if self.options.breed: obj.set_breed(self.options.breed) + if self.options.owners: + obj.set_owners(self.options.owners) return self.object_manipulator_finish(obj, self.api.distros, self.options) diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py index e9d1d23..9912607 100644 --- a/cobbler/modules/cli_profile.py +++ b/cobbler/modules/cli_profile.py @@ -46,6 +46,7 @@ class ProfileFunction(commands.CobblerFunction): p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'") p.add_option("--name", dest="name", help="a name for the profile (REQUIRED)") + if "copy" in args or "rename" in args: p.add_option("--newname", dest="newname") @@ -53,6 +54,7 @@ class ProfileFunction(commands.CobblerFunction): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") if not self.matches_args(args,["report", "list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") + p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") if self.matches_args(args,["remove"]): p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects") @@ -93,6 +95,9 @@ class ProfileFunction(commands.CobblerFunction): if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag) if self.options.server_override: obj.set_server(self.options.server) + if self.options.owners: + obj.set_owners(self.options.owners) + return self.object_manipulator_finish(obj, self.api.profiles, self.options) diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py index 96afa6f..88e45dd 100644 --- a/cobbler/modules/cli_repo.py +++ b/cobbler/modules/cli_repo.py @@ -59,6 +59,8 @@ class RepoFunction(commands.CobblerFunction): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") if not self.matches_args(args,["report","list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") + if not self.matches_args(args,["remove","report","list"]): + p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") def run(self): @@ -75,6 +77,9 @@ class RepoFunction(commands.CobblerFunction): if self.options.mirror: obj.set_mirror(self.options.mirror) if self.options.yumopts: obj.set_yumopts(self.options.yumopts) + if self.options.owners: + obj.set_owners(self.options.owners) + return self.object_manipulator_finish(obj, self.api.repos, self.options) diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index c463b8c..01ead35 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -63,6 +63,7 @@ class SystemFunction(commands.CobblerFunction): if not self.matches_args(args,["remove","report","list"]): + p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") p.add_option("--profile", dest="profile", help="name of cobbler profile (REQUIRED)") p.add_option("--server-override", dest="server_override", help="overrides server value in settings file") p.add_option("--subnet", dest="subnet", help="for static IP / templating usage") @@ -99,6 +100,9 @@ class SystemFunction(commands.CobblerFunction): if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag, my_interface) if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge, my_interface) + if self.options.owners: + obj.set_owners(self.options.owners) + return self.object_manipulator_finish(obj, self.api.systems, self.options) diff --git a/cobbler/settings.py b/cobbler/settings.py index 495a4b5..e3d8a46 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -58,7 +58,6 @@ DEFAULTS = { "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "syslog_port" : 25150, - "tftpboot" : -1, # special, see note below "tftpd_bin" : "/usr/sbin/in.tftpd", "tftpd_conf" : "/etc/xinetd.d/tftp", "webdir" : "/var/www/cobbler", @@ -109,11 +108,6 @@ class Settings(serializable.Serializable): self._attributes = datastruct - # this last attribute is special. In F9, the tftpboot location moves, so - # what we have in settings is not (neccessarily) correct. So instead - # of using settings we determine it by looking at the OS. - self._attributes["tftpboot"] = utils.tftpboot_location() - return self def __getattr__(self,name): diff --git a/cobbler/utils.py b/cobbler/utils.py index 86ff41d..9c770d0 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -230,6 +230,20 @@ def find_kickstart(url): return url return None +def input_string_or_list(options,delim=","): + """ + Accepts a delimited list of stuff or a list, but always returns a list. + """ + if options is None or options == "delete": + return [] + elif type(options) == list: + return options + elif type(options) == str: + tokens = options.split(",") + return tokens + else: + raise CX(_("invalid input type")) + def input_string_or_hash(options,delim=","): """ Older cobbler files stored configurations in a flat way, such that all values for strings. @@ -261,7 +275,7 @@ def input_string_or_hash(options,delim=","): options.pop('',None) return (True, options) else: - raise CX(_("Foreign options type")) + raise CX(_("invalid input type")) def grab_tree(api_handle, obj): """ diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index b2abf09..157b156 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1205174715.425076 -__CHEETAH_genTimestamp__ = 'Mon Mar 10 14:45:15 2008' +__CHEETAH_genTime__ = 1206550188.6649311 +__CHEETAH_genTimestamp__ = 'Wed Mar 26 12:49:48 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' diff --git a/docs/cobbler.pod b/docs/cobbler.pod index a1f174f..719850d 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -102,6 +102,10 @@ arguments appropriately. Support for other types of distributions is possible The file used for the answer file, regardless of the breed setting, is the value used for --kickstart when creating the profile. +=item owners + +Users with small sites and a limited number of admins can probably ignore this option. All cobbler objects (distros, profiles, systems, and repos) can take a --owners parameter to specify what cobbler users can edit particular objects. This only applies to the Cobbler WebUI and XMLRPC interface, not the "cobbler" command line tool run from the shell. Furthermore, this is only respected by the "authz_ownership" module which must be enabled in /etc/cobbler/modules.conf. The value for --owners is a comma seperated list of users and groups as specified in /etc/cobbler/users.conf. For more information see the users.conf file as well as the Cobbler Wiki. In the default Cobbler configuration, this value is completely ignored, as is users.conf. + =back =head2 ADDING A PROFILE diff --git a/tests/tests.py b/tests/tests.py index 99ba0b9..3fb4177 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -100,6 +100,41 @@ class BootTest(unittest.TestCase): self.assertTrue(self.api.add_system(system)) self.assertTrue(self.api.find_system(name="drwily.rdu.redhat.com")) + repo = self.api.new_repo() + try: + os.makedirs("/tmp/test_example_cobbler_repo") + except: + pass + fd = open("/tmp/test_example_cobbler_repo/test.file", "w+") + fd.write("hello!") + fd.close() + self.assertTrue(repo.set_name("test_repo")) + self.assertTrue(repo.set_mirror("/tmp/test_example_cobbler_repo")) + self.assertTrue(self.api.repos().add(repo)) + + +class Ownership(BootTest): + + def test_ownership_params(self): + return # FIXME + + # NOTE: these tests are relaeively weak because they only test + # that the options are usable, not that they work, since cobbler + # as a command line tool ignores them, and cobblerd only cares + # in certain modes. + distro = self.api.find_distro(name="testdistro0") + profile = self.api.find_distro(name="testprofile0") + system = self.api.find_distro(name="drwily.rdu.redhat.com") + repo = self.api.find_repo(name="test_repo") + self.assertTrue(distro.set_owners("a,b")) + self.assertTrue(profile.set_owners("c,d")) + self.assertTrue(system.set_owners("e")) + self.assertTrue(repo.set_owners("f,g")) + self.api.add_distro(distro) + self.api.add_profile(profile) + self.api.add_system(system) + self.api.add_repo(repo) + class MultiNIC(BootTest): def test_multi_nic_support(self): -- cgit From 0be993e430bfe2c7c742f7f4d100fb73cb3e317c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 26 Mar 2008 17:45:01 -0400 Subject: Missing file --- config/users.conf | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 config/users.conf diff --git a/config/users.conf b/config/users.conf new file mode 100644 index 0000000..94a3566 --- /dev/null +++ b/config/users.conf @@ -0,0 +1,26 @@ +# Cobbler WebUI / Web Services authorization config file +# +# NOTICE: +# this file is only used when /etc/cobbler/modules.conf +# specifies an authorization mode of either: +# +# (A) authz_configfile +# (B) authz_ownership +# +# For (A), any user in this file, in any group, are allowed +# full access to any object in cobbler configuration. +# +# For (B), users in the "admins" group are allowed full access +# to any object, otherwise users can only edit an object if +# their username/group is listed as an owner of that object. If a +# user is not listed in this file they will have no access. +# +# cobbler command line example: +# +# cobbler system edit --name=server1 --owner=dbas,mac,pete,jack + + +[admins] +admin +cobbler + -- cgit From 5b2e32746600a45af8ce85f645cb3c0d8ae2d084 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 27 Mar 2008 16:50:58 -0400 Subject: Adding ownership module + tests and associated changes to cobblerd to make it work a little better. This module is not fully tested yet, so don't use it yet in production. --- cobbler/modules/authz_ownership.py | 155 +++++++++++++++++++++++++++++++++++++ cobbler/remote.py | 8 +- tests/tests.py | 109 +++++++++++++++++++------- 3 files changed, 238 insertions(+), 34 deletions(-) create mode 100644 cobbler/modules/authz_ownership.py diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py new file mode 100644 index 0000000..9b271f4 --- /dev/null +++ b/cobbler/modules/authz_ownership.py @@ -0,0 +1,155 @@ +""" +Authorization module that allow users listed in +/etc/cobbler/users.conf to be permitted to access resources, with +the further restriction that cobbler objects can be edited to only +allow certain users/groups to access those specific objects. + +Copyright 2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import distutils.sysconfig +import ConfigParser +import sys +import os +from rhpl.translate import _, N_, textdomain, utf8 + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +import cexceptions +import utils + + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "authz" + +def __parse_config(debug=False): + etcfile='/etc/cobbler/users.conf' + if not os.path.exists(etcfile): + raise CX(_("/etc/cobbler/users.conf does not exist")) + config = ConfigParser.ConfigParser() + config.read(etcfile) + alldata = {} + sections = config.sections() + if debug: + print "[OWNERSHIP] sections=%s" % sections + for g in sections: + alldata[str(g)] = {} + opts = config.options(g) + if debug: + print "[OWNERSHIP] for group %s, users: %s" % (g,opts) + for o in opts: + alldata[g][o] = 1 + return alldata + + +def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): + """ + Validate a user against a resource. + All users in the file are permitted by this module. + """ + + user_groups = __parse_config(debug) + if debug: + print "[OWNERSHIP] ------------" + print "can user %s do %s (arg1=%s)?" % (user,resource,arg1) + print "consult db: %s" % user_groups + + # classify the type of operation + save_or_remove = False + for criteria in ["save","remove","modify"]: + if resource.find(criteria) != -1: + save_or_remove = True + + # FIXME: is everyone allowed to copy? I think so. + # FIXME: deal with the problem of deleted parents and promotion + + found_user = False + for g in user_groups: + if user in user_groups[g]: + found_user = True + # if user is in the admin group, always authorize + # regardless of the ownership of the object. + if g == "admin": + if debug: + print "[OWNERSHIP] user % is an admin, PASS" % user + return 1 + break + + if not found_user: + # if the user isn't anywhere in the file, reject regardless + # they can still use read-only XMLRPC + if debug: + print "[OWNERSHIP] user %s not found in list, FAIL" % user + return 0 + if not save_or_remove: + # sufficient to allow access for non save/remove ops to all + # users for now, may want to refine later. + if debug: + print "[OWNERSHIP] user %s is cleared for non-edit ops, PASS" % user + return 1 + + # now we have a save_or_remove op, so we must check ownership + # of the object. remove ops pass in arg1 as a string name, + # saves pass in actual objects, so we must treat them differently. + + obj = None + if resource.find("remove") != -1: + if resource == "remove_distro": + obj = api_handle.find_distro(arg1) + elif resource == "remove_profile": + obj = api_handle.find_profile(arg1) + elif resource == "remove_system": + obj = api_handle.find_system(arg1) + elif resource == "remove_repo": + obj = api_handle.find_system(arg1) + else: + obj = arg1 + + # if the object has no ownership data, allow access regardless + if obj.owners is None or obj.owners == []: + if debug: + print "[OWNERSHIP] user %s is cleared, object is not owned, PASS" % user + return 1 + + # otherwise, ownership by user/group + for allowed in obj.owners: + if user == allowed: + # user match + if debug: + print "[OWNERSHIP] user %s in match list, PASS" % user + return 1 + for group in user_groups: + if user in user_groups[group]: + if debug: + print "[OWNERSHIP] user %s matched by group, PASS" % user + return 1 + + # can't find user or group in ownership list and ownership is defined + # so reject the operation + if debug: + print "[OWNERSHIP] user %s rejected by default policy, FAIL" % user + return 0 + + +if __name__ == "__main__": + import api as cobbler_api + api = cobbler_api.BootAPI() + print __parse_config() + print authorize(api, "admin1", "sync") + d = api.find_distro("F9B-i386") + print authorize(api, "admin1", "save_distro", d, debug=True) + + # real tests are contained in tests/tests.py diff --git a/cobbler/remote.py b/cobbler/remote.py index 4b04fcb..76ffbcf 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -777,8 +777,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Saves a newly created or modified distro object to disk. """ self.log("save_distro",object_id=object_id,token=token) - self.check_access(token,"save_distro") obj = self.__get_object(object_id) + self.check_access(token,"save_distro",obj) return self.api.distros().add(obj,save=True) def save_profile(self,object_id,token): @@ -786,8 +786,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Saves a newly created or modified profile object to disk. """ self.log("save_profile",token=token,object_id=object_id) - self.check_access(token,"save_profile") obj = self.__get_object(object_id) + self.check_access(token,"save_profile",obj) return self.api.profiles().add(obj,save=True) def save_system(self,object_id,token): @@ -795,8 +795,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Saves a newly created or modified system object to disk. """ self.log("save_system",token=token,object_id=object_id) - self.check_access(token,"save_system") obj = self.__get_object(object_id) + self.check_access(token,"save_system",obj) return self.api.systems().add(obj,save=True) def save_repo(self,object_id,token=None): @@ -804,8 +804,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Saves a newly created or modified repo object to disk. """ self.log("save_repo",object_id=object_id,token=token) - self.check_access(token,"save_repo") obj = self.__get_object(object_id) + self.check_access(token,"save_repo",obj) return self.api.repos().add(obj,save=True) def copy_distro(self,object_id,newname,token=None): diff --git a/tests/tests.py b/tests/tests.py index 3fb4177..546c83f 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -2,16 +2,6 @@ # # Michael DeHaan -TRY_GRAPH = False -HAS_GRAPH = False - -if TRY_GRAPH: - try: - import pycallgraph_mod as pycallgraph - HAS_GRAPH = True - except: - pass - import sys import unittest import os @@ -25,6 +15,7 @@ from cobbler import settings from cobbler import collection_distros from cobbler import collection_profiles from cobbler import collection_systems +import cobbler.modules.authz_ownership as authz_module from cobbler import api @@ -54,11 +45,11 @@ class BootTest(unittest.TestCase): except: pass - self.fk_initrd = os.path.join(self.topdir, FAKE_INITRD) + self.fk_initrd = os.path.join(self.topdir, FAKE_INITRD) self.fk_initrd2 = os.path.join(self.topdir, FAKE_INITRD2) self.fk_initrd3 = os.path.join(self.topdir, FAKE_INITRD3) - self.fk_kernel = os.path.join(self.topdir, FAKE_KERNEL) + self.fk_kernel = os.path.join(self.topdir, FAKE_KERNEL) self.fk_kernel2 = os.path.join(self.topdir, FAKE_KERNEL2) self.fk_kernel3 = os.path.join(self.topdir, FAKE_KERNEL3) @@ -75,9 +66,6 @@ class BootTest(unittest.TestCase): shutil.rmtree(self.topdir,ignore_errors=True) self.api = None - if HAS_GRAPH: - pycallgraph.save_dot("%s.dot" % self.__class__.__name__) - def make_basic_config(self): distro = self.api.new_distro() self.assertTrue(distro.set_name("testdistro0")) @@ -116,25 +104,90 @@ class BootTest(unittest.TestCase): class Ownership(BootTest): def test_ownership_params(self): - return # FIXME - # NOTE: these tests are relaeively weak because they only test - # that the options are usable, not that they work, since cobbler - # as a command line tool ignores them, and cobblerd only cares - # in certain modes. + # initially just test that we can set ownership on various components + distro = self.api.find_distro(name="testdistro0") - profile = self.api.find_distro(name="testprofile0") - system = self.api.find_distro(name="drwily.rdu.redhat.com") + profile = self.api.find_profile(name="testprofile0") + system = self.api.find_system(name="drwily.rdu.redhat.com") repo = self.api.find_repo(name="test_repo") - self.assertTrue(distro.set_owners("a,b")) - self.assertTrue(profile.set_owners("c,d")) - self.assertTrue(system.set_owners("e")) - self.assertTrue(repo.set_owners("f,g")) + self.assertTrue(distro.set_owners("superlab,basement1")) + self.assertTrue(profile.set_owners("superlab,basement1")) + self.assertTrue(system.set_owners("superlab,basement1")) + self.assertTrue(repo.set_owners([])) self.api.add_distro(distro) self.api.add_profile(profile) self.api.add_system(system) self.api.add_repo(repo) + # now edit the groups file. We won't test the full XMLRPC + # auth stack here, but just the module in question + + authorize = authz_module.authorize + + # if the users.conf file exists, back it up for the tests + #if os.path.exists("/etc/cobbler/users.conf"): + # shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak") + + fd = open("/etc/cobbler/users.conf","w+") + fd.write("\n") + fd.write("[admins]\n") + fd.write("admin1 = 1\n") + fd.write("\n") + fd.write("[superlab]\n") + fd.write("superlab1 = 1\n") + fd.write("\n") + fd.write("[basement]\n") + fd.write("basement1 = 1\n") + fd.write("basement2 = 1\n") + fd.close() + + xo = self.api.find_distro("testdistro0") + xn = "testdistro0" + ro = self.api.find_repo("testrepo0") + rn = "testrepo0" + + # ensure admin1 can edit (he's an admin) and do other tasks + # same applies to basement1 who is explicitly added as a user + # and superlab1 who is in a group in the ownership list + for user in ["admin1","superlab1","basement1"]: + self.assertTrue(1==authorize(self.api, user, "save_distro", xo, debug=True),"%s can save_distro" % user) + self.assertTrue(1==authorize(self.api, user, "modify_distro", xo, debug=True),"%s can modify_distro" % user) + self.assertTrue(1==authorize(self.api, user, "copy_distro", xo, debug=True),"%s can copy_distro" % user) + self.assertTrue(1==authorize(self.api, user, "remove_distro", xn, debug=True),"%s can remove_distro" % user) + + # ensure all users in the file can sync + for user in [ "admin1", "superlab1", "basement1", "basement2" ]: + self.assertTrue(1==authorize(self.api, user, "sync", debug=True)) + + # make sure basement2 can't edit (not in group) + # and same goes for "dne" (does not exist in users.conf) + + for user in [ "basement2", "dne" ]: + self.assertTrue(0==authorize(self.api, user, "save_distro", xo, debug=True), "user %s cannot save_distro" % user) + self.assertTrue(0==authorize(self.api, user, "modify_distro", xo, debug=True), "user %s cannot modify_distro" % user) + self.assertTrue(0==authorize(self.api, user, "remove_distro", xn, debug=True), "user %s cannot remove_distro" % user) + + # basement2 is in the file so he can still copy + self.assertTrue(1==authorize(self.api, "basement2", "copy_distro", xo, debug=True), "basement2 can copy_distro") + + # dne can not copy or sync either (not in the users.conf) + self.assertTrue(0==authorize(self.api, "dne", "copy_distro", xo, debug=True), "dne cannot copy_distro") + self.assertTrue(0==authorize(self.api, "dne", "sync", debug=True), "dne cannot sync") + + # unlike the distro testdistro0, testrepo0 is unowned + # so any user in the file will be able to edit it. + for user in [ "admin1", "superlab1", "basement1", "basement2" ]: + self.assertTrue(1==authorize(self.api, user, "save_repo", ro, debug=True), "user %s can save_repo" % user) + + # though dne is still not listed and will be denied + self.assertTrue(0==authorize(self.api, "dne", "save_repo", ro, debug=True), "dne cannot save_repo") + + # if we survive, restore the users file as module testing is done + #if os.path.exists("/tmp/cobbler_ubak"): + # shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak") + + class MultiNIC(BootTest): def test_multi_nic_support(self): @@ -612,14 +665,10 @@ if __name__ == "__main__": if not os.path.exists("setup.py"): print "tests: must invoke from top level directory" sys.exit(1) - if HAS_GRAPH: - pycallgraph.start_trace() loader = unittest.defaultTestLoader test_module = __import__("tests") # self import considered harmful? tests = loader.loadTestsFromModule(test_module) runner = unittest.TextTestRunner() runner.run(tests) - if HAS_GRAPH: - pycallgraph.make_graph('cg_dot.png', tool='dot') sys.exit(0) -- cgit From 9e7e5953469db24a22710f68dd6c5c337e5c0365 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 27 Mar 2008 17:42:45 -0400 Subject: Fixes to authz_ownership module, tests pass now. --- cobbler/modules/authz_ownership.py | 39 +++++++++++++++++++++++--------------- tests/tests.py | 10 +++++----- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index 9b271f4..43cf523 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -69,7 +69,7 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): # classify the type of operation save_or_remove = False - for criteria in ["save","remove","modify"]: + for criteria in ["save_","remove_","modify_"]: if resource.find(criteria) != -1: save_or_remove = True @@ -78,15 +78,18 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): found_user = False for g in user_groups: - if user in user_groups[g]: - found_user = True - # if user is in the admin group, always authorize - # regardless of the ownership of the object. - if g == "admin": - if debug: - print "[OWNERSHIP] user % is an admin, PASS" % user - return 1 - break + for x in user_groups[g]: + if debug: + print "[OWNERSHIP] noted user %s in group %s" % (x,g) + if x == user: + found_user = True + # if user is in the admin group, always authorize + # regardless of the ownership of the object. + if g == "admins" or g == "admin": + if debug: + print "[OWNERSHIP] user %s is an admin, PASS" % user + return 1 + break if not found_user: # if the user isn't anywhere in the file, reject regardless @@ -107,6 +110,8 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): obj = None if resource.find("remove") != -1: + if debug: + print "[OWNERSHIP] looking up object %s" % (arg1) if resource == "remove_distro": obj = api_handle.find_distro(arg1) elif resource == "remove_profile": @@ -115,7 +120,9 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): obj = api_handle.find_system(arg1) elif resource == "remove_repo": obj = api_handle.find_system(arg1) - else: + elif resource.find("save") != -1 or resource.find("modify") != -1: + if debug: + print "[OWNERSHIP] object being considered is: %s for %s" % (arg1, resource) obj = arg1 # if the object has no ownership data, allow access regardless @@ -132,9 +139,9 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): print "[OWNERSHIP] user %s in match list, PASS" % user return 1 for group in user_groups: - if user in user_groups[group]: + if group == allowed and user in user_groups[group]: if debug: - print "[OWNERSHIP] user %s matched by group, PASS" % user + print "[OWNERSHIP] user %s matched by group (%s), PASS" % (user, group) return 1 # can't find user or group in ownership list and ownership is defined @@ -145,11 +152,13 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): if __name__ == "__main__": + # real tests are contained in tests/tests.py import api as cobbler_api api = cobbler_api.BootAPI() print __parse_config() print authorize(api, "admin1", "sync") d = api.find_distro("F9B-i386") + d.set_owners(["allowed"]) + api.add_distro(d) print authorize(api, "admin1", "save_distro", d, debug=True) - - # real tests are contained in tests/tests.py + print authorize(api, "basement2", "save_distro", d, debug=True) diff --git a/tests/tests.py b/tests/tests.py index 546c83f..426ddf2 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -111,9 +111,9 @@ class Ownership(BootTest): profile = self.api.find_profile(name="testprofile0") system = self.api.find_system(name="drwily.rdu.redhat.com") repo = self.api.find_repo(name="test_repo") - self.assertTrue(distro.set_owners("superlab,basement1")) - self.assertTrue(profile.set_owners("superlab,basement1")) - self.assertTrue(system.set_owners("superlab,basement1")) + self.assertTrue(distro.set_owners(["superlab","basement1"])) + self.assertTrue(profile.set_owners(["superlab","basement1"])) + self.assertTrue(system.set_owners(["superlab","basement1"])) self.assertTrue(repo.set_owners([])) self.api.add_distro(distro) self.api.add_profile(profile) @@ -144,8 +144,8 @@ class Ownership(BootTest): xo = self.api.find_distro("testdistro0") xn = "testdistro0" - ro = self.api.find_repo("testrepo0") - rn = "testrepo0" + ro = self.api.find_repo("test_repo") + rn = "test_repo" # ensure admin1 can edit (he's an admin) and do other tasks # same applies to basement1 who is explicitly added as a user -- cgit From 08ca3dda388b39b02f58121803ca95e8c8d09ac3 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 27 Mar 2008 17:46:23 -0400 Subject: Now that authz_ownership works, remove debug code to make it easier to read. Also enable code in test scripts to prevent clobbering a users "users.conf" file when running unit tests. --- cobbler/modules/authz_ownership.py | 38 +++++--------------------------------- tests/tests.py | 34 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 50 deletions(-) diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index 43cf523..3befc4a 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -35,7 +35,7 @@ def register(): """ return "authz" -def __parse_config(debug=False): +def __parse_config(): etcfile='/etc/cobbler/users.conf' if not os.path.exists(etcfile): raise CX(_("/etc/cobbler/users.conf does not exist")) @@ -43,29 +43,21 @@ def __parse_config(debug=False): config.read(etcfile) alldata = {} sections = config.sections() - if debug: - print "[OWNERSHIP] sections=%s" % sections for g in sections: alldata[str(g)] = {} opts = config.options(g) - if debug: - print "[OWNERSHIP] for group %s, users: %s" % (g,opts) for o in opts: alldata[g][o] = 1 return alldata -def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): +def authorize(api_handle,user,resource,arg1=None,arg2=None): """ Validate a user against a resource. All users in the file are permitted by this module. """ - user_groups = __parse_config(debug) - if debug: - print "[OWNERSHIP] ------------" - print "can user %s do %s (arg1=%s)?" % (user,resource,arg1) - print "consult db: %s" % user_groups + user_groups = __parse_config() # classify the type of operation save_or_remove = False @@ -79,29 +71,21 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): found_user = False for g in user_groups: for x in user_groups[g]: - if debug: - print "[OWNERSHIP] noted user %s in group %s" % (x,g) if x == user: found_user = True # if user is in the admin group, always authorize # regardless of the ownership of the object. if g == "admins" or g == "admin": - if debug: - print "[OWNERSHIP] user %s is an admin, PASS" % user return 1 break if not found_user: # if the user isn't anywhere in the file, reject regardless # they can still use read-only XMLRPC - if debug: - print "[OWNERSHIP] user %s not found in list, FAIL" % user return 0 if not save_or_remove: # sufficient to allow access for non save/remove ops to all # users for now, may want to refine later. - if debug: - print "[OWNERSHIP] user %s is cleared for non-edit ops, PASS" % user return 1 # now we have a save_or_remove op, so we must check ownership @@ -110,8 +94,6 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): obj = None if resource.find("remove") != -1: - if debug: - print "[OWNERSHIP] looking up object %s" % (arg1) if resource == "remove_distro": obj = api_handle.find_distro(arg1) elif resource == "remove_profile": @@ -121,33 +103,23 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None,debug=False): elif resource == "remove_repo": obj = api_handle.find_system(arg1) elif resource.find("save") != -1 or resource.find("modify") != -1: - if debug: - print "[OWNERSHIP] object being considered is: %s for %s" % (arg1, resource) obj = arg1 # if the object has no ownership data, allow access regardless if obj.owners is None or obj.owners == []: - if debug: - print "[OWNERSHIP] user %s is cleared, object is not owned, PASS" % user return 1 # otherwise, ownership by user/group for allowed in obj.owners: if user == allowed: # user match - if debug: - print "[OWNERSHIP] user %s in match list, PASS" % user return 1 for group in user_groups: if group == allowed and user in user_groups[group]: - if debug: - print "[OWNERSHIP] user %s matched by group (%s), PASS" % (user, group) return 1 # can't find user or group in ownership list and ownership is defined # so reject the operation - if debug: - print "[OWNERSHIP] user %s rejected by default policy, FAIL" % user return 0 @@ -160,5 +132,5 @@ if __name__ == "__main__": d = api.find_distro("F9B-i386") d.set_owners(["allowed"]) api.add_distro(d) - print authorize(api, "admin1", "save_distro", d, debug=True) - print authorize(api, "basement2", "save_distro", d, debug=True) + print authorize(api, "admin1", "save_distro", d) + print authorize(api, "basement2", "save_distro", d) diff --git a/tests/tests.py b/tests/tests.py index 426ddf2..c3cbea7 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -126,8 +126,8 @@ class Ownership(BootTest): authorize = authz_module.authorize # if the users.conf file exists, back it up for the tests - #if os.path.exists("/etc/cobbler/users.conf"): - # shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak") + if os.path.exists("/etc/cobbler/users.conf"): + shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak") fd = open("/etc/cobbler/users.conf","w+") fd.write("\n") @@ -151,41 +151,41 @@ class Ownership(BootTest): # same applies to basement1 who is explicitly added as a user # and superlab1 who is in a group in the ownership list for user in ["admin1","superlab1","basement1"]: - self.assertTrue(1==authorize(self.api, user, "save_distro", xo, debug=True),"%s can save_distro" % user) - self.assertTrue(1==authorize(self.api, user, "modify_distro", xo, debug=True),"%s can modify_distro" % user) - self.assertTrue(1==authorize(self.api, user, "copy_distro", xo, debug=True),"%s can copy_distro" % user) - self.assertTrue(1==authorize(self.api, user, "remove_distro", xn, debug=True),"%s can remove_distro" % user) + self.assertTrue(1==authorize(self.api, user, "save_distro", xo),"%s can save_distro" % user) + self.assertTrue(1==authorize(self.api, user, "modify_distro", xo),"%s can modify_distro" % user) + self.assertTrue(1==authorize(self.api, user, "copy_distro", xo),"%s can copy_distro" % user) + self.assertTrue(1==authorize(self.api, user, "remove_distro", xn),"%s can remove_distro" % user) # ensure all users in the file can sync for user in [ "admin1", "superlab1", "basement1", "basement2" ]: - self.assertTrue(1==authorize(self.api, user, "sync", debug=True)) + self.assertTrue(1==authorize(self.api, user, "sync")) # make sure basement2 can't edit (not in group) # and same goes for "dne" (does not exist in users.conf) for user in [ "basement2", "dne" ]: - self.assertTrue(0==authorize(self.api, user, "save_distro", xo, debug=True), "user %s cannot save_distro" % user) - self.assertTrue(0==authorize(self.api, user, "modify_distro", xo, debug=True), "user %s cannot modify_distro" % user) - self.assertTrue(0==authorize(self.api, user, "remove_distro", xn, debug=True), "user %s cannot remove_distro" % user) + self.assertTrue(0==authorize(self.api, user, "save_distro", xo), "user %s cannot save_distro" % user) + self.assertTrue(0==authorize(self.api, user, "modify_distro", xo), "user %s cannot modify_distro" % user) + self.assertTrue(0==authorize(self.api, user, "remove_distro", xn), "user %s cannot remove_distro" % user) # basement2 is in the file so he can still copy - self.assertTrue(1==authorize(self.api, "basement2", "copy_distro", xo, debug=True), "basement2 can copy_distro") + self.assertTrue(1==authorize(self.api, "basement2", "copy_distro", xo), "basement2 can copy_distro") # dne can not copy or sync either (not in the users.conf) - self.assertTrue(0==authorize(self.api, "dne", "copy_distro", xo, debug=True), "dne cannot copy_distro") - self.assertTrue(0==authorize(self.api, "dne", "sync", debug=True), "dne cannot sync") + self.assertTrue(0==authorize(self.api, "dne", "copy_distro", xo), "dne cannot copy_distro") + self.assertTrue(0==authorize(self.api, "dne", "sync"), "dne cannot sync") # unlike the distro testdistro0, testrepo0 is unowned # so any user in the file will be able to edit it. for user in [ "admin1", "superlab1", "basement1", "basement2" ]: - self.assertTrue(1==authorize(self.api, user, "save_repo", ro, debug=True), "user %s can save_repo" % user) + self.assertTrue(1==authorize(self.api, user, "save_repo", ro), "user %s can save_repo" % user) # though dne is still not listed and will be denied - self.assertTrue(0==authorize(self.api, "dne", "save_repo", ro, debug=True), "dne cannot save_repo") + self.assertTrue(0==authorize(self.api, "dne", "save_repo", ro), "dne cannot save_repo") # if we survive, restore the users file as module testing is done - #if os.path.exists("/tmp/cobbler_ubak"): - # shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak") + if os.path.exists("/tmp/cobbler_ubak"): + shutil.copyfile("/etc/cobbler/users.conf","/tmp/cobbler_ubak") class MultiNIC(BootTest): -- cgit From af4f5696e9fa1a800a45289931ed147b7fb6f5ed Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 28 Mar 2008 17:23:12 -0400 Subject: Add owners list to WebUI pages, also customize function for saving lists to avoid extra whitespace on comma delimited inputs. --- cobbler/utils.py | 4 +++- webui_templates/distro_edit.tmpl | 18 ++++++++++++++++++ webui_templates/profile_edit.tmpl | 18 ++++++++++++++++++ webui_templates/repo_edit.tmpl | 17 +++++++++++++++++ webui_templates/system_edit.tmpl | 18 ++++++++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/cobbler/utils.py b/cobbler/utils.py index 9c770d0..4d2b635 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -239,7 +239,9 @@ def input_string_or_list(options,delim=","): elif type(options) == list: return options elif type(options) == str: - tokens = options.split(",") + tokens = options.split(delim) + if delim == ",": + tokens = [t.lstrip().rstrip() for t in tokens] return tokens else: raise CX(_("invalid input type")) diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 4729816..8b7d2cb 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -172,6 +172,24 @@ function disablename(value) + + + + + + #if $distro + #set ownerslist = ','.join($distro.owners) + #end if + +

Applies only if using authz_ownership module, comma-delimited

+ + + + #if $distro diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index f9eca09..95ad1fd 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -308,6 +308,24 @@ function disablename(value) + + + + + + #if $profile + #set ownerslist = ','.join($profile.owners) + #end if + +

Applies only if using authz_ownership module, comma-delimited

+ + + #if $profile diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index 445218f..8d22879 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -164,6 +164,23 @@ function disablename(value) + + + + + + #if $repo + #set ownerslist = ','.join($repo.owners) + #end if + +

Applies only if using authz_ownership module, comma-delimited

+ + + #if $repo diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 22bdda4..bc8a0a3 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -208,6 +208,24 @@ function page_onload() { + + + + + + #if $system + #set ownerslist = ','.join($system.owners) + #end if + +

Applies only if using authz_ownership module, comma-delimited

+ + + + ## ====================================== start of looping through interfaces -- cgit From 2e71866208f81f0df7205bce724168b480db83e7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 28 Mar 2008 17:25:53 -0400 Subject: Add code to make WebUI process new fields --- cobbler/webui/CobblerWeb.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 9a0cf90..ebbfbf6 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -169,7 +169,7 @@ class CobblerWeb(object): } ) def distro_save(self,name=None,oldname=None,new_or_edit=None,editmode='edit',kernel=None, - initrd=None,kopts=None,ksmeta=None,arch=None,breed=None, + initrd=None,kopts=None,ksmeta=None,owners=None,arch=None,breed=None, delete1=None,delete2=None,**args): if not self.__xmlrpc_setup(): @@ -220,6 +220,8 @@ class CobblerWeb(object): self.remote.modify_distro(distro, 'kopts', kopts, self.token) if ksmeta: self.remote.modify_distro(distro, 'ksmeta', ksmeta, self.token) + if owners: + self.remote.modify_distro(distro, 'owners', owners, self.token) if arch: self.remote.modify_distro(distro, 'arch', arch, self.token) if breed: @@ -288,7 +290,7 @@ class CobblerWeb(object): def system_save(self,name=None,oldname=None,editmode="edit",profile=None, new_or_edit=None, - kopts=None, ksmeta=None, server_override=None, netboot='n', + kopts=None, ksmeta=None, owners=None, server_override=None, netboot='n', delete1=None, delete2=None, **args): if not self.__xmlrpc_setup(): @@ -332,6 +334,8 @@ class CobblerWeb(object): self.remote.modify_system(system, 'kopts', kopts, self.token) if ksmeta: self.remote.modify_system(system, 'ksmeta', ksmeta, self.token) + if owners: + self.remote.modify_system(system, 'owners', owners, self.token) if netboot: self.remote.modify_system(system, 'netboot-enabled', netboot, self.token) if server_override: @@ -441,7 +445,7 @@ class CobblerWeb(object): def profile_save(self,new_or_edit=None,editmode='edit',name=None,oldname=None, distro=None,kickstart=None,kopts=None, - ksmeta=None,virtfilesize=None,virtram=None,virttype=None, + ksmeta=None,owners=None,virtfilesize=None,virtram=None,virttype=None, virtpath=None,repos=None,dhcptag=None,delete1=None,delete2=None, parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,**args): @@ -495,6 +499,8 @@ class CobblerWeb(object): self.remote.modify_profile(profile, 'kickstart', kickstart, self.token) if kopts: self.remote.modify_profile(profile, 'kopts', kopts, self.token) + if owners: + self.remote.modify_profile(profile, 'owners', kopts, self.token) if ksmeta: self.remote.modify_profile(profile, 'ksmeta', ksmeta, self.token) if virtfilesize: @@ -571,7 +577,7 @@ class CobblerWeb(object): } ) def repo_save(self,name=None,oldname=None,new_or_edit=None,editmode="edit", - mirror=None,keep_updated=None,priority=99, + mirror=None,owners=None,keep_updated=None,priority=99, rpm_list=None,createrepo_flags=None,arch=None,yumopts=None, delete1=None,delete2=None,**args): if not self.__xmlrpc_setup(): @@ -624,6 +630,8 @@ class CobblerWeb(object): self.remote.modify_repo(repo, 'arch', arch, self.token) if yumopts: self.remote.modify_repo(repo, 'yumopts', yumopts, self.token) + if owners: + self.remote.modify_repo(repo, 'owners', owners, self.token) self.remote.save_repo(repo, self.token) -- cgit From 9a2c291b0e068d622f6e70e55df29bc7dcc60359 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 28 Mar 2008 17:39:50 -0400 Subject: Fix some typos --- cobbler/remote.py | 4 +++- cobbler/webui/CobblerWeb.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index 76ffbcf..fa74e17 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -624,7 +624,9 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def __authorize(self,token,resource,arg1=None,arg2=None): user = self.get_user_from_token(token) - self.log("calling authorize for resource %s" % resource, user=user) + args = [ resource, arg1, arg2 ] + self.log("calling authorize for resource %s" % args, user=user) + rc = self.api.authorize(user,resource,arg1,arg2) if rc: return True diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index ebbfbf6..f53ba4d 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -500,7 +500,7 @@ class CobblerWeb(object): if kopts: self.remote.modify_profile(profile, 'kopts', kopts, self.token) if owners: - self.remote.modify_profile(profile, 'owners', kopts, self.token) + self.remote.modify_profile(profile, 'owners', owners, self.token) if ksmeta: self.remote.modify_profile(profile, 'ksmeta', ksmeta, self.token) if virtfilesize: -- cgit From c9ff51e05c0e5e8a86d6a5d3654bf505a3dfdb17 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 28 Mar 2008 18:00:11 -0400 Subject: Fix some more bugs with ownership editing/saving. Working well in the WebUI now with respect to being able to change things, still need to work on better showing denials and what folks can edit. --- cobbler/item_repo.py | 2 ++ cobbler/item_system.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index d65d09b..e796561 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -39,6 +39,7 @@ class Repo(item.Item): self.depth = 2 # arbitrary, as not really apart of the graph self.arch = "" # use default arch self.yumopts = {} + self.owners = [] def from_datastruct(self,seed_data): self.parent = self.load_item(seed_data, 'parent') @@ -51,6 +52,7 @@ class Repo(item.Item): self.arch = self.load_item(seed_data, 'arch') self.depth = self.load_item(seed_data, 'depth', 2) self.yumopts = self.load_item(seed_data, 'yumopts', {}) + self.owners = self.load_item(seed_data, 'owners', []) # force this to be saved as a boolean self.set_keep_updated(self.keep_updated) diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 51dd063..6b2315a 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -81,7 +81,7 @@ class System(item.Item): self.parent = self.load_item(seed_data, 'parent') self.name = self.load_item(seed_data, 'name') - self.owners = self.load_item(seed_data, 'owners') + self.owners = self.load_item(seed_data, 'owners', []) self.profile = self.load_item(seed_data, 'profile') self.kernel_options = self.load_item(seed_data, 'kernel_options', {}) self.ks_meta = self.load_item(seed_data, 'ks_meta', {}) @@ -338,7 +338,7 @@ class System(item.Item): def to_datastruct(self): return { 'name' : self.name, - 'owners' : self.set_owners, + 'owners' : self.owners, 'profile' : self.profile, 'kernel_options' : self.kernel_options, 'ks_meta' : self.ks_meta, -- cgit From 328f4f65d79ad8af4bb888330563689f1f09e21a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 31 Mar 2008 15:28:55 -0400 Subject: Remove trailing commas in config file --- config/settings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/settings b/config/settings index d9b1785..11c21f2 100644 --- a/config/settings +++ b/config/settings @@ -20,10 +20,10 @@ kernel_options: ksdevice: eth0 lang: ' ' text: ~ -ldap_server: "ldap.example.com", -ldap_base_dn: "DC=example,DC=com", -ldap_port: 389, -ldap_tls: 1, +ldap_server: "ldap.example.com" +ldap_base_dn: "DC=example,DC=com" +ldap_port: 389 +ldap_tls: 1 manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' -- cgit From ca136de01b6d6604ca0fdf2990395c9212abd6a8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 31 Mar 2008 15:34:10 -0400 Subject: Fix default config file format. --- config/users.conf | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/users.conf b/config/users.conf index 94a3566..129659a 100644 --- a/config/users.conf +++ b/config/users.conf @@ -18,9 +18,11 @@ # cobbler command line example: # # cobbler system edit --name=server1 --owner=dbas,mac,pete,jack - +# +# NOTE: yes, you do need the equal sign after the names. +# don't remove that part. It's reserved for future use. [admins] -admin -cobbler +admin = "" +cobbler = "" -- cgit From eee7bfa18052bb1e3d0c84421f2b425073043888 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 31 Mar 2008 16:15:55 -0400 Subject: Minor changes to the authn/z test program so they don't go through Apache auth filters and can produce slightly more useful feedback, also removed a stray print. --- cobbler/demo_connect.py | 7 ++++++- cobbler/modules/authn_ldap.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py index 6397a6b..94fa390 100644 --- a/cobbler/demo_connect.py +++ b/cobbler/demo_connect.py @@ -18,11 +18,16 @@ if __name__ == "__main__": p = optparse.OptionParser() p.add_option("-u","--user",dest="user",default="test") p.add_option("-p","--pass",dest="password",default="test") - sp = ServerProxy("http://127.0.0.1/cobbler_api_rw") + + # NOTE: if you've changed your xmlrpc_rw port or + # disabled xmlrpc_rw this test probably won't work + + sp = ServerProxy("http://127.0.0.1:25152") (options, args) = p.parse_args() print "- trying to login with user=%s" % options.user token = sp.login(options.user,options.password) print "- token: %s" % token + print "- authenticated ok, now seeing if user is authorized" check = sp.check_access(token,"imaginary_method_name") print "- access ok? %s" % check diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index 6d190bd..8e54a45 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -83,7 +83,7 @@ def authenticate(api_handle,username,password): # ignore entry; we don't need it pass else: - print "FAIL 2" + # print "FAIL 2" return False try: @@ -92,7 +92,7 @@ def authenticate(api_handle,username,password): dir.unbind() return True except: - traceback.print_exc() + # traceback.print_exc() return False # catch-all return False -- cgit From c3437479cc300683ac3ffd270114eed3dcaf4f39 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 31 Mar 2008 17:09:26 -0400 Subject: Distro now instrumented with a warning panel that indicates when you probably won't be able to edit an object. --- cobbler/remote.py | 44 ++++++++++++++++++++++++++++++++++++---- cobbler/webui/CobblerWeb.py | 7 ++++++- webui_templates/distro_edit.tmpl | 9 ++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index fa74e17..5e3fb83 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -593,6 +593,42 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): self.log("invalid token",token=token) raise CX(_("invalid token: %s" % token)) + def __name_to_object(self,resource,name): + if resource.find("distro") != -1: + return self.api.find_distro(name) + if resource.find("profile") != -1: + return self.api.find_profile(name) + if resource.find("system") != -1: + return self.api.find_system(name) + if resource.find("repo") != -1: + return self.api.find_repo(name) + return None + + def check_access_no_fail(self,token,resource,arg1=None,arg2=None): + """ + This is called by the WUI to decide whether an element + is editable or not. It differs form check_access in that + it is supposed to /not/ log the access checks (TBA) and does + not raise exceptions. + """ + + need_remap = False + for x in [ "distro", "profile", "system", "repo" ]: + if arg1 is not None and resource.find(x) != -1: + need_remap = True + break + + if need_remap: + # we're called with an object name, but need an object + arg1 = self.__name_to_object(resource,arg1) + + try: + self.check_access(token,resource,arg1,arg2) + return True + except: + utils.log_exc(self.logger) + return False + def check_access(self,token,resource,arg1=None,arg2=None): validated = self.__validate_token(token) user = self.get_user_from_token(token) @@ -881,8 +917,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Allows modification of certain attributes on newly created or existing distro object handle. """ - self.check_access(token, "modify_distro", attribute, arg) obj = self.__get_object(object_id) + self.check_access(token, "modify_distro", obj, attribute) return self.__call_method(obj, attribute, arg) def modify_profile(self,object_id,attribute,arg,token): @@ -890,8 +926,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Allows modification of certain attributes on newly created or existing profile object handle. """ - self.check_access(token, "modify_profile", attribute, arg) obj = self.__get_object(object_id) + self.check_access(token, "modify_profile", obj, attribute) return self.__call_method(obj, attribute, arg) def modify_system(self,object_id,attribute,arg,token): @@ -899,8 +935,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Allows modification of certain attributes on newly created or existing system object handle. """ - self.check_access(token, "modify_system", attribute, arg) obj = self.__get_object(object_id) + self.check_access(token, "modify_system", obj, attribute) return self.__call_method(obj, attribute, arg) def modify_repo(self,object_id,attribute,arg,token): @@ -908,8 +944,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Allows modification of certain attributes on newly created or existing repo object handle. """ - self.check_access(token, "modify_repo", attribute, arg) obj = self.__get_object(object_id) + self.check_access(token, "modify_repo", obj, attribute) return self.__call_method(obj, attribute, arg) def remove_distro(self,name,token,recursive=1): diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index f53ba4d..b2de916 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -162,9 +162,14 @@ class CobblerWeb(object): input_distro = None if name is not None: input_distro = self.remote.get_distro(name, True) - + can_edit = self.remote.check_access_no_fail(self.token,"modify_distro",name) + else: + can_edit = self.remote.check_access_no_fail(self.token,"new_distro",None) + + return self.__render( 'distro_edit.tmpl', { 'edit' : True, + 'editable' : can_edit, 'distro': input_distro, } ) diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 8b7d2cb..ccbb72f 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -15,6 +15,15 @@ function disablename(value) #end if +#if $editable != True +
+$editable + +WARNING: It looks like you do not have permission to make changes. +To recieve access, contact your Cobbler server administrator. +
+#end if +
-- cgit From 2c30d3b241e50370f4d4e0cb67ec10d35b35ff99 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 31 Mar 2008 17:17:30 -0400 Subject: Added ownership checks for the various objects in the WebUI to make things more obvious. Still need to disable save/reset buttons. Note that kickstart editing (supported via the WebUI) is not yet protected and we still need to add that seperately. This is complicated. Currently anyone can sync and we'll keep it that way. --- cobbler/webui/CobblerWeb.py | 15 ++++++++++++++- webui_templates/distro_edit.tmpl | 2 -- webui_templates/profile_edit.tmpl | 7 +++++++ webui_templates/repo_edit.tmpl | 6 ++++++ webui_templates/system_edit.tmpl | 8 +++++++- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index b2de916..2012708 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -399,9 +399,13 @@ class CobblerWeb(object): input_system = None if name is not None: input_system = self.remote.get_system(name,True) + can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name) + else: + can_edit = self.remote.check_access_no_fail(self.token,"new_system",None) return self.__render( 'system_edit.tmpl', { 'edit' : True, + 'editable' : can_edit, 'system': input_system, 'profiles': self.remote.get_profiles() } ) @@ -436,10 +440,14 @@ class CobblerWeb(object): input_profile = None if name is not None: - input_profile = self.remote.get_profile(name,True) + input_profile = self.remote.get_profile(name,True) + can_edit = self.remote.check_access_no_fail(self.token,"modify_profile",name) + else: + can_edit = self.remote.check_access_no_fail(self.token,"new_profile",None) return self.__render( 'profile_edit.tmpl', { 'edit' : True, + 'editable' : can_edit, 'profile': input_profile, 'distros': self.remote.get_distros(), 'profiles': self.remote.get_profiles(), @@ -576,9 +584,14 @@ class CobblerWeb(object): input_repo = None if name is not None: input_repo = self.remote.get_repo(name, True) + can_edit = self.remote.check_access_no_fail(self.token,"modify_repo",name) + else: + can_edit = self.remote.check_access_no_fail(self.token,"new_repo",None) + return self.__render( 'repo_edit.tmpl', { 'repo': input_repo, + 'editable' : can_edit } ) def repo_save(self,name=None,oldname=None,new_or_edit=None,editmode="edit", diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index ccbb72f..3fc2f5a 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -17,8 +17,6 @@ function disablename(value) #if $editable != True
-$editable - WARNING: It looks like you do not have permission to make changes. To recieve access, contact your Cobbler server administrator.
diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index 95ad1fd..4077c57 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -13,6 +13,13 @@ function disablename(value) #end if +#if $editable != True +
+WARNING: It looks like you do not have permission to make changes. +To recieve access, contact your Cobbler server administrator. +
+#end if +
diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index 8d22879..bb40ee7 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -13,6 +13,12 @@ function disablename(value) #end if +#if $editable != True +
+WARNING: It looks like you do not have permission to make changes. +To recieve access, contact your Cobbler server administrator. +
+#end if
diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index bc8a0a3..309d3b2 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -65,7 +65,6 @@ function get_random_mac(field) #set $defined_interfaces = [ "intf0" ] #end if - ### ### now generate the onload function. ### @@ -87,6 +86,13 @@ function page_onload() { } +#if $editable != True +
+WARNING: It looks like you do not have permission to make changes. +To recieve access, contact your Cobbler server administrator. +
+#end if +
-- cgit From 339e79b493b8bcf41dbf59a1931d171e04c0284d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 1 Apr 2008 14:21:16 -0400 Subject: Remove save/edit buttons for WebUI objects we know the user can't edit, to prevent them from trying and getting the auth error as it is processed. --- webui_templates/distro_edit.tmpl | 2 ++ webui_templates/profile_edit.tmpl | 2 ++ webui_templates/repo_edit.tmpl | 11 +++++++---- webui_templates/system_edit.tmpl | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 3fc2f5a..6089ac9 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -210,6 +210,7 @@ To recieve access, contact your Cobbler server administrator. #end if + #if $editable == True @@ -218,6 +219,7 @@ To recieve access, contact your Cobbler server administrator. + #end if
diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index 4077c57..a0b6fdc 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -347,6 +347,7 @@ To recieve access, contact your Cobbler server administrator. #end if + #if $editable == True @@ -355,6 +356,7 @@ To recieve access, contact your Cobbler server administrator. + #end if
#end block body diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index bb40ee7..fdfd840 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -201,6 +201,7 @@ To recieve access, contact your Cobbler server administrator. #end if + #if $editable == True @@ -208,6 +209,7 @@ To recieve access, contact your Cobbler server administrator. + #end if
@@ -215,10 +217,11 @@ To recieve access, contact your Cobbler server administrator.
-Note: Newly added repos contain no package content until "cobbler reposync" is run -from the command line, which means that profiles relying on these repositories will -not install. Placing "cobbler reposync" on a crontab to ensure frequent updates -is recommended procedure. +Note: Newly added repos contain no package content until +"cobbler reposync" is run from the command line, which means +that profiles relying on these repositories will not install. +Placing "cobbler reposync" on a crontab to ensure frequent +updates is recommended procedure.

diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 309d3b2..517f569 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -429,6 +429,7 @@ To recieve access, contact your Cobbler server administrator. #end if + #if $editable == True @@ -437,6 +438,7 @@ To recieve access, contact your Cobbler server administrator. + #end if -- cgit From 952cdfbd85671c2ed6dfa55fa34b33739815e288 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 1 Apr 2008 15:00:38 -0400 Subject: Move the authentication error messages in the WebUI to a common template file so they can be reused, in those messages, explain who is in the access control list. Also make the system "remove" buttons for NICs change to "hide" buttons in non-editable modes. --- setup.py | 1 + webui_templates/distro_edit.tmpl | 8 +++----- webui_templates/enoaccess.tmpl | 10 ++++++++++ webui_templates/profile_edit.tmpl | 8 +++----- webui_templates/repo_edit.tmpl | 8 +++----- webui_templates/system_edit.tmpl | 31 ++++++++++++++++++++----------- 6 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 webui_templates/enoaccess.tmpl diff --git a/setup.py b/setup.py index 8253216..3285b2f 100644 --- a/setup.py +++ b/setup.py @@ -138,6 +138,7 @@ if __name__ == "__main__": (wwwtmpl, ['webui_templates/empty.tmpl']), (wwwtmpl, ['webui_templates/blank.tmpl']), + (wwwtmpl, ['webui_templates/enoaccess.tmpl']), (wwwtmpl, ['webui_templates/distro_list.tmpl']), (wwwtmpl, ['webui_templates/distro_edit.tmpl']), (wwwtmpl, ['webui_templates/profile_list.tmpl']), diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 6089ac9..47b7e10 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -16,10 +16,8 @@ function disablename(value) #end if #if $editable != True -
-WARNING: It looks like you do not have permission to make changes. -To recieve access, contact your Cobbler server administrator. -
+#set global $owners = $distro.owners +#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl" #end if @@ -197,7 +195,7 @@ To recieve access, contact your Cobbler server administrator. - #if $distro + #if $distro and $editable == True diff --git a/webui_templates/enoaccess.tmpl b/webui_templates/enoaccess.tmpl new file mode 100644 index 0000000..5af1ae3 --- /dev/null +++ b/webui_templates/enoaccess.tmpl @@ -0,0 +1,10 @@ +#set $myowners = ", ".join($owners) + +
+WARNING: You do not have permission to make changes to this +object. To recieve access, contact your Cobbler server administrator. +
+ +The access control list for this object is: $myowners. +
+ diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index a0b6fdc..258a8f2 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -14,10 +14,8 @@ function disablename(value) #end if #if $editable != True -
-WARNING: It looks like you do not have permission to make changes. -To recieve access, contact your Cobbler server administrator. -
+#set global $owners = $profile.owners +#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl" #end if @@ -334,7 +332,7 @@ To recieve access, contact your Cobbler server administrator. - #if $profile + #if $profile and $editable == True diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index fdfd840..b58e54f 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -14,10 +14,8 @@ function disablename(value) #end if #if $editable != True -
-WARNING: It looks like you do not have permission to make changes. -To recieve access, contact your Cobbler server administrator. -
+#set global $owners = $repo.owners +#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl" #end if @@ -188,7 +186,7 @@ To recieve access, contact your Cobbler server administrator. - #if $repo + #if $repo and $editable == True diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 517f569..cdb1adc 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -14,9 +14,11 @@ function delete_interface(num) { - #for $field in $fields - document.getElementById("${field}-intf" + num).value = ""; - #end for + #if $editable == True + #for $field in $fields + document.getElementById("${field}-intf" + num).value = ""; + #end for + #end if toggleRowVisibility("id" + num); } @@ -87,10 +89,8 @@ function page_onload() { #if $editable != True -
-WARNING: It looks like you do not have permission to make changes. -To recieve access, contact your Cobbler server administrator. -
+#set global $owners = $system.owners +#include "/usr/share/cobbler/webui_templates/enoaccess.tmpl" #end if @@ -390,11 +390,20 @@ To recieve access, contact your Cobbler server administrator. #if $interface != "intf0" - + #if $editable == True + + #else + + #end if - -

Clicking this button removes the interface from the configuration.

+ #if $editable == True + +

Clicking this button removes the interface from the configuration.

+ #else + + + #end if #end if @@ -416,7 +425,7 @@ To recieve access, contact your Cobbler server administrator. - #if $system + #if $system and $editable == True -- cgit From 2e0c028db2cb84722169524718cd75a80a3132bb Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 1 Apr 2008 15:23:15 -0400 Subject: Apply vlaurenz's LDAP patch to allow non-anonymous bind and other magic neccessary in some configurations. Wiki has/will be updated to explain usage for those who need it. For those that don't the defaults should be sufficient for the new parameters. --- cobbler/modules/authn_ldap.py | 38 ++++++++++++++++++++++++-------------- cobbler/settings.py | 4 ++++ config/settings | 4 ++++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index 8e54a45..cec913b 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -12,14 +12,12 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ import distutils.sysconfig -#import ConfigParser import sys import os from rhpl.translate import _, N_, textdomain, utf8 import md5 import traceback import ldap -import traceback plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib @@ -29,8 +27,6 @@ import cexceptions import utils import api as cobbler_api -#CONFIG_FILE='/etc/cobbler/auth_ldap.conf' - def register(): """ The mandatory cobbler module registration hook. @@ -42,14 +38,13 @@ def authenticate(api_handle,username,password): """ Validate an ldap bind, returning True/False """ - - server = api_handle.settings().ldap_server - basedn = api_handle.settings().ldap_base_dn - port = api_handle.settings().ldap_port - tls = api_handle.settings().ldap_tls - # parse CONFIG_FILE - # server,basedn,port,tls = __parse_config() + server = api_handle.settings().ldap_server + basedn = api_handle.settings().ldap_base_dn + port = api_handle.settings().ldap_port + tls = api_handle.settings().ldap_tls + anon_bind = api_handle.settings().ldap_anonymous_bind + prefix = api_handle.settings().ldap_search_prefix # form our ldap uri based on connection port if port == '389': @@ -73,17 +68,32 @@ def authenticate(api_handle,username,password): traceback.print_exc() return False + # if we're not allowed to search anonymously, + # grok the search bind settings and attempt to bind + anon_bind = str(anon_bind).lower() + if anon_bind not in [ "on", "true", "yes", "1" ]: + searchdn = api_handle.settings().ldap_search_bind_dn + searchpw = api_handle.settings().ldap_search_passwd + + if searchdn == '' or searchpw == '': + raise "Missing search bind settings" + + try: + dir.simple_bind_s(searchdn, searchpw) + except: + traceback.print_exc() + return False + # perform a subtree search in basedn to find the full dn of the user # TODO: what if username is a CN? maybe it goes into the config file as well? - filter = "uid=" + username + filter = prefix + username result = dir.search_s(basedn, ldap.SCOPE_SUBTREE, filter, []) if result: for dn,entry in result: - # uid should be unique so we should only have one result + # username _should_ be unique so we should only have one result # ignore entry; we don't need it pass else: - # print "FAIL 2" return False try: diff --git a/cobbler/settings.py b/cobbler/settings.py index e3d8a46..7681188 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -44,6 +44,10 @@ DEFAULTS = { "ldap_base_dn" : "DC=devel,DC=redhat,DC=com", "ldap_port" : 389, "ldap_tls" : "on", + "ldap_anonymous_bind" : 1, + "ldap_search_bind_dn" : '', + "ldap_search_passwd" : '', + "ldap_search_prefix" : 'uid=', "kerberos_realm" : "EXAMPLE.COM", "kernel_options" : { "lang" : " ", diff --git a/config/settings b/config/settings index 11c21f2..2fd2736 100644 --- a/config/settings +++ b/config/settings @@ -24,6 +24,10 @@ ldap_server: "ldap.example.com" ldap_base_dn: "DC=example,DC=com" ldap_port: 389 ldap_tls: 1 +ldap_anonymous_bind: 1 +ldap_search_bind_dn: '' +ldap_search_passwd: '' +ldap_search_prefix: 'uid=' manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' -- cgit From 8eff9bca139d66f86391362a28abdc07966817a3 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 2 Apr 2008 15:21:38 -0400 Subject: Implementated authorization logic around shared kickstart templates. --- cobbler/modules/authz_ownership.py | 69 ++++++++++++++++++++++++++++---------- cobbler/remote.py | 5 ++- tests/tests.py | 31 +++++++++++++++-- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index 3befc4a..ff5c016 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -50,6 +50,47 @@ def __parse_config(): alldata[g][o] = 1 return alldata +def __authorize_kickstart(api_handle, user, user_groups, file): + # the authorization rules for kickstart editing are a bit + # of a special case. Non-admin users can edit a kickstart + # only if all objects that depend on that kickstart are + # editable by the user in question. + # + # Example: + # if Pinky owns ProfileA + # and the Brain owns ProfileB + # and both profiles use the same kickstart template + # and neither Pinky nor the Brain is an admin + # neither is allowed to edit the kickstart template + # because they would make unwanted changes to each other + # + # In the above scenario the UI will explain the problem + # and ask that the user asks the admin to resolve it if required. + # NOTE: this function is only called by authorize so admin users are + # cleared before this function is called. + + lst = api_handle.find_profile(kickstart=kickstart, return_list=True) + lst.extend(api_handle.find_system(kickstart=kickstart, return_list=True)) + for obj in lst: + if not __is_user_allowed(obj, user, user_groups): + return 0 + return 1 + +def __is_user_allowed(obj, user, user_groups): + if obj.owners == []: + # no ownership restrictions, cleared + return 1 + for allowed in obj.owners: + if user == allowed: + # user match + return 1 + # else look for a group match + for group in user_groups: + if group == allowed and user in user_groups[group]: + return 1 + return 0 + + def authorize(api_handle,user,resource,arg1=None,arg2=None): """ @@ -60,10 +101,10 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): user_groups = __parse_config() # classify the type of operation - save_or_remove = False - for criteria in ["save_","remove_","modify_"]: + modify_operation = False + for criteria in ["save","remove","modify","write","edit"]: if resource.find(criteria) != -1: - save_or_remove = True + modify_operation = True # FIXME: is everyone allowed to copy? I think so. # FIXME: deal with the problem of deleted parents and promotion @@ -83,14 +124,19 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): # if the user isn't anywhere in the file, reject regardless # they can still use read-only XMLRPC return 0 - if not save_or_remove: + if not modify_operation: # sufficient to allow access for non save/remove ops to all # users for now, may want to refine later. return 1 - # now we have a save_or_remove op, so we must check ownership + # now we have a modify_operation op, so we must check ownership # of the object. remove ops pass in arg1 as a string name, # saves pass in actual objects, so we must treat them differently. + # kickstarts are even more special so we call those out to another + # function, rather than going through the rest of the code here. + + if resource.find("kickstart"): + return authorize_kickstart(api_handle,user,user_groups,arg1) obj = None if resource.find("remove") != -1: @@ -109,18 +155,7 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): if obj.owners is None or obj.owners == []: return 1 - # otherwise, ownership by user/group - for allowed in obj.owners: - if user == allowed: - # user match - return 1 - for group in user_groups: - if group == allowed and user in user_groups[group]: - return 1 - - # can't find user or group in ownership list and ownership is defined - # so reject the operation - return 0 + return __is_user_allowed(user,group,user_groups) if __name__ == "__main__": diff --git a/cobbler/remote.py b/cobbler/remote.py index 5e3fb83..ebb01f7 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -1050,7 +1050,10 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ self.log("read_or_write_kickstart_template",name=kickstart_file,token=token) - self.check_access(token,"read_or_write_kickstart_templates",kickstart_file,is_read) + if is_read: + self.check_access(token,"read_kickstart",kickstart_file) + else: + self.check_access(token,"modify_kickstart",kickstart_file) if kickstart_file.find("..") != -1 or not kickstart_file.startswith("/"): raise CX(_("tainted file location")) diff --git a/tests/tests.py b/tests/tests.py index c3cbea7..f9bc368 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -105,7 +105,9 @@ class Ownership(BootTest): def test_ownership_params(self): - # initially just test that we can set ownership on various components + fd = open("/tmp/test_cobbler_kickstart","w+") + fd.write("") + fd.close() distro = self.api.find_distro(name="testdistro0") profile = self.api.find_profile(name="testprofile0") @@ -113,7 +115,8 @@ class Ownership(BootTest): repo = self.api.find_repo(name="test_repo") self.assertTrue(distro.set_owners(["superlab","basement1"])) self.assertTrue(profile.set_owners(["superlab","basement1"])) - self.assertTrue(system.set_owners(["superlab","basement1"])) + self.assertTrue(profile.set_kickstart("/tmp/test_cobbler_kickstart")) + self.assertTrue(system.set_owners(["superlab","basement1","basement3"])) self.assertTrue(repo.set_owners([])) self.api.add_distro(distro) self.api.add_profile(profile) @@ -136,17 +139,41 @@ class Ownership(BootTest): fd.write("\n") fd.write("[superlab]\n") fd.write("superlab1 = 1\n") + fd.write("superlab2 = 1\n") fd.write("\n") fd.write("[basement]\n") fd.write("basement1 = 1\n") fd.write("basement2 = 1\n") + fd.write("basement3 = 1\n") fd.close() + xo = self.api.find_distro("testdistro0") xn = "testdistro0" ro = self.api.find_repo("test_repo") rn = "test_repo" + # WARNING: complex test explanation follows! + # we must ensure those who can edit the kickstart are only those + # who can edit all objects that depend on the said kickstart + # in this test, superlab & basement1 can edit test_profile0 + # superlab & basement1/3 can edit test_system0 + # the systems share a common kickstart record (in this case + # explicitly set, which is a bit arbitrary as they are parent/child + # nodes, but the concept is not limited to this). + # Therefore the correct result is that the following users can edit: + # admin1, superlab1, superlab2 + # And these folks can't + # basement1, basement2 + # Basement2 is rejected because the kickstart is shared by something + # basmeent2 can not edit. + + for user in [ "admin1", "superlab1", "superlab2", "basement1" ]: + self.assertTrue(1==authorize(self.api, user, "modify_kickstart", xo), "%s can modify_kickstart" % user) + + for user in [ "basement2", "dne" ]: + self.assertTrue(0==authorize(self.api, user, "modify_kickstart", xo), "%s can modify_kickstart" % user) + # ensure admin1 can edit (he's an admin) and do other tasks # same applies to basement1 who is explicitly added as a user # and superlab1 who is in a group in the ownership list -- cgit From 511d6fc9976bd10344c920a4f03ba57da70527d9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 2 Apr 2008 16:32:26 -0400 Subject: Make changes in order to assure that users not in users.conf can still read web content if cleared past the authentication layer. Also make changes to the kickstart editor to indicate to users when they don't have permission to edit kickstarts -- and to show possible causes. --- cobbler/modules/authz_ownership.py | 7 +++++++ cobbler/webui/CobblerWeb.py | 5 +++++ setup.py | 1 - webui_templates/enoaccess.tmpl | 2 ++ webui_templates/ksfile_edit.tmpl | 33 ++++++++++++++++++++++++++++++--- webui_templates/ksfile_view.tmpl | 6 ------ 6 files changed, 44 insertions(+), 10 deletions(-) delete mode 100644 webui_templates/ksfile_view.tmpl diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index ff5c016..9e7a217 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -98,6 +98,13 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): All users in the file are permitted by this module. """ + # everybody can get read-only access to everything + # if they pass authorization, they don't have to be in users.conf + if resource is not None: + for x in [ "get", "read", "/cobbler/web" ]: + if resource.startswith(x): + return 1 + user_groups = __parse_config() # classify the type of operation diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 2012708..f6afe2f 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -677,10 +677,15 @@ class CobblerWeb(object): } ) def ksfile_edit(self, name=None,**spam): + + if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() + + can_edit = self.remote.check_access_no_fail(self.token,"modify_kickstart",name) return self.__render( 'ksfile_edit.tmpl', { 'name': name, + 'editable' : can_edit, 'ksdata': self.remote.read_or_write_kickstart_template(name,True,"",self.token) } ) diff --git a/setup.py b/setup.py index 3285b2f..51ffe80 100644 --- a/setup.py +++ b/setup.py @@ -159,7 +159,6 @@ if __name__ == "__main__": # Web UI kickstart file editing (wwwtmpl, ['webui_templates/ksfile_edit.tmpl']), (wwwtmpl, ['webui_templates/ksfile_list.tmpl']), - (wwwtmpl, ['webui_templates/ksfile_view.tmpl']), # Web UI support files (wwwgfx, ['docs/wui.html']), diff --git a/webui_templates/enoaccess.tmpl b/webui_templates/enoaccess.tmpl index 5af1ae3..b3a001e 100644 --- a/webui_templates/enoaccess.tmpl +++ b/webui_templates/enoaccess.tmpl @@ -5,6 +5,8 @@ WARNING: You do not have permission to make changes to this object. To recieve access, contact your Cobbler server administrator.
+#if $owners != [] The access control list for this object is: $myowners. +#end if diff --git a/webui_templates/ksfile_edit.tmpl b/webui_templates/ksfile_edit.tmpl index 5e0c5ae..87c9f41 100644 --- a/webui_templates/ksfile_edit.tmpl +++ b/webui_templates/ksfile_edit.tmpl @@ -1,7 +1,22 @@ #extends cobbler.webui.master -#attr $title = "Cobbler: Edit Kickstart File $ksfile" +##attr $title = "Cobbler: Edit Kickstart File $ksfile" #block body + +#if $editable != True +
+NOTE: You do not have permission to make changes to this +kickstart template and can only read it. It is possible that +other Cobbler users has secured permissions on Cobbler +profiles/systems that depend on this template -- changing this +template would ultimately affect those profile/system records which +you do not have access to. Alternatively, you may not have access +to edit *any* kickstart templates. Contact your Cobbler server administrator +if you need to resolve this. +
+
+#end if +
@@ -10,8 +25,20 @@

- - + #if $editable == True + + + #end if +
+ +#if $editable == True +
+
+NOTE: Run a cobbler sync to after making changes here in order +for kickstart files to be regenerated. +
+#end if + #end block body diff --git a/webui_templates/ksfile_view.tmpl b/webui_templates/ksfile_view.tmpl deleted file mode 100644 index b6abf67..0000000 --- a/webui_templates/ksfile_view.tmpl +++ /dev/null @@ -1,6 +0,0 @@ -#extends cobbler.webui.master - -#block body -
$ksdata
-#end block body - -- cgit From cb93964cf63ad7e56fd5d581df3b59c0e7e8d1ac Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 2 Apr 2008 17:44:06 -0400 Subject: Some fixes to the ownership module. --- cobbler/modules/authz_ownership.py | 8 ++++---- cobbler/webui/CobblerWeb.py | 8 ++++---- webui_templates/distro_edit.tmpl | 1 + webui_templates/profile_edit.tmpl | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index 9e7a217..1fe25a9 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -50,7 +50,7 @@ def __parse_config(): alldata[g][o] = 1 return alldata -def __authorize_kickstart(api_handle, user, user_groups, file): +def __authorize_kickstart(api_handle, user, user_groups, kickstart): # the authorization rules for kickstart editing are a bit # of a special case. Non-admin users can edit a kickstart # only if all objects that depend on that kickstart are @@ -142,8 +142,8 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): # kickstarts are even more special so we call those out to another # function, rather than going through the rest of the code here. - if resource.find("kickstart"): - return authorize_kickstart(api_handle,user,user_groups,arg1) + if resource.find("kickstart") != -1: + return __authorize_kickstart(api_handle,user,user_groups,arg1) obj = None if resource.find("remove") != -1: @@ -162,7 +162,7 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): if obj.owners is None or obj.owners == []: return 1 - return __is_user_allowed(user,group,user_groups) + return __is_user_allowed(obj,user,user_groups) if __name__ == "__main__": diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index f6afe2f..cdeef24 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -175,7 +175,7 @@ class CobblerWeb(object): def distro_save(self,name=None,oldname=None,new_or_edit=None,editmode='edit',kernel=None, initrd=None,kopts=None,ksmeta=None,owners=None,arch=None,breed=None, - delete1=None,delete2=None,**args): + delete1=None,delete2=None,recursive=False,**args): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -188,7 +188,7 @@ class CobblerWeb(object): # handle deletes as a special case if new_or_edit == 'edit' and delete1 and delete2: try: - self.remote.remove_distro(name,self.token,1) # recursive + self.remote.remove_distro(name,self.token,recursive) except Exception, e: return self.error_page("could not delete %s, %s" % (name,str(e))) return self.distro_list() @@ -460,7 +460,7 @@ class CobblerWeb(object): distro=None,kickstart=None,kopts=None, ksmeta=None,owners=None,virtfilesize=None,virtram=None,virttype=None, virtpath=None,repos=None,dhcptag=None,delete1=None,delete2=None, - parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,**args): + parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,recursive=False,**args): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -480,7 +480,7 @@ class CobblerWeb(object): # handle deletes as a special case if new_or_edit == 'edit' and delete1 and delete2: try: - self.remote.remove_profile(name,self.token,1) + self.remote.remove_profile(name,self.token,recursive) except Exception, e: return self.error_page("could not delete %s, %s" % (name,str(e))) return self.profile_list() diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 47b7e10..80536f5 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -203,6 +203,7 @@ function disablename(value) Yes Really + Delete child objects?

Check both buttons and click save to delete this object

diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index 258a8f2..82be534 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -340,6 +340,7 @@ function disablename(value) Yes Really + Delete child objects?

Check both buttons and click save to delete this object

-- cgit From 3e29f5d729da704f608b2cd2dc7cf16a42d934f4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 2 Apr 2008 17:53:42 -0400 Subject: Deletion from the WebUI now has a checkbox for distros/profiles to decide whether you want them to delete recursively or not. The default is not, and if you own a parent object you /are/ allowed to delete the children following the ownership model at this point in time. --- cobbler/remote.py | 2 +- cobbler/webui/CobblerWeb.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index ebb01f7..ed3cc53 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -953,7 +953,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): Deletes a distro from a collection. Note that this just requires the name of the distro, not a handle. """ - self.log("remove_distro",name=name,token=token) + self.log("remove_distro (%s)" % recursive,name=name,token=token) self.check_access(token, "remove_distro", name) rc = self.api._config.distros().remove(name,recursive=True) return rc diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index cdeef24..8813006 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -187,8 +187,12 @@ class CobblerWeb(object): # handle deletes as a special case if new_or_edit == 'edit' and delete1 and delete2: - try: - self.remote.remove_distro(name,self.token,recursive) + try: + if recursive is None: + self.remote.remove_distro(name,self.token,False) + else: + self.remote.remove_distro(name,self.token,True) + except Exception, e: return self.error_page("could not delete %s, %s" % (name,str(e))) return self.distro_list() @@ -480,7 +484,11 @@ class CobblerWeb(object): # handle deletes as a special case if new_or_edit == 'edit' and delete1 and delete2: try: - self.remote.remove_profile(name,self.token,recursive) + if recursive: + self.remote.remove_profile(name,self.token,True) + else: + self.remote.remove_profile(name,self.token,False) + except Exception, e: return self.error_page("could not delete %s, %s" % (name,str(e))) return self.profile_list() -- cgit From 3c08531c4e913268b94618b9939a44161d2dc556 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 2 Apr 2008 18:29:06 -0400 Subject: Added some code to allow assignment of default ownership from settings for new objects. Also, ownership field for new objects in the WebUI is prepopulated with the username of the logged in user, to prevent accidental lockouts. All of this is untested at this point -- will be polished up soon. --- cobbler/item_distro.py | 4 ++-- cobbler/item_profile.py | 4 ++-- cobbler/item_system.py | 4 ++-- cobbler/settings.py | 1 + cobbler/webui/CobblerWeb.py | 4 ++++ config/settings | 1 + tests/tests.py | 4 ++-- webui_templates/distro_edit.tmpl | 2 ++ webui_templates/profile_edit.tmpl | 2 ++ webui_templates/repo_edit.tmpl | 2 ++ webui_templates/system_edit.tmpl | 2 ++ 11 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index c40f002..3b82595 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -32,7 +32,7 @@ class Distro(item.Item): Reset this object. """ self.name = None - self.owners = [] + self.owners = self.settings.default_ownership self.kernel = (None, '<>')[is_subobject] self.initrd = (None, '<>')[is_subobject] self.kernel_options = ({}, '<>')[is_subobject] @@ -61,7 +61,7 @@ class Distro(item.Item): """ self.parent = self.load_item(seed_data,'parent') self.name = self.load_item(seed_data,'name') - self.owners = self.load_item(seed_data,'owners',[]) + self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership) self.kernel = self.load_item(seed_data,'kernel') self.initrd = self.load_item(seed_data,'initrd') self.kernel_options = self.load_item(seed_data,'kernel_options') diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index 3145b82..9135e53 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -34,7 +34,7 @@ class Profile(item.Item): Reset this object. """ self.name = None - self.owners = [] # should not be inheritable + self.owners = self.settings.default_ownership self.distro = (None, '<>')[is_subobject] self.kickstart = (self.settings.default_kickstart , '<>')[is_subobject] self.kernel_options = ({}, '<>')[is_subobject] @@ -58,7 +58,7 @@ class Profile(item.Item): self.parent = self.load_item(seed_data,'parent','') self.name = self.load_item(seed_data,'name') - self.owners = self.load_item(seed_data,'owners',[]) + self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership) self.distro = self.load_item(seed_data,'distro') self.kickstart = self.load_item(seed_data,'kickstart') self.kernel_options = self.load_item(seed_data,'kernel_options') diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 6b2315a..7346d33 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -30,7 +30,7 @@ class System(item.Item): def clear(self,is_subobject=False): self.name = None - self.owners = None + self.owners = self.settings.default_ownership self.profile = None self.kernel_options = {} self.ks_meta = {} @@ -81,7 +81,7 @@ class System(item.Item): self.parent = self.load_item(seed_data, 'parent') self.name = self.load_item(seed_data, 'name') - self.owners = self.load_item(seed_data, 'owners', []) + self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership) self.profile = self.load_item(seed_data, 'profile') self.kernel_options = self.load_item(seed_data, 'kernel_options', {}) self.ks_meta = self.load_item(seed_data, 'ks_meta', {}) diff --git a/cobbler/settings.py b/cobbler/settings.py index 7681188..4372f3a 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -33,6 +33,7 @@ DEFAULTS = { "default_virt_type" : "auto", "default_virt_file_size" : "5", "default_virt_ram" : "512", + "default_ownership" : [ "admin" ], "dhcpd_conf" : "/etc/dhcpd.conf", "dhcpd_bin" : "/usr/sbin/dhcpd", "dnsmasq_bin" : "/usr/sbin/dnsmasq", diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 8813006..ee2d700 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -168,6 +168,7 @@ class CobblerWeb(object): return self.__render( 'distro_edit.tmpl', { + 'user' : self.username, 'edit' : True, 'editable' : can_edit, 'distro': input_distro, @@ -408,6 +409,7 @@ class CobblerWeb(object): can_edit = self.remote.check_access_no_fail(self.token,"new_system",None) return self.__render( 'system_edit.tmpl', { + 'user' : self.username, 'edit' : True, 'editable' : can_edit, 'system': input_system, @@ -450,6 +452,7 @@ class CobblerWeb(object): can_edit = self.remote.check_access_no_fail(self.token,"new_profile",None) return self.__render( 'profile_edit.tmpl', { + 'user' : self.username, 'edit' : True, 'editable' : can_edit, 'profile': input_profile, @@ -598,6 +601,7 @@ class CobblerWeb(object): return self.__render( 'repo_edit.tmpl', { + 'user' : self.username, 'repo': input_repo, 'editable' : can_edit } ) diff --git a/config/settings b/config/settings index 2fd2736..f4c2487 100644 --- a/config/settings +++ b/config/settings @@ -9,6 +9,7 @@ default_virt_bridge: xenbr0 default_virt_type: auto default_virt_file_size: 5 default_virt_ram: 512 +default_ownership: [ "admin" ] dhcpd_bin: /usr/sbin/dhcpd dhcpd_conf: /etc/dhcpd.conf dnsmasq_bin: /usr/sbin/dnsmasq diff --git a/tests/tests.py b/tests/tests.py index f9bc368..2c055ba 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -169,10 +169,10 @@ class Ownership(BootTest): # basmeent2 can not edit. for user in [ "admin1", "superlab1", "superlab2", "basement1" ]: - self.assertTrue(1==authorize(self.api, user, "modify_kickstart", xo), "%s can modify_kickstart" % user) + self.assertTrue(1==authorize(self.api, user, "modify_kickstart", "/tmp/test_cobbler_kickstart"), "%s can modify_kickstart" % user) for user in [ "basement2", "dne" ]: - self.assertTrue(0==authorize(self.api, user, "modify_kickstart", xo), "%s can modify_kickstart" % user) + self.assertTrue(0==authorize(self.api, user, "modify_kickstart", "/tmp/test_cobbler_kickstart"), "%s can modify_kickstart" % user) # ensure admin1 can edit (he's an admin) and do other tasks # same applies to basement1 who is explicitly added as a user diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 80536f5..5d19a43 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -188,6 +188,8 @@ function disablename(value)

Applies only if using authz_ownership module, comma-delimited

diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index 82be534..2f7a732 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -324,6 +324,8 @@ function disablename(value) diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index b58e54f..469854a 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -179,6 +179,8 @@ function disablename(value)

Applies only if using authz_ownership module, comma-delimited

diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index cdb1adc..5396f1d 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -225,6 +225,8 @@ function page_onload() {

Applies only if using authz_ownership module, comma-delimited

-- cgit From ae3014f2498a8566acbe4439eb0a6916456ce27f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 3 Apr 2008 14:39:50 -0400 Subject: Fix default ownership on cobbler owned objects to come from the settings file (who's default is "admin" through the CLI) ... the webui will prepopulate the field with the name of the logged in user. --- CHANGELOG | 1 + cobbler/item.py | 1 - cobbler/item_distro.py | 2 ++ cobbler/item_profile.py | 2 ++ cobbler/item_repo.py | 7 ++++--- cobbler/item_system.py | 3 ++- cobbler/settings.py | 2 +- config/settings | 2 +- tests/tests.py | 12 +++++++++++- 9 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 804254a..60e6369 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ Cobbler CHANGELOG - tftpboot location is now inferred from xinetd config (added for F9 compat) - added authn_ldap and stub for authz_configfile - authz_configfile allows filtering ldap/other users by config file +- WebUI now has checkbox on distro/profile for deleting child objects - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/cobbler/item.py b/cobbler/item.py index f51f959..027472a 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -51,7 +51,6 @@ class Item(serializable.Serializable): self.clear(is_subobject) # reset behavior differs for inheritance cases self.parent = '' # all objects by default are not subobjects self.children = {} # caching for performance reasons, not serialized - self.owners = [] self.log_func = self.config.api.log def clear(self): diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index 3b82595..98eaae7 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -77,6 +77,8 @@ class Distro(item.Item): if self.ks_meta != "<>" and type(self.ks_meta) != dict: self.set_ksmeta(self.ks_meta) + self.set_owners(self.owners) + return self def set_kernel(self,kernel): diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index 9135e53..2e3a539 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -89,6 +89,8 @@ class Profile(item.Item): if self.repos != "<>" and type(self.ks_meta) != list: self.set_repos(self.repos) + self.set_owners(self.owners) + return self def set_parent(self,parent_name): diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index e796561..9063c5b 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -39,7 +39,7 @@ class Repo(item.Item): self.depth = 2 # arbitrary, as not really apart of the graph self.arch = "" # use default arch self.yumopts = {} - self.owners = [] + self.owners = self.settings.default_ownership def from_datastruct(self,seed_data): self.parent = self.load_item(seed_data, 'parent') @@ -52,10 +52,11 @@ class Repo(item.Item): self.arch = self.load_item(seed_data, 'arch') self.depth = self.load_item(seed_data, 'depth', 2) self.yumopts = self.load_item(seed_data, 'yumopts', {}) - self.owners = self.load_item(seed_data, 'owners', []) + self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership) - # force this to be saved as a boolean + # coerce types from input file self.set_keep_updated(self.keep_updated) + self.set_owners(self.owners) return self diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 7346d33..d09d579 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -128,8 +128,9 @@ class System(item.Item): # explicitly re-call the set_name function to possibily populate MAC/IP. self.set_name(self.name) - # coerce this into a boolean + # coerce types from input file self.set_netboot_enabled(self.netboot_enabled) + self.set_owners(self.owners) return self diff --git a/cobbler/settings.py b/cobbler/settings.py index 4372f3a..0ef2ab7 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -33,7 +33,7 @@ DEFAULTS = { "default_virt_type" : "auto", "default_virt_file_size" : "5", "default_virt_ram" : "512", - "default_ownership" : [ "admin" ], + "default_ownership" : "admin", "dhcpd_conf" : "/etc/dhcpd.conf", "dhcpd_bin" : "/usr/sbin/dhcpd", "dnsmasq_bin" : "/usr/sbin/dnsmasq", diff --git a/config/settings b/config/settings index f4c2487..36cd9b4 100644 --- a/config/settings +++ b/config/settings @@ -9,7 +9,7 @@ default_virt_bridge: xenbr0 default_virt_type: auto default_virt_file_size: 5 default_virt_ram: 512 -default_ownership: [ "admin" ] +default_ownership: "admin" dhcpd_bin: /usr/sbin/dhcpd dhcpd_conf: /etc/dhcpd.conf dnsmasq_bin: /usr/sbin/dnsmasq diff --git a/tests/tests.py b/tests/tests.py index 2c055ba..8a86bab 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -73,7 +73,7 @@ class BootTest(unittest.TestCase): self.assertTrue(distro.set_initrd(self.fk_initrd)) self.assertTrue(self.api.add_distro(distro)) self.assertTrue(self.api.find_distro(name="testdistro0")) - + profile = self.api.new_profile() self.assertTrue(profile.set_name("testprofile0")) self.assertTrue(profile.set_distro("testdistro0")) @@ -109,10 +109,20 @@ class Ownership(BootTest): fd.write("") fd.close() + # find things we are going to test with distro = self.api.find_distro(name="testdistro0") profile = self.api.find_profile(name="testprofile0") system = self.api.find_system(name="drwily.rdu.redhat.com") repo = self.api.find_repo(name="test_repo") + + # as we didn't specify an owner for objects, the default + # ownership should be as specified in settings + default_owner = self.api.settings().default_ownership + for obj in [ distro, profile, system, repo ]: + self.assertTrue(obj is not None) + self.assertEquals(obj.owners, default_owner, "default owner for %s" % obj) + + # verify we can test things self.assertTrue(distro.set_owners(["superlab","basement1"])) self.assertTrue(profile.set_owners(["superlab","basement1"])) self.assertTrue(profile.set_kickstart("/tmp/test_cobbler_kickstart")) -- cgit From 07443e6ec0a7d2c3c370b944d066a074692ca139 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 3 Apr 2008 15:15:23 -0400 Subject: If logging in with token and not a new login, grab the username from the token for use in filling in form data. --- cobbler/webui/CobblerWeb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index ee2d700..e186f35 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -60,6 +60,7 @@ class CobblerWeb(object): # validate that our token is still good try: self.remote.token_check(self.token) + self.username = self.remote.get_user_from_token(self.token) return True except Exception, e: if str(e).find("invalid token") != -1: -- cgit From 884aa8a7b2124ba3901b75df64d8506be8182284 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 3 Apr 2008 15:54:15 -0400 Subject: Add friendlier messaging to users who do not have permission to create new objects. --- cobbler/webui/CobblerWeb.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index e186f35..3aa0d9d 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -167,6 +167,12 @@ class CobblerWeb(object): else: can_edit = self.remote.check_access_no_fail(self.token,"new_distro",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) + return self.__render( 'distro_edit.tmpl', { 'user' : self.username, @@ -408,6 +414,12 @@ class CobblerWeb(object): can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name) else: can_edit = self.remote.check_access_no_fail(self.token,"new_system",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) + return self.__render( 'system_edit.tmpl', { 'user' : self.username, @@ -451,6 +463,12 @@ class CobblerWeb(object): can_edit = self.remote.check_access_no_fail(self.token,"modify_profile",name) else: can_edit = self.remote.check_access_no_fail(self.token,"new_profile",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) + return self.__render( 'profile_edit.tmpl', { 'user' : self.username, @@ -599,6 +617,11 @@ class CobblerWeb(object): can_edit = self.remote.check_access_no_fail(self.token,"modify_repo",name) else: can_edit = self.remote.check_access_no_fail(self.token,"new_repo",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) return self.__render( 'repo_edit.tmpl', { -- cgit From 596c540d9b5435f8c99c26130d8f7cf4aca3f8dc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 3 Apr 2008 16:16:11 -0400 Subject: Show friendly error message if user does not have sync permissions, also update WebUI "docs" page that points to other public docs. --- cobbler/webui/CobblerWeb.py | 7 +++++++ docs/wui.html | 25 +++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 3aa0d9d..727e4cb 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -742,6 +742,13 @@ class CobblerWeb(object): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() + can_edit = self.remote.check_access_no_fail(self.token,"sync",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) + try: rc = self.remote.sync(self.token) if not rc: diff --git a/docs/wui.html b/docs/wui.html index c05a6f6..ebb9b39 100644 --- a/docs/wui.html +++ b/docs/wui.html @@ -26,21 +26,23 @@

Welcome

-This is the Web UI for your local Cobbler Server. -

+This is the web configuration interface for a Cobbler Server. Cobbler is an automated net install and update server for Linux operating systems. You can use this web interface to decide what you want to install and where -- and then deploy that configuration using network booting (PXE), or do reinstalls and virtual installs with "koan". There is also a koan live CD if you can't set up a PXE environment and still have bare-metal install needs.

-The Cobbler WebUI is designed for day-to-day usage of the Cobbler provisioning server. It performs -most but not all of the functions Cobbler can perform. Nearly all of what you would -need for routine maintaince of your deployment setups can be done through the web. If you have +The Cobbler WebUI is designed for simplifying day-to-day usage of the Cobbler provisioning server. It performs +most but not all of the functions the command line tool "cobbler" or the API can perform. If you have not already done so, you may be interested to read more about Cobbler at -cobbler.et.redhat.com and hosted.fedoraproject.org. +cobbler.et.redhat.com and at fedorahosted.org. Those pages contain further documentation, tips & tricks, and links to the mailing list and users/developers IRC channel.

-It is expected that you have read the Cobbler manpage, which for the most part focuses on cobbler as run from the command line. You will need to use the command line some, so please do read the docs. For starters, you should have started your cobbler install with running "cobbler check" locally. If not, +It is probably a good idea to read the Cobbler manpage, which for the most part focuses on cobbler as run from the command line. Admins will need to use the command line some, so please do read the docs if you are in charge of the Cobbler server -- or if you need more detail about what some of the options mean. +

+ +

+The Cobbler server install should have started your cobbler install with running "cobbler check" locally to resolve potential configuration problems. If not, please do so now before continuing. This will make sure your installation is configured and ready to go. The rest of this document will mainly be detailing the differences between the CLI (as described in the manpage) and the Web interface.

@@ -52,12 +54,15 @@ content from a DVD or an rsync mirror -- running cobbler import locally is also

-Another command that you cannot run locally is "cobbler reposync", which is a fairly long-running operation +Another command that you cannot run via the web interface is "cobbler reposync", which can be a fairly long-running operation that you may want to put on a crontab. You also cannot edit the cobbler settings file (this is more of a precaution against locking yourself out of the WebUI), but you can view it. For instance this means that if you want Cobbler to help manage your DHCP config (great!) you can do that through the Web UI -but you have to turn that on in the settings file. Furthermore, files like /etc/cobbler/dhcp.template -have to be edited locally. +but you have to turn that on in the settings file (be sure to restart cobblerd after making changes). Furthermore, other configuration and template files in /etc/cobbler (that are not kickstart templates) also have to be edited locally. +

+ +

+Your Cobbler server administrator may have set cobbler up to allow access to only certain objects for certain user accounts. In the event of this, you will see messages in the interface that indicate when you do not have permissions to perform certain actions. Contact your Cobbler server administrator to resolve these items or to learn more if you have questions.

-- cgit From d0bc28d37734a35c332e3e60ba56ccff5e037018 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Apr 2008 16:34:48 -0400 Subject: Working on adding checks for duplicate names/ips/macs to cobbler --- cobbler/api.py | 16 +++--- cobbler/collection.py | 69 +++++++++++++++++++++-- cobbler/commands.py | 30 +++++++++- cobbler/modules/cli_system.py | 7 ++- cobbler/settings.py | 2 + config/settings | 2 + templates/dnsmasq.template | 2 +- tests/tests.py | 125 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 238 insertions(+), 15 deletions(-) diff --git a/cobbler/api.py b/cobbler/api.py index 13fc5f3..54c315b 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -221,21 +221,21 @@ class BootAPI: self.log("new_repo",[is_subobject]) return self._config.new_repo(is_subobject=is_subobject) - def add_distro(self, ref): + def add_distro(self, ref, check_for_duplicate_names=False): self.log("add_distro",[ref.name]) - return self._config.distros().add(ref,save=True) + return self._config.distros().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) - def add_profile(self, ref): + def add_profile(self, ref, check_for_duplicate_names=False): self.log("add_profile",[ref.name]) - return self._config.profiles().add(ref,save=True) + return self._config.profiles().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) - def add_system(self,ref): + def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False): self.log("add_system",[ref.name]) - return self._config.systems().add(ref,save=True) + return self._config.systems().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo) - def add_repo(self,ref): + def add_repo(self, ref, check_for_duplicate_names=False): self.log("add_repo",[ref.name]) - return self._config.repos().add(ref,save=True) + return self._config.repos().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) def find_distro(self, name=None, return_list=False, **kargs): return self._config.distros().find(name=name, return_list=return_list, **kargs) diff --git a/cobbler/collection.py b/cobbler/collection.py index 339a4b2..14bba57 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -35,7 +35,8 @@ class Collection(serializable.Serializable): """ self.config = config self.clear() - self.log_func = self.config.api.log + self.api = self.config.api + self.log_func = self.api.log self.lite_sync = None def factory_produce(self,config,seed_data): @@ -125,10 +126,10 @@ class Collection(serializable.Serializable): k.set_parent(newname) else: k.set_distro(newname) - self.config.api.profiles().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers) + self.api.profiles().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers) elif k.COLLECTION_TYPE == "system": k.set_profile(newname) - self.config.api.systems().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers) + self.api.systems().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers) elif k.COLLECTION_TYPE == "repo": raise CX(_("internal error, not expected to have repo child objects")) else: @@ -139,7 +140,7 @@ class Collection(serializable.Serializable): return True - def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=False): + def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=False,check_for_duplicate_names=False,check_for_duplicate_netinfo=False): """ Add an object to the collection, if it's valid. Returns True if the object was added to the collection. Returns False if the @@ -167,6 +168,11 @@ class Collection(serializable.Serializable): # if not saving the object, you can't run these features with_triggers = False with_sync = False + + # Avoid adding objects to the collection + # if an object of the same/ip/mac already exists. + self.__duplication_checks(ref,check_for_duplicate_names,check_for_duplicate_netinfo) + if ref is None or not ref.is_valid(): raise CX(_("insufficient or invalid arguments supplied")) @@ -220,6 +226,61 @@ class Collection(serializable.Serializable): def _run_triggers(self,ref,globber): return utils.run_triggers(ref,globber) + def __duplication_checks(self,ref,check_for_duplicate_names,check_for_duplicate_netinfo): + """ + Prevents adding objects with the same name. + Prevents adding or editing to provide the same IP, or MAC. + This applies to new "add" commands, + Edits that are not copies/renames should only yelp if they match + an object that is not the same as the object being edited. (FIXME) + """ + + # always protect against duplicate names + if check_for_duplicate_names: + match = None + if isinstance(ref, item_system.System): + match = self.api.find_system(ref.name) + elif isinstance(ref, item_profile.Profile): + match = self.api.find_profile(ref.name) + elif isinstance(ref, item_distro.Distro): + match = self.api.find_distro(ref.name) + elif isinstance(ref, item_repo.Repo): + match = self.api.find_repo(ref.name) + + if match: + raise CX(_("An object already exists with that name. Try 'edit'?")) + + # the duplicate mac/ip checks can be disabled. + if not check_for_duplicate_netinfo: + return + + # FIXME: if we run this command on edits it may yield false + # positives when ref is set to replace an object of an existing + # name, so we should probably /not/ run this on edits yet at this + # point. Logic to deal with renames vs. edit/copies is somewhat + # involved. + if isinstance(ref, item_system.System): + for (name, intf) in ref.interfaces.iteritems(): + match_ip = [] + match_mac = [] + input_mac = intf["mac_address"] + input_ip = intf["ip_address"] + if not self.api.settings().allow_duplicate_macs and input_mac is not None and input_mac != "": + match_mac = self.api.find_system(mac_address=input_mac,return_list=True) + if not self.api.settings().allow_duplicate_ips and input_ip is not None and input_ip != "": + match_ip = self.api.find_system(ip_address=input_ip,return_list=True) + # it's ok to conflict with your own net info. + # FIXME: copies won't ever work this way, so they should NOT + # use the flags that engadge these checks. Renames should + # be exempt also + + for x in match_mac: + if x.name != ref.name: + raise CX(_("Can't save system %s. The MAC address (%s) is already used by system %s (%s)") % (ref.name, intf["mac_address"], x.name, name)) + for x in match_ip: + if x.name != ref.name: + raise CX(_("Can't save system %s. The IP address (%s) is already used by system %s (%s)") % (ref.name, intf["ip_address"], x.name, name)) + def printable(self): """ Creates a printable representation of the collection suitable diff --git a/cobbler/commands.py b/cobbler/commands.py index d97a6e1..6bb0419 100644 --- a/cobbler/commands.py +++ b/cobbler/commands.py @@ -251,10 +251,38 @@ class CobblerFunction: opt_sync = not options.nosync opt_triggers = not options.notriggers + # ** WARNING: COMPLICATED ** + # what operation we call depends on what type of object we are editing + # and what the operation is. The details behind this is that the + # add operation has special semantics around adding objects that might + # clobber other objects, and we don't want that to happen. Edit + # does not have to check for named clobbering but still needs + # to check for IP/MAC clobbering in some scenarios (FIXME). + # this is all enforced by collections.py though we need to make + # the apppropriate call to add to invoke the safety code in the right + # places -- and not in places where the safety code will generate + # errors under legit circumstances. + if not ("rename" in self.args): - rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers) + if "add" in self.args: + if obj.COLLECTION_TYPE == "system": + # duplicate names and netinfo are both bad. + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=True) + else: + # duplicate names are bad + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=False) + else: + # editing or copying (but not renaming), so duplicate netinfo + # CAN be bad, duplicate names are already handled, though + # we need to clean up checks around duplicate netinfo here + # (FIXME) so they are made and work. + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers) + else: + # we are renaming here, so duplicate netinfo checks also + # need to be made.(FIXME) rc = collect_fn().rename(obj, self.options.newname) + return rc def reporting_sorter(self, a, b): diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 01ead35..01f2ba6 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -103,8 +103,13 @@ class SystemFunction(commands.CobblerFunction): if self.options.owners: obj.set_owners(self.options.owners) - return self.object_manipulator_finish(obj, self.api.systems, self.options) + rc = self.object_manipulator_finish(obj, self.api.systems, self.options) + if ["copy"] in self.args: + # run through and find conflicts + print _("WARNING: after copying systems, be sure that the ip/mac information is unique"). + + return rc ######################################################## diff --git a/cobbler/settings.py b/cobbler/settings.py index 0ef2ab7..c201ddd 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -24,6 +24,8 @@ TESTMODE = False DEFAULTS = { "allow_cgi_mac_registration" : 0, "allow_cgi_profile_change" : 0, + "allow_duplicate_macs" : 0, + "allow_duplicate_ips" : 0, "bootloaders" : { "standard" : "/usr/lib/syslinux/pxelinux.0", "ia64" : "/var/lib/cobbler/elilo-3.6-ia64.efi" diff --git a/config/settings b/config/settings index 36cd9b4..8fc0fdf 100644 --- a/config/settings +++ b/config/settings @@ -1,6 +1,8 @@ --- allow_cgi_mac_registration: 0 allow_cgi_profile_change: 0 +allow_duplicate_macs: 0 +allow_duplicate_ips: 0 bootloaders: ia64: /var/lib/cobbler/elilo-3.6-ia64.efi standard: /usr/lib/syslinux/pxelinux.0 diff --git a/templates/dnsmasq.template b/templates/dnsmasq.template index 0646b71..28297d2 100644 --- a/templates/dnsmasq.template +++ b/templates/dnsmasq.template @@ -6,7 +6,7 @@ #no-poll #enable-dbus read-ethers -addn-hosts = /var/lib/cobbler/hosts_cobbler/ +addn-hosts = /var/lib/cobbler/cobbler_hosts dhcp-range=192.168.1.5,192.168.1.200 dhcp-option=3,$next_server diff --git a/tests/tests.py b/tests/tests.py index 8a86bab..4060eef 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -8,6 +8,7 @@ import os import subprocess import tempfile import shutil +import traceback from cobbler.cexceptions import * @@ -84,6 +85,7 @@ class BootTest(unittest.TestCase): system = self.api.new_system() self.assertTrue(system.set_name("drwily.rdu.redhat.com")) self.assertTrue(system.set_mac_address("BB:EE:EE:EE:EE:FF","intf0")) + self.assertTrue(system.set_ip_address("192.51.51.50","intf0")) self.assertTrue(system.set_profile("testprofile0")) self.assertTrue(self.api.add_system(system)) self.assertTrue(self.api.find_system(name="drwily.rdu.redhat.com")) @@ -100,6 +102,129 @@ class BootTest(unittest.TestCase): self.assertTrue(repo.set_mirror("/tmp/test_example_cobbler_repo")) self.assertTrue(self.api.repos().add(repo)) +class DuplicateNamesAndIpPrevention(BootTest): + + """ + The command line (and WebUI) have checks to prevent new system + additions from conflicting with existing systems and overwriting + them inadvertantly. This class tests that code. NOTE: General API + users will /not/ encounter these checks. + """ + + def test_duplicate_prevention(self): + + # find things we are going to test with + distro1 = self.api.find_distro(name="testdistro0") + profile1 = self.api.find_profile(name="testprofile0") + system1 = self.api.find_system(name="drwily.rdu.redhat.com") + repo1 = self.api.find_repo(name="test_repo") + + # make sure we can't overwrite a previous distro with + # the equivalent of an "add" (not an edit) on the + # command line. + distro2 = self.api.new_distro() + self.assertTrue(distro2.set_name("testdistro0")) + self.assertTrue(distro2.set_kernel(self.fk_kernel)) + self.assertTrue(distro2.set_initrd(self.fk_initrd)) + self.assertTrue(distro2.set_owners("canary")) + # this should fail + try: + self.api.add_distro(distro2,check_for_duplicate_names=True) + self.assertTrue(1==2,"distro add should fail") + except CobblerException: + pass + except: + self.assertTrue(1==2,"exception type") + # we caught the exception but make doubly sure there was no write + distro_check = self.api.find_distro(name="testdistro0") + self.assertTrue("canary" not in distro_check.owners) + + # repeat the check for profiles + profile2 = self.api.new_profile() + self.assertTrue(profile2.set_name("testprofile0")) + self.assertTrue(profile2.set_distro("testdistro0")) + # this should fail + try: + self.api.add_profile(profile2,check_for_duplicate_names=True) + self.assertTrue(1==2,"profile add should fail") + except CobblerException: + pass + except: + traceback.print_exc() + self.assertTrue(1==2,"exception type") + + # repeat the check for systems (just names this time) + system2 = self.api.new_system() + self.assertTrue(system2.set_name("drwily.rdu.redhat.com")) + self.assertTrue(system2.set_profile("testprofile0")) + # this should fail + try: + self.api.add_system(system2,check_for_duplicate_names=True) + self.assertTrue(1==2,"system add should fail") + except CobblerException: + pass + except: + traceback.print_exc() + self.assertTrue(1==2,"exception type") + + # repeat the check for repos + repo2 = self.api.new_repo() + self.assertTrue(repo2.set_name("test_repo")) + self.assertTrue(repo2.set_mirror("http://imaginary")) + # self.failUnlessRaises(CobblerException,self.api.add_repo,[repo,check_for_duplicate_names=True]) + try: + self.api.add_repo(repo2,check_for_duplicate_names=True) + self.assertTrue(1==2,"repo add should fail") + except CobblerException: + pass + except: + self.assertTrue(1==2,"exception type") + + # now one more check to verify we can't add a system + # of a different name but duplicate netinfo. + system3 = self.api.new_system() + self.assertTrue(system3.set_name("unused_name")) + self.assertTrue(system3.set_profile("testprofile0")) + # MAC is initially accepted + self.assertTrue(system3.set_mac_address("BB:EE:EE:EE:EE:FF","intf3")) + # can't add as this MAC already exists! + + #self.failUnlessRaises(CobblerException,self.api.add_system,[system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True) + try: + self.api.add_system(system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True) + except CobblerException: + pass + except: + traceback.print_exc() + self.assertTrue(1==2,"wrong exception type") + + # set the MAC to a different value and try again + self.assertTrue(system3.set_mac_address("FF:EE:EE:EE:EE:DD","intf3")) + # it should work + self.assertTrue(self.api.add_system(system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)) + # now set the IP so that collides + self.assertTrue(system3.set_ip_address("192.51.51.50","intf6")) + # this should also fail + + # self.failUnlessRaises(CobblerException,self.api.add_system,[system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True) + try: + self.api.add_system(system3,check_for_duplicate_names=True,check_for_duplicate_netinfo=True) + self.assertTrue(1==2,"system add should fail") + except CobblerException: + pass + except: + self.assertTrue(1==2,"wrong exception type") + + # fix the IP and Mac back + self.assertTrue(system3.set_ip_address("192.86.75.30","intf6")) + self.assertTrue(system3.set_mac_address("AE:BE:DE:CE:AE:EE","intf3")) + # now it works again + # note that we will not check for duplicate names as we want + # to test this as an 'edit' operation. + self.assertTrue(self.api.add_system(system3,check_for_duplicate_names=False,check_for_duplicate_netinfo=True)) + + # FIXME: note -- how netinfo is handled when doing renames/copies/edits + # is more involved and we probably should add tests for that also. class Ownership(BootTest): -- cgit From 7fcd436b4581f849df3a4114f6df1a862d788404 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Apr 2008 16:36:02 -0400 Subject: changelog edits --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 60e6369..04fc8b5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,8 @@ Cobbler CHANGELOG - added authn_ldap and stub for authz_configfile - authz_configfile allows filtering ldap/other users by config file - WebUI now has checkbox on distro/profile for deleting child objects +- cli has different semantics between "add" and "edit" now +- cobbler wants to keep IPs/MACs unique now in configuration (can be disabled) - ??? - 0.8.3 - fix WebUI documentation URL -- cgit From e387ae10607ec65fb2ca21292a3c889d5f1a2805 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 4 Apr 2008 16:38:08 -0400 Subject: add dnsmasq change to changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 04fc8b5..64cfb2f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ Cobbler CHANGELOG - fix bug in /etc/cobbler/modules.conf regarding pluggable authn/z - fix default flags for yumdownloader - fix for RHEL 4u6 DVD/tree import x86_64 arch detection +- fix for dnsmasq template file host config path * Fri Feb 22 2008 - 0.8.2 - fix to webui to allow repos to be edited there on profile page -- cgit From b7e606d6fe4c431c8fb95e96ac6b4c5e101f202d Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Apr 2008 16:40:25 -0400 Subject: Remove check from this area of the command line. --- cobbler/modules/cli_system.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 01f2ba6..b173b4b 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -105,10 +105,6 @@ class SystemFunction(commands.CobblerFunction): rc = self.object_manipulator_finish(obj, self.api.systems, self.options) - if ["copy"] in self.args: - # run through and find conflicts - print _("WARNING: after copying systems, be sure that the ip/mac information is unique"). - return rc -- cgit From 9337f9411092bcd3da4005fa29557f875fbf68e8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 4 Apr 2008 17:12:32 -0400 Subject: Get duplicate name/ip/mac checks implemented for WebUI and XMLRPC now that command line/local-API is done. --- cobbler/remote.py | 31 +++++++++++++++++++++++-------- cobbler/webui/CobblerWeb.py | 10 +++++----- webui_templates/distro_edit.tmpl | 2 ++ webui_templates/profile_edit.tmpl | 2 ++ webui_templates/repo_edit.tmpl | 2 ++ webui_templates/system_edit.tmpl | 2 ++ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index ed3cc53..d9f937b 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -810,41 +810,56 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): found = self.api.repos().find(name) return self.__store_object(found) - def save_distro(self,object_id,token): + def save_distro(self,object_id,token,editmode="bypass"): """ Saves a newly created or modified distro object to disk. """ self.log("save_distro",object_id=object_id,token=token) obj = self.__get_object(object_id) self.check_access(token,"save_distro",obj) - return self.api.distros().add(obj,save=True) + if editmode == "new": + return self.api.distros().add(obj,save=True,check_for_duplicate_names=True) + else: + return self.api.distros().add(obj,save=True) - def save_profile(self,object_id,token): + def save_profile(self,object_id,token,editmode="bypass"): """ Saves a newly created or modified profile object to disk. """ self.log("save_profile",token=token,object_id=object_id) obj = self.__get_object(object_id) self.check_access(token,"save_profile",obj) - return self.api.profiles().add(obj,save=True) + if editmode == "new": + return self.api.profiles().add(obj,save=True,check_for_duplicate_names=True) + else: + return self.api.profiles().add(obj,save=True) - def save_system(self,object_id,token): + def save_system(self,object_id,token,editmode="bypass"): """ Saves a newly created or modified system object to disk. """ self.log("save_system",token=token,object_id=object_id) obj = self.__get_object(object_id) self.check_access(token,"save_system",obj) - return self.api.systems().add(obj,save=True) + if editmode == "new": + return self.api.systems().add(obj,save=True,check_for_duplicate_names=True,check_for_duplicate_netinfo=True) + elif editmode == "edit": + return self.api.systems().add(obj,save=True,check_for_duplicate_netinfo=True) + else: + return self.api.systems().add(obj,save=True) + - def save_repo(self,object_id,token=None): + def save_repo(self,object_id,token=None,editmode="bypass"): """ Saves a newly created or modified repo object to disk. """ self.log("save_repo",object_id=object_id,token=token) obj = self.__get_object(object_id) self.check_access(token,"save_repo",obj) - return self.api.repos().add(obj,save=True) + if editmode == "new": + return self.api.repos().add(obj,save=True,check_for_duplicate_names=True) + else: + return self.api.repos().add(obj,save=True) def copy_distro(self,object_id,newname,token=None): """ diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 727e4cb..628d776 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -243,7 +243,8 @@ class CobblerWeb(object): self.remote.modify_distro(distro, 'arch', arch, self.token) if breed: self.remote.modify_distro(distro, 'breed', breed, self.token) - self.remote.save_distro(distro, self.token) + # now time to save, do we want to run duplication checks? + self.remote.save_distro(distro, self.token, editmode) except Exception, e: log_exc(self.apache) return self.error_page("Error while saving distro: %s" % str(e)) @@ -385,8 +386,7 @@ class CobblerWeb(object): mods["gateway-%s" % interface] = gateway self.remote.modify_system(system,'modify-interface', mods, self.token) - # now commit the edits - self.remote.save_system( system, self.token) + self.remote.save_system(system, self.token, editmode) except Exception, e: log_exc(self.apache) @@ -572,7 +572,7 @@ class CobblerWeb(object): if dhcptag: self.remote.modify_profile(profile, 'dhcp-tag', dhcptag, self.token) - self.remote.save_profile(profile,self.token) + self.remote.save_profile(profile,self.token, editmode) except Exception, e: log_exc(self.apache) return self.error_page("Error while saving profile: %s" % str(e)) @@ -687,7 +687,7 @@ class CobblerWeb(object): if owners: self.remote.modify_repo(repo, 'owners', owners, self.token) - self.remote.save_repo(repo, self.token) + self.remote.save_repo(repo, self.token, editmode) except Exception, e: log_exc(self.apache) diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index 5d19a43..f72ab11 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -66,6 +66,8 @@ function disablename(value)

How do you want to modify this object?

+ #else + #end if diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index 2f7a732..0c8f156 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -80,6 +80,8 @@ function disablename(value)

How do you want to modify this object?

+ #else + #end if #if $subprofile diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index 469854a..30d516d 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -62,6 +62,8 @@ function disablename(value)

How do you want to modify this object?

+ #else + #end if diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 5396f1d..684c6d1 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -138,6 +138,8 @@ function page_onload() {

How do you want to modify this object?

+ #else + #end if -- cgit From 2d14d31b9aa6b34eaacfad650b99f132c3a103ad Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 4 Apr 2008 17:24:37 -0400 Subject: remove FIXMEs as things have been tested and work WRT the duplication prevention feature. --- cobbler/collection.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/cobbler/collection.py b/cobbler/collection.py index 14bba57..e63c3ca 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -230,9 +230,7 @@ class Collection(serializable.Serializable): """ Prevents adding objects with the same name. Prevents adding or editing to provide the same IP, or MAC. - This applies to new "add" commands, - Edits that are not copies/renames should only yelp if they match - an object that is not the same as the object being edited. (FIXME) + Enforcement is based on whether the API caller requests it. """ # always protect against duplicate names @@ -254,11 +252,6 @@ class Collection(serializable.Serializable): if not check_for_duplicate_netinfo: return - # FIXME: if we run this command on edits it may yield false - # positives when ref is set to replace an object of an existing - # name, so we should probably /not/ run this on edits yet at this - # point. Logic to deal with renames vs. edit/copies is somewhat - # involved. if isinstance(ref, item_system.System): for (name, intf) in ref.interfaces.iteritems(): match_ip = [] @@ -270,9 +263,6 @@ class Collection(serializable.Serializable): if not self.api.settings().allow_duplicate_ips and input_ip is not None and input_ip != "": match_ip = self.api.find_system(ip_address=input_ip,return_list=True) # it's ok to conflict with your own net info. - # FIXME: copies won't ever work this way, so they should NOT - # use the flags that engadge these checks. Renames should - # be exempt also for x in match_mac: if x.name != ref.name: -- cgit From f3a7e9f8a2b85fb936d98aa118bec5189b26140f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 4 Apr 2008 17:45:32 -0400 Subject: Added a "--clobber" option to all add commands that allows the add to function in an "add or edit" mode. Before in Cobbler, add and edit were largely aliases so this was not needed -- now it is. Using --clobber the object will be created if it does not exist, or it will overwrite the existing one (as "edit" does) when it is there. If --clobber is left off, add will refuse to overwrite an existing object. This is a minor break to scripts that are calling cobbler directly but not those to API users -- scripts /should/ be checking return codes. --- CHANGELOG | 3 ++- cobbler/commands.py | 14 ++++++++++++-- cobbler/modules/cli_distro.py | 4 ++++ cobbler/modules/cli_profile.py | 5 +++++ cobbler/modules/cli_repo.py | 4 ++++ cobbler/modules/cli_system.py | 3 +++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 64cfb2f..54bfac6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,8 +10,9 @@ Cobbler CHANGELOG - added authn_ldap and stub for authz_configfile - authz_configfile allows filtering ldap/other users by config file - WebUI now has checkbox on distro/profile for deleting child objects -- cli has different semantics between "add" and "edit" now +- cli has different semantics between "add" and "edit" now for safety reasons - cobbler wants to keep IPs/MACs unique now in configuration (can be disabled) +- added --clobber option to allow add to overwrite existing objects (for scripts) - ??? - 0.8.3 - fix WebUI documentation URL diff --git a/cobbler/commands.py b/cobbler/commands.py index 6bb0419..2dc627b 100644 --- a/cobbler/commands.py +++ b/cobbler/commands.py @@ -241,6 +241,10 @@ class CobblerFunction: Boilerplate for objects that offer add/edit/delete/remove/copy functionality. """ + clobber = False + if "add" in self.args: + clobber = options.clobber + if "copy" in self.args: # or "rename" in self.args: if self.options.newname: obj = obj.make_clone() @@ -267,10 +271,16 @@ class CobblerFunction: if "add" in self.args: if obj.COLLECTION_TYPE == "system": # duplicate names and netinfo are both bad. - rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=True) + if not clobber: + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=True) + else: + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=True) else: # duplicate names are bad - rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=False) + if not clobber: + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=False) + else: + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=False) else: # editing or copying (but not renaming), so duplicate netinfo # CAN be bad, duplicate names are already handled, though diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py index 9e0637f..8c7b70f 100644 --- a/cobbler/modules/cli_distro.py +++ b/cobbler/modules/cli_distro.py @@ -40,6 +40,9 @@ class DistroFunction(commands.CobblerFunction): if not self.matches_args(args,["remove","report","list"]): p.add_option("--arch", dest="arch", help="ex: x86, x86_64, ia64") p.add_option("--breed", dest="breed", help="ex: redhat, debian, suse") + if self.matches_args(args,["add"]): + p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") + if not self.matches_args(args,["remove","report","list"]): p.add_option("--initrd", dest="initrd", help="absolute path to initrd.img (REQUIRED)") p.add_option("--kernel", dest="kernel", help="absolute path to vmlinuz (REQUIRED)") p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'") @@ -48,6 +51,7 @@ class DistroFunction(commands.CobblerFunction): p.add_option("--name", dest="name", help="ex: 'RHEL-5-i386' (REQUIRED)") + if self.matches_args(args,["copy","rename"]): p.add_option("--newname", dest="newname", help="for copy/rename commands") if not self.matches_args(args,["remove","report","list"]): diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py index 9912607..0bf4b8c 100644 --- a/cobbler/modules/cli_profile.py +++ b/cobbler/modules/cli_profile.py @@ -36,6 +36,11 @@ class ProfileFunction(commands.CobblerFunction): return [ "add", "edit", "copy", "rename", "remove", "list", "report" ] def add_options(self, p, args): + + if self.matches_args(args,["add"]): + p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") + + if not self.matches_args(args,["remove","report","list"]): p.add_option("--distro", dest="distro", help="ex: 'RHEL-5-i386' (REQUIRED)") diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py index 88e45dd..e8ec04a 100644 --- a/cobbler/modules/cli_repo.py +++ b/cobbler/modules/cli_repo.py @@ -37,9 +37,13 @@ class RepoFunction(commands.CobblerFunction): def add_options(self, p, args): + if not self.matches_args(args,["remove","report","list"]): p.add_option("--arch", dest="arch", help="overrides repo arch if required") + if self.matches_args(args,["add"]): + p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") + if not self.matches_args(args,["remove","report","list"]): p.add_option("--createrepo-flags", dest="createrepo_flags", help="additional flags for createrepo") p.add_option("--keep-updated", dest="keep_updated", help="update on each reposync, yes/no") diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index b173b4b..34c8886 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -37,6 +37,9 @@ class SystemFunction(commands.CobblerFunction): def add_options(self, p, args): + if self.matches_args(args,["add"]): + p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") + if not self.matches_args(args,["remove","report","list"]): p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configurations") p.add_option("--gateway", dest="gateway", help="for static IP / templating usage") -- cgit From cdca9443cab19f992e76a205533be0d13c091d47 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 8 Apr 2008 13:11:09 -0400 Subject: Merge. --- CHANGELOG | 9 +-------- cobbler.spec | 6 +----- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3f8cf5a..2bd50fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,7 @@ Cobbler CHANGELOG (all entries mdehaan@redhat.com unless noted otherwise) -<<<<<<< HEAD:CHANGELOG - Mon Mar 10 2008 - 0.9.0 -======= -* Tue Apr 08 2008 - 0.8.3 ->>>>>>> master:CHANGELOG -- Make createrepo get run for local cobbler reposync invocations as needed - patch to allow yumopts to override gpgcheck - applied patch to send hostname from ISC - added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete @@ -19,16 +14,14 @@ Cobbler CHANGELOG - added --clobber option to allow add to overwrite existing objects (for scripts) - ??? - 0.8.3 +- Make createrepo get run for local cobbler reposync invocations as needed - fix WebUI documentation URL - fix bug in /etc/cobbler/modules.conf regarding pluggable authn/z - fix default flags for yumdownloader - fix for RHEL 4u6 DVD/tree import x86_64 arch detection -<<<<<<< HEAD:CHANGELOG - fix for dnsmasq template file host config path -======= - fix dnsmasq template to point at the correct hosts file - force all names to be alphanumeric ->>>>>>> master:CHANGELOG * Fri Feb 22 2008 - 0.8.2 - fix to webui to allow repos to be edited there on profile page diff --git a/cobbler.spec b/cobbler.spec index b4e04bd..9e64040 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -191,15 +191,11 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -<<<<<<< HEAD:cobbler.spec -* Wed Mar 26 2008 Michael DeHaan - 0.9.0-1 +* Tue Apr 08 2008 Michael DeHaan - 0.9.0-1 - Upstream changes (see CHANGELOG) - packaged /etc/cobbler/users.conf -* Mon Mar 10 2008 Michael DeHaan - 0.8.3-1 -======= * Tue Apr 08 2008 Michael DeHaan - 0.8.3-1 ->>>>>>> master:cobbler.spec - Upstream changes (see CHANGELOG) * Fri Mar 07 2008 Michael DeHaan - 0.8.2-1 -- cgit From 52c1048879ddf3a6ea87fe32ba99ba8f76a6e20e Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 8 Apr 2008 16:20:43 -0400 Subject: fix changelog --- CHANGELOG | 1 + scripts/cobbler_auth_help | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2bd50fa..75e3425 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ Cobbler CHANGELOG - cli has different semantics between "add" and "edit" now for safety reasons - cobbler wants to keep IPs/MACs unique now in configuration (can be disabled) - added --clobber option to allow add to overwrite existing objects (for scripts) +- updated/tested kerberos support for those needing to auth against it - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/scripts/cobbler_auth_help b/scripts/cobbler_auth_help index 198e95b..c43cd5b 100644 --- a/scripts/cobbler_auth_help +++ b/scripts/cobbler_auth_help @@ -43,7 +43,7 @@ my $kerberos = Authen::Simple::Kerberos->new( realm => $realm ); -print "authenticating: $username against $method $realm ($password)\n" if $verbose; +print "authenticating: $username against (realm=$realm) (pass=$password)\n" if $verbose; if ( $kerberos->authenticate( $username, $password ) ) { print "ok\n" if $verbose; -- cgit From 68469d9c744b17d4bd291718dd4d03ff02b64db7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 8 Apr 2008 16:21:25 -0400 Subject: Allow underscores. --- cobbler/item.py | 2 +- cobbler/item_system.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/item.py b/cobbler/item.py index 454b704..249a50c 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -116,7 +116,7 @@ class Item(serializable.Serializable): if type(name) != type(""): raise CX(_("name must be a string")) for x in name: - if not x.isalnum() and not x in [ "-", ".", ":", "+" ] : + if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] : raise CX(_("invalid characters in name")) self.name = name return True diff --git a/cobbler/item_system.py b/cobbler/item_system.py index dd7bd64..975a6d2 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -156,7 +156,7 @@ class System(item.Item): if type(name) != type(""): raise CX(_("name must be a string")) for x in name: - if not x.isalnum() and not x in [ "-", ".", ":", "+" ] : + if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] : raise CX(_("invalid characters in name")) if utils.is_mac(name): -- cgit From c4a6d241faa677b9dbe9572addcd1148a72ffaba Mon Sep 17 00:00:00 2001 From: root Date: Tue, 8 Apr 2008 16:24:06 -0400 Subject: merge --- cobbler.spec | 4 ---- cobbler/webui/master.py | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/cobbler.spec b/cobbler.spec index 070bff3..ca6640f 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -191,15 +191,11 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -<<<<<<< HEAD:cobbler.spec * Tue Apr 08 2008 Michael DeHaan - 0.9.0-1 - Upstream changes (see CHANGELOG) - packaged /etc/cobbler/users.conf -* Tue Apr 08 2008 Michael DeHaan - 0.8.3-1 -======= * Tue Apr 08 2008 Michael DeHaan - 0.8.3-2 ->>>>>>> master:cobbler.spec - Upstream changes (see CHANGELOG) * Fri Mar 07 2008 Michael DeHaan - 0.8.2-1 diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 0a6e9dd..8f7de66 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1207681739.292002 -__CHEETAH_genTimestamp__ = 'Tue Apr 8 15:08:59 2008' +__CHEETAH_genTime__ = 1207686192.9310019 +__CHEETAH_genTimestamp__ = 'Tue Apr 8 16:23:12 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' -- cgit From eeb2cc4214f3047536e9cd4a5b814581a84f286a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 8 Apr 2008 17:59:21 -0400 Subject: Update menu.c32 so timeouts work in the PXE menus --- loaders/menu.c32 | Bin 26496 -> 51788 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/loaders/menu.c32 b/loaders/menu.c32 index 7912a08..a3d8628 100644 Binary files a/loaders/menu.c32 and b/loaders/menu.c32 differ -- cgit From 421abb9ba9d45037cc4fce41408d0f5dfd3cf62e Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 8 Apr 2008 18:24:42 -0400 Subject: update templates to invoke menu.c32 automatically w/ timeout, skipping the step of having to type "menu" at the PXE prompt, which is occasionally a source of user confusion. We can now also do submenus if we want to, now that menu.c32 is updated. --- CHANGELOG | 2 ++ templates/pxedefault.template | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 75e3425..6c0cf96 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,8 @@ Cobbler CHANGELOG - cobbler wants to keep IPs/MACs unique now in configuration (can be disabled) - added --clobber option to allow add to overwrite existing objects (for scripts) - updated/tested kerberos support for those needing to auth against it +- update menu.c32 to 3.62 to allow for timeouts during menu (and future submenu) +- update PXE defaults to invoke menu.c32 automatically w/ timeout - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/templates/pxedefault.template b/templates/pxedefault.template index d731892..d95d7cc 100644 --- a/templates/pxedefault.template +++ b/templates/pxedefault.template @@ -1,5 +1,5 @@ -DEFAULT local -PROMPT 1 +DEFAULT menu +PROMPT 0 MENU TITLE Cobbler | http://cobbler.et.redhat.com TIMEOUT 200 TOTALTIMEOUT 6000 @@ -10,4 +10,8 @@ LABEL local MENU DEFAULT LOCALBOOT 0 +MENU seperator + $pxe_menu_items + +MENU end -- cgit From a5575530aee5642bfb0d99af5088d0519845b0f4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 9 Apr 2008 11:46:29 -0400 Subject: Apply iranzo's memtest patch --- cobbler/action_sync.py | 53 +++++++++++++++++++++++++++++++++++++++++-- templates/pxedefault.template | 2 -- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 8a0eadf..5cb0977 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -109,6 +109,11 @@ class BootSync: self.copyfile(path, destpath) self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) + # Copy memtest to tftpboot if package is installed on system + for memtest in glob.glob('/boot/memtest*'): + base = os.path.basename(memtest) + self.copyfile(memtest,os.path.join(self.bootloc,"images",base)) + def write_dhcp_file(self): """ DHCP files are written when manage_dhcp is set in @@ -818,13 +823,57 @@ class BootSync: contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False) if contents is not None: pxe_menu_items = pxe_menu_items + contents + "\n" - - # save the template. + + # if we have any memtest files in images, make entries for them + # after we list the profiles + memtests = glob.glob(self.bootloc + "/images/memtest*") + if len(memtests) > 0: + pxe_menu_items = pxe_menu_items + "\n\n" + for memtest in glob.glob(self.bootloc + '/memtest*'): + base = os.path.basename(memtest) + contents = self.write_memtest_pxe("/images/%s" % base) + pxe_menu_items = pxe_menu_items + contents + "\n" + + # save the template. metadata = { "pxe_menu_items" : pxe_menu_items } outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") self.apply_template(template_data, metadata, outfile) template_src.close() + def write_memtest_pxe(self,filename): + """ + Write a configuration file for memtest + """ + + # --- + # just some random variables + template = None + metadata = {} + buffer = "" + + # --- + template = "/etc/cobbler/pxeprofile.template" + + # --- + # store variables for templating + metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename) + metadata["profile_name"] = os.path.basename(filename) + metadata["kernel_path"] = "/images/%s" % os.path.basename(filename) + metadata["initrd_path"] = "" + metadata["append_line"] = "" + + # --- + # get the template + template_fh = open(template) + template_data = template_fh.read() + template_fh.close() + + # --- + # return results + buffer = self.apply_template(template_data, metadata, None) + return buffer + + def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True): """ diff --git a/templates/pxedefault.template b/templates/pxedefault.template index d95d7cc..bb09893 100644 --- a/templates/pxedefault.template +++ b/templates/pxedefault.template @@ -10,8 +10,6 @@ LABEL local MENU DEFAULT LOCALBOOT 0 -MENU seperator - $pxe_menu_items MENU end -- cgit From 4e33bffad605c41ebae28142eaf5203b4465f809 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 9 Apr 2008 16:42:57 -0400 Subject: Two things -- (A) remove rhpl dep as we aren't using it and we want to use more standard xlat for other distros potentially, (B) fix the kerb module some more. --- cobbler.spec | 1 - cobbler/action_check.py | 2 +- cobbler/action_import.py | 2 +- cobbler/action_litesync.py | 2 +- cobbler/action_reposync.py | 2 +- cobbler/action_status.py | 2 +- cobbler/action_sync.py | 2 +- cobbler/action_validate.py | 2 +- cobbler/api.py | 2 +- cobbler/cobbler.py | 3 +-- cobbler/cobblerd.py | 2 +- cobbler/collection.py | 2 +- cobbler/collection_distros.py | 2 +- cobbler/collection_profiles.py | 2 +- cobbler/collection_repos.py | 2 +- cobbler/collection_systems.py | 2 +- cobbler/commands.py | 2 +- cobbler/config.py | 2 +- cobbler/item.py | 2 +- cobbler/item_distro.py | 2 +- cobbler/item_profile.py | 2 +- cobbler/item_repo.py | 2 +- cobbler/item_system.py | 2 +- cobbler/module_loader.py | 2 +- cobbler/modules/authn_configfile.py | 2 +- cobbler/modules/authn_kerberos.py | 4 ++-- cobbler/modules/authn_ldap.py | 2 +- cobbler/modules/authz_allowall.py | 2 +- cobbler/modules/authz_configfile.py | 2 +- cobbler/modules/authz_ownership.py | 2 +- cobbler/modules/cli_distro.py | 2 +- cobbler/modules/cli_misc.py | 2 +- cobbler/modules/cli_profile.py | 2 +- cobbler/modules/cli_repo.py | 2 +- cobbler/modules/cli_system.py | 2 +- cobbler/modules/serializer_shelve.py | 2 +- cobbler/modules/serializer_yaml.py | 2 +- cobbler/remote.py | 2 +- cobbler/serializable.py | 2 +- cobbler/serializer.py | 2 +- cobbler/settings.py | 2 +- cobbler/utils.py | 6 +++++- cobbler/webui/master.py | 38 +++++++++++++----------------------- tests/tests.py | 18 ++++++++--------- 44 files changed, 69 insertions(+), 77 deletions(-) diff --git a/cobbler.spec b/cobbler.spec index ca6640f..2b6c88d 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -14,7 +14,6 @@ Requires: python-devel Requires: createrepo Requires: mod_python Requires: python-cheetah -Requires: rhpl Requires: rsync Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 1fb4734..85084d7 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -18,7 +18,7 @@ import re import sub_process import action_sync import utils -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class BootCheck: diff --git a/cobbler/action_import.py b/cobbler/action_import.py index db09818..d224e55 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -23,7 +23,7 @@ import glob import api import utils import shutil -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ WGET_CMD = "wget --mirror --no-parent --no-host-directories --directory-prefix %s/%s %s" RSYNC_CMD = "rsync -a %s '%s' %s/ks_mirror/%s --exclude-from=/etc/cobbler/rsync.exclude --progress" diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index 9200a3e..a73e7c3 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -30,7 +30,7 @@ from cexceptions import * import traceback import errno -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class BootLiteSync: diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index c86e0be..d6b97a0 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -25,7 +25,7 @@ from cexceptions import * import traceback import errno -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class RepoSync: """ diff --git a/cobbler/action_status.py b/cobbler/action_status.py index cc6ebd4..7366060 100644 --- a/cobbler/action_status.py +++ b/cobbler/action_status.py @@ -19,7 +19,7 @@ import glob import time import api as cobbler_api -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class BootStatusReport: diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 5cb0977..5c8aab9 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -35,7 +35,7 @@ import item_system from Cheetah.Template import Template -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class BootSync: diff --git a/cobbler/action_validate.py b/cobbler/action_validate.py index f898478..8ede60c 100644 --- a/cobbler/action_validate.py +++ b/cobbler/action_validate.py @@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os import re import sub_process -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Validate: diff --git a/cobbler/api.py b/cobbler/api.py index 54c315b..0eacb78 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -29,7 +29,7 @@ import module_loader import logging import os import fcntl -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ ERROR = 100 INFO = 10 diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index c02302e..c164244 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -23,7 +23,7 @@ import optparse import commands from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ I18N_DOMAIN = "cobbler" #################################################### @@ -31,7 +31,6 @@ I18N_DOMAIN = "cobbler" class BootCLI: def __init__(self): - textdomain(I18N_DOMAIN) self.api = api.BootAPI() self.loader = commands.FunctionLoader() climods = self.api.get_modules_in_category("cli") diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py index 5640aec..8859e03 100644 --- a/cobbler/cobblerd.py +++ b/cobbler/cobblerd.py @@ -16,7 +16,7 @@ import time import os import SimpleXMLRPCServer import glob -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import xmlrpclib from server import xmlrpclib2 diff --git a/cobbler/collection.py b/cobbler/collection.py index e63c3ca..1b509f2 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -25,7 +25,7 @@ import item_profile import item_distro import item_repo -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Collection(serializable.Serializable): diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py index f78dd64..5857b9a 100644 --- a/cobbler/collection_distros.py +++ b/cobbler/collection_distros.py @@ -18,7 +18,7 @@ import collection import item_distro as distro from cexceptions import * import action_litesync -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Distros(collection.Collection): diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py index f00a8dc..139c94e 100644 --- a/cobbler/collection_profiles.py +++ b/cobbler/collection_profiles.py @@ -20,7 +20,7 @@ import utils import collection from cexceptions import * import action_litesync -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ #-------------------------------------------- diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py index 44bef2a..91e0667 100644 --- a/cobbler/collection_repos.py +++ b/cobbler/collection_repos.py @@ -18,7 +18,7 @@ import item_repo as repo import utils import collection from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ TESTMODE = False diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py index 140a981..8a967be 100644 --- a/cobbler/collection_systems.py +++ b/cobbler/collection_systems.py @@ -18,7 +18,7 @@ import utils import collection from cexceptions import * import action_litesync -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ #-------------------------------------------- diff --git a/cobbler/commands.py b/cobbler/commands.py index 2dc627b..7a6a25b 100644 --- a/cobbler/commands.py +++ b/cobbler/commands.py @@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import optparse from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import sys HELP_FORMAT = "%-20s%s" diff --git a/cobbler/config.py b/cobbler/config.py index 258b241..4a1b9a4 100644 --- a/cobbler/config.py +++ b/cobbler/config.py @@ -29,7 +29,7 @@ import modules.serializer_yaml as serializer_yaml import settings import serializer -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Config: diff --git a/cobbler/item.py b/cobbler/item.py index 249a50c..2549dce 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -16,7 +16,7 @@ import exceptions import serializable import utils from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Item(serializable.Serializable): diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index 98eaae7..1e392a5 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -20,7 +20,7 @@ import weakref import os from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Distro(item.Item): diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index 2e3a539..a352ce1 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -16,7 +16,7 @@ import utils import item from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Profile(item.Item): diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index 1ebea30..f9e9714 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import utils import item from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class Repo(item.Item): diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 975a6d2..73eda71 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import utils import item from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ class System(item.Item): diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py index b695a04..d584ce3 100644 --- a/cobbler/module_loader.py +++ b/cobbler/module_loader.py @@ -19,7 +19,7 @@ import distutils.sysconfig import os import sys import glob -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import ConfigParser MODULE_CACHE = {} diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py index ffe87a7..be453be 100644 --- a/cobbler/modules/authn_configfile.py +++ b/cobbler/modules/authn_configfile.py @@ -17,7 +17,7 @@ import distutils.sysconfig import ConfigParser import sys import os -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import md5 import traceback diff --git a/cobbler/modules/authn_kerberos.py b/cobbler/modules/authn_kerberos.py index 7f85db6..46c01ad 100644 --- a/cobbler/modules/authn_kerberos.py +++ b/cobbler/modules/authn_kerberos.py @@ -30,7 +30,7 @@ import distutils.sysconfig import ConfigParser import sys import os -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import md5 import traceback # since sub_process isn't available on older OS's @@ -58,7 +58,7 @@ def authenticate(api_handle,username,password): Uses cobbler_auth_helper """ - realm = self.api.settings().kerberos_realm + realm = api_handle.settings().kerberos_realm api_handle.logger.debug("authenticating %s against %s" % (username,realm)) rc = subprocess.call([ diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index cec913b..eef4b2a 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import distutils.sysconfig import sys import os -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import md5 import traceback import ldap diff --git a/cobbler/modules/authz_allowall.py b/cobbler/modules/authz_allowall.py index 1b05630..10d4b17 100644 --- a/cobbler/modules/authz_allowall.py +++ b/cobbler/modules/authz_allowall.py @@ -16,7 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import distutils.sysconfig import ConfigParser import sys -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib diff --git a/cobbler/modules/authz_configfile.py b/cobbler/modules/authz_configfile.py index c183721..84343e2 100644 --- a/cobbler/modules/authz_configfile.py +++ b/cobbler/modules/authz_configfile.py @@ -16,7 +16,7 @@ import distutils.sysconfig import ConfigParser import sys import os -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index 1fe25a9..fbd00f9 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -19,7 +19,7 @@ import distutils.sysconfig import ConfigParser import sys import os -from rhpl.translate import _, N_, textdomain, utf8 +from cobbler.utils import _ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py index 8c7b70f..4d86448 100644 --- a/cobbler/modules/cli_distro.py +++ b/cobbler/modules/cli_distro.py @@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import commands import cexceptions diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index ebbba47..0fd44a6 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import commands from cexceptions import * HELP_FORMAT = commands.HELP_FORMAT diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py index 0bf4b8c..b543b80 100644 --- a/cobbler/modules/cli_profile.py +++ b/cobbler/modules/cli_profile.py @@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import commands import cexceptions diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py index e8ec04a..a567d2d 100644 --- a/cobbler/modules/cli_repo.py +++ b/cobbler/modules/cli_repo.py @@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import commands import cexceptions diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 34c8886..976c380 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import commands import cexceptions diff --git a/cobbler/modules/serializer_shelve.py b/cobbler/modules/serializer_shelve.py index 246a92a..09495dd 100644 --- a/cobbler/modules/serializer_shelve.py +++ b/cobbler/modules/serializer_shelve.py @@ -24,7 +24,7 @@ import os import sys import glob import traceback -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import os import shelve import time diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py index 6425bd1..724265d 100644 --- a/cobbler/modules/serializer_yaml.py +++ b/cobbler/modules/serializer_yaml.py @@ -21,7 +21,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import utils import yaml # Howell-Clark version import cexceptions diff --git a/cobbler/remote.py b/cobbler/remote.py index d9f937b..f7e2226 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -18,7 +18,7 @@ import socket import time import os import SimpleXMLRPCServer -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import xmlrpclib import random import base64 diff --git a/cobbler/serializable.py b/cobbler/serializable.py index ba8f015..ad9e64e 100644 --- a/cobbler/serializable.py +++ b/cobbler/serializable.py @@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import exceptions class Serializable: diff --git a/cobbler/serializer.py b/cobbler/serializer.py index ae9f18c..25d0d60 100644 --- a/cobbler/serializer.py +++ b/cobbler/serializer.py @@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import errno import os -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ import fcntl from cexceptions import * diff --git a/cobbler/settings.py b/cobbler/settings.py index c201ddd..bc16835 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import serializable import utils -from rhpl.translate import _, N_, textdomain, utf8 +from utils import _ TESTMODE = False diff --git a/cobbler/utils.py b/cobbler/utils.py index 4d2b635..8757762 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -22,7 +22,11 @@ import shutil import string import traceback from cexceptions import * -from rhpl.translate import _, N_, textdomain, utf8 + +#placeholder for translation +def _(foo): + return foo + MODULE_CACHE = {} diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 8f7de66..3e0c2fb 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -31,12 +31,12 @@ VFFSL=valueFromFrameOrSearchList VFSL=valueFromSearchList VFN=valueForName currentTime=time.time -__CHEETAH_version__ = '2.0.1' -__CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1207686192.9310019 -__CHEETAH_genTimestamp__ = 'Tue Apr 8 16:23:12 2008' +__CHEETAH_version__ = '2.0rc7' +__CHEETAH_versionTuple__ = (2, 0, 0, 'candidate', 7) +__CHEETAH_genTime__ = 1207768044.4095509 +__CHEETAH_genTimestamp__ = 'Wed Apr 9 21:07:24 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' -__CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' +__CHEETAH_srcLastModified__ = 'Wed Apr 9 21:03:21 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: @@ -158,20 +158,16 @@ class master(Template):
  • Distros
  • -
  • Distros
  • \n
  • Profiles
  • -
  • Profiles
  • \n
  • Systems
  • -
  • Systems
  • \n
  • Kickstarts
  • -
  • Kickstarts
  • \n
  • Repos
  • @@ -180,25 +176,19 @@ class master(Template):
  • Distro
  • -
  • Distro
  • \n
  • Profile
  • -
  • Profile
  • \n
  • Subprofile
  • -
  • Subprofile
  • \n
  • System
  • -
  • System
  • \n
  • Repo
  • -


  • -
  • Repo
  • \n


  • \n
  • Sync
  • diff --git a/tests/tests.py b/tests/tests.py index 4060eef..8536a70 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -813,15 +813,15 @@ class TestListings(BootTest): self.assertTrue(len(self.api.profiles().printable()) > 0) self.assertTrue(len(self.api.distros().printable()) > 0) -class TestCLIBasic(BootTest): - - def test_cli(self): - # just invoke the CLI to increase coverage and ensure - # nothing major is broke at top level. Full CLI command testing - # is not included (yet) since the API tests hit that fairly throughly - # and it would easily double the length of the tests. - app = "/usr/bin/python" - self.assertTrue(subprocess.call([app,"cobbler/cobbler.py","list"]) == 0) +#class TestCLIBasic(BootTest): +# +# def test_cli(self): +# # just invoke the CLI to increase coverage and ensure +# # nothing major is broke at top level. Full CLI command testing +# # is not included (yet) since the API tests hit that fairly throughly +# # and it would easily double the length of the tests. +# app = "/usr/bin/python" +# self.assertTrue(subprocess.call([app,"cobbler/cobbler.py","list"]) == 0) if __name__ == "__main__": if not os.path.exists("setup.py"): -- cgit From 82c540e75dfe750b2877f7177a87b4e91d5e5b1e Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 9 Apr 2008 16:44:24 -0400 Subject: Add Ronald to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 3e4dcef..5d2c797 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,6 +28,7 @@ Patches and other contributions from: Christophe Sahut Scott Seago Al Tobey + Ronald van den Blink Tim Verhoeven [...send patches to get your name here...] -- cgit From 249ccbea4d2615a7623ac91149dda863eefec093 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 9 Apr 2008 17:00:44 -0400 Subject: update changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 6c0cf96..dee7f3c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ Cobbler CHANGELOG - updated/tested kerberos support for those needing to auth against it - update menu.c32 to 3.62 to allow for timeouts during menu (and future submenu) - update PXE defaults to invoke menu.c32 automatically w/ timeout +- removed dependency on rhpl - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed -- cgit From 5da91f89c12b998a4e83db4d21ec6086aca37feb Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 11 Apr 2008 12:17:42 -0400 Subject: Import now takes an --arch, which is now a recommended field, to ensure best practices in naming. --- CHANGELOG | 1 + cobbler/action_check.py | 22 ++++++++++-------- cobbler/action_import.py | 23 ++++++++++++++++++- cobbler/api.py | 4 ++-- cobbler/modules/cli_misc.py | 8 ++++--- cobbler/utils.py | 56 +++++++++++++++++++++++++++++---------------- config/cobblerd | 19 +++++++++++---- docs/cobbler.pod | 12 ++++++---- website/new/download.html | 25 ++++++++------------ 9 files changed, 110 insertions(+), 60 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dee7f3c..80aa334 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ Cobbler CHANGELOG - update menu.c32 to 3.62 to allow for timeouts during menu (and future submenu) - update PXE defaults to invoke menu.c32 automatically w/ timeout - removed dependency on rhpl +- import can now take an --arch (and is recommended usage) - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 85084d7..e91396f 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -19,7 +19,6 @@ import sub_process import action_sync import utils from utils import _ - class BootCheck: def __init__(self,config): @@ -61,10 +60,18 @@ class BootCheck: return status def check_service(self, status, which): - if os.path.exists("/etc/rc.d/init.d/%s" % which): - rc = sub_process.call("/sbin/service %s status >/dev/null 2>/dev/null" % which, shell=True) - if rc != 0: - status.append(_("service %s is not running") % which) + if utils.check_dist() == "redhat": + if os.path.exists("/etc/rc.d/init.d/%s" % which): + rc = sub_process.call("/sbin/service %s status >/dev/null 2>/dev/null" % which, shell=True) + if rc != 0: + status.append(_("service %s is not running") % which) + elif utils.check_dist() == "debian": + if os.path.exists("/etc/init.d/%s" % which): + rc = sub_process.call("/etc/init.d/%s status /dev/null 2>/dev/null" % which, shell=True) + if rc != 0: + status.append(_("service %s is not running") % which) + else: + status.append(_("Unknown distribution type, cannot check for running service %s" % which)) def check_iptables(self, status): if os.path.exists("/etc/rc.d/init.d/iptables"): @@ -95,10 +102,7 @@ class BootCheck: """ Check if Apache is installed. """ - if not os.path.exists(self.settings.httpd_bin): - status.append(_("Apache doesn't appear to be installed")) - else: - self.check_service(status,"httpd") + self.check_service(status,"httpd") def check_dhcpd_bin(self,status): diff --git a/cobbler/action_import.py b/cobbler/action_import.py index d224e55..7f5409b 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -36,7 +36,7 @@ TRY_LIST = [ class Importer: - def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None): + def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None): """ Performs an import of a install tree (or trees) from the given mirror address. The prefix of the distro is to be specified @@ -59,6 +59,7 @@ class Importer: self.distros_added = [] self.kickstart_file = kickstart_file self.rsync_flags = rsync_flags + self.arch = arch # ---------------------------------------------------------------------- @@ -67,6 +68,26 @@ class Importer: raise CX(_("import failed. no --mirror specified")) if self.mirror_name is None: raise CX(_("import failed. no --name specified")) + if self.arch is not None: + self.arch = self.arch.lower() + if self.arch not in [ "x86", "ia64", "x86_64" ]: + raise CX(_("arch must be x86, x86_64, or ia64")) + + mpath = os.path.join(self.settings.webdir, "ks_mirror", self.mirror_name) + if os.path.exists(mpath) and self.arch is None: + if not found: + raise CX(_("Something already exists at this import location (%s). You must specify --arch to avoid potentially overwriting existing files.") % mpath) + + if self.arch: + # append the arch path to the name if the arch is not already + # found in the name. + found = False + for x in [ "ia64", "i386", "x86_64", "x86" ]: + if self.mirror_name.lower().find(x) != -1: + found = True + break + if not found: + self.mirror_name = self.mirror_name + "-" + self.arch if self.mirror_name is None: raise CX(_("import failed. no --name specified")) diff --git a/cobbler/api.py b/cobbler/api.py index 0eacb78..ef6fa8e 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -336,7 +336,7 @@ class BootAPI: statusifier = action_status.BootStatusReport(self._config, mode) return statusifier.run() - def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None): + def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None): """ Automatically import a directory tree full of distribution files. mirror_url can be a string that represents a path, a user@host @@ -346,7 +346,7 @@ class BootAPI: """ self.log("import_tree",[mirror_url, mirror_name, network_root, kickstart_file, rsync_flags]) importer = action_import.Importer( - self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags + self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags, arch ) return importer.run() diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index 0fd44a6..f8b0a7d 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -73,11 +73,12 @@ class ImportFunction(commands.CobblerFunction): return "import" def add_options(self, p, args): + p.add_option("--arch", dest="arch", help="explicitly specify the architecture being imported (RECOMENDED)") p.add_option("--path", dest="mirror", help="local path or rsync location (REQUIRED)") p.add_option("--mirror", dest="mirror_alt", help="alias for --path") p.add_option("--name", dest="name", help="name, ex 'RHEL-5', (REQUIRED)") - p.add_option("--available-as", dest="available_as", help="do not mirror, use this as install tree") - p.add_option("--kickstart", dest="kickstart_file", help="use the kickstart file specified as the profile's kickstart file") + p.add_option("--available-as", dest="available_as", help="do not mirror, use this as install tree base") + p.add_option("--kickstart", dest="kickstart_file", help="use the kickstart file specified as the profile's kickstart file, do not auto-assign") p.add_option("--rsync-flags", dest="rsync_flags", help="pass additional flags to rsync") def run(self): @@ -92,7 +93,8 @@ class ImportFunction(commands.CobblerFunction): self.options.name, network_root=self.options.available_as, kickstart_file=self.options.kickstart_file, - rsync_flags=self.options.rsync_flags + rsync_flags=self.options.rsync_flags, + arch=self.options.arch ) diff --git a/cobbler/utils.py b/cobbler/utils.py index 8757762..d8cf6fc 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -487,28 +487,44 @@ def fix_mod_python_select_submission(repos): repos = repos.replace('"',"") repos = repos.lstrip().rstrip() return repos +def check_dist(): + if os.path.exists("/etc/debian_version"): + return "debian" + else: + return "redhat" def redhat_release(): - if not os.path.exists("/bin/rpm"): - return ("unknown", 0) - args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"] - cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE) - data = cmd.communicate()[0] - data = data.rstrip().lower() - make = "other" - if data.find("redhat") != -1: - make = "redhat" - elif data.find("centos") != -1: - make = "centos" - elif data.find("fedora") != -1: - make = "fedora" - version = data.split("release-")[-1] - rest = 0 - if version.find("-"): - parts = version.split("-") - version = parts[0] - rest = parts[1] - return (make, float(version), rest) + if check_dist() == "redhat": + + if not os.path.exists("/bin/rpm"): + return ("unknown", 0) + args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"] + cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE) + data = cmd.communicate()[0] + data = data.rstrip().lower() + make = "other" + if data.find("redhat") != -1: + make = "redhat" + elif data.find("centos") != -1: + make = "centos" + elif data.find("fedora") != -1: + make = "fedora" + version = data.split("release-")[-1] + rest = 0 + if version.find("-"): + parts = version.split("-") + version = parts[0] + rest = parts[1] + return (make, float(version), rest) + elif check_dist() == "debian": + fd = open("/etc/debian_version") + parts = fd.read().split(".") + version = parts[0] + rest = parts[1] + make = "debian" + return (make, float(version), rest) + else: + return ("unknown",0) def tftpboot_location(): diff --git a/config/cobblerd b/config/cobblerd index 6b52956..7d6d571 100755 --- a/config/cobblerd +++ b/config/cobblerd @@ -25,12 +25,23 @@ # Sanity checks. [ -x /usr/bin/cobblerd ] || exit 0 +DEBIAN_VERSION=/etc/debian_version # Source function library. -. /etc/rc.d/init.d/functions +if [ -e $DEBIAN_VERSION ]; then + + . /etc/init.d/functions +else + . /etc/rc.d/init.d/functions +fi SERVICE=cobblerd PROCESS=cobblerd CONFIG_ARGS=" " +if [ -e $DEBIAN_VERSION ]; then + LOCKFILE=/var/lock/$SERVICE +else + LOCKFILE=/var/lock/subsys/$SERVICE +fi RETVAL=0 @@ -39,7 +50,7 @@ start() { daemon --check $SERVICE $PROCESS --daemon $CONFIG_ARGS RETVAL=$? echo - [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + [ $RETVAL -eq 0 ] && touch $LOCKFILE return $RETVAL } @@ -49,7 +60,7 @@ stop() { RETVAL=$? echo if [ $RETVAL -eq 0 ]; then - rm -f /var/lock/subsys/$SERVICE + rm -f $LOCKFILE rm -f /var/run/$SERVICE.pid fi } @@ -69,7 +80,7 @@ case "$1" in RETVAL=$? ;; condrestart) - [ -f /var/lock/subsys/$SERVICE ] && restart || : + [ -f $LOCKFILE ] && restart || : ;; reload) echo "can't reload configuration, you have to restart it" diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 719850d..d7a4550 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -410,20 +410,22 @@ run after systems are added to regenerate and reload the DHCP configuration. =head2 IMPORT WORKFLOW -This example shows how to create a provisioning infrastructure from a distribution mirror. +This example shows how to create a provisioning infrastructure from a distribution mirror or DVD ISO. Then a default PXE configuration is created, so that by default systems will PXE boot into a fully automated install process for that distribution. You can use a network rsync mirror, a mounted DVD location, or a tree you have available via a network filesystem. +Import knows how to autodetect the architecture of what is being imported, though to make sure things are named correctly, it's always a good idea to specify --arch. For instance, if you import a distribution named "fedora8" from an ISO, and it's an x86_64 ISO, specify --arch=x86_64 and the distro will be named "fedora8-x86_64" automatically, and the right architecture field will also be set on the distribution object. If you are batch importing an entire mirror (containing multiple distributions and arches), you don't have to do this, as cobbler will set the names for things based on the paths it finds. + B -B +B # OR -B +B # OR (using an eternal NAS box without mirroring) @@ -576,11 +578,11 @@ After an import is run, cobbler will try to detect the distribution type and aut Mirrored content is saved automatically in /var/www/cobbler/ks_mirror. -Example: B +Example: B Example2: B -Example3: B +Example3: B Example4: B diff --git a/website/new/download.html b/website/new/download.html index 821d684..977fb1b 100644 --- a/website/new/download.html +++ b/website/new/download.html @@ -48,31 +48,24 @@ The lastest stable releases of Cobbler and Koan are included in

    -

    Source RPM Build Instructions for RHEL5

    +

    Source RPM Build Instructions for RHEL 4/5

      - -
    • Grab python-setuputils from here
    • -
    • Grab python-cheetah and yum-utils from here: here
    • -
    • rpmbuild --rebuild those RPMs and then install them
    • -
    • now rebuild --rebuild and install Cobbler from it's source RPM
    • +
    • Grab python-setuputils and python-cheetah from EPEL and install them. These are found in the main EPEL repo, not EPEL testing -- here's the link: i386 and x86_64.
    • +
    • now rpmbuild --rebuild cobbler*.src.rpm
    • +
    • install the RPM, which is now built in /usr/src/redhat/RPMS/noarch
    • +
    • satisfy any dependencies you have by using yum and the EPEL 5 repos
    -- cgit From 650fcae97e686108592cff603ad6e95c5dc43c7d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 11 Apr 2008 15:50:09 -0400 Subject: Now possible to override snippets on a per-profile or per-system basis, as discussed on the mailing list for usage for doing disk/package config, etc --- CHANGELOG | 1 + cobbler/action_litesync.py | 20 ++-- cobbler/action_sync.py | 259 ++++++++------------------------------------- cobbler/templar.py | 189 +++++++++++++++++++++++++++++++++ cobbler/utils.py | 75 +++++++++++-- 5 files changed, 312 insertions(+), 232 deletions(-) create mode 100644 cobbler/templar.py diff --git a/CHANGELOG b/CHANGELOG index 80aa334..b6d6d14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ Cobbler CHANGELOG - update PXE defaults to invoke menu.c32 automatically w/ timeout - removed dependency on rhpl - import can now take an --arch (and is recommended usage) +- now possible to override snippets on a profile/system specific basis - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index a73e7c3..bc7ffb2 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -68,13 +68,13 @@ class BootLiteSync: def remove_single_distro(self, name): bootloc = utils.tftpboot_location() # delete distro YAML file in distros/$name in webdir - self.sync.rmfile(os.path.join(self.settings.webdir, "distros", name)) + utils.rmfile(os.path.join(self.settings.webdir, "distros", name)) # delete contents of images/$name directory in webdir - self.sync.rmtree(os.path.join(self.settings.webdir, "images", name)) + utils.rmtree(os.path.join(self.settings.webdir, "images", name)) # delete contents of images/$name in tftpboot - self.sync.rmtree(os.path.join(bootloc, "images", name)) + utils.rmtree(os.path.join(bootloc, "images", name)) # delete potential symlink to tree in webdir/links - self.sync.rmfile(os.path.join(self.settings.webdir, "links", name)) + utils.rmfile(os.path.join(self.settings.webdir, "links", name)) def add_single_profile(self, name): # get the profile object: @@ -101,9 +101,9 @@ class BootLiteSync: # rebuild profile_list YAML file in webdir self.sync.write_listings() # delete profiles/$name file in webdir - self.sync.rmfile(os.path.join(self.settings.webdir, "profiles", name)) + utils.rmfile(os.path.join(self.settings.webdir, "profiles", name)) # delete contents on kickstarts/$name directory in webdir - self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts", name)) + utils.rmtree(os.path.join(self.settings.webdir, "kickstarts", name)) def update_system_netboot_status(self,name): system = self.systems.find(name=name) @@ -133,13 +133,13 @@ class BootLiteSync: # rebuild system_list file in webdir self.sync.write_listings() # delete system YAML file in systems/$name in webdir - self.sync.rmfile(os.path.join(self.settings.webdir, "systems", name)) + utils.rmfile(os.path.join(self.settings.webdir, "systems", name)) # delete contents of kickstarts_sys/$name in webdir system_record = self.systems.find(name=name) # delete any kickstart files related to this system for (name,interface) in system_record.interfaces.iteritems(): filename = utils.get_config_filename(system_record,interface=name) - self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename)) + utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename)) # unneeded #if not system_record.is_pxe_supported(): @@ -154,7 +154,7 @@ class BootLiteSync: if distro is not None and distro in [ "ia64", "IA64"]: itanic = True if not itanic: - self.sync.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) + utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) else: - self.sync.rmfile(os.path.join(bootloc, filename)) + utils.rmfile(os.path.join(bootloc, filename)) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 5c8aab9..1e65e42 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -1,10 +1,9 @@ """ -Builds out a TFTP/cobbler boot tree based on the object tree. +Builds out filesystem trees/data based on the object tree. This is the code behind 'cobbler sync'. -Copyright 2006,2007, Red Hat, Inc +Copyright 2006-2008, Red Hat, Inc Michael DeHaan -Tim Verhoeven This software may be freely redistributed under the terms of the GNU general public license. @@ -22,11 +21,13 @@ import yaml # Howell-Clark version import sub_process import sys import glob +import traceback +import errno import utils from cexceptions import * -import traceback -import errno +import templar + import item_distro import item_profile @@ -55,8 +56,7 @@ class BootSync: self.systems = config.systems() self.settings = config.settings() self.repos = config.repos() - self.blend_cache = {} - self.load_snippet_cache() + self.templar = templar.Templar(config) self.bootloc = utils.tftpboot_location() def run(self): @@ -106,13 +106,13 @@ class BootSync: path = self.settings.bootloaders[loader] newname = os.path.basename(path) destpath = os.path.join(self.bootloc, newname) - self.copyfile(path, destpath) - self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) + utils.copyfile(path, destpath) + utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) # Copy memtest to tftpboot if package is installed on system for memtest in glob.glob('/boot/memtest*'): base = os.path.basename(memtest) - self.copyfile(memtest,os.path.join(self.bootloc,"images",base)) + utils.copyfile(memtest,os.path.join(self.bootloc,"images",base)) def write_dhcp_file(self): """ @@ -221,7 +221,7 @@ class BootSync: continue metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] - self.apply_template(template_data, metadata, settings_file) + self.templar.render(template_data, metadata, settings_file, None) def regen_ethers(self): # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date @@ -276,16 +276,16 @@ class BootSync: path = os.path.join(self.settings.webdir,x) if os.path.isfile(path): if not x.endswith(".py"): - self.rmfile(path) + utils.rmfile(path) if os.path.isdir(path): if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links","repo_profile","repo_system"] : # delete directories that shouldn't exist - self.rmtree(path) + utils.rmtree(path) if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]: # clean out directory contents - self.rmtree_contents(path) - self.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) - self.rmtree_contents(os.path.join(self.bootloc, "images")) + utils.rmtree_contents(path) + utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) + utils.rmtree_contents(os.path.join(self.bootloc, "images")) def copy_distros(self): """ @@ -306,7 +306,7 @@ class BootSync: for dirtree in [self.bootloc, self.settings.webdir]: distros = os.path.join(dirtree, "images") distro_dir = os.path.join(distros,d.name) - self.mkdir(distro_dir) + utils.mkdir(distro_dir) kernel = utils.find_kernel(d.kernel) # full path initrd = utils.find_initrd(d.initrd) # full path if kernel is None or not os.path.isfile(kernel): @@ -316,13 +316,13 @@ class BootSync: b_kernel = os.path.basename(kernel) b_initrd = os.path.basename(initrd) if kernel.startswith(dirtree): - self.linkfile(kernel, os.path.join(distro_dir, b_kernel)) + utils.linkfile(kernel, os.path.join(distro_dir, b_kernel)) else: - self.copyfile(kernel, os.path.join(distro_dir, b_kernel)) + utils.copyfile(kernel, os.path.join(distro_dir, b_kernel)) if initrd.startswith(dirtree): - self.linkfile(initrd, os.path.join(distro_dir, b_initrd)) + utils.linkfile(initrd, os.path.join(distro_dir, b_initrd)) else: - self.copyfile(initrd, os.path.join(distro_dir, b_initrd)) + utils.copyfile(initrd, os.path.join(distro_dir, b_initrd)) def validate_kickstarts(self): """ @@ -356,7 +356,7 @@ class BootSync: def validate_kickstart_for_specific_profile(self,g): distro = g.get_conceptual_parent() - meta = utils.blender(self.api, False, g, self.blend_cache) + meta = utils.blender(self.api, False, g) if distro is None: raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro }) kickstart_path = utils.find_kickstart(meta["kickstart"]) @@ -367,10 +367,10 @@ class BootSync: "kickstarts", # profile kickstarts go here g.name ) - self.mkdir(copy_path) + utils.mkdir(copy_path) dest = os.path.join(copy_path, "ks.cfg") try: - meta = utils.blender(self.api, False, g, self.blend_cache) + meta = utils.blender(self.api, False, g) ksmeta = meta["ks_meta"] del meta["ks_meta"] meta.update(ksmeta) # make available at top level @@ -379,7 +379,7 @@ class BootSync: meta["kickstart_done"] = self.generate_kickstart_signal(g, None) meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) kfile = open(kickstart_path) - self.apply_template(kfile, meta, dest) + self.templar.render(kfile, meta, dest, g) kfile.close() except: traceback.print_exc() # leave this in, for now... @@ -404,7 +404,7 @@ class BootSync: if system: blend_this = system - blended = utils.blender(self.api, False, blend_this, self.blend_cache) + blended = utils.blender(self.api, False, blend_this) kickstart = blended.get("kickstart",None) buf = "" @@ -436,7 +436,7 @@ class BootSync: """ buf = "" - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) configs = self.get_repo_filenames(obj,is_profile) repos = self.repos @@ -479,7 +479,7 @@ class BootSync: baseurls """ - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) urlseg = self.get_repo_segname(is_profile) topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"]) @@ -503,7 +503,7 @@ class BootSync: if not is_profile: distro = distro.get_conceptual_parent() - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) configs = self.get_repo_filenames(obj, is_profile) buf = "" @@ -545,14 +545,14 @@ class BootSync: if profile is None: raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile }) distro = profile.get_conceptual_parent() - meta = utils.blender(self.api, False, s, self.blend_cache) + meta = utils.blender(self.api, False, s) kickstart_path = utils.find_kickstart(meta["kickstart"]) if kickstart_path and os.path.exists(kickstart_path): copy_path = os.path.join(self.settings.webdir, "kickstarts_sys", # system kickstarts go here s.name ) - self.mkdir(copy_path) + utils.mkdir(copy_path) dest = os.path.join(copy_path, "ks.cfg") try: ksmeta = meta["ks_meta"] @@ -563,107 +563,12 @@ class BootSync: meta["kickstart_done"] = self.generate_kickstart_signal(profile, s) meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) kfile = open(kickstart_path) - self.apply_template(kfile, meta, dest) + self.templar.render(kfile, meta, dest, s) kfile.close() except: traceback.print_exc() raise CX(_("Error templating file %(src)s to %(dest)s") % { "src" : meta["kickstart"], "dest" : dest }) - def load_snippet_cache(self): - - # first load all of the files in /var/lib/cobbler/snippets and load them, for use - # in adding long bits to kickstart templates without having to have them hard coded - # inside the sync code. - - snippet_cache = {} - snippets = glob.glob("%s/*" % self.settings.snippetsdir) - for snip in snippets: - if os.path.isdir(snip): - continue - snip_file = open(snip) - data = snip_file.read() - snip_file.close() - snippet_cache[os.path.basename(snip)] = data - self.snippet_cache = snippet_cache - - - def apply_template(self, data_input, metadata, out_path): - """ - Take filesystem file kickstart_input, apply metadata using - Cheetah and save as out_path. - """ - - if type(data_input) != str: - data = data_input.read() - else: - data = data_input - - # backward support for Cobbler's legacy (and slightly more readable) - # template syntax. - data = data.replace("TEMPLATE::","$") - - # replace contents of the data stream with items from the snippet cache - # do not use Cheetah yet, Cheetah can't really be run twice on the same - # stream and be expected to do the right thing - newdata = "" - for line in data.split("\n"): - for x in self.snippet_cache: - if not line.startswith("#"): - line = line.replace("SNIPPET::%s" % x, self.snippet_cache[x]) - newdata = "\n".join((newdata, line)) - data = newdata - - # HACK: the ksmeta field may contain nfs://server:/mount in which - # case this is likely WRONG for kickstart, which needs the NFS - # directive instead. Do this to make the templates work. - newdata = "" - if metadata.has_key("tree") and metadata["tree"].startswith("nfs://"): - for line in data.split("\n"): - if line.find("--url") != -1 and line.find("url ") != -1: - rest = metadata["tree"][6:] # strip off "nfs://" part - try: - (server, dir) = rest.split(":",2) - except: - raise CX(_("Invalid syntax for NFS path given during import: %s" % metadata["tree"])) - line = "nfs --server %s --dir %s" % (server,dir) - # but put the URL part back in so koan can still see - # what the original value was - line = line + "\n" + "#url --url=%s" % metadata["tree"] - newdata = newdata + line + "\n" - data = newdata - - # tell Cheetah not to blow up if it can't find a symbol for something - data = "#errorCatcher Echo\n" + data - - # now do full templating scan, where we will also templatify the snippet insertions - t = Template(source=data, searchList=[metadata]) - try: - data_out = str(t) - except: - print _("There appears to be an formatting error in the template file.") - print _("For completeness, the traceback from Cheetah has been included below.") - raise - - # now apply some magic post-filtering that is used by cobbler import and some - # other places, but doesn't use Cheetah. Forcing folks to double escape - # things would be very unwelcome. - - for x in metadata: - if type(metadata[x]) == str: - data_out = data_out.replace("@@%s@@" % x, metadata[x]) - - # remove leading newlines which apparently breaks AutoYAST ? - if data_out.startswith("\n"): - data_out = data_out.strip() - - if out_path is not None: - self.mkdir(os.path.dirname(out_path)) - fd = open(out_path, "w+") - fd.write(data_out) - fd.close() - - return data_out - def build_trees(self): """ Now that kernels and initrds are copied and kickstarts are all valid, @@ -703,7 +608,7 @@ class BootSync: and also potentially in listed in the source_repos structure of the distro object, however these files have server URLs in them that must be templated out. This function does this. """ - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) if is_profile: outseg = "repos_profile" @@ -735,7 +640,7 @@ class BootSync: dispname = infile.split("/")[-1].replace(".repo","") confdir = os.path.join(self.settings.webdir, outseg) outdir = os.path.join(confdir, blended["name"]) - self.mkdir(outdir) + utils.mkdir(outdir) try: infile_h = open(infile) except: @@ -744,7 +649,7 @@ class BootSync: infile_data = infile_h.read() infile_h.close() outfile = os.path.join(outdir, "%s.repo" % (dispname)) - self.apply_template(infile_data, blended, outfile) + self.templar.render(infile_data, blended, outfile, None) def write_all_system_files(self,system,just_edit_pxe=False): @@ -788,7 +693,7 @@ class BootSync: self.write_pxe_file(f2,system,profile,distro,True) else: # ensure the file doesn't exist - self.rmfile(f2) + utils.rmfile(f2) if not just_edit_pxe: # allows netboot-disable to be highly performant @@ -834,10 +739,10 @@ class BootSync: contents = self.write_memtest_pxe("/images/%s" % base) pxe_menu_items = pxe_menu_items + contents + "\n" - # save the template. + # save the template. metadata = { "pxe_menu_items" : pxe_menu_items } outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") - self.apply_template(template_data, metadata, outfile) + self.templar.render(template_data, metadata, outfile, None) template_src.close() def write_memtest_pxe(self,filename): @@ -845,16 +750,13 @@ class BootSync: Write a configuration file for memtest """ - # --- # just some random variables template = None metadata = {} buffer = "" - # --- template = "/etc/cobbler/pxeprofile.template" - # --- # store variables for templating metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename) metadata["profile_name"] = os.path.basename(filename) @@ -862,15 +764,13 @@ class BootSync: metadata["initrd_path"] = "" metadata["append_line"] = "" - # --- # get the template template_fh = open(template) template_data = template_fh.read() template_fh.close() - # --- # return results - buffer = self.apply_template(template_data, metadata, None) + buffer = self.templar.render(template_data, metadata, None) return buffer @@ -902,7 +802,7 @@ class BootSync: initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd)) # Find the kickstart if we inherit from another profile - kickstart_path = utils.blender(self.api, True, profile, self.blend_cache)["kickstart"] + kickstart_path = utils.blender(self.api, True, profile)["kickstart"] # --- # choose a template @@ -915,12 +815,11 @@ class BootSync: # now build the kernel command line if system is not None: - blended = utils.blender(self.api, True,system,self.blend_cache) + blended = utils.blender(self.api, True, system) else: - blended = utils.blender(self.api, True,profile,self.blend_cache) + blended = utils.blender(self.api, True,profile) kopts = blended["kernel_options"] - # --- # generate the append line append_line = "append %s" % utils.hash_to_string(kopts) if not is_ia64: @@ -928,7 +827,6 @@ class BootSync: if len(append_line) >= 255 + len("append "): print _("warning: kernel option length exceeds 255") - # --- # kickstart path rewriting (get URLs for local files) if kickstart_path is not None and kickstart_path != "": @@ -945,7 +843,6 @@ class BootSync: append_line = "%s auto=true url=%s" % (append_line, kickstart_path) append_line = append_line.replace("ksdevice","interface") - # --- # store variables for templating metadata["menu_label"] = "" if not is_ia64 and system is None: @@ -955,15 +852,13 @@ class BootSync: metadata["initrd_path"] = initrd_path metadata["append_line"] = append_line - # --- # get the template template_fh = open(template) template_data = template_fh.read() template_fh.close() - # --- # save file and/or return results, depending on how called. - buffer = self.apply_template(template_data, metadata, None) + buffer = self.templar.render(template_data, metadata, None) if filename is not None: fd = open(filename, "w") fd.write(buffer) @@ -991,7 +886,7 @@ class BootSync: """ Create distro information for koan install """ - blended = utils.blender(self.api, True, distro, self.blend_cache) + blended = utils.blender(self.api, True, distro) filename = os.path.join(self.settings.webdir,"distros",distro.name) fd = open(filename, "w+") fd.write(yaml.dump(blended)) @@ -1004,7 +899,7 @@ class BootSync: NOTE: relevant to http only """ - blended = utils.blender(self.api, True, profile, self.blend_cache) + blended = utils.blender(self.api, True, profile) filename = os.path.join(self.settings.webdir,"profiles",profile.name) fd = open(filename, "w+") if blended.has_key("kickstart") and blended["kickstart"].startswith("/"): @@ -1020,73 +915,11 @@ class BootSync: NOTE: relevant to http only """ - blended = utils.blender(self.api, True, system, self.blend_cache) + blended = utils.blender(self.api, True, system) filename = os.path.join(self.settings.webdir,"systems",system.name) fd = open(filename, "w+") fd.write(yaml.dump(blended)) fd.close() - def linkfile(self, src, dst): - """ - Attempt to create a link dst that points to src. Because file - systems suck we attempt several different methods or bail to - self.copyfile() - """ - try: - return os.link(src, dst) - except (IOError, OSError): - pass - - try: - return os.symlink(src, dst) - except (IOError, OSError): - pass - - return self.copyfile(src, dst) - - def copyfile(self,src,dst): - try: - return shutil.copyfile(src,dst) - except: - if not os.path.samefile(src,dst): - # accomodate for the possibility that we already copied - # the file as a symlink/hardlink - raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) - - def rmfile(self,path): - try: - os.unlink(path) - return True - except OSError, ioe: - if not ioe.errno == errno.ENOENT: # doesn't exist - traceback.print_exc() - raise CX(_("Error deleting %s") % path) - return True - - def rmtree_contents(self,path): - what_to_delete = glob.glob("%s/*" % path) - for x in what_to_delete: - self.rmtree(x) - - def rmtree(self,path): - try: - if os.path.isfile(path): - return self.rmfile(path) - else: - return shutil.rmtree(path,ignore_errors=True) - except OSError, ioe: - traceback.print_exc() - if not ioe.errno == errno.ENOENT: # doesn't exist - raise CX(_("Error deleting %s") % path) - return True - - def mkdir(self,path,mode=0777): - try: - return os.makedirs(path,mode) - except OSError, oe: - if not oe.errno == 17: # already exists (no constant for 17?) - traceback.print_exc() - print oe.errno - raise CX(_("Error creating") % path) diff --git a/cobbler/templar.py b/cobbler/templar.py new file mode 100644 index 0000000..fa401b8 --- /dev/null +++ b/cobbler/templar.py @@ -0,0 +1,189 @@ +""" +Cobbler uses Cheetah templates for lots of stuff, but there's +some additional magic around that to deal with snippets/etc. +(And it's not spelled wrong!) + +Copyright 2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import glob +import utils +from cexceptions import * +from Cheetah.Template import Template + +class Templar: + + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.api = config.api + self.settings = config.settings() + self.cache = {} + + def render(self, data_input, search_table, out_path, subject=None): + """ + Render data_input back into a file. + data_input is either a string or a filename + search_table is a hash of metadata keys and values + out_path if not-none writes the results to a file + (though results are always returned) + subject is a profile or system object, if available (for snippet eval) + """ + + if type(data_input) != str: + raw_data = data_input.read() + else: + raw_data = data_input + + # backward support for Cobbler's legacy (and slightly more readable) + # template syntax. + raw_data = raw_data.replace("TEMPLATE::","$") + + # replace snippets with the proper Cheetah include directives prior to processing. + # see Wiki for full details on how snippets operate. + snippet_results = "" + for line in raw_data.split("\n"): + line = self.replace_snippets(line,subject) + snippet_results = "\n".join((snippet_results, line)) + raw_data = snippet_results + + # HACK: the ksmeta field may contain nfs://server:/mount in which + # case this is likely WRONG for kickstart, which needs the NFS + # directive instead. Do this to make the templates work. + newdata = "" + if search_table.has_key("tree") and search_table["tree"].startswith("nfs://"): + for line in data.split("\n"): + if line.find("--url") != -1 and line.find("url ") != -1: + rest = search_table["tree"][6:] # strip off "nfs://" part + try: + (server, dir) = rest.split(":",2) + except: + raise CX(_("Invalid syntax for NFS path given during import: %s" % search_table["tree"])) + line = "nfs --server %s --dir %s" % (server,dir) + # but put the URL part back in so koan can still see + # what the original value was + line = line + "\n" + "#url --url=%s" % search_table["tree"] + newdata = newdata + line + "\n" + raw_data = newdata + + # tell Cheetah not to blow up if it can't find a symbol for something + raw_data = "#errorCatcher Echo\n" + raw_data + + # now do full templating scan, where we will also templatify the snippet insertions + t = Template(source=raw_data, searchList=[search_table]) + try: + data_out = str(t) + except: + print _("There appears to be an formatting error in the template file.") + print _("For completeness, the traceback from Cheetah has been included below.") + raise + + # now apply some magic post-filtering that is used by cobbler import and some + # other places, but doesn't use Cheetah. Forcing folks to double escape + # things would be very unwelcome. + + for x in search_table: + if type(search_table[x]) == str: + data_out = data_out.replace("@@%s@@" % x, search_table[x]) + + # remove leading newlines which apparently breaks AutoYAST ? + if data_out.startswith("\n"): + data_out = data_out.strip() + + if out_path is not None: + utils.mkdir(os.path.dirname(out_path)) + fd = open(out_path, "w+") + fd.write(data_out) + fd.close() + + return data_out + + def replace_snippets(self,line,subject): + """ + Replace all SNIPPET:: syntaxes on a line with the + results of evaluating the snippet, taking care not + to replace tabs with spaces or anything like that + """ + tokens = line.split(None) + for t in tokens: + if t.startswith("SNIPPET::"): + snippet_name = t.replace("SNIPPET::","") + line = line.replace(t,self.eval_snippet(snippet_name,subject)) + return line + + def eval_snippet(self,name,subject): + """ + Replace SNIPPET::foo with contents of files: + Use /var/lib/cobbler/snippets/per_system/$name/$sysname + /var/lib/cobbler/snippets/per_profile/$name/$proname + /var/lib/cobbler/snippets/$name + in order... (first one wins) + """ + + sd = self.settings.snippetsdir + default_path = "%s/%s" % (sd,name) + + if subject is None: + if os.path.exists(default_path): + return self.slurp(default_path) + else: + return self.slurp(None) + + + if subject.COLLECTION_TYPE == "system": + profile = self.api.find_profile(name=subject.profile) + sys_path = "%s/per_system/%s/%s" % (sd,name,subject.name) + pro_path = "%s/per_profile/%s/%s" % (sd,name,profile.name) + if os.path.exists(sys_path): + return self.slurp(sys_path) + elif os.path.exists(pro_path): + return self.slurp(pro_path) + elif os.path.exists(default_path): + return self.slurp(default_path) + else: + return self.slurp(None) + + if subject.COLLECTION_TYPE == "profile": + pro_path = "%s/per_profile/%s/%s" % (sd,name,subject.name) + if os.path.exists(pro_path): + return self.slurp(pro_path) + elif os.path.exists(default_path): + return self.slurp(default_path) + else: + return self.slurp(None) + + return self.slurp(None) + + def slurp(self,filename): + """ + Get the contents of a filename but if none is specified + just include some generic error text for the rendered + template. + """ + + if filename is None: + return "# error: no snippet data found" + + # potentially eliminate a ton of system calls if syncing + # thousands of systems that use the same template + if self.cache.has_key(filename): + return self.cache[filename] + + fd = open(filename,"r") + data = fd.read() + self.cache[filename] = data + fd.close() + + return data diff --git a/cobbler/utils.py b/cobbler/utils.py index d8cf6fc..2d601c7 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -21,6 +21,7 @@ import sub_process import shutil import string import traceback +import errno from cexceptions import * #placeholder for translation @@ -296,19 +297,14 @@ def grab_tree(api_handle, obj): results.append(settings) return results -def blender(api_handle,remove_hashes, root_obj, blend_cache=None): +def blender(api_handle,remove_hashes, root_obj): """ Combine all of the data in an object tree from the perspective of that point on the tree, and produce a merged hash containing consolidated data. """ - cache_enabled = False # FIXME: disabled for now as there a few bugs in this impl. - blend_key = "%s/%s/%s" % (root_obj.TYPE_NAME, root_obj.name, remove_hashes) - if cache_enabled and blend_cache is not None: - if blend_cache.has_key(blend_key): - return blend_cache[blend_key] settings = api_handle.settings() tree = grab_tree(api_handle, root_obj) @@ -353,9 +349,6 @@ def blender(api_handle,remove_hashes, root_obj, blend_cache=None): # sanitize output for koan and kernel option lines, etc if remove_hashes: results = flatten(results) - - if cache_enabled and blend_cache is not None: - blend_cache[blend_key] = results return results def flatten(data): @@ -548,6 +541,70 @@ def tftpboot_location(): return "/var/lib/tftpboot" return "/tftpboot" +def linkfile(src, dst): + """ + Attempt to create a link dst that points to src. Because file + systems suck we attempt several different methods or bail to + copyfile() + """ + + try: + return os.link(src, dst) + except (IOError, OSError): + pass + + try: + return os.symlink(src, dst) + except (IOError, OSError): + pass + + return utils.copyfile(src, dst) + +def copyfile(src,dst): + try: + return shutil.copyfile(src,dst) + except: + if not os.path.samefile(src,dst): + # accomodate for the possibility that we already copied + # the file as a symlink/hardlink + raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) + +def rmfile(path): + try: + os.unlink(path) + return True + except OSError, ioe: + if not ioe.errno == errno.ENOENT: # doesn't exist + traceback.print_exc() + raise CX(_("Error deleting %s") % path) + return True + +def rmtree_contents(path): + what_to_delete = glob.glob("%s/*" % path) + for x in what_to_delete: + rmtree(x) + +def rmtree(path): + try: + if os.path.isfile(path): + return rmfile(path) + else: + return shutil.rmtree(path,ignore_errors=True) + except OSError, ioe: + traceback.print_exc() + if not ioe.errno == errno.ENOENT: # doesn't exist + raise CX(_("Error deleting %s") % path) + return True + +def mkdir(path,mode=0777): + try: + return os.makedirs(path,mode) + except OSError, oe: + if not oe.errno == 17: # already exists (no constant for 17?) + traceback.print_exc() + print oe.errno + raise CX(_("Error creating") % path) + if __name__ == "__main__": # print redhat_release() print tftpboot_location() -- cgit From 6f6c1c700aac364d5cb2f29d039c950f26767f10 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 14 Apr 2008 11:07:24 -0400 Subject: F8/9 kickstart format support --- CHANGELOG | 1 + cobbler/action_import.py | 9 ++------- cobbler/utils.py | 10 ++++++++-- setup.py | 1 + 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b6d6d14..5e1e8ca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ Cobbler CHANGELOG - removed dependency on rhpl - import can now take an --arch (and is recommended usage) - now possible to override snippets on a profile/system specific basis +- provide a different default sample kickstart for imports of F8 and later - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_import.py b/cobbler/action_import.py index 7f5409b..377b122 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -192,13 +192,6 @@ class Importer: print _("- skipping distro %s since it wasn't imported this time") % profile.distro continue - # THIS IS OBSOLETE: - # - #if not distro.kernel.startswith("%s/ks_mirror/" % self.settings.webdir): - # # this isn't a mirrored profile, so we won't touch it - # print _("- skipping %s since profile isn't mirrored") % profile.name - # continue - if (self.kickstart_file == None): kdir = os.path.dirname(distro.kernel) base_dir = "/".join(kdir.split("/")[0:-2]) @@ -287,6 +280,8 @@ class Importer: def set_kickstart(self, profile, flavor, major, minor): if flavor == "fedora": + if major >= 8: + return profile.set_kickstart("/etc/cobbler/sample_end.ks") if major >= 6: return profile.set_kickstart("/etc/cobbler/sample.ks") if flavor == "redhat" or flavor == "centos": diff --git a/cobbler/utils.py b/cobbler/utils.py index 2d601c7..8cc75bb 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -480,13 +480,19 @@ def fix_mod_python_select_submission(repos): repos = repos.replace('"',"") repos = repos.lstrip().rstrip() return repos + def check_dist(): + """ + Determines what distro we're running under. + """ if os.path.exists("/etc/debian_version"): return "debian" else: + # valid for Fedora and all Red Hat / Fedora derivatives return "redhat" -def redhat_release(): +def os_release(): + if check_dist() == "redhat": if not os.path.exists("/bin/rpm"): @@ -536,7 +542,7 @@ def tftpboot_location(): return t # otherwise, guess based on the distro - (make,version,rest) = redhat_release() + (make,version,rest) = os_release() if make == "fedora" and version >= 9: return "/var/lib/tftpboot" return "/tftpboot" diff --git a/setup.py b/setup.py index 51ffe80..31ead00 100644 --- a/setup.py +++ b/setup.py @@ -88,6 +88,7 @@ if __name__ == "__main__": # sample kickstart files (etcpath, ['kickstarts/legacy.ks']), (etcpath, ['kickstarts/sample.ks']), + (etcpath, ['kickstarts/sample_end.ks']), (etcpath, ['kickstarts/default.ks']), # templates for DHCP and syslinux configs -- cgit From 51119d1acc532cfad68b9fe4a1daa945fe7cd3f0 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 14 Apr 2008 16:31:08 -0400 Subject: Better kerberos support. See the Wiki. --- CHANGELOG | 1 + MANIFEST.in | 1 - Makefile | 2 + cobbler.spec | 1 - cobbler/cobblerd.py | 35 +++++++++++++---- cobbler/modules/authn_kerberos.py | 81 --------------------------------------- cobbler/modules/authn_ldap.py | 7 +++- cobbler/modules/authn_passthru.py | 49 +++++++++++++++++++++++ cobbler/utils.py | 2 +- scripts/cobbler_auth_help | 55 -------------------------- scripts/index.py | 25 +++++++++++- setup.py | 2 +- 12 files changed, 111 insertions(+), 150 deletions(-) delete mode 100644 cobbler/modules/authn_kerberos.py create mode 100644 cobbler/modules/authn_passthru.py delete mode 100644 scripts/cobbler_auth_help diff --git a/CHANGELOG b/CHANGELOG index 5e1e8ca..0713969 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ Cobbler CHANGELOG - import can now take an --arch (and is recommended usage) - now possible to override snippets on a profile/system specific basis - provide a different default sample kickstart for imports of F8 and later +- support for kerberos authentication - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/MANIFEST.in b/MANIFEST.in index 4c8ed20..c553c03 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -24,7 +24,6 @@ include scripts/findks.cgi include scripts/nopxe.cgi include scripts/gateway.py include scripts/post_install_trigger.cgi -include scripts/cobbler_auth_help include snippets/* recursive-include po *.pot recursive-include po *.po diff --git a/Makefile b/Makefile index e742e39..bc71166 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ install: clean manpage devinstall: cp /var/lib/cobbler/settings /tmp/cobbler_settings cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf + cp /etc/httpd/conf.d/cobbler.conf /tmp/cobbler_http.conf cp /etc/cobbler/users.conf /tmp/cobbler_users.conf -cp /etc/cobbler/users.digest /tmp/cobbler_users.digest make install @@ -45,6 +46,7 @@ devinstall: cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf cp /tmp/cobbler_users.conf /etc/cobbler/users.conf -cp /tmp/cobbler_users.digest /etc/cobbler/users.digest + cp /tmp/cobbler_http.conf /etc/httpd/conf.d/cobbler.conf find /var/lib/cobbler/triggers | xargs chmod +x chown -R apache /var/www/cobbler chown -R apache /var/www/cgi-bin/cobbler diff --git a/cobbler.spec b/cobbler.spec index 2b6c88d..9b775cd 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -120,7 +120,6 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /tftpboot/images %{_bindir}/cobbler %{_bindir}/cobblerd -%{_bindir}/cobbler_auth_help %dir /etc/cobbler %config(noreplace) /etc/cobbler/*.ks %config(noreplace) /etc/cobbler/*.template diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py index 8859e03..065e99e 100644 --- a/cobbler/cobblerd.py +++ b/cobbler/cobblerd.py @@ -18,6 +18,7 @@ import SimpleXMLRPCServer import glob from utils import _ import xmlrpclib +import binascii from server import xmlrpclib2 import api as cobbler_api @@ -40,6 +41,8 @@ def core(logger=None): pid = os.fork() + regen_ss_file() + if pid == 0: # part one: XMLRPC -- which may be just read-only or both read-only and read-write do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger) @@ -47,6 +50,21 @@ def core(logger=None): # part two: syslog, or syslog+avahi if avahi is installed do_other_tasks(bootapi, settings, syslog_port, logger) +def regen_ss_file(): + # this is only used for Kerberos auth at the moment. + # it identifies XMLRPC requests from Apache that have already + # been cleared by Kerberos. + + 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() + os.system("chmod 700 /var/lib/cobbler/web.ss") + os.system("chown apache /var/lib/cobbler/web.ss") + return 1 + def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger): if str(settings.xmlrpc_rw_enabled) != "0": pid2 = os.fork() @@ -195,11 +213,14 @@ if __name__ == "__main__": #main() - bootapi = cobbler_api.BootAPI() - settings = bootapi.settings() - syslog_port = settings.syslog_port - xmlrpc_port = settings.xmlrpc_port - xmlrpc_port2 = settings.xmlrpc_rw_port - logger = bootapi.logger_remote - do_xmlrpc_unix(bootapi, settings, logger) + #bootapi = cobbler_api.BootAPI() + #settings = bootapi.settings() + #syslog_port = settings.syslog_port + #xmlrpc_port = settings.xmlrpc_port + #xmlrpc_port2 = settings.xmlrpc_rw_port + #logger = bootapi.logger_remote + #do_xmlrpc_unix(bootapi, settings, logger) + + regen_ss_file() + diff --git a/cobbler/modules/authn_kerberos.py b/cobbler/modules/authn_kerberos.py deleted file mode 100644 index 46c01ad..0000000 --- a/cobbler/modules/authn_kerberos.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Authentication module that uses kerberos. - -Copyright 2007, Red Hat, Inc -Michael DeHaan - -This software may be freely redistributed under the terms of the GNU -general public license. - -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., 675 Mass Ave, Cambridge, MA 02139, USA. -""" - -# NOTE: this is not using 'straight up' kerberos in that we -# relay passwords through cobblerd for authentication, that may -# be done later. It does of course check against kerberos, -# however. - -# ALSO NOTE: we're calling out to a Perl program to make -# this work. You must install Authen::Simple::Kerberos -# from CPAN and the Kerberos libraries for this to work. -# See the Cobbler Wiki for more info. - -# ALSO ALSO NOTE: set kerberos_realm in /var/lib/cobbler/settings -# to something appropriate or this will never work. CASING -# MATTERS. example.com != EXAMPLE.COM. - -import distutils.sysconfig -import ConfigParser -import sys -import os -from utils import _ -import md5 -import traceback -# since sub_process isn't available on older OS's -try: - import sub_process as subprocess -except: - import subprocess - -plib = distutils.sysconfig.get_python_lib() -mod_path="%s/cobbler" % plib -sys.path.insert(0, mod_path) - -import cexceptions -import utils - -def register(): - """ - The mandatory cobbler module registration hook. - """ - return "authn" - -def authenticate(api_handle,username,password): - """ - Validate a username/password combo, returning True/False - Uses cobbler_auth_helper - """ - - realm = api_handle.settings().kerberos_realm - api_handle.logger.debug("authenticating %s against %s" % (username,realm)) - - rc = subprocess.call([ - "/usr/bin/cobbler_auth_help", - "--method=kerberos", - "--username=%s" % username, - "--password=%s" % password, - "--realm=%s" % realm - ]) - print rc - if rc == 42: - api_handle.logger.debug("authenticated ok") - # authentication ok (FIXME: log) - return True - else: - api_handle.logger.debug("authentication failed") - # authentication failed - return False - - diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index eef4b2a..ff31750 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -17,7 +17,10 @@ import os from utils import _ import md5 import traceback -import ldap + +# we'll import this just a bit later +# to keep it from being a requirement +# import ldap plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib @@ -38,6 +41,8 @@ def authenticate(api_handle,username,password): """ Validate an ldap bind, returning True/False """ + + import ldap server = api_handle.settings().ldap_server basedn = api_handle.settings().ldap_base_dn diff --git a/cobbler/modules/authn_passthru.py b/cobbler/modules/authn_passthru.py new file mode 100644 index 0000000..ebbe79a --- /dev/null +++ b/cobbler/modules/authn_passthru.py @@ -0,0 +1,49 @@ +""" +Authentication module that defers to Apache and trusts +what Apache trusts. + +Copyright 2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import distutils.sysconfig +import sys +import os +from utils import _ +import traceback + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +import cexceptions +import utils + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "authn" + +def authenticate(api_handle,username,password): + """ + Validate a username/password combo, returning True/False + Uses cobbler_auth_helper + """ + + fd = open("/var/lib/cobbler/web.ss") + data = fd.read() + if password == data: + rc = 1 + else: + rc = 0 + fd.close() + return data + diff --git a/cobbler/utils.py b/cobbler/utils.py index 8cc75bb..8a09025 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -564,7 +564,7 @@ def linkfile(src, dst): except (IOError, OSError): pass - return utils.copyfile(src, dst) + return copyfile(src, dst) def copyfile(src,dst): try: diff --git a/scripts/cobbler_auth_help b/scripts/cobbler_auth_help deleted file mode 100644 index c43cd5b..0000000 --- a/scripts/cobbler_auth_help +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/perl - -# Kerberos helper for logins -# -# Copyright 2007, Red Hat, Inc -# Michael DeHaan -# -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -# Usage: -# cobbler_auth_helper kerberos username pass -# (may do other auth types later) -# Returns: -# 0 on ok, non-0 on failure -# API info: -# http://search.cpan.org/~chansen/Authen-Simple-Kerberos-0.1/ - -use warnings; -use strict; - -use Authen::Simple::Kerberos; -use Getopt::Long; - -my $method; -my $username; -my $realm; -my $password; -my $verbose=1; - -my $result = GetOptions( - "method=s" => \$method, - "username=s" => \$username, - "realm=s" => \$realm, - "password=s" => \$password, -); - -my $kerberos = Authen::Simple::Kerberos->new( - realm => $realm -); - -print "authenticating: $username against (realm=$realm) (pass=$password)\n" if $verbose; - -if ( $kerberos->authenticate( $username, $password ) ) { - print "ok\n" if $verbose; - exit(42); -} - -print "denied\n" if $verbose; -exit(1); - diff --git a/scripts/index.py b/scripts/index.py index d32a3a6..281e36e 100755 --- a/scripts/index.py +++ b/scripts/index.py @@ -18,6 +18,7 @@ from mod_python import util import xmlrpclib import cgi +import os from cobbler.webui import CobblerWeb XMLRPC_SERVER = "http://127.0.0.1:25152" # was http://127.0.0.1/cobbler_api_rw" @@ -70,7 +71,28 @@ def handler(req): my_user = __get_user(req) my_uri = req.uri sess = __get_session(req) - token = sess['cobbler_token'] + + if not sess.has_key('cobbler_token'): + # using Kerberos instead of Python Auth handler? + # We need to get our own token for use with authn_passthru + # which should also be configured in /etc/cobbler/modules.conf + # if another auth mode is configured in modules.conf this will + # most certaintly fail. + try: + if not os.path.exists("/var/lib/cobbler/web.ss"): + apache.log_error("cannot load /var/lib/cobbler/web.ss") + return apache.HTTP_UNAUTHORIZED + fd = open("/var/lib/cobbler/web.ss") + data = fd.read() + my_pw = data + fd.close() + token = xmlrpc_server.login(my_user,my_pw) + except Exception, e: + apache.log_error(str(e)) + return apache.HTTP_UNAUTHORIZED + sess['cobbler_token'] = token + else: + token = sess['cobbler_token'] # needed? req.add_common_vars() @@ -118,7 +140,6 @@ def authenhandler(req): my_user = req.user my_uri = req.uri - apache.log_error("authenhandler called: %s" % my_user) try: token = xmlrpc_server.login(my_user,my_pw) except Exception, e: diff --git a/setup.py b/setup.py index 31ead00..6d4a1cd 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ if __name__ == "__main__": "cobbler/server", "cobbler/webui", ], - scripts = ["scripts/cobbler", "scripts/cobblerd", "scripts/cobbler_auth_help"], + scripts = ["scripts/cobbler", "scripts/cobblerd"], data_files = [ (modpython, ['scripts/index.py']), # cgi files -- cgit From 2ccbb4b130afac3d1707433b3988259ea109db7f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 15 Apr 2008 17:34:50 -0400 Subject: Replaced the existing cobbler pre/post install triggers system with a much more flexible model that (for each system) passes in the following. First arg: the word "system" or "profile", Second arg: the name of the said system or profile, Third: the MAC if available, Fourth: the IP. This is all logged by a default "status" trigger to /var/log/cobbler/install.log, for being read by the soon-to-be-revamped cobbler check. The check system logs all of this in order, followed by the word "start" or "stop", followed by the number of seconds since Epoch. --- CHANGELOG | 1 + Makefile | 1 + cobbler.spec | 3 ++ cobbler/action_status.py | 5 +++ cobbler/action_sync.py | 56 +++++++++++++++--------- cobbler/remote.py | 32 +++++++------- cobbler/settings.py | 2 +- cobbler/utils.py | 10 +++-- config/cobblerd_rotate | 7 +++ config/settings | 2 +- kickstarts/sample.ks | 1 + scripts/install_trigger.cgi | 92 ++++++++++++++++++++++++++++++++++++++++ scripts/post_install_trigger.cgi | 72 ------------------------------- setup.py | 5 ++- triggers/status_post.trigger | 16 +++++++ triggers/status_pre.trigger | 16 +++++++ 16 files changed, 207 insertions(+), 114 deletions(-) create mode 100644 scripts/install_trigger.cgi delete mode 100644 scripts/post_install_trigger.cgi create mode 100644 triggers/status_post.trigger create mode 100644 triggers/status_pre.trigger diff --git a/CHANGELOG b/CHANGELOG index 0713969..d1fb79d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ Cobbler CHANGELOG - now possible to override snippets on a profile/system specific basis - provide a different default sample kickstart for imports of F8 and later - support for kerberos authentication +- revamped pre/post install triggers system (triggered via cgi from kickstart wget) - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/Makefile b/Makefile index bc71166..b8e5d0a 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ devinstall: webtest: devinstall /sbin/service cobblerd restart /sbin/service httpd restart + chmod +x /var/www/cgi-bin/cobbler/*.cgi sdist: clean messages updatewui python setup.py sdist diff --git a/cobbler.spec b/cobbler.spec index 9b775cd..5e63f48 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -163,11 +163,14 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /var/lib/cobbler/triggers/delete/repo/post %dir /var/lib/cobbler/triggers/sync/pre %dir /var/lib/cobbler/triggers/sync/post +%dir /var/lib/cobbler/triggers/install/pre %dir /var/lib/cobbler/triggers/install/post %dir /var/lib/cobbler/snippets/ %defattr(744,root,root) %config(noreplace) /var/lib/cobbler/triggers/sync/post/restart-services.trigger +%config(noreplace) /var/lib/cobbler/triggers/install/pre/status_pre.trigger +%config(noreplace) /var/lib/cobbler/triggers/install/post/status_post.trigger %defattr(664,root,root) %config(noreplace) /var/lib/cobbler/settings diff --git a/cobbler/action_status.py b/cobbler/action_status.py index 7366060..703bf41 100644 --- a/cobbler/action_status.py +++ b/cobbler/action_status.py @@ -100,6 +100,11 @@ class BootStatusReport: tracking will be incomplete. This should be noted in the docs. """ + + print "NOTE: this function is being replaced right now. Stay tuned!" + return 0 + + api = cobbler_api.BootAPI() apache_results = self.scan_apache_logfiles() diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 1e65e42..5b7b546 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -376,7 +376,8 @@ class BootSync: meta.update(ksmeta) # make available at top level meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True) meta["yum_config_stanza"] = self.generate_config_stanza(g,True) - meta["kickstart_done"] = self.generate_kickstart_signal(g, None) + meta["kickstart_done"] = self.generate_kickstart_signal(0, g, None) + meta["kickstart_start"] = self.generate_kickstart_signal(1, g, None) meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) kfile = open(kickstart_path) self.templar.render(kfile, meta, dest, g) @@ -386,40 +387,56 @@ class BootSync: msg = "err_kickstart2" raise CX(_("Error while rendering kickstart file %(src)s to %(dest)s") % { "src" : kickstart_path, "dest" : dest }) - def generate_kickstart_signal(self, profile, system=None): + def generate_kickstart_signal(self, is_pre=0, profile=None, system=None): """ - Do things that we do at the end of kickstarts... - * signal the status watcher we're done - * disable PXE if needed - * save the original kickstart file for debug + Do things that we do at the start/end of kickstarts... + * start: signal the status watcher we're starting + * end: signal the status watcher we're done + * end: disable PXE if needed + * end: save the original kickstart file for debug """ # FIXME: watcher is more of a request than a packaged file # we should eventually package something and let it do something important" - pattern1 = "wget \"http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s\"" - pattern2 = "wget \"http://%s/cobbler/%s/%s/ks.cfg\" -O /root/cobbler.ks" - pattern3 = "wget \"http://%s/cgi-bin/cobbler/post_install_trigger.cgi?system=%s\"" + + nopxe = "\nwget \"http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s\"" + saveks = "\nwget \"http://%s/cobbler/%s/%s/ks.cfg\" -O /root/cobbler.ks" + runpost = "\nwget \"http://%s/cgi-bin/cobbler/install_trigger.cgi?mode=post&%s=%s\"" + runpre = "\nwget \"http://%s/cgi-bin/cobbler/install_trigger.cgi?mode=pre&%s=%s\"" + what = "profile" blend_this = profile if system: + what = "system" blend_this = system blended = utils.blender(self.api, False, blend_this) kickstart = blended.get("kickstart",None) buf = "" + srv = blended["http_server"] if system is not None: - if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]: - buf = buf + "\n" + pattern1 % (blended["http_server"], system.name) - if kickstart and os.path.exists(kickstart): - buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts_sys", system.name) - if self.settings.run_post_install_trigger: - buf = buf + "\n" + pattern3 % (blended["http_server"], system.name) + if not is_pre: + if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]: + buf = buf + nopxe % (srv, system.name) + if kickstart and os.path.exists(kickstart): + buf = buf + saveks % (srv, "kickstarts_sys", system.name) + if self.settings.run_install_trigger: + buf = buf + runpost % (srv, what, system.name) + else: + if self.settings.run_install_trigger: + buf = buf + runpre % (srv, what, system.name) else: - if kickstart and os.path.exists(kickstart): - buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts", profile.name) - + if not is_pre: + if kickstart and os.path.exists(kickstart): + buf = buf + saveks % (srv, "kickstarts", profile.name) + if self.settings.run_install_trigger: + buf = buf + runpost % (srv, what, profile.name) + else: + if self.settings.run_install_trigger: + buf = buf + runpre % (srv, what, profile.name) + return buf def get_repo_segname(self, is_profile): @@ -560,7 +577,8 @@ class BootSync: meta.update(ksmeta) # make available at top level meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False) meta["yum_config_stanza"] = self.generate_config_stanza(s, False) - meta["kickstart_done"] = self.generate_kickstart_signal(profile, s) + meta["kickstart_done"] = self.generate_kickstart_signal(0, profile, s) + meta["kickstart_start"] = self.generate_kickstart_signal(1, profile, s) meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) kfile = open(kickstart_path) self.templar.render(kfile, meta, dest, s) diff --git a/cobbler/remote.py b/cobbler/remote.py index f7e2226..b606bbc 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -230,26 +230,28 @@ class CobblerXMLRPCInterface: systems.add(obj,save=True,with_triggers=False,with_sync=False,quick_pxe_update=True) return True - def run_post_install_triggers(self,name,token=None): + def run_install_triggers(self,mode,objtype,name,mac,ip,token=None): + """ - This is a feature used to run the post install trigger. - It passes the system named "name" to the trigger. Disabled by default as - this requires public API access and is technically a read-write operation. + This is a feature used to run the pre/post install triggers. + See CobblerTriggers on Wiki for details """ - self.log("run_post_install_triggers",token=token) - # used by postinstalltrigger.cgi - self.api.clear() - self.api.deserialize() - if not self.api.settings().run_post_install_trigger: - # feature disabled! + self.log("run_install_triggers",token=token) + + if mode != "pre" and mode != "post": return False - systems = self.api.systems() - obj = systems.find(name=name) - if obj == None: - # system not found! + if objtype != "system" and objtype !="profile": return False - utils.run_triggers(obj, "/var/lib/cobbler/triggers/install/post/*") + + # the trigger script is called with name,mac, and ip as arguments 1,2, and 3 + # we do not do API lookups here because they are rather expensive at install + # time if reinstalling all of a cluster all at once. + # we can do that at "cobbler check" time. + + utils.run_triggers(None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,mac,ip]) + + return True def _refresh(self): diff --git a/cobbler/settings.py b/cobbler/settings.py index bc16835..9066d7d 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -61,7 +61,7 @@ DEFAULTS = { "manage_dhcp_mode" : "isc", "next_server" : "127.0.0.1", "pxe_just_once" : 0, - "run_post_install_trigger" : 0, + "run_install_trigger" : 1, "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "syslog_port" : 25150, diff --git a/cobbler/utils.py b/cobbler/utils.py index 8a09025..0f09345 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -430,7 +430,7 @@ def hash_to_string(hash): buffer = buffer + str(key) + "=" + str(value) + " " return buffer -def run_triggers(ref,globber): +def run_triggers(ref,globber,additional=[]): """ Runs all the trigger scripts in a given directory. ref can be a cobbler object, if not None, the name will be passed @@ -447,10 +447,12 @@ def run_triggers(ref,globber): # skip .rpmnew files that may have been installed # in the triggers directory continue + arglist = [ file ] if ref: - rc = sub_process.call([file,ref.name], shell=False) - else: - rc = sub_process.call([file], shell=False) + arglist.append(ref.name) + for x in additional: + arglist.append(x) + rc = sub_process.call(arglist, shell=False) except: print _("Warning: failed to execute trigger: %s" % file) continue diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate index 0e4bcbf..c7a8b19 100644 --- a/config/cobblerd_rotate +++ b/config/cobblerd_rotate @@ -21,3 +21,10 @@ fi endscript } + +/var/log/cobbler/install.log { + missingok + notifempty + rotate 4 + weekly +} diff --git a/config/settings b/config/settings index 8fc0fdf..33eb4b1 100644 --- a/config/settings +++ b/config/settings @@ -35,7 +35,7 @@ manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' pxe_just_once: 0 -run_post_install_trigger: 0 +run_install_trigger: 1 server: '127.0.0.1' snippetsdir: /var/lib/cobbler/snippets syslog_port: 25150 diff --git a/kickstarts/sample.ks b/kickstarts/sample.ks index 5208ed7..1a7b731 100644 --- a/kickstarts/sample.ks +++ b/kickstarts/sample.ks @@ -39,6 +39,7 @@ zerombr # Magically figure out how to partition this thing SNIPPET::partition_select +$kickstart_start %packages diff --git a/scripts/install_trigger.cgi b/scripts/install_trigger.cgi new file mode 100644 index 0000000..493591f --- /dev/null +++ b/scripts/install_trigger.cgi @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# This script runs post install triggers in /var/lib/cobbler/triggers/install/post +# if the triggers are enabled in the settings file. +# +# (C) Tim Verhoeven , 2007 +# tweaked: Michael DeHaan , 2007-2008 + +import cgi +import cgitb +import time +import os +import sys +import socket +import xmlrpclib +from cobbler import sub_process as sub_process + +COBBLER_BASE = "/var/www/cobbler" +XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" + +#---------------------------------------------------------------------- + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, url=None): + xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) + +#---------------------------------------------------------------------- + +def parse_query(): + """ + Read arguments from query string. + """ + + form = cgi.parse() + + mac = "?" + if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): + devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] + mac = devicepair.split()[1].strip() + + ip = "?" + if os.environ.has_key("REMOTE_ADDR"): + ip = os.environ["REMOTE_ADDR"] + + name = "?" + objtype = "?" + if form.has_key("system"): + name = form["system"][0] + objtype = "system" + elif form.has_key("profile"): + name = form["profile"][0] + objtype = "profile" + + mode = "?" + if form.has_key("mode"): + mode = form["mode"][0] + + return (mode,objtype,name,mac,ip) + +def invoke(mode,objtype,name,mac,ip): + """ + Determine if this feature is enabled. + """ + + xmlrpc_server = ServerProxy(XMLRPC_SERVER) + print xmlrpc_server.run_install_triggers(mode,objtype,name,mac,ip) + + return True + +#---------------------------------------------------------------------- + +def header(): + print "Content-type: text/plain" + print + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + cgitb.enable(format='text') + header() + (mode,objtype,name,mac,ip) = parse_query() + invoke(mode,objtype,name,mac,ip) + + diff --git a/scripts/post_install_trigger.cgi b/scripts/post_install_trigger.cgi deleted file mode 100644 index 4a79c8b..0000000 --- a/scripts/post_install_trigger.cgi +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python - -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. -# -# This script runs post install triggers in /var/lib/cobbler/triggers/install/post -# if the triggers are enabled in the settings file. -# -# (C) Tim Verhoeven , 2007 -# tweaked: Michael DeHaan - -import cgi -import cgitb -import time -import os -import sys -import socket -import xmlrpclib -from cobbler import sub_process as sub_process - -COBBLER_BASE = "/var/www/cobbler" -XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" - -#---------------------------------------------------------------------- - -class ServerProxy(xmlrpclib.ServerProxy): - - def __init__(self, url=None): - xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) - -#---------------------------------------------------------------------- - -def parse_query(): - """ - Read arguments from query string. - """ - - form = cgi.parse() - - if form.has_key("system"): - return form["system"][0] - return 0 - -def invoke(name): - """ - Determine if this feature is enabled. - """ - - xmlrpc_server = ServerProxy(XMLRPC_SERVER) - print xmlrpc_server.run_post_install_triggers(name) - - return True - -#---------------------------------------------------------------------- - -def header(): - print "Content-type: text/plain" - print - -#---------------------------------------------------------------------- - -if __name__ == "__main__": - cgitb.enable(format='text') - header() - name = parse_query() - invoke(name) - - diff --git a/setup.py b/setup.py index 6d4a1cd..c8a6c99 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ if __name__ == "__main__": (modpython, ['scripts/index.py']), # cgi files (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']), - (cgipath, ['scripts/post_install_trigger.cgi']), + (cgipath, ['scripts/install_trigger.cgi']), # miscellaneous config files (rotpath, ['config/cobblerd_rotate']), @@ -192,7 +192,8 @@ if __name__ == "__main__": ("%sdelete/repo/pre" % trigpath, []), ("%sdelete/repo/post" % trigpath, []), ("%sdelete/repo/post" % trigpath, []), - ("%sinstall/post" % trigpath, []), + ("%sinstall/pre" % trigpath, [ "triggers/status_pre.trigger"]), + ("%sinstall/post" % trigpath, [ "triggers/status_post.trigger"]), ("%ssync/pre" % trigpath, []), ("%ssync/post" % trigpath, [ "triggers/restart-services.trigger" ]) ], diff --git a/triggers/status_post.trigger b/triggers/status_post.trigger new file mode 100644 index 0000000..32934c8 --- /dev/null +++ b/triggers/status_post.trigger @@ -0,0 +1,16 @@ +#!/usr/bin/python + +import os +import sys +import time + +objtype = sys.argv[1] # "system" or "profile" +name = sys.argv[2] # name of system or profile +mac = sys.argv[3] # mac or "?" +ip = sys.argv[4] # ip or "?" + +fd = open("/var/log/cobbler/install.log","a+") +fd.write("%s\t%s\t%s\t%s\tstop\t%s\n" % (objtype,name,mac,ip,time.time())) +fd.close() + +sys.exit(0) diff --git a/triggers/status_pre.trigger b/triggers/status_pre.trigger new file mode 100644 index 0000000..385beaa --- /dev/null +++ b/triggers/status_pre.trigger @@ -0,0 +1,16 @@ +#!/usr/bin/python + +import os +import sys +import time + +objtype = sys.argv[1] # "system" or "profile" +name = sys.argv[2] # name of system or profile +mac = sys.argv[3] # mac or "?" +ip = sys.argv[4] # ip or "?" + +fd = open("/var/log/cobbler/install.log","a+") +fd.write("%s\t%s\t%s\t%s\tstart\t%s\n" % (objtype,name,mac,ip,time.time())) +fd.close() + +sys.exit(0) -- cgit From 2eabc9130522acc1e1de034ec062338572076a02 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 15 Apr 2008 18:01:27 -0400 Subject: Add missing file --- kickstarts/sample_end.ks | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 kickstarts/sample_end.ks diff --git a/kickstarts/sample_end.ks b/kickstarts/sample_end.ks new file mode 100644 index 0000000..b856a3d --- /dev/null +++ b/kickstarts/sample_end.ks @@ -0,0 +1,55 @@ +# kickstart template for Fedora 8 and later. +# (includes %end blocks) +# do not use with earlier distros + +#platform=x86, AMD64, or Intel EM64T +# System authorization information +auth --useshadow --enablemd5 +# System bootloader configuration +bootloader --location=mbr +# Partition clearing information +clearpart --all --initlabel +# Use text mode install +text +# Firewall configuration +firewall --enabled +# Run the Setup Agent on first boot +firstboot --disable +# System keyboard +keyboard us +# System language +lang en_US +# Use network installation +url --url=$tree +# If any cobbler repo definitions were referenced in the kickstart profile, include them here. +$yum_repo_stanza +# Network information +network --bootproto=dhcp --device=eth0 --onboot=on +# Reboot after installation +reboot + +#Root password +rootpw --iscrypted \$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac. +# SELinux configuration +selinux --disabled +# Do not configure the X Window System +skipx +# System timezone +timezone America/New_York +# Install OS instead of upgrade +install +# Clear the Master Boot Record +zerombr + +# Magically figure out how to partition this thing +SNIPPET::partition_select +$kickstart_start + + +%packages +%end + +%post +$yum_config_stanza +$kickstart_done +%end -- cgit From 98e7ccdd019dd6b2fe6eb7325a681cb132a36741 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 15 Apr 2008 18:21:08 -0400 Subject: logrotate should not send emails to root when restarting services. --- CHANGELOG | 1 + config/cobblerd_rotate | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d1fb79d..d48e3d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ Cobbler CHANGELOG - provide a different default sample kickstart for imports of F8 and later - support for kerberos authentication - revamped pre/post install triggers system (triggered via cgi from kickstart wget) +- logrotate should not send emails to root when restarting services - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate index c7a8b19..e739c47 100644 --- a/config/cobblerd_rotate +++ b/config/cobblerd_rotate @@ -5,7 +5,7 @@ weekly postrotate if [ -f /var/lock/subsys/cobblerd ]; then - /etc/init.d/cobblerd condrestart + /etc/init.d/cobblerd condrestart > /dev/null fi endscript } @@ -17,7 +17,7 @@ weekly postrotate if [ -f /var/lock/subsys/cobblerd ]; then - /etc/init.d/cobblerd condrestart + /etc/init.d/cobblerd condrestart > /dev/null fi endscript } -- cgit From c70084fdd316b4ee8e4cd80a4ab8d66a7655043c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 15 Apr 2008 18:26:19 -0400 Subject: Fix manifest.in to add missing file, remove one file no longer used. --- MANIFEST.in | 3 +-- cobbler/webui/master.py | 38 ++++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index c553c03..12f9f9e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -22,8 +22,7 @@ include scripts/index.py include scripts/cobblerd include scripts/findks.cgi include scripts/nopxe.cgi -include scripts/gateway.py -include scripts/post_install_trigger.cgi +include scripts/install_trigger.cgi include snippets/* recursive-include po *.pot recursive-include po *.po diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 3e0c2fb..1e6d30d 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -31,12 +31,12 @@ VFFSL=valueFromFrameOrSearchList VFSL=valueFromSearchList VFN=valueForName currentTime=time.time -__CHEETAH_version__ = '2.0rc7' -__CHEETAH_versionTuple__ = (2, 0, 0, 'candidate', 7) -__CHEETAH_genTime__ = 1207768044.4095509 -__CHEETAH_genTimestamp__ = 'Wed Apr 9 21:07:24 2008' +__CHEETAH_version__ = '2.0.1' +__CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) +__CHEETAH_genTime__ = 1208298360.4598751 +__CHEETAH_genTimestamp__ = 'Tue Apr 15 18:26:00 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' -__CHEETAH_srcLastModified__ = 'Wed Apr 9 21:03:21 2008' +__CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: @@ -158,16 +158,20 @@ class master(Template):
  • Distros
  • \n
  • Distros
  • +
  • Profiles
  • \n
  • Profiles
  • +
  • Systems
  • \n
  • Systems
  • +
  • Kickstarts
  • \n
  • Kickstarts
  • +
  • Repos
  • @@ -176,19 +180,25 @@ class master(Template):
  • Distro
  • \n
  • Distro
  • +
  • Profile
  • \n
  • Profile
  • +
  • Subprofile
  • \n
  • Subprofile
  • +
  • System
  • \n
  • System
  • +
  • Repo
  • \n


  • \n
  • Repo
  • +


  • +
  • Sync
  • -- cgit From 53a87f698e38627c95e1e5e0f99a3b2f9f55ff13 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 16 Apr 2008 12:17:32 -0400 Subject: To make things easier to understand, enable RW-xmlrpc by default, just change the authentication modes to deny_all in the config file (by default) --- CHANGELOG | 2 ++ cobbler/action_import.py | 3 +-- cobbler/modules/authn_denyall.py | 43 ++++++++++++++++++++++++++++++++++++++++ config/modules.conf | 2 +- config/settings | 2 +- 5 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 cobbler/modules/authn_denyall.py diff --git a/CHANGELOG b/CHANGELOG index d48e3d7..6fb090f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,8 @@ Cobbler CHANGELOG - support for kerberos authentication - revamped pre/post install triggers system (triggered via cgi from kickstart wget) - logrotate should not send emails to root when restarting services +- default core (but not repo add) repos to priority 1 (lowest) if using priorities plugin +- change default authentication to deny_all, xmlrpc_rw_enabled now on by default - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_import.py b/cobbler/action_import.py index 377b122..2e70c80 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -458,8 +458,7 @@ class Importer: config_file.write("baseurl=http://@@http_server@@/cobbler/ks_mirror/%s\n" % (urlseg)) config_file.write("enabled=1\n") config_file.write("gpgcheck=0\n") - # NOTE: yum priority defaults to 99 if that plugin is enabled - # so don't need to add priority=99 here + config_file.write("priority=1\n") config_file.close() # don't run creatrepo twice -- this can happen easily for Xen and PXE, when diff --git a/cobbler/modules/authn_denyall.py b/cobbler/modules/authn_denyall.py new file mode 100644 index 0000000..91e27d4 --- /dev/null +++ b/cobbler/modules/authn_denyall.py @@ -0,0 +1,43 @@ +""" +Authentication module that denies everything. +Used to disable the WebUI by default. + +Copyright 2007-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import distutils.sysconfig +import sys + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "authn" + +def authenticate(api_handle,username,password): + """ + Validate a username/password combo, returning True/False + + Thanks to http://trac.edgewall.org/ticket/845 for supplying + the algorithm info. + """ + + # debugging only (not safe to enable) + # api_handle.logger.debug("backend authenticate (%s,%s)" % (username,password)) + + return False + + diff --git a/config/modules.conf b/config/modules.conf index 2daf0e4..88aa134 100644 --- a/config/modules.conf +++ b/config/modules.conf @@ -6,7 +6,7 @@ system = serializer_yaml repo = serializer_yaml [authentication] -module = authn_configfile +module = authn_denyall [authorization] module = authz_allowall diff --git a/config/settings b/config/settings index 33eb4b1..fc6739a 100644 --- a/config/settings +++ b/config/settings @@ -43,7 +43,7 @@ tftpd_bin: /usr/sbin/in.tftpd tftpd_conf: /etc/xinetd.d/tftp webdir: /var/www/cobbler xmlrpc_port: 25151 -xmlrpc_rw_enabled: 0 +xmlrpc_rw_enabled: 1 xmlrpc_rw_port: 25152 yum_post_install_mirror: 0 yumdownloader_flags: "--resolve" -- cgit From 6c371de03a0bbd9901223b707575e0804b9786a5 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 16 Apr 2008 13:46:20 -0400 Subject: Change settings. --- cobbler/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/settings.py b/cobbler/settings.py index 9066d7d..1695cbb 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -69,7 +69,7 @@ DEFAULTS = { "tftpd_conf" : "/etc/xinetd.d/tftp", "webdir" : "/var/www/cobbler", "xmlrpc_port" : 25151, - "xmlrpc_rw_enabled" : 0, + "xmlrpc_rw_enabled" : 1, "xmlrpc_rw_port" : 25152, "yum_post_install_mirror" : 1, "yumdownloader_flags" : "--resolve" -- cgit From 30f0d9f1f5e3ebe897c0786f52e2f404cd88b737 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 16 Apr 2008 14:13:16 -0400 Subject: Fix if clause --- cobbler/action_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/action_import.py b/cobbler/action_import.py index 2e70c80..3e726a6 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -74,9 +74,9 @@ class Importer: raise CX(_("arch must be x86, x86_64, or ia64")) mpath = os.path.join(self.settings.webdir, "ks_mirror", self.mirror_name) + if os.path.exists(mpath) and self.arch is None: - if not found: - raise CX(_("Something already exists at this import location (%s). You must specify --arch to avoid potentially overwriting existing files.") % mpath) + raise CX(_("Something already exists at this import location (%s). You must specify --arch to avoid potentially overwriting existing files.") % mpath) if self.arch: # append the arch path to the name if the arch is not already -- cgit From 143f4e068dc1a7e2f5b7196bcfc9a7317bf11fcc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 16 Apr 2008 18:37:48 -0400 Subject: This is the new status engine in progress. I still need to do some things to parse out times and indicate how long installs have been active (so there is an easier way to grep for last installs). Technically this will also provide ways of looking at an install history which is probably something we could add if it was interesting. Lots of options. --- CHANGELOG | 2 + cobbler/action_status.py | 295 ++++++++++++++++---------------------------- cobbler/api.py | 4 +- cobbler/item_repo.py | 7 ++ cobbler/modules/cli_misc.py | 2 +- cobbler/utils.py | 3 - 6 files changed, 119 insertions(+), 194 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6fb090f..f721aa3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,8 @@ Cobbler CHANGELOG - logrotate should not send emails to root when restarting services - default core (but not repo add) repos to priority 1 (lowest) if using priorities plugin - change default authentication to deny_all, xmlrpc_rw_enabled now on by default +- additional fix for mod_python select box submissions +- set repo arch if found in the URL and no --arch is specified - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_status.py b/cobbler/action_status.py index 703bf41..cd76a39 100644 --- a/cobbler/action_status.py +++ b/cobbler/action_status.py @@ -2,7 +2,7 @@ Reports on kickstart activity by examining the logs in /var/log/cobbler. -Copyright 2007, Red Hat, Inc +Copyright 2007-2008, Red Hat, Inc Michael DeHaan This software may be freely redistributed under the terms of the GNU @@ -19,7 +19,7 @@ import glob import time import api as cobbler_api -from utils import _ +#from utils import _ class BootStatusReport: @@ -30,207 +30,126 @@ class BootStatusReport: """ self.config = config self.settings = config.settings() + self.ip_data = {} self.mode = mode # ------------------------------------------------------- - def scan_apache_logfiles(self): - results = {} - files = [ "/var/log/httpd/access_log" ] - for x in range(1,4): - consider = "/var/log/httpd/access_log.%s" % x - if os.path.exists(consider): - files.append(consider) - for fname in files: - fh = open(fname) - data = fh.readline() - while (data is not None and data != ""): - data = fh.readline() - tokens = data.split(None) - if len(tokens) < 6: - continue - ip = tokens[0] - stime = tokens[3].replace("[","") - req = tokens[6] - if req.find("/cblr") == -1: - continue - ttime = time.strptime(stime,"%d/%b/%Y:%H:%M:%S") - itime = time.mktime(ttime) - if not results.has_key(ip): - results[ip] = {} - results[ip][itime] = req - - return results - - # ------------------------------------------------------- - - def scan_syslog_logfiles(self): - - # find all of the logged IP addrs - filelist = glob.glob("/var/log/cobbler/syslog/*") - filelist.sort() - results = {} - - for fullname in filelist: - #fname = os.path.basename(fullname) - logfile = open(fullname, "r") - # for each line in the file... - data = logfile.readline() - while(data is not None and data != ""): - data = logfile.readline() - - try: - (epoch, strdate, ip, request) = data.split("\t", 3) - epoch = float(epoch) - except: - continue - - if not results.has_key(ip): - results[ip] = {} - results[ip][epoch] = request - - return results - - # ------------------------------------------------------- + def scan_logfiles(self): - def run(self): - """ - Calculate and print a kickstart-status report. - For kickstart trees not in /var/lib/cobbler (or a symlink off of there) - tracking will be incomplete. This should be noted in the docs. - """ + #profile foosball ? 127.0.0.1 start 1208294043.58 + #system neo ? 127.0.0.1 start 1208295122.86 - print "NOTE: this function is being replaced right now. Stay tuned!" - return 0 + files = glob.glob("/var/log/cobbler/install.log*") + for fname in files: + fd = open(fname) + data = fd.read() + for line in data.split("\n"): + tokens = line.split() + if len(tokens) == 0: + continue + (profile_or_system, name, mac, ip, start_or_stop, ts) = tokens + self.catalog(profile_or_system,name,mac,ip,start_or_stop,ts) + fd.close() + + # ------------------------------------------------------ + + def catalog(self,profile_or_system,name,mac,ip,start_or_stop,ts): + ip_data = self.ip_data + + if not ip_data.has_key(ip): + ip_data[ip] = {} + elem = ip_data[ip] + + ts = float(ts) + + if not elem.has_key("most_recent_start"): + elem["most_recent_start"] = -1 + if not elem.has_key("most_recent_stop"): + elem["most_recent_stop"] = -1 + if not elem.has_key("most_recent_target"): + elem["most_recent_target"] = "?" + if not elem.has_key("seen_start"): + elem["seen_start"] = 0 + if not elem.has_key("seen_stop"): + elem["seen_stop"] = 0 + if not elem.has_key("mac"): + elem["mac"] = "?" + + mrstart = elem["most_recent_start"] + mrstop = elem["most_recent_stop"] + mrtarg = elem["most_recent_target"] + snstart = elem["seen_start"] + snstop = elem["seen_stop"] + snmac = elem["mac"] + + + if start_or_stop == "start": + if mrstart < ts: + mrstart = ts + mrtarg = "%s:%s" % (profile_or_system, name) + snmac = mac + elem["seen_start"] = elem["seen_start"] + 1 + + if start_or_stop == "stop": + if mrstop < ts: + mrstop = ts + mrtarg = "%s:%s" % (profile_or_system, name) + snmac = mac + elem["seen_stop"] = elem["seen_stop"] + 1 + + elem["most_recent_start"] = mrstart + elem["most_recent_stop"] = mrstop + elem["most_recent_target"] = mrtarg + elem["mac"] = mac + # ------------------------------------------------------- - api = cobbler_api.BootAPI() + def process_results(self): + # FIXME: this should update the times here + print "DEBUG: %s" % self.ip_data + return self.ip_data - apache_results = self.scan_apache_logfiles() - syslog_results = self.scan_syslog_logfiles() - ips = apache_results.keys() + def get_printable_results(self): + # ip | last mac | last target | start | stop | count + format = "%-15s %-17s %-20s %-17s %-17s %5s" + ip_data = self.ip_data + ips = ip_data.keys() ips.sort() - ips2 = syslog_results.keys() - ips2.sort() - - ips.extend(ips2) - ip_printed = {} - - last_recorded_time = 0 - time_collisions = 0 - - #header = ("Name", "State", "Started", "Last Request", "Seconds", "Log Entries") - print "%-20s | %-15s | %-25s | %-25s | %-10s | %-6s" % ( - _("Name"), - _("State"), - _("Last Request"), - _("Started"), - _("Seconds"), - _("Log Entries") + line = ( + "ip", + "mac", + "target", + "start", + "stop", + "count", ) - - + print "DEBUG:", line + buf = format % line for ip in ips: - if ip_printed.has_key(ip): - continue - ip_printed[ip] = 1 - entries = {} # hash of access times and messages - if apache_results.has_key(ip): - times = apache_results[ip].keys() - for logtime in times: - request = apache_results[ip][logtime] - if request.find("?system_done") != -1: - entries[logtime] = "DONE" - elif request.find("?profile_done") != -1: - entries[logtime] = "DONE" - else: - entries[logtime] = "1" # don't really care what the filename was - - if syslog_results.has_key(ip): - times = syslog_results[ip].keys() - for logtime in times: - request = syslog_results[ip][logtime] - if request.find("methodcomplete") != -1: - entries[logtime] = "DONE" - elif request.find("Method =") != -1: - entries[logtime] = "START" - else: - entries[logtime] = "1" - - obj = api.systems().find(ip_address=ip) - - if obj is not None: - self.generate_report(entries,obj.name) - else: - self.generate_report(entries,ip) - - return True + elem = ip_data[ip] + line = ( + ip, + elem["mac"], + elem["most_recent_target"], + elem["most_recent_start"], # clean up + elem["most_recent_stop"], # clean up + elem["seen_stop"] + ) + print "DEBUG: ", line + buf = buf + "\n" + format % line + return buf - #----------------------------------------- + # ------------------------------------------------------- - def generate_report(self,entries,name): + def run(self): """ - Given the information about transferred files and kickstart finish times, attempt - to produce a report that most describes the state of the system. + Calculate and print a kickstart-status report. """ - # sort the access times - rtimes = entries.keys() - rtimes.sort() - - # variables for calculating kickstart state - last_request_time = 0 - last_start_time = 0 - last_done_time = 0 - fcount = 0 - - if len(rtimes) == 0: - print _("%s: ?") % name - return - - # for each request time the machine has made - for rtime in rtimes: - - rtime = rtime - fname = entries[rtime] - - if fname == "START": - install_state = "installing" - last_start_time = rtime - last_request_time = rtime - fcount = 0 - elif fname == "DONE": - # kickstart finished - last_done_time = rtime - install_state = "done" - else: - install_state = "?" - last_request_time = rtime - fcount = fcount + 1 - - # calculate elapsed time for kickstart - elapsed_time = 0 - if install_state == "done": - elapsed_time = int(last_done_time - last_start_time) - else: - elapsed_time = int(last_request_time - last_start_time) - - # FIXME: IP to MAC mapping where cobbler knows about it would be nice. - display_start = time.asctime(time.localtime(last_start_time)) - display_last = time.asctime(time.localtime(last_request_time)) - - if display_start.find(" 1969") != -1: - display_start = "?" - elapsed_time = "?" - - # print the status line for this IP address - print "%-20s | %-15s | %-25s | %-25s | %-10s | %-6s" % ( - name, - install_state, - display_start, - display_last, - elapsed_time, - fcount - ) - + + self.scan_logfiles() + self.process_results() + print self.get_printable_results() + return True diff --git a/cobbler/api.py b/cobbler/api.py index ef6fa8e..6f7d1b1 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -332,8 +332,8 @@ class BootAPI: return reposync.run(name) def status(self,mode): - self.log("status",[mode]) - statusifier = action_status.BootStatusReport(self._config, mode) + self.log("status") + statusifier = action_status.BootStatusReport(self._config,mode) return statusifier.run() def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None): diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index f9e9714..b87528d 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -66,6 +66,13 @@ class Repo(item.Item): reposync/repotrack integration over HTTP might come later. """ self.mirror = mirror + if self.arch is None or self.arch == "": + if mirror.find("x86_64") != -1: + self.set_arch("x86_64") + elif mirror.find("x86") != -1 or mirror.find("i386") != -1: + self.set_arch("x86") + elif mirror.find("ia64") != -1: + self.set_arch("ia64") return True def set_keep_updated(self,keep_updated): diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index f8b0a7d..af2f6b2 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -200,7 +200,7 @@ class StatusFunction(commands.CobblerFunction): return "status" def run(self): - return self.api.status("text") # no other output modes supported yet + return self.api.status("text") # no other output modes supported yet ######################################################## diff --git a/cobbler/utils.py b/cobbler/utils.py index 0f09345..7bd37ad 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -467,9 +467,6 @@ def fix_mod_python_select_submission(repos): which doesn't seem to happen on all versions of python/mp. """ - if str(repos).find("Field(") == -1: - return repos # no hack needed - # should be nice regex, but this is readable :) repos = str(repos) repos = repos.replace("'repos'","") -- cgit From 71d051435420f49f915aab8895d3ca4396c8ccc8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 17 Apr 2008 16:42:50 -0400 Subject: (A) The Python-yaml open source code that we had been using apparently didn't have copyright headers (not sure why, we absolutely positively want them there) so I'm adding them now. Similarly, I have included a copy of the license of the library in the docs directory. (B) This checkin also includes some work on the status command. --- cobbler/action_status.py | 85 ++++++++++++++++++++++++-------------------- cobbler/yaml/__init__.py | 6 ++++ cobbler/yaml/dump.py | 7 ++++ cobbler/yaml/implicit.py | 6 ++++ cobbler/yaml/inline.py | 6 ++++ cobbler/yaml/klass.py | 6 ++++ cobbler/yaml/load.py | 6 ++++ cobbler/yaml/ordered_dict.py | 7 ++++ cobbler/yaml/redump.py | 6 ++++ cobbler/yaml/stream.py | 6 ++++ cobbler/yaml/timestamp.py | 7 ++++ cobbler/yaml/ypath.py | 7 ++++ docs/pyyaml-license.htm | 78 ++++++++++++++++++++++++++++++++++++++++ scripts/install_trigger.cgi | 5 +-- 14 files changed, 198 insertions(+), 40 deletions(-) create mode 100644 docs/pyyaml-license.htm diff --git a/cobbler/action_status.py b/cobbler/action_status.py index cd76a39..df5062e 100644 --- a/cobbler/action_status.py +++ b/cobbler/action_status.py @@ -21,9 +21,18 @@ import api as cobbler_api #from utils import _ +# ARRAY INDEXES +MOST_RECENT_START = 0 +MOST_RECENT_STOP = 1 +MOST_RECENT_TARGET = 2 +SEEN_START = 3 +SEEN_STOP = 4 +MAC = 5 +STATE = 6 class BootStatusReport: + def __init__(self,config,mode): """ Constructor @@ -59,30 +68,17 @@ class BootStatusReport: ip_data = self.ip_data if not ip_data.has_key(ip): - ip_data[ip] = {} + ip_data[ip] = [ -1, -1, "?", 0, 0, "?", "?" ] elem = ip_data[ip] ts = float(ts) - - if not elem.has_key("most_recent_start"): - elem["most_recent_start"] = -1 - if not elem.has_key("most_recent_stop"): - elem["most_recent_stop"] = -1 - if not elem.has_key("most_recent_target"): - elem["most_recent_target"] = "?" - if not elem.has_key("seen_start"): - elem["seen_start"] = 0 - if not elem.has_key("seen_stop"): - elem["seen_stop"] = 0 - if not elem.has_key("mac"): - elem["mac"] = "?" - - mrstart = elem["most_recent_start"] - mrstop = elem["most_recent_stop"] - mrtarg = elem["most_recent_target"] - snstart = elem["seen_start"] - snstop = elem["seen_stop"] - snmac = elem["mac"] + + mrstart = elem[MOST_RECENT_START] + mrstop = elem[MOST_RECENT_STOP] + mrtarg = elem[MOST_RECENT_TARGET] + snstart = elem[SEEN_START] + snstop = elem[SEEN_STOP] + snmac = elem[MAC] if start_or_stop == "start": @@ -90,30 +86,47 @@ class BootStatusReport: mrstart = ts mrtarg = "%s:%s" % (profile_or_system, name) snmac = mac - elem["seen_start"] = elem["seen_start"] + 1 + elem[SEEN_START] = elem[SEEN_START] + 1 if start_or_stop == "stop": if mrstop < ts: mrstop = ts mrtarg = "%s:%s" % (profile_or_system, name) snmac = mac - elem["seen_stop"] = elem["seen_stop"] + 1 + elem[SEEN_STOP] = elem[SEEN_STOP] + 1 - elem["most_recent_start"] = mrstart - elem["most_recent_stop"] = mrstop - elem["most_recent_target"] = mrtarg - elem["mac"] = mac + elem[MOST_RECENT_START] = mrstart + elem[MOST_RECENT_STOP] = mrstop + elem[MOST_RECENT_TARGET] = mrtarg + elem[MAC] = mac # ------------------------------------------------------- def process_results(self): # FIXME: this should update the times here - print "DEBUG: %s" % self.ip_data + + tnow = int(time.time()) + for ip in self.ip_data.keys(): + elem = self.ip_data[ip] + + start = int(elem[MOST_RECENT_START]) + stop = int(elem[MOST_RECENT_STOP]) + if (stop > start): + elem[STATE] = "finished" + else: + delta = tnow - start + min = delta / 60 + sec = delta % 60 + if min > 100: + elem[STATE] = "unknown/stalled" + else: + elem[STATE] = "installing (%sm %ss)" % (min,sec) + return self.ip_data def get_printable_results(self): # ip | last mac | last target | start | stop | count - format = "%-15s %-17s %-20s %-17s %-17s %5s" + format = "%-15s|%-17s|%-20s|%-17s|%-17s" ip_data = self.ip_data ips = ip_data.keys() ips.sort() @@ -122,22 +135,18 @@ class BootStatusReport: "mac", "target", "start", - "stop", - "count", + "state", ) - print "DEBUG:", line buf = format % line for ip in ips: elem = ip_data[ip] line = ( ip, - elem["mac"], - elem["most_recent_target"], - elem["most_recent_start"], # clean up - elem["most_recent_stop"], # clean up - elem["seen_stop"] + elem[MAC], + elem[MOST_RECENT_TARGET], + time.ctime(elem[MOST_RECENT_START]), + elem[STATE] ) - print "DEBUG: ", line buf = buf + "\n" + format % line return buf diff --git a/cobbler/yaml/__init__.py b/cobbler/yaml/__init__.py index 419d1f3..bd21b40 100644 --- a/cobbler/yaml/__init__.py +++ b/cobbler/yaml/__init__.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + __version__ = "0.32" from load import loadFile, load, Parser, l from dump import dump, dumpToFile, Dumper, d diff --git a/cobbler/yaml/dump.py b/cobbler/yaml/dump.py index b8e9d79..eb34955 100644 --- a/cobbler/yaml/dump.py +++ b/cobbler/yaml/dump.py @@ -1,3 +1,10 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + + import types import string from types import StringType, UnicodeType, IntType, FloatType diff --git a/cobbler/yaml/implicit.py b/cobbler/yaml/implicit.py index 6172564..49d65e0 100644 --- a/cobbler/yaml/implicit.py +++ b/cobbler/yaml/implicit.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + import re import string from timestamp import timestamp, matchTime diff --git a/cobbler/yaml/inline.py b/cobbler/yaml/inline.py index 8e647de..d4f6439 100644 --- a/cobbler/yaml/inline.py +++ b/cobbler/yaml/inline.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + import re import string diff --git a/cobbler/yaml/klass.py b/cobbler/yaml/klass.py index edcf5a8..c182fcf 100644 --- a/cobbler/yaml/klass.py +++ b/cobbler/yaml/klass.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + import new import re diff --git a/cobbler/yaml/load.py b/cobbler/yaml/load.py index 259178d..54931d6 100644 --- a/cobbler/yaml/load.py +++ b/cobbler/yaml/load.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + import re, string from implicit import convertImplicit from inline import InlineTokenizer diff --git a/cobbler/yaml/ordered_dict.py b/cobbler/yaml/ordered_dict.py index b3788b7..5bc2e3e 100644 --- a/cobbler/yaml/ordered_dict.py +++ b/cobbler/yaml/ordered_dict.py @@ -1,3 +1,10 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + + # This is extremely crude implementation of an OrderedDict. # If you know of a better implementation, please send it to # the author Steve Howell. You can find my email via diff --git a/cobbler/yaml/redump.py b/cobbler/yaml/redump.py index 56ea958..eefd68e 100644 --- a/cobbler/yaml/redump.py +++ b/cobbler/yaml/redump.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + from ordered_dict import OrderedDict from load import Parser from dump import Dumper diff --git a/cobbler/yaml/stream.py b/cobbler/yaml/stream.py index cc78c4b..dcd65c3 100644 --- a/cobbler/yaml/stream.py +++ b/cobbler/yaml/stream.py @@ -1,3 +1,9 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + import re import string diff --git a/cobbler/yaml/timestamp.py b/cobbler/yaml/timestamp.py index abcb2e6..5c522f6 100644 --- a/cobbler/yaml/timestamp.py +++ b/cobbler/yaml/timestamp.py @@ -1,3 +1,10 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + + import time, re, string from types import ListType, TupleType diff --git a/cobbler/yaml/ypath.py b/cobbler/yaml/ypath.py index 51d9d2f..b183a23 100644 --- a/cobbler/yaml/ypath.py +++ b/cobbler/yaml/ypath.py @@ -1,3 +1,10 @@ +""" +pyyaml legacy +Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved +(see open source license information in docs/ directory) +""" + + from types import ListType, StringType, IntType, DictType, InstanceType import re from urllib import quote diff --git a/docs/pyyaml-license.htm b/docs/pyyaml-license.htm new file mode 100644 index 0000000..6993ea9 --- /dev/null +++ b/docs/pyyaml-license.htm @@ -0,0 +1,78 @@ +NOTE: the directory ".../yaml" contains the a derivative +of the PyYaml library. It follows the license below +and has been modified to disable the YAML "anchor" +behavior. + +======================================================== + +A. HISTORY OF THE SOFTWARE +========================== + +The Python library for YAML was started by Steve Howell in +February 2002. Steve is the primary author of the project, +but others have contributed. See the README for more on +the project. The term "PyYaml" refers to the entire +distribution of this library, including examples, documentation, +and test files, as well as the core implementation. + +This library is intended for general use, and the license +below protects the "open source" nature of the library. The +license does, however, allow for use of the library in +commercial applications as well, subject to the terms +and conditions listed. The license below is a minor +rewrite of the Python 2.2 license, with no substantive +differences. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PyYaml +=============================================================== + +LICENSE AGREEMENT FOR PyYaml +---------------------------- + +1. This LICENSE AGREEMENT is between Stephen S. Howell ("Author"), +and the Individual or Organization ("Licensee") accessing and +otherwise using PyYaml software in source or binary form and its +associated documentation. + +2. Subject to the terms and conditions of this License Agreement, Author +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use PyYaml +alone or in any derivative version, provided, however, that Author's +License Agreement and Author's notice of copyright, i.e., "Copyright (c) +2001 Steve Howell and Friends; All Rights Reserved" are never removed +from PyYaml, and are included in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates PyYaml or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to PyYaml. + +4. Author is making PyYaml available to Licensee on an "AS IS" +basis. Author MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, Author MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PyYaml WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. Author SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +2.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.2, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between Author and +Licensee. This License Agreement does not grant permission to use Author +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using PyYaml, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + diff --git a/scripts/install_trigger.cgi b/scripts/install_trigger.cgi index 493591f..c8d065b 100644 --- a/scripts/install_trigger.cgi +++ b/scripts/install_trigger.cgi @@ -42,8 +42,9 @@ def parse_query(): form = cgi.parse() mac = "?" - if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): - devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] + + if os.environ.has_key("X-RHN-Provisioning-MAC-0"): + devicepair = os.environ["X-RHN-Provisioning-MAC-0"] mac = devicepair.split()[1].strip() ip = "?" -- cgit From 7b33e5feeddd51f08d3b5377c0bacf8ca23a0915 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 17 Apr 2008 17:20:52 -0400 Subject: Fix install trigger back since the modifications also were not pulling in the MAC in F9. --- scripts/install_trigger.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install_trigger.cgi b/scripts/install_trigger.cgi index c8d065b..abe5cf6 100644 --- a/scripts/install_trigger.cgi +++ b/scripts/install_trigger.cgi @@ -43,8 +43,8 @@ def parse_query(): mac = "?" - if os.environ.has_key("X-RHN-Provisioning-MAC-0"): - devicepair = os.environ["X-RHN-Provisioning-MAC-0"] + if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): + devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] mac = devicepair.split()[1].strip() ip = "?" -- cgit From ad17059740c03a4e503867d4c39bc3647502287c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 11:44:57 -0400 Subject: kssendmac does not make this information available at the time of this wget --- cobbler/remote.py | 4 ++-- scripts/install_trigger.cgi | 16 +++++----------- triggers/status_post.trigger | 5 ++--- triggers/status_pre.trigger | 5 ++--- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index b606bbc..0cbaf22 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -230,7 +230,7 @@ class CobblerXMLRPCInterface: systems.add(obj,save=True,with_triggers=False,with_sync=False,quick_pxe_update=True) return True - def run_install_triggers(self,mode,objtype,name,mac,ip,token=None): + def run_install_triggers(self,mode,objtype,name,ip,token=None): """ This is a feature used to run the pre/post install triggers. @@ -249,7 +249,7 @@ class CobblerXMLRPCInterface: # time if reinstalling all of a cluster all at once. # we can do that at "cobbler check" time. - utils.run_triggers(None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,mac,ip]) + utils.run_triggers(None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,ip]) return True diff --git a/scripts/install_trigger.cgi b/scripts/install_trigger.cgi index abe5cf6..b83ff57 100644 --- a/scripts/install_trigger.cgi +++ b/scripts/install_trigger.cgi @@ -41,12 +41,6 @@ def parse_query(): form = cgi.parse() - mac = "?" - - if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): - devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] - mac = devicepair.split()[1].strip() - ip = "?" if os.environ.has_key("REMOTE_ADDR"): ip = os.environ["REMOTE_ADDR"] @@ -64,15 +58,15 @@ def parse_query(): if form.has_key("mode"): mode = form["mode"][0] - return (mode,objtype,name,mac,ip) + return (mode,objtype,name,ip) -def invoke(mode,objtype,name,mac,ip): +def invoke(mode,objtype,name,ip): """ Determine if this feature is enabled. """ xmlrpc_server = ServerProxy(XMLRPC_SERVER) - print xmlrpc_server.run_install_triggers(mode,objtype,name,mac,ip) + print xmlrpc_server.run_install_triggers(mode,objtype,name,ip) return True @@ -87,7 +81,7 @@ def header(): if __name__ == "__main__": cgitb.enable(format='text') header() - (mode,objtype,name,mac,ip) = parse_query() - invoke(mode,objtype,name,mac,ip) + (mode,objtype,name,ip) = parse_query() + invoke(mode,objtype,name,ip) diff --git a/triggers/status_post.trigger b/triggers/status_post.trigger index 32934c8..753cceb 100644 --- a/triggers/status_post.trigger +++ b/triggers/status_post.trigger @@ -6,11 +6,10 @@ import time objtype = sys.argv[1] # "system" or "profile" name = sys.argv[2] # name of system or profile -mac = sys.argv[3] # mac or "?" -ip = sys.argv[4] # ip or "?" +ip = sys.argv[3] # ip or "?" fd = open("/var/log/cobbler/install.log","a+") -fd.write("%s\t%s\t%s\t%s\tstop\t%s\n" % (objtype,name,mac,ip,time.time())) +fd.write("%s\t%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time())) fd.close() sys.exit(0) diff --git a/triggers/status_pre.trigger b/triggers/status_pre.trigger index 385beaa..8d6f6b6 100644 --- a/triggers/status_pre.trigger +++ b/triggers/status_pre.trigger @@ -6,11 +6,10 @@ import time objtype = sys.argv[1] # "system" or "profile" name = sys.argv[2] # name of system or profile -mac = sys.argv[3] # mac or "?" -ip = sys.argv[4] # ip or "?" +ip = sys.argv[3] # ip or "?" fd = open("/var/log/cobbler/install.log","a+") -fd.write("%s\t%s\t%s\t%s\tstart\t%s\n" % (objtype,name,mac,ip,time.time())) +fd.write("%s\t%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time())) fd.close() sys.exit(0) -- cgit From b15ca0fe01a01ee6792c857e766642d9d50ab760 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 12:03:40 -0400 Subject: Finish removing mac from new install tracking --- cobbler/action_status.py | 20 ++++++------------- cobbler/modules/authn_testing.py | 42 ++++++++++++++++++++++++++++++++++++++++ triggers/status_post.trigger | 2 +- triggers/status_pre.trigger | 2 +- 4 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 cobbler/modules/authn_testing.py diff --git a/cobbler/action_status.py b/cobbler/action_status.py index df5062e..79f9083 100644 --- a/cobbler/action_status.py +++ b/cobbler/action_status.py @@ -27,8 +27,7 @@ MOST_RECENT_STOP = 1 MOST_RECENT_TARGET = 2 SEEN_START = 3 SEEN_STOP = 4 -MAC = 5 -STATE = 6 +STATE = 5 class BootStatusReport: @@ -58,17 +57,17 @@ class BootStatusReport: tokens = line.split() if len(tokens) == 0: continue - (profile_or_system, name, mac, ip, start_or_stop, ts) = tokens - self.catalog(profile_or_system,name,mac,ip,start_or_stop,ts) + (profile_or_system, name, ip, start_or_stop, ts) = tokens + self.catalog(profile_or_system,name,ip,start_or_stop,ts) fd.close() # ------------------------------------------------------ - def catalog(self,profile_or_system,name,mac,ip,start_or_stop,ts): + def catalog(self,profile_or_system,name,ip,start_or_stop,ts): ip_data = self.ip_data if not ip_data.has_key(ip): - ip_data[ip] = [ -1, -1, "?", 0, 0, "?", "?" ] + ip_data[ip] = [ -1, -1, "?", 0, 0, "?" ] elem = ip_data[ip] ts = float(ts) @@ -78,27 +77,23 @@ class BootStatusReport: mrtarg = elem[MOST_RECENT_TARGET] snstart = elem[SEEN_START] snstop = elem[SEEN_STOP] - snmac = elem[MAC] if start_or_stop == "start": if mrstart < ts: mrstart = ts mrtarg = "%s:%s" % (profile_or_system, name) - snmac = mac elem[SEEN_START] = elem[SEEN_START] + 1 if start_or_stop == "stop": if mrstop < ts: mrstop = ts mrtarg = "%s:%s" % (profile_or_system, name) - snmac = mac elem[SEEN_STOP] = elem[SEEN_STOP] + 1 elem[MOST_RECENT_START] = mrstart elem[MOST_RECENT_STOP] = mrstop elem[MOST_RECENT_TARGET] = mrtarg - elem[MAC] = mac # ------------------------------------------------------- @@ -125,14 +120,12 @@ class BootStatusReport: return self.ip_data def get_printable_results(self): - # ip | last mac | last target | start | stop | count - format = "%-15s|%-17s|%-20s|%-17s|%-17s" + format = "%-15s|%-20s|%-17s|%-17s" ip_data = self.ip_data ips = ip_data.keys() ips.sort() line = ( "ip", - "mac", "target", "start", "state", @@ -142,7 +135,6 @@ class BootStatusReport: elem = ip_data[ip] line = ( ip, - elem[MAC], elem[MOST_RECENT_TARGET], time.ctime(elem[MOST_RECENT_START]), elem[STATE] diff --git a/cobbler/modules/authn_testing.py b/cobbler/modules/authn_testing.py new file mode 100644 index 0000000..cd74cdf --- /dev/null +++ b/cobbler/modules/authn_testing.py @@ -0,0 +1,42 @@ +""" +Authentication module that denies everything. +Unsafe demo. Allows anyone in with testing/testing. + +Copyright 2007-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import distutils.sysconfig +import sys + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "authn" + +def authenticate(api_handle,username,password): + """ + Validate a username/password combo, returning True/False + + Thanks to http://trac.edgewall.org/ticket/845 for supplying + the algorithm info. + """ + + if username == "testing" and password == "testing": + return True + return False + + diff --git a/triggers/status_post.trigger b/triggers/status_post.trigger index 753cceb..f69afe2 100644 --- a/triggers/status_post.trigger +++ b/triggers/status_post.trigger @@ -9,7 +9,7 @@ name = sys.argv[2] # name of system or profile ip = sys.argv[3] # ip or "?" fd = open("/var/log/cobbler/install.log","a+") -fd.write("%s\t%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time())) +fd.write("%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time())) fd.close() sys.exit(0) diff --git a/triggers/status_pre.trigger b/triggers/status_pre.trigger index 8d6f6b6..95df1fb 100644 --- a/triggers/status_pre.trigger +++ b/triggers/status_pre.trigger @@ -9,7 +9,7 @@ name = sys.argv[2] # name of system or profile ip = sys.argv[3] # ip or "?" fd = open("/var/log/cobbler/install.log","a+") -fd.write("%s\t%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time())) +fd.write("%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time())) fd.close() sys.exit(0) -- cgit From efbcc041464733e90af670a5d1dfe13e70aaa05c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 17:31:26 -0400 Subject: Kickstarts are now dynamically generated by mod_python, CGI's now fall under mod_python, kickstart templating code now moved out of sync function. --- CHANGELOG | 4 + MANIFEST.in | 7 +- Makefile | 1 + cobbler.spec | 5 +- cobbler/action_litesync.py | 13 +- cobbler/action_sync.py | 365 +------------------------------------------- cobbler/api.py | 11 +- cobbler/kickgen.py | 272 +++++++++++++++++++++++++++++++++ cobbler/remote.py | 8 + cobbler/services.py | 94 ++++++++++++ cobbler/settings.py | 5 +- cobbler/webui/master.py | 4 +- config/cobbler_svc.conf | 12 ++ config/settings | 5 +- legacy/change_profile.cgi | 86 +++++++++++ legacy/findks.cgi | 153 +++++++++++++++++++ legacy/install_trigger.cgi | 87 +++++++++++ legacy/nopxe.cgi | 80 ++++++++++ legacy/register_mac.cgi | 105 +++++++++++++ legacy/watcher.py | 63 ++++++++ scripts/change_profile.cgi | 86 ----------- scripts/findks.cgi | 153 ------------------- scripts/install_trigger.cgi | 87 ----------- scripts/nopxe.cgi | 80 ---------- scripts/register_mac.cgi | 105 ------------- scripts/services.py | 67 ++++++++ scripts/watcher.py | 63 -------- setup.py | 9 +- 28 files changed, 1067 insertions(+), 963 deletions(-) create mode 100644 cobbler/kickgen.py create mode 100644 cobbler/services.py create mode 100644 config/cobbler_svc.conf create mode 100755 legacy/change_profile.cgi create mode 100755 legacy/findks.cgi create mode 100644 legacy/install_trigger.cgi create mode 100755 legacy/nopxe.cgi create mode 100755 legacy/register_mac.cgi create mode 100755 legacy/watcher.py delete mode 100755 scripts/change_profile.cgi delete mode 100755 scripts/findks.cgi delete mode 100644 scripts/install_trigger.cgi delete mode 100755 scripts/nopxe.cgi delete mode 100755 scripts/register_mac.cgi create mode 100755 scripts/services.py delete mode 100755 scripts/watcher.py diff --git a/CHANGELOG b/CHANGELOG index f721aa3..a97dd5e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,10 @@ Cobbler CHANGELOG - change default authentication to deny_all, xmlrpc_rw_enabled now on by default - additional fix for mod_python select box submissions - set repo arch if found in the URL and no --arch is specified +- CGI scripts have been moved under mod_python for speed/consolidation +- kickstart templates are now evaluated dynamically +- optional MAC registration is now built-in to requesting kickstarts +- legacy static file generation from /var/www/cobbler removed - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/MANIFEST.in b/MANIFEST.in index 12f9f9e..644c0c2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,12 +17,7 @@ include docs/cobbler.1.gz include docs/cobbler.html include docs/wui.html include COPYING AUTHORS README CHANGELOG -include scripts/watcher.py -include scripts/index.py -include scripts/cobblerd -include scripts/findks.cgi -include scripts/nopxe.cgi -include scripts/install_trigger.cgi +include scripts/*.py include snippets/* recursive-include po *.pot recursive-include po *.po diff --git a/Makefile b/Makefile index b8e5d0a..6287f5d 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ devinstall: chown -R apache /var/www/cobbler chown -R apache /var/www/cgi-bin/cobbler chmod -R +x /var/www/cobbler/web + chmod -R +x /var/www/cobbler/svc webtest: devinstall /sbin/service cobblerd restart diff --git a/cobbler.spec b/cobbler.spec index 5e63f48..7b4e525 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -83,8 +83,8 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %defattr(755,apache,apache) %dir /var/www/cobbler/web/ /var/www/cobbler/web/*.py* -%dir /var/www/cgi-bin/cobbler/ -/var/www/cgi-bin/cobbler/*.cgi +%dir /var/www/cobbler/svc/ +/var/www/cobbler/svc/*.py* %defattr(755,apache,apache) %dir /usr/share/cobbler/webui_templates @@ -195,6 +195,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT * Tue Apr 08 2008 Michael DeHaan - 0.9.0-1 - Upstream changes (see CHANGELOG) - packaged /etc/cobbler/users.conf +- remaining CGI replaced with mod_python * Tue Apr 08 2008 Michael DeHaan - 0.8.3-2 - Upstream changes (see CHANGELOG) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index bc7ffb2..3a2de8e 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -82,11 +82,7 @@ class BootLiteSync: if profile is None: raise CX(_("error in profile lookup")) # rebuild profile_list YAML file in webdir - self.sync.write_listings() - # add profiles/$name YAML file in webdir self.sync.write_profile_file(profile) - # generate kickstart for kickstarts/$name/ks.cfg in webdir - self.sync.validate_kickstart_for_specific_profile(profile) # rebuild the yum configuration files for any attached repos self.sync.retemplate_yum_repos(profile,True) # cascade sync @@ -98,8 +94,6 @@ class BootLiteSync: self.add_single_system(k.name) def remove_single_profile(self, name): - # rebuild profile_list YAML file in webdir - self.sync.write_listings() # delete profiles/$name file in webdir utils.rmfile(os.path.join(self.settings.webdir, "profiles", name)) # delete contents on kickstarts/$name directory in webdir @@ -109,7 +103,7 @@ class BootLiteSync: system = self.systems.find(name=name) if system is None: raise CX(_("error in system lookup for %s") % name) - self.sync.write_all_system_files(system,True) + self.sync.write_all_system_files(system) def add_single_system(self, name): # get the system object: @@ -119,19 +113,14 @@ class BootLiteSync: # rebuild system_list file in webdir self.sync.regen_ethers() # /etc/ethers, for dnsmasq & rarpd self.sync.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq - self.sync.write_listings() # write the PXE and YAML files for the system self.sync.write_all_system_files(system) # per system kickstarts - self.sync.validate_kickstart_for_specific_system(system) - # rebuild the yum configuration files for any attached repos self.sync.retemplate_yum_repos(system,False) def remove_single_system(self, name): bootloc = utils.tftpboot_location() system_record = self.systems.find(name=name) - # rebuild system_list file in webdir - self.sync.write_listings() # delete system YAML file in systems/$name in webdir utils.rmfile(os.path.join(self.settings.webdir, "systems", name)) # delete contents of kickstarts_sys/$name in webdir diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 5b7b546..aa53d12 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -27,7 +27,7 @@ import errno import utils from cexceptions import * import templar - +import kickgen import item_distro import item_profile @@ -57,6 +57,7 @@ class BootSync: self.settings = config.settings() self.repos = config.repos() self.templar = templar.Templar(config) + self.kickgen = kickgen.KickGen(config) self.bootloc = utils.tftpboot_location() def run(self): @@ -82,9 +83,9 @@ class BootSync: self.clean_trees() self.copy_bootloaders() self.copy_distros() + for x in self.systems: + self.write_all_system_files(x) self.retemplate_all_yum_repos() - self.validate_kickstarts() - self.build_trees() if self.settings.manage_dhcp: # these functions DRT for ISC or dnsmasq self.write_dhcp_file() @@ -278,7 +279,7 @@ class BootSync: if not x.endswith(".py"): utils.rmfile(path) if os.path.isdir(path): - if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links","repo_profile","repo_system"] : + if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc"] : # delete directories that shouldn't exist utils.rmtree(path) if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]: @@ -324,294 +325,6 @@ class BootSync: else: utils.copyfile(initrd, os.path.join(distro_dir, b_initrd)) - def validate_kickstarts(self): - """ - Similar to what we do for distros, ensure all the kickstarts - in conf file are valid. kickstarts are referenced by URL - (http or ftp), can stay as is. kickstarts referenced by absolute - path (i.e. are files path) will be mirrored over http. - """ - - self.validate_kickstarts_per_profile() - self.validate_kickstarts_per_system() - return True - - def validate_kickstarts_per_profile(self): - """ - Koan provisioning (Virt + auto-ks) needs kickstarts - per profile. Validate them as needed. Local kickstarts - get template substitution. Since http:// kickstarts might - get generated via magic URLs, those are *not* substituted. - NFS kickstarts are also not substituted when referenced - by NFS URL's as we don't copy those files over to the cobbler - directories. They are supposed to be live such that an - admin can update those without needing to run 'sync' again. - - NOTE: kickstart only uses the web directory (if it uses them at all) - """ - - for g in self.profiles: - print _("sync profile: %s") % g.name - self.validate_kickstart_for_specific_profile(g) - - def validate_kickstart_for_specific_profile(self,g): - distro = g.get_conceptual_parent() - meta = utils.blender(self.api, False, g) - if distro is None: - raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro }) - kickstart_path = utils.find_kickstart(meta["kickstart"]) - if kickstart_path is not None and os.path.exists(kickstart_path): - # the input is an *actual* file, hence we have to copy it - copy_path = os.path.join( - self.settings.webdir, - "kickstarts", # profile kickstarts go here - g.name - ) - utils.mkdir(copy_path) - dest = os.path.join(copy_path, "ks.cfg") - try: - meta = utils.blender(self.api, False, g) - ksmeta = meta["ks_meta"] - del meta["ks_meta"] - meta.update(ksmeta) # make available at top level - meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True) - meta["yum_config_stanza"] = self.generate_config_stanza(g,True) - meta["kickstart_done"] = self.generate_kickstart_signal(0, g, None) - meta["kickstart_start"] = self.generate_kickstart_signal(1, g, None) - meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) - kfile = open(kickstart_path) - self.templar.render(kfile, meta, dest, g) - kfile.close() - except: - traceback.print_exc() # leave this in, for now... - msg = "err_kickstart2" - raise CX(_("Error while rendering kickstart file %(src)s to %(dest)s") % { "src" : kickstart_path, "dest" : dest }) - - def generate_kickstart_signal(self, is_pre=0, profile=None, system=None): - """ - Do things that we do at the start/end of kickstarts... - * start: signal the status watcher we're starting - * end: signal the status watcher we're done - * end: disable PXE if needed - * end: save the original kickstart file for debug - """ - - # FIXME: watcher is more of a request than a packaged file - # we should eventually package something and let it do something important" - - nopxe = "\nwget \"http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s\"" - saveks = "\nwget \"http://%s/cobbler/%s/%s/ks.cfg\" -O /root/cobbler.ks" - runpost = "\nwget \"http://%s/cgi-bin/cobbler/install_trigger.cgi?mode=post&%s=%s\"" - runpre = "\nwget \"http://%s/cgi-bin/cobbler/install_trigger.cgi?mode=pre&%s=%s\"" - - what = "profile" - blend_this = profile - if system: - what = "system" - blend_this = system - - blended = utils.blender(self.api, False, blend_this) - kickstart = blended.get("kickstart",None) - - buf = "" - srv = blended["http_server"] - if system is not None: - if not is_pre: - if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]: - buf = buf + nopxe % (srv, system.name) - if kickstart and os.path.exists(kickstart): - buf = buf + saveks % (srv, "kickstarts_sys", system.name) - if self.settings.run_install_trigger: - buf = buf + runpost % (srv, what, system.name) - else: - if self.settings.run_install_trigger: - buf = buf + runpre % (srv, what, system.name) - - else: - if not is_pre: - if kickstart and os.path.exists(kickstart): - buf = buf + saveks % (srv, "kickstarts", profile.name) - if self.settings.run_install_trigger: - buf = buf + runpost % (srv, what, profile.name) - else: - if self.settings.run_install_trigger: - buf = buf + runpre % (srv, what, profile.name) - - return buf - - def get_repo_segname(self, is_profile): - if is_profile: - return "repos_profile" - else: - return "repos_system" - - def generate_repo_stanza(self, obj, is_profile=True): - - """ - Automatically attaches yum repos to profiles/systems in kickstart files - that contain the magic $yum_repo_stanza variable. - """ - - buf = "" - blended = utils.blender(self.api, False, obj) - configs = self.get_repo_filenames(obj,is_profile) - repos = self.repos - - for c in configs: - name = c.split("/")[-1].replace(".repo","") - (is_core, baseurl) = self.analyze_repo_config(c) - for repo in repos: - if repo.name == name: - if not repo.yumopts.has_key('enabled') or repo.yumopts['enabled'] == '1': - buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl) - return buf - - def analyze_repo_config(self, filename): - fd = open(filename) - data = fd.read() - lines = data.split("\n") - ret = False - baseurl = None - for line in lines: - if line.find("ks_mirror") != -1: - ret = True - if line.find("baseurl") != -1: - first, baseurl = line.split("=") - fd.close() - return (ret, baseurl) - - def get_repo_baseurl(self, server, repo_name, is_repo_mirror=True): - """ - Construct the URL to a repo definition. - """ - if is_repo_mirror: - return "http://%s/cobbler/repo_mirror/%s" % (server, repo_name) - else: - return "http://%s/cobbler/ks_mirror/config/%s" % (server, repo_name) - - def get_repo_filenames(self, obj, is_profile=True): - """ - For a given object, return the paths to repo configuration templates - that will be used to generate per-object repo configuration files and - baseurls - """ - - blended = utils.blender(self.api, False, obj) - urlseg = self.get_repo_segname(is_profile) - - topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"]) - files = glob.glob(topdir) - return files - - - def generate_config_stanza(self, obj, is_profile=True): - - """ - Add in automatic to configure /etc/yum.repos.d on the remote system - if the kickstart file contains the magic $yum_config_stanza. - """ - - if not self.settings.yum_post_install_mirror: - return "" - - urlseg = self.get_repo_segname(is_profile) - - distro = obj.get_conceptual_parent() - if not is_profile: - distro = distro.get_conceptual_parent() - - blended = utils.blender(self.api, False, obj) - configs = self.get_repo_filenames(obj, is_profile) - buf = "" - - # for each kickstart template we have rendered ... - for c in configs: - - name = c.split("/")[-1].replace(".repo","") - # add the line to create the yum config file on the target box - conf = self.get_repo_config_file(blended["http_server"],urlseg,blended["name"],name) - buf = buf + "wget \"%s\" --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name) - - return buf - - def get_repo_config_file(self,server,urlseg,obj_name,repo_name): - """ - Construct the URL to a repo config file that is usable in kickstart - for use with yum. This is different than the templates cobbler reposync - creates, as this file will allow the server to migrate and have different - variables for different subnets/profiles/etc. - """ - return "http://%s/cblr/%s/%s/%s.repo" % (server,urlseg,obj_name,repo_name) - - def validate_kickstarts_per_system(self): - """ - PXE provisioning needs kickstarts evaluated per system. - Profiles would normally be sufficient, but not in cases - such as static IP, where we want to be able to do templating - on a system basis. - - NOTE: kickstart only uses the web directory (if it uses them at all) - """ - - for s in self.systems: - print _("sync system: %s") % s.name - self.validate_kickstart_for_specific_system(s) - - def validate_kickstart_for_specific_system(self,s): - profile = s.get_conceptual_parent() - if profile is None: - raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile }) - distro = profile.get_conceptual_parent() - meta = utils.blender(self.api, False, s) - kickstart_path = utils.find_kickstart(meta["kickstart"]) - if kickstart_path and os.path.exists(kickstart_path): - copy_path = os.path.join(self.settings.webdir, - "kickstarts_sys", # system kickstarts go here - s.name - ) - utils.mkdir(copy_path) - dest = os.path.join(copy_path, "ks.cfg") - try: - ksmeta = meta["ks_meta"] - del meta["ks_meta"] - meta.update(ksmeta) # make available at top level - meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False) - meta["yum_config_stanza"] = self.generate_config_stanza(s, False) - meta["kickstart_done"] = self.generate_kickstart_signal(0, profile, s) - meta["kickstart_start"] = self.generate_kickstart_signal(1, profile, s) - meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) - kfile = open(kickstart_path) - self.templar.render(kfile, meta, dest, s) - kfile.close() - except: - traceback.print_exc() - raise CX(_("Error templating file %(src)s to %(dest)s") % { "src" : meta["kickstart"], "dest" : dest }) - - def build_trees(self): - """ - Now that kernels and initrds are copied and kickstarts are all valid, - build the pxelinux.cfg tree, which contains a directory for each - configured IP or MAC address. Also build a tree for Virt info. - - NOTE: some info needs to go in TFTP and HTTP directories, but not all. - Usually it's just one or the other. - - """ - - self.write_listings() - - # create pxelinux.cfg under tftpboot - # and file for each MAC or IP (hex encoded 01-XX-XX-XX-XX-XX-XX) - - for d in self.distros: - self.write_distro_file(d) - - for p in self.profiles: - self.write_profile_file(p) - - for system in self.systems: - self.write_all_system_files(system) - def retemplate_all_yum_repos(self): for p in self.profiles: self.retemplate_yum_repos(p,True) @@ -619,8 +332,6 @@ class BootSync: self.retemplate_yum_repos(system,False) def retemplate_yum_repos(self,obj,is_profile): - # FIXME: blender could use caching for performance - # FIXME: make stanza generation code load stuff from the right place """ Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo and also potentially in listed in the source_repos structure of the distro object, however @@ -670,7 +381,7 @@ class BootSync: self.templar.render(infile_data, blended, outfile, None) - def write_all_system_files(self,system,just_edit_pxe=False): + def write_all_system_files(self,system): profile = system.get_conceptual_parent() if profile is None: @@ -713,11 +424,6 @@ class BootSync: # ensure the file doesn't exist utils.rmfile(f2) - if not just_edit_pxe: - # allows netboot-disable to be highly performant - # by not invoking the Cheetah engine - self.write_system_file(f3,system) - counter = counter + 1 @@ -849,9 +555,9 @@ class BootSync: if kickstart_path is not None and kickstart_path != "": if system is not None and kickstart_path.startswith("/"): - kickstart_path = "http://%s/cblr/kickstarts_sys/%s/ks.cfg" % (blended["http_server"], system.name) + kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: - kickstart_path = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name) + kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) if distro.breed is None or distro.breed == "redhat": append_line = "%s ks=%s" % (append_line, kickstart_path) @@ -884,60 +590,5 @@ class BootSync: return buffer - def write_listings(self): - """ - Creates a very simple index of available systems and profiles - that cobbler knows about. Just the names, no details. - """ - names1 = [x.name for x in self.profiles] - names2 = [x.name for x in self.systems] - data1 = yaml.dump(names1) - data2 = yaml.dump(names2) - fd1 = open(os.path.join(self.settings.webdir, "profile_list"), "w+") - fd2 = open(os.path.join(self.settings.webdir, "system_list"), "w+") - fd1.write(data1) - fd2.write(data2) - fd1.close() - fd2.close() - - def write_distro_file(self,distro): - """ - Create distro information for koan install - """ - blended = utils.blender(self.api, True, distro) - filename = os.path.join(self.settings.webdir,"distros",distro.name) - fd = open(filename, "w+") - fd.write(yaml.dump(blended)) - fd.close() - - def write_profile_file(self,profile): - """ - Create profile information for virt install - - NOTE: relevant to http only - """ - - blended = utils.blender(self.api, True, profile) - filename = os.path.join(self.settings.webdir,"profiles",profile.name) - fd = open(filename, "w+") - if blended.has_key("kickstart") and blended["kickstart"].startswith("/"): - # write the file location as needed by koan - blended["kickstart"] = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name) - fd.write(yaml.dump(blended)) - fd.close() - - def write_system_file(self,filename,system): - """ - Create system information for virt install - - NOTE: relevant to http only - """ - - blended = utils.blender(self.api, True, system) - filename = os.path.join(self.settings.webdir,"systems",system.name) - fd = open(filename, "w+") - fd.write(yaml.dump(blended)) - fd.close() - diff --git a/cobbler/api.py b/cobbler/api.py index 6f7d1b1..ebe987e 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -25,6 +25,7 @@ import action_validate from cexceptions import * import sub_process import module_loader +import kickgen import logging import os @@ -79,6 +80,7 @@ class BootAPI: "module", "authz_allowall" ) + self.kickgen = kickgen.KickGen(self._config) self.logger.debug("API handle initialized") def __setup_logger(self,name): @@ -284,7 +286,14 @@ class BootAPI: # run cobbler reposync to apply changes return True - + + def generate_kickstart(self,profile,system): + self.log("generate_kickstart") + if system: + return self.kickgen.generate_kickstart_for_system(system) + else: + return self.kickgen.generate_kickstart_for_profile(profile) + def check(self): """ See if all preqs for network booting are valid. This returns diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py new file mode 100644 index 0000000..dce33f3 --- /dev/null +++ b/cobbler/kickgen.py @@ -0,0 +1,272 @@ +""" +Builds out filesystem trees/data based on the object tree. +This is the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +class KickGen: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + + def generate_kickstart_for_profile(self,g): + + g = self.api.find_profile(name=g) + if g is None: + return "# profile not found" + + distro = g.get_conceptual_parent() + meta = utils.blender(self.api, False, g) + if distro is None: + raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro }) + kickstart_path = utils.find_kickstart(meta["kickstart"]) + if kickstart_path is not None and os.path.exists(kickstart_path): + # the input is an *actual* file, hence we have to copy it + try: + meta = utils.blender(self.api, False, g) + ksmeta = meta["ks_meta"] + del meta["ks_meta"] + meta.update(ksmeta) # make available at top level + meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True) + meta["yum_config_stanza"] = self.generate_config_stanza(g,True) + meta["kickstart_done"] = self.generate_kickstart_signal(0, g, None) + meta["kickstart_start"] = self.generate_kickstart_signal(1, g, None) + meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) + kfile = open(kickstart_path) + data = self.templar.render(kfile, meta, None, g) + kfile.close() + return data + except: + traceback.print_exc() # leave this in, for now... + msg = "err_kickstart2" + raise CX(_("Error while rendering kickstart file")) + + def generate_kickstart_signal(self, is_pre=0, profile=None, system=None): + """ + Do things that we do at the start/end of kickstarts... + * start: signal the status watcher we're starting + * end: signal the status watcher we're done + * end: disable PXE if needed + * end: save the original kickstart file for debug + """ + + nopxe = "\nwget \"http://%s/cblr/svc/?op=nopxe&system=%s\" -O /dev/null" + saveks = "\nwget \"http://%s/cblr/svc/?op=ks&%s=%s\" -O /root/cobbler.ks" + runpost = "\nwget \"http://%s/cblr/srv/?op=trig&?mode=post&%s=%s\" -O /dev/null" + runpre = "\nwget \"http://%s/cblr/srv/?op=trig&?mode=pre&%s=%s\" -O /dev/null" + + what = "profile" + blend_this = profile + if system: + what = "system" + blend_this = system + + blended = utils.blender(self.api, False, blend_this) + kickstart = blended.get("kickstart",None) + + buf = "" + srv = blended["http_server"] + if system is not None: + if not is_pre: + if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]: + buf = buf + nopxe % (srv, system.name) + if kickstart and os.path.exists(kickstart): + buf = buf + saveks % (srv, "system", system.name) + if self.settings.run_install_triggers: + buf = buf + runpost % (srv, what, system.name) + else: + if self.settings.run_install_triggers: + buf = buf + runpre % (srv, what, system.name) + + else: + if not is_pre: + if kickstart and os.path.exists(kickstart): + buf = buf + saveks % (srv, "profile", profile.name) + if self.settings.run_install_triggers: + buf = buf + runpost % (srv, what, profile.name) + else: + if self.settings.run_install_triggers: + buf = buf + runpre % (srv, what, profile.name) + + return buf + + def generate_repo_stanza(self, obj, is_profile=True): + + """ + Automatically attaches yum repos to profiles/systems in kickstart files + that contain the magic $yum_repo_stanza variable. + """ + + buf = "" + blended = utils.blender(self.api, False, obj) + configs = self.get_repo_filenames(obj,is_profile) + repos = self.repos + + for c in configs: + name = c.split("/")[-1].replace(".repo","") + (is_core, baseurl) = self.analyze_repo_config(c) + for repo in repos: + if repo.name == name: + if not repo.yumopts.has_key('enabled') or repo.yumopts['enabled'] == '1': + buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl) + return buf + + def analyze_repo_config(self, filename): + fd = open(filename) + data = fd.read() + lines = data.split("\n") + ret = False + baseurl = None + for line in lines: + if line.find("ks_mirror") != -1: + ret = True + if line.find("baseurl") != -1: + first, baseurl = line.split("=") + fd.close() + return (ret, baseurl) + + def get_repo_baseurl(self, server, repo_name, is_repo_mirror=True): + """ + Construct the URL to a repo definition. + """ + if is_repo_mirror: + return "http://%s/cobbler/repo_mirror/%s" % (server, repo_name) + else: + return "http://%s/cobbler/ks_mirror/config/%s" % (server, repo_name) + + def get_repo_filenames(self, obj, is_profile=True): + """ + For a given object, return the paths to repo configuration templates + that will be used to generate per-object repo configuration files and + baseurls + """ + + blended = utils.blender(self.api, False, obj) + urlseg = self.get_repo_segname(is_profile) + + topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"]) + files = glob.glob(topdir) + return files + + + def get_repo_segname(self, is_profile): + if is_profile: + return "repos_profile" + else: + return "repos_system" + + + def generate_config_stanza(self, obj, is_profile=True): + + """ + Add in automatic to configure /etc/yum.repos.d on the remote system + if the kickstart file contains the magic $yum_config_stanza. + """ + + if not self.settings.yum_post_install_mirror: + return "" + + urlseg = self.get_repo_segname(is_profile) + + distro = obj.get_conceptual_parent() + if not is_profile: + distro = distro.get_conceptual_parent() + + blended = utils.blender(self.api, False, obj) + configs = self.get_repo_filenames(obj, is_profile) + buf = "" + + # for each kickstart template we have rendered ... + for c in configs: + + name = c.split("/")[-1].replace(".repo","") + # add the line to create the yum config file on the target box + conf = self.get_repo_config_file(blended["http_server"],urlseg,blended["name"],name) + buf = buf + "wget \"%s\" --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name) + + return buf + + def get_repo_config_file(self,server,urlseg,obj_name,repo_name): + """ + Construct the URL to a repo config file that is usable in kickstart + for use with yum. This is different than the templates cobbler reposync + creates, as this file will allow the server to migrate and have different + variables for different subnets/profiles/etc. + """ + return "http://%s/cblr/%s/%s/%s.repo" % (server,urlseg,obj_name,repo_name) + + def generate_kickstart_for_system(self,s): + + + s = self.api.find_system(name=s) + if s is None: + return "# system not found" + + profile = s.get_conceptual_parent() + if profile is None: + raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile }) + distro = profile.get_conceptual_parent() + meta = utils.blender(self.api, False, s) + kickstart_path = utils.find_kickstart(meta["kickstart"]) + if kickstart_path and os.path.exists(kickstart_path): + try: + ksmeta = meta["ks_meta"] + del meta["ks_meta"] + meta.update(ksmeta) # make available at top level + meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False) + meta["yum_config_stanza"] = self.generate_config_stanza(s, False) + meta["kickstart_done"] = self.generate_kickstart_signal(0, profile, s) + meta["kickstart_start"] = self.generate_kickstart_signal(1, profile, s) + meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) + kfile = open(kickstart_path) + data = self.templar.render(kfile, meta, None, s) + kfile.close() + return data + except: + traceback.print_exc() + raise CX(_("Error templating file")) + diff --git a/cobbler/remote.py b/cobbler/remote.py index 0cbaf22..a87355b 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -158,6 +158,14 @@ class CobblerXMLRPCInterface: return self._fix_none(data) + def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,reg=None): + self.log("generate_kickstart") + + if reg is not None and profile and not system: + regrc = self.register_mac(REMOTE_MAC,profile) + + return self.api.generate_kickstart(profile,system) + def get_settings(self,token=None): """ Return the contents of /var/lib/cobbler/settings, which is a hash. diff --git a/cobbler/services.py b/cobbler/services.py new file mode 100644 index 0000000..9ced0c6 --- /dev/null +++ b/cobbler/services.py @@ -0,0 +1,94 @@ +# Mod Python service functions for Cobbler's public interface +# (aka cool stuff that works with wget) +# +# Copyright 2007 Albert P. Tobey +# additions: Michael DeHaan +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import exceptions +import xmlrpclib +import os +import traceback +import string +import sys +import time + +def log_exc(apache): + """ + Log active traceback to logfile. + """ + (t, v, tb) = sys.exc_info() + apache.log_error("Exception occured: %s" % t ) + apache.log_error("Exception value: %s" % v) + apache.log_error("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb)))) + +class CobblerSvc(object): + """ + Interesting mod python functions are all keyed off the parameter + mode, which defaults to index. All options are passed + as parameters into the function. + """ + def __init__(self, server=None, apache=None): + self.server = server + self.apache = apache + self.remote = None + + def __xmlrpc_setup(self): + """ + Sets up the connection to the Cobbler XMLRPC server. + This is the version that does not require logins. + """ + self.remote = xmlrpclib.Server(self.server, allow_none=True) + + def modes(self): + """ + Returns a list of methods in this object that can be run as web + modes. + """ + retval = list() + for m in dir(self): + func = getattr( self, m ) + if hasattr(func, 'exposed') and getattr(func,'exposed'): + retval.append(m) + return retval + + def index(self,**args): + return "no mode specified" + + def ks(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,reg=None,**rest): + """ + Generate kickstart files... + """ + self.__xmlrpc_setup() + return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC) + + def trig(self,mode="?",profile=None,system=None,REMOTE_ADDR=None,**rest): + """ + Hook to call install triggers. + """ + self.__xmlrpc_setup() + ip = REMOTE_ADDR + if profile: + rc = self.remote.run_install_triggers(mode,"profile",profile,ip) + else: + rc = self.remote.run_install_triggers(mode,"system",system,ip) + return str(rc) + + def nopxe(self,system=None,**rest): + self.__xmlrpc_setup() + return str(self.remote.disable_netboot(system)) + + # ======================================================= + # list of functions that are callable via mod_python: + modes.exposed = False + index.exposed = True + ks.exposed = True + trig.exposed = True + + diff --git a/cobbler/settings.py b/cobbler/settings.py index 1695cbb..40ed571 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -22,8 +22,6 @@ TESTMODE = False # we need. DEFAULTS = { - "allow_cgi_mac_registration" : 0, - "allow_cgi_profile_change" : 0, "allow_duplicate_macs" : 0, "allow_duplicate_ips" : 0, "bootloaders" : { @@ -61,7 +59,8 @@ DEFAULTS = { "manage_dhcp_mode" : "isc", "next_server" : "127.0.0.1", "pxe_just_once" : 0, - "run_install_trigger" : 1, + "register_new_installs" : 0, + "run_install_triggers" : 1, "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "syslog_port" : 25150, diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 1e6d30d..0eec178 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1208298360.4598751 -__CHEETAH_genTimestamp__ = 'Tue Apr 15 18:26:00 2008' +__CHEETAH_genTime__ = 1208545841.2024181 +__CHEETAH_genTimestamp__ = 'Fri Apr 18 15:10:41 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' diff --git a/config/cobbler_svc.conf b/config/cobbler_svc.conf new file mode 100644 index 0000000..f0c86de --- /dev/null +++ b/config/cobbler_svc.conf @@ -0,0 +1,12 @@ +# This configuration file allows cobbler data +# to be accessed over HTTP. + +# mod_python WebUI/services + + + SetHandler mod_python + PythonHandler services + PythonDebug on + + + diff --git a/config/settings b/config/settings index fc6739a..10e06e2 100644 --- a/config/settings +++ b/config/settings @@ -1,6 +1,4 @@ --- -allow_cgi_mac_registration: 0 -allow_cgi_profile_change: 0 allow_duplicate_macs: 0 allow_duplicate_ips: 0 bootloaders: @@ -35,7 +33,8 @@ manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' pxe_just_once: 0 -run_install_trigger: 1 +register_new_installs: 0 +run_install_triggers: 1 server: '127.0.0.1' snippetsdir: /var/lib/cobbler/snippets syslog_port: 25150 diff --git a/legacy/change_profile.cgi b/legacy/change_profile.cgi new file mode 100755 index 0000000..f7330f1 --- /dev/null +++ b/legacy/change_profile.cgi @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +# Michael DeHaan +# (C) 2008 Red Hat Inc +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# what is this? This is a +# script to change cobbler profiles for the requestor +# from one profile to another, as specified by ?profile=foo +# ex: wget http://cobbler.example.org/cgi-bin/change_profile.cgi?profile=foo +# suitable to be called from kickstart,etc + +import cgi +import cgitb +import time +import os +import sys +import socket +import xmlrpclib + +COBBLER_BASE = "/var/www/cobbler" +XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" +DEFAULT_PROFILE = "default" + +#---------------------------------------------------------------------- + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, url=None): + xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) + +#---------------------------------------------------------------------- + +def parse_query(): + + form = cgi.parse() + + mac = "-1" + if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): + # FIXME: will not key off other NICs + devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] + return devicepair.split()[1].strip() + + if form.has_key("profile"): + profile = form["profile"][0] + else: + profile = DEFAULT_PROFILE + system = autodetect() + print "# incoming profile = %s" % profile + print "# incoming system = %s" % system + return (system["name"],profile) + +#---------------------------------------------------------------------- + +def autodetect(): + # get mac address, requires kssendmac on the kernel options line. + else: + return "-1" + + +#---------------------------------------------------------------------- + +def header(): + print "Content-type: text/plain" + print + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + cgitb.enable(format='text') + header() + server = ServerProxy(XMLRPC_SERVER) + (mac, profile) = parse_query() + try: + ip = os.environ["REMOTE_ADDR"] + except: + ip = "???" + print "# attempting to change system(mac=%s) to profile(%s)" % (mac,profile) + server.change_profile(mac,profile) + diff --git a/legacy/findks.cgi b/legacy/findks.cgi new file mode 100755 index 0000000..39adbcf --- /dev/null +++ b/legacy/findks.cgi @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# based on: +# Cobbler ks-serving script +# July 5 2007 +# Adam Wolf +# http://feelslikeburning.com/projects/live-cd-restoring-with-cobbler/ + +import cgi +import cgitb +import time +import os +import sys +import socket +import xmlrpclib + +COBBLER_BASE = "/var/www/cobbler" +XMLRPC_SERVER = "http://127.0.0.1/cobbler_api_rw" + +#---------------------------------------------------------------------- + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, url=None): + xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) + +#---------------------------------------------------------------------- + +def parse_query(): + + form = cgi.parse() + + if form.has_key("system"): + name = form["system"][0] + type = "system" + elif form.has_key("profile"): + name = form["profile"][0] + type = "profile" + else: + type = "system" + name = autodetect() + return (name,type) + +#---------------------------------------------------------------------- + +def autodetect(): + + # connect to cobblerd and get the list of systems + + try: + xmlrpc_server = ServerProxy(XMLRPC_SERVER) + systems = xmlrpc_server.get_systems() + except: + print "# could not contact cobblerd at %s" % XMLRPC_SERVER + sys.exit(1) + + # if kssendmac was in the kernel options line, see + # if a system can be found matching the MAC address. This + # is more specific than an IP match. + + if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): + # FIXME: will not key off other NICs + devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] + mac = devicepair.split()[1].strip() + # mac is the macaddress of the first nic reported by anaconda + + candidates = [] + for x in systems: + + for y in x["interfaces"]: + if x["interfaces"][y]["ip_address"] == ip: + candidates.append(x) + + if len(candidates) == 0: + print "# no system entries with MAC %s found" % mac + print "# trying IP lookup" + elif len(candidates) > 1: + print "# multiple system entries with MAC %s found" % mac + sys.exit(1) + elif len(candidates) == 1: + print "# kickstart matched by MAC: %s" % mac + return candidates[0] + + # attempt to match by the IP. + + ip = os.environ["REMOTE_ADDR"] + candidates = [system['name'] for system in systems if system['ip_address'] == ip] + + if len(candidates) == 0: + print "# no system entries with ip %s found" % ip + sys.exit(1) + elif len(candidates) > 1: + print "# multiple system entries with ip %s found" % ip + sys.exit(1) + elif len(candidates) == 1: + return candidates[0] + +#---------------------------------------------------------------------- + +def serve_file(name): + + # never hurts to be safe... + name = name.replace("/","") + name = name.replace("..","") + name = name.replace(";","") + + if type == "system": + ks_path = "%s/kickstarts_sys/%s/ks.cfg" % (COBBLER_BASE, name) + elif type == "profile": + ks_path = "%s/kickstarts/%s/ks.cfg" % (COBBLER_BASE, name) + + if not os.path.exists(ks_path): + print "# no such cobbler object" + sys.exit(1) + + try: + ksfile = open(ks_path) + except: + print "# Cannot open file %s" % ks_path + sys.exit(1) + + for line in ksfile: + print line.strip() + ksfile.close() + +#---------------------------------------------------------------------- + +def header(): + print "Content-type: text/plain" + print + print "# kickstart managed by Cobbler -- http://cobbler.et.redhat.com" + print "# served on %s" % time.ctime() + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + cgitb.enable(format='text') + header() + (name, type) = parse_query() + print "# %s %s" % (type,name) + print "# requestor ip = %s" % os.environ["REMOTE_ADDR"] + print "# =============================" + print " " + serve_file(name) + + diff --git a/legacy/install_trigger.cgi b/legacy/install_trigger.cgi new file mode 100644 index 0000000..b83ff57 --- /dev/null +++ b/legacy/install_trigger.cgi @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# This script runs post install triggers in /var/lib/cobbler/triggers/install/post +# if the triggers are enabled in the settings file. +# +# (C) Tim Verhoeven , 2007 +# tweaked: Michael DeHaan , 2007-2008 + +import cgi +import cgitb +import time +import os +import sys +import socket +import xmlrpclib +from cobbler import sub_process as sub_process + +COBBLER_BASE = "/var/www/cobbler" +XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" + +#---------------------------------------------------------------------- + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, url=None): + xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) + +#---------------------------------------------------------------------- + +def parse_query(): + """ + Read arguments from query string. + """ + + form = cgi.parse() + + ip = "?" + if os.environ.has_key("REMOTE_ADDR"): + ip = os.environ["REMOTE_ADDR"] + + name = "?" + objtype = "?" + if form.has_key("system"): + name = form["system"][0] + objtype = "system" + elif form.has_key("profile"): + name = form["profile"][0] + objtype = "profile" + + mode = "?" + if form.has_key("mode"): + mode = form["mode"][0] + + return (mode,objtype,name,ip) + +def invoke(mode,objtype,name,ip): + """ + Determine if this feature is enabled. + """ + + xmlrpc_server = ServerProxy(XMLRPC_SERVER) + print xmlrpc_server.run_install_triggers(mode,objtype,name,ip) + + return True + +#---------------------------------------------------------------------- + +def header(): + print "Content-type: text/plain" + print + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + cgitb.enable(format='text') + header() + (mode,objtype,name,ip) = parse_query() + invoke(mode,objtype,name,ip) + + diff --git a/legacy/nopxe.cgi b/legacy/nopxe.cgi new file mode 100755 index 0000000..a2eae88 --- /dev/null +++ b/legacy/nopxe.cgi @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# This script disables the netboot flag for a given +# system if (and only if) pxe_just_once is enabled in settings. +# It must not be able to do anything else for security +# reasons. +# +# +# (C) Red Hat, 2007 +# Michael DeHaan +# + +import cgi +import cgitb +import time +import os +import sys +import socket +import xmlrpclib +from cobbler import sub_process as sub_process + +COBBLER_BASE = "/var/www/cobbler" +XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" + +#---------------------------------------------------------------------- + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, url=None): + xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) + +#---------------------------------------------------------------------- + +def parse_query(): + """ + Read arguments from query string. + """ + + form = cgi.parse() + + if form.has_key("system"): + return form["system"][0] + return 0 + +def disable(name): + """ + Determine if this feature is enabled. + """ + + #try: + xmlrpc_server = ServerProxy(XMLRPC_SERVER) + print xmlrpc_server.disable_netboot(name) + #except: + # print "# could not contact cobblerd at %s" % XMLRPC_SERVER + # sys.exit(1) + + return True + +#---------------------------------------------------------------------- + +def header(): + print "Content-type: text/plain" + print + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + cgitb.enable(format='text') + header() + name = parse_query() + disable(name) + + diff --git a/legacy/register_mac.cgi b/legacy/register_mac.cgi new file mode 100755 index 0000000..3f251c4 --- /dev/null +++ b/legacy/register_mac.cgi @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +# Michael DeHaan +# (C) 2008 Red Hat Inc +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# what is this? This is a +# script to auto add systems who make a wget into cobbler. +# right now it requires "kssendmac" in kernel options and takes only 1 arg +# ex: wget http://cobbler.example.org/cgi-bin/register_mac?profile=foo +# suitable to be called from kickstart,etc + +import cgi +#import cgitb +import time +import os +import sys +import socket +import xmlrpclib + +# FIXME: edit these two variables to match your webui configuration +USERNAME = "cobbler" +PASSWORD = "cobbler" + +COBBLER_BASE = "/var/www/cobbler" +XMLRPC_SERVER = "http://127.0.0.1/cobbler_api_rw" +DEFAULT_PROFILE = "default" + +#---------------------------------------------------------------------- + +class ServerProxy(xmlrpclib.ServerProxy): + + def __init__(self, url=None): + xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) + +#---------------------------------------------------------------------- + +def parse_query(): + + form = cgi.parse() + + if form.has_key("profile"): + profile = form["profile"][0] + else: + profile = DEFAULT_PROFILE + mac = autodetect() + print "# incoming profile = %s" % profile + return (mac,profile) + +#---------------------------------------------------------------------- + +def autodetect(): + + # connect to cobblerd and get the list of systems + + try: + xmlrpc_server = ServerProxy(XMLRPC_SERVER) + systems = xmlrpc_server.get_systems() + except: + print "# could not contact cobblerd at %s" % XMLRPC_SERVER + sys.exit(1) + + # if kssendmac was in the kernel options line, see + # if a system can be found matching the MAC address. This + # is more specific than an IP match. + + if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): + # FIXME: will not key off other NICs + devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] + mac = devicepair.split()[1].strip() + print "# discovered MAC: %s" % mac.lower() + return mac.lower() + else: + print "# missing kssendmac in the kernel args? Can't continue." + return "BB:EE:EE:EE:EE:FF" + +#---------------------------------------------------------------------- + + +def make_change(server,mac,profile,token): + server.register_mac(mac,profile) + +#---------------------------------------------------------------------- + +def header(): + print "Content-type: text/plain" + print + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + #cgitb.enable(format='text') + header() + server = ServerProxy(XMLRPC_SERVER) + token = server.login(USERNAME,PASSWORD) + (mac, profile) = parse_query() + print "# running for %s %s" % (mac,profile) + make_change(server,mac,profile,token) + diff --git a/legacy/watcher.py b/legacy/watcher.py new file mode 100755 index 0000000..dfa8dc3 --- /dev/null +++ b/legacy/watcher.py @@ -0,0 +1,63 @@ +# cobbler mod_python handler for observing kickstart activity +# +# Copyright 2007, Red Hat, Inc +# Michael DeHaan +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import time +from mod_python import apache + +def outputfilter(filter): + + + # extract important info + request = filter.req + connection = request.connection + (address,port) = connection.remote_addr + + # open the logfile (directory be set writeable by installer) + logfile = open("/var/log/cobbler/kicklog/%s" % address,"a+") + + log_it = True + if request.the_request.find("cobbler_track") == -1 and request.the_request.find("cblr/") == -1: + log_it = False + + if log_it: + # write the timestamp + t = time.localtime() + seconds = str(time.mktime(t)) + logfile.write(seconds) + logfile.write("\t") + timestr = str(time.asctime(t)) + logfile.write(timestr) + logfile.write("\t") + + # write the IP address of the client + logfile.write(address) + logfile.write("\t") + + # write the filename being requested + logfile.write(request.the_request) + # logfile.write(request.filename) + logfile.write("\n") + + # if requesting this file, don't return it + if request.the_request.find("watcher.py") != -1: + filter.close() + return + + # pass-through filter + s = filter.read() + while s: + filter.write(s) + s = filter.read() + if s is None: + filter.close() + logfile.close() + diff --git a/scripts/change_profile.cgi b/scripts/change_profile.cgi deleted file mode 100755 index f7330f1..0000000 --- a/scripts/change_profile.cgi +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python - -# Michael DeHaan -# (C) 2008 Red Hat Inc -# -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -# what is this? This is a -# script to change cobbler profiles for the requestor -# from one profile to another, as specified by ?profile=foo -# ex: wget http://cobbler.example.org/cgi-bin/change_profile.cgi?profile=foo -# suitable to be called from kickstart,etc - -import cgi -import cgitb -import time -import os -import sys -import socket -import xmlrpclib - -COBBLER_BASE = "/var/www/cobbler" -XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" -DEFAULT_PROFILE = "default" - -#---------------------------------------------------------------------- - -class ServerProxy(xmlrpclib.ServerProxy): - - def __init__(self, url=None): - xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) - -#---------------------------------------------------------------------- - -def parse_query(): - - form = cgi.parse() - - mac = "-1" - if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): - # FIXME: will not key off other NICs - devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] - return devicepair.split()[1].strip() - - if form.has_key("profile"): - profile = form["profile"][0] - else: - profile = DEFAULT_PROFILE - system = autodetect() - print "# incoming profile = %s" % profile - print "# incoming system = %s" % system - return (system["name"],profile) - -#---------------------------------------------------------------------- - -def autodetect(): - # get mac address, requires kssendmac on the kernel options line. - else: - return "-1" - - -#---------------------------------------------------------------------- - -def header(): - print "Content-type: text/plain" - print - -#---------------------------------------------------------------------- - -if __name__ == "__main__": - cgitb.enable(format='text') - header() - server = ServerProxy(XMLRPC_SERVER) - (mac, profile) = parse_query() - try: - ip = os.environ["REMOTE_ADDR"] - except: - ip = "???" - print "# attempting to change system(mac=%s) to profile(%s)" % (mac,profile) - server.change_profile(mac,profile) - diff --git a/scripts/findks.cgi b/scripts/findks.cgi deleted file mode 100755 index 39adbcf..0000000 --- a/scripts/findks.cgi +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python - -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -# based on: -# Cobbler ks-serving script -# July 5 2007 -# Adam Wolf -# http://feelslikeburning.com/projects/live-cd-restoring-with-cobbler/ - -import cgi -import cgitb -import time -import os -import sys -import socket -import xmlrpclib - -COBBLER_BASE = "/var/www/cobbler" -XMLRPC_SERVER = "http://127.0.0.1/cobbler_api_rw" - -#---------------------------------------------------------------------- - -class ServerProxy(xmlrpclib.ServerProxy): - - def __init__(self, url=None): - xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) - -#---------------------------------------------------------------------- - -def parse_query(): - - form = cgi.parse() - - if form.has_key("system"): - name = form["system"][0] - type = "system" - elif form.has_key("profile"): - name = form["profile"][0] - type = "profile" - else: - type = "system" - name = autodetect() - return (name,type) - -#---------------------------------------------------------------------- - -def autodetect(): - - # connect to cobblerd and get the list of systems - - try: - xmlrpc_server = ServerProxy(XMLRPC_SERVER) - systems = xmlrpc_server.get_systems() - except: - print "# could not contact cobblerd at %s" % XMLRPC_SERVER - sys.exit(1) - - # if kssendmac was in the kernel options line, see - # if a system can be found matching the MAC address. This - # is more specific than an IP match. - - if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): - # FIXME: will not key off other NICs - devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] - mac = devicepair.split()[1].strip() - # mac is the macaddress of the first nic reported by anaconda - - candidates = [] - for x in systems: - - for y in x["interfaces"]: - if x["interfaces"][y]["ip_address"] == ip: - candidates.append(x) - - if len(candidates) == 0: - print "# no system entries with MAC %s found" % mac - print "# trying IP lookup" - elif len(candidates) > 1: - print "# multiple system entries with MAC %s found" % mac - sys.exit(1) - elif len(candidates) == 1: - print "# kickstart matched by MAC: %s" % mac - return candidates[0] - - # attempt to match by the IP. - - ip = os.environ["REMOTE_ADDR"] - candidates = [system['name'] for system in systems if system['ip_address'] == ip] - - if len(candidates) == 0: - print "# no system entries with ip %s found" % ip - sys.exit(1) - elif len(candidates) > 1: - print "# multiple system entries with ip %s found" % ip - sys.exit(1) - elif len(candidates) == 1: - return candidates[0] - -#---------------------------------------------------------------------- - -def serve_file(name): - - # never hurts to be safe... - name = name.replace("/","") - name = name.replace("..","") - name = name.replace(";","") - - if type == "system": - ks_path = "%s/kickstarts_sys/%s/ks.cfg" % (COBBLER_BASE, name) - elif type == "profile": - ks_path = "%s/kickstarts/%s/ks.cfg" % (COBBLER_BASE, name) - - if not os.path.exists(ks_path): - print "# no such cobbler object" - sys.exit(1) - - try: - ksfile = open(ks_path) - except: - print "# Cannot open file %s" % ks_path - sys.exit(1) - - for line in ksfile: - print line.strip() - ksfile.close() - -#---------------------------------------------------------------------- - -def header(): - print "Content-type: text/plain" - print - print "# kickstart managed by Cobbler -- http://cobbler.et.redhat.com" - print "# served on %s" % time.ctime() - -#---------------------------------------------------------------------- - -if __name__ == "__main__": - cgitb.enable(format='text') - header() - (name, type) = parse_query() - print "# %s %s" % (type,name) - print "# requestor ip = %s" % os.environ["REMOTE_ADDR"] - print "# =============================" - print " " - serve_file(name) - - diff --git a/scripts/install_trigger.cgi b/scripts/install_trigger.cgi deleted file mode 100644 index b83ff57..0000000 --- a/scripts/install_trigger.cgi +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python - -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. -# -# This script runs post install triggers in /var/lib/cobbler/triggers/install/post -# if the triggers are enabled in the settings file. -# -# (C) Tim Verhoeven , 2007 -# tweaked: Michael DeHaan , 2007-2008 - -import cgi -import cgitb -import time -import os -import sys -import socket -import xmlrpclib -from cobbler import sub_process as sub_process - -COBBLER_BASE = "/var/www/cobbler" -XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" - -#---------------------------------------------------------------------- - -class ServerProxy(xmlrpclib.ServerProxy): - - def __init__(self, url=None): - xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) - -#---------------------------------------------------------------------- - -def parse_query(): - """ - Read arguments from query string. - """ - - form = cgi.parse() - - ip = "?" - if os.environ.has_key("REMOTE_ADDR"): - ip = os.environ["REMOTE_ADDR"] - - name = "?" - objtype = "?" - if form.has_key("system"): - name = form["system"][0] - objtype = "system" - elif form.has_key("profile"): - name = form["profile"][0] - objtype = "profile" - - mode = "?" - if form.has_key("mode"): - mode = form["mode"][0] - - return (mode,objtype,name,ip) - -def invoke(mode,objtype,name,ip): - """ - Determine if this feature is enabled. - """ - - xmlrpc_server = ServerProxy(XMLRPC_SERVER) - print xmlrpc_server.run_install_triggers(mode,objtype,name,ip) - - return True - -#---------------------------------------------------------------------- - -def header(): - print "Content-type: text/plain" - print - -#---------------------------------------------------------------------- - -if __name__ == "__main__": - cgitb.enable(format='text') - header() - (mode,objtype,name,ip) = parse_query() - invoke(mode,objtype,name,ip) - - diff --git a/scripts/nopxe.cgi b/scripts/nopxe.cgi deleted file mode 100755 index a2eae88..0000000 --- a/scripts/nopxe.cgi +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python - -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -# This script disables the netboot flag for a given -# system if (and only if) pxe_just_once is enabled in settings. -# It must not be able to do anything else for security -# reasons. -# -# -# (C) Red Hat, 2007 -# Michael DeHaan -# - -import cgi -import cgitb -import time -import os -import sys -import socket -import xmlrpclib -from cobbler import sub_process as sub_process - -COBBLER_BASE = "/var/www/cobbler" -XMLRPC_SERVER = "http://127.0.0.1/cobbler_api" - -#---------------------------------------------------------------------- - -class ServerProxy(xmlrpclib.ServerProxy): - - def __init__(self, url=None): - xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) - -#---------------------------------------------------------------------- - -def parse_query(): - """ - Read arguments from query string. - """ - - form = cgi.parse() - - if form.has_key("system"): - return form["system"][0] - return 0 - -def disable(name): - """ - Determine if this feature is enabled. - """ - - #try: - xmlrpc_server = ServerProxy(XMLRPC_SERVER) - print xmlrpc_server.disable_netboot(name) - #except: - # print "# could not contact cobblerd at %s" % XMLRPC_SERVER - # sys.exit(1) - - return True - -#---------------------------------------------------------------------- - -def header(): - print "Content-type: text/plain" - print - -#---------------------------------------------------------------------- - -if __name__ == "__main__": - cgitb.enable(format='text') - header() - name = parse_query() - disable(name) - - diff --git a/scripts/register_mac.cgi b/scripts/register_mac.cgi deleted file mode 100755 index 5507525..0000000 --- a/scripts/register_mac.cgi +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python - -# Michael DeHaan -# (C) 2008 Red Hat Inc -# -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -# what is this? This is a -# script to auto add systems who make a wget into cobbler. -# right now it requires "kssendmac" in kernel options and takes only 1 arg -# ex: wget http://cobbler.example.org/cgi-bin/regsister_mac?profile=foo -# suitable to be called from kickstart,etc - -import cgi -#import cgitb -import time -import os -import sys -import socket -import xmlrpclib - -# FIXME: edit these two variables to match your webui configuration -USERNAME = "cobbler" -PASSWORD = "cobbler" - -COBBLER_BASE = "/var/www/cobbler" -XMLRPC_SERVER = "http://127.0.0.1/cobbler_api_rw" -DEFAULT_PROFILE = "default" - -#---------------------------------------------------------------------- - -class ServerProxy(xmlrpclib.ServerProxy): - - def __init__(self, url=None): - xmlrpclib.ServerProxy.__init__(self, url, allow_none=True) - -#---------------------------------------------------------------------- - -def parse_query(): - - form = cgi.parse() - - if form.has_key("profile"): - profile = form["profile"][0] - else: - profile = DEFAULT_PROFILE - mac = autodetect() - print "# incoming profile = %s" % profile - return (mac,profile) - -#---------------------------------------------------------------------- - -def autodetect(): - - # connect to cobblerd and get the list of systems - - try: - xmlrpc_server = ServerProxy(XMLRPC_SERVER) - systems = xmlrpc_server.get_systems() - except: - print "# could not contact cobblerd at %s" % XMLRPC_SERVER - sys.exit(1) - - # if kssendmac was in the kernel options line, see - # if a system can be found matching the MAC address. This - # is more specific than an IP match. - - if os.environ.has_key("HTTP_X_RHN_PROVISIONING_MAC_0"): - # FIXME: will not key off other NICs - devicepair = os.environ["HTTP_X_RHN_PROVISIONING_MAC_0"] - mac = devicepair.split()[1].strip() - print "# discovered MAC: %s" % mac.lower() - return mac.lower() - else: - print "# missing kssendmac in the kernel args? Can't continue." - return "BB:EE:EE:EE:EE:FF" - -#---------------------------------------------------------------------- - - -def make_change(server,mac,profile,token): - server.register_mac(mac,profile) - -#---------------------------------------------------------------------- - -def header(): - print "Content-type: text/plain" - print - -#---------------------------------------------------------------------- - -if __name__ == "__main__": - #cgitb.enable(format='text') - header() - server = ServerProxy(XMLRPC_SERVER) - token = server.login(USERNAME,PASSWORD) - (mac, profile) = parse_query() - print "# running for %s %s" % (mac,profile) - make_change(server,mac,profile,token) - diff --git a/scripts/services.py b/scripts/services.py new file mode 100755 index 0000000..07243ae --- /dev/null +++ b/scripts/services.py @@ -0,0 +1,67 @@ +""" +mod_python gateway to cgi-like cobbler web functions + +Copyright 2007-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +from mod_python import apache +from mod_python import Session +from mod_python import util + +import xmlrpclib +import cgi +import os +from cobbler.services import CobblerSvc + +#======================================= + +def handler(req): + + """ + Right now, index serves everything. + + Hitting this URL means we've already cleared authn/authz + but we still need to use the token for all remote requests. + """ + + my_uri = req.uri + + # apache.log_error("cannot load /var/lib/cobbler/web.ss") + req.add_common_vars() + + # process form and qs data, if any + fs = util.FieldStorage(req) + form = {} + for x in fs.keys(): + form[x] = str(fs.get(x,'default')) + + form["REMOTE_ADDR"] = req.subprocess_env.get("REMOTE_ADDR",None) + form["REMOTE_MAC"] = req.subprocess_env.get("HTTP_X_RHN_PROVISIONING_MAC_0",None) + + # instantiate a CobblerWeb object + cw = CobblerSvc( + apache = apache, + server = "http://127.0.0.1/cobbler_api" + ) + + # check for a valid path/mode + # handle invalid paths gracefully + mode = form.get('op','index') + + func = getattr( cw, mode ) + content = func( **form ) + + # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form))) + req.content_type = "text/plain" + req.write(content) + + return apache.OK + diff --git a/scripts/watcher.py b/scripts/watcher.py deleted file mode 100755 index dfa8dc3..0000000 --- a/scripts/watcher.py +++ /dev/null @@ -1,63 +0,0 @@ -# cobbler mod_python handler for observing kickstart activity -# -# Copyright 2007, Red Hat, Inc -# Michael DeHaan -# -# This software may be freely redistributed under the terms of the GNU -# general public license. -# -# 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -import time -from mod_python import apache - -def outputfilter(filter): - - - # extract important info - request = filter.req - connection = request.connection - (address,port) = connection.remote_addr - - # open the logfile (directory be set writeable by installer) - logfile = open("/var/log/cobbler/kicklog/%s" % address,"a+") - - log_it = True - if request.the_request.find("cobbler_track") == -1 and request.the_request.find("cblr/") == -1: - log_it = False - - if log_it: - # write the timestamp - t = time.localtime() - seconds = str(time.mktime(t)) - logfile.write(seconds) - logfile.write("\t") - timestr = str(time.asctime(t)) - logfile.write(timestr) - logfile.write("\t") - - # write the IP address of the client - logfile.write(address) - logfile.write("\t") - - # write the filename being requested - logfile.write(request.the_request) - # logfile.write(request.filename) - logfile.write("\n") - - # if requesting this file, don't return it - if request.the_request.find("watcher.py") != -1: - filter.close() - return - - # pass-through filter - s = filter.read() - while s: - filter.write(s) - s = filter.read() - if s is None: - filter.close() - logfile.close() - diff --git a/setup.py b/setup.py index c8a6c99..eb969d1 100644 --- a/setup.py +++ b/setup.py @@ -44,8 +44,9 @@ if __name__ == "__main__": tftp_cfg = "/tftpboot/pxelinux.cfg" tftp_images = "/tftpboot/images" rotpath = "/etc/logrotate.d" - cgipath = "/var/www/cgi-bin/cobbler" + # cgipath = "/var/www/cgi-bin/cobbler" modpython = "/var/www/cobbler/web" + modpythonsvc = "/var/www/cobbler/svc" setup( name="cobbler", version = VERSION, @@ -63,13 +64,15 @@ if __name__ == "__main__": scripts = ["scripts/cobbler", "scripts/cobblerd"], data_files = [ (modpython, ['scripts/index.py']), + (modpythonsvc, ['scripts/services.py']), # cgi files - (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']), - (cgipath, ['scripts/install_trigger.cgi']), + # (cgipath, ['scripts/nopxe.cgi']), + # (cgipath, ['scripts/install_trigger.cgi']), # miscellaneous config files (rotpath, ['config/cobblerd_rotate']), (wwwconf, ['config/cobbler.conf']), + (wwwconf, ['config/cobbler_svc.conf']), (cobpath, ['config/cobbler_hosts']), (etcpath, ['config/modules.conf']), (etcpath, ['config/users.digest']), -- cgit From bb84177f0e5b3de18afc12378ec18d089170a10e Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 17:45:24 -0400 Subject: Fix some things found by the tests and also update the tests to not look for static files --- cobbler/action_litesync.py | 10 +--------- tests/tests.py | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index 3a2de8e..89e24b4 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -56,8 +56,6 @@ class BootLiteSync: distro = self.distros.find(name=name) if distro is None: raise CX(_("error in distro lookup: %s") % name) - # generate YAML file in distros/$name in webdir - self.sync.write_distro_file(distro) # copy image files to images/$name in webdir & tftpboot: self.sync.copy_single_distro_files(distro) # cascade sync @@ -67,8 +65,6 @@ class BootLiteSync: def remove_single_distro(self, name): bootloc = utils.tftpboot_location() - # delete distro YAML file in distros/$name in webdir - utils.rmfile(os.path.join(self.settings.webdir, "distros", name)) # delete contents of images/$name directory in webdir utils.rmtree(os.path.join(self.settings.webdir, "images", name)) # delete contents of images/$name in tftpboot @@ -81,8 +77,6 @@ class BootLiteSync: profile = self.profiles.find(name=name) if profile is None: raise CX(_("error in profile lookup")) - # rebuild profile_list YAML file in webdir - self.sync.write_profile_file(profile) # rebuild the yum configuration files for any attached repos self.sync.retemplate_yum_repos(profile,True) # cascade sync @@ -113,7 +107,7 @@ class BootLiteSync: # rebuild system_list file in webdir self.sync.regen_ethers() # /etc/ethers, for dnsmasq & rarpd self.sync.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq - # write the PXE and YAML files for the system + # write the PXE files for the system self.sync.write_all_system_files(system) # per system kickstarts self.sync.retemplate_yum_repos(system,False) @@ -121,8 +115,6 @@ class BootLiteSync: def remove_single_system(self, name): bootloc = utils.tftpboot_location() system_record = self.systems.find(name=name) - # delete system YAML file in systems/$name in webdir - utils.rmfile(os.path.join(self.settings.webdir, "systems", name)) # delete contents of kickstarts_sys/$name in webdir system_record = self.systems.find(name=name) # delete any kickstart files related to this system diff --git a/tests/tests.py b/tests/tests.py index 8536a70..bb64dd1 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -743,7 +743,7 @@ class SyncContents(BootTest): fh = open("/tftpboot/pxelinux.cfg/%s" % converted) data = fh.read() - self.assertTrue(data.find("kickstarts_sys") != -1) + self.assertTrue(data.find("op=ks") != -1) fh.close() # ensure that after sync is applied, the blender cache still allows @@ -753,7 +753,7 @@ class SyncContents(BootTest): self.api.sync() fh = open("/tftpboot/pxelinux.cfg/%s" % converted) data = fh.read() - self.assertTrue(data.find("kickstarts_sys") != -1) + self.assertTrue(data.find("op=ks") != -1) fh.close() -- cgit From aae7d2bb2133fcd171207e0c2a70ed009136d447 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 17:48:49 -0400 Subject: Update specfile to no longer package legacy directories --- cobbler.spec | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cobbler.spec b/cobbler.spec index 7b4e525..5ebe403 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -96,17 +96,12 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /var/log/cobbler/kicklog %dir /var/www/cobbler/ %dir /var/www/cobbler/localmirror -%dir /var/www/cobbler/kickstarts -%dir /var/www/cobbler/kickstarts_sys %dir /var/www/cobbler/repo_mirror %dir /var/www/cobbler/repos_profile %dir /var/www/cobbler/repos_system %dir /var/www/cobbler/ks_mirror %dir /var/www/cobbler/ks_mirror/config %dir /var/www/cobbler/images -%dir /var/www/cobbler/distros -%dir /var/www/cobbler/profiles -%dir /var/www/cobbler/systems %dir /var/www/cobbler/links %defattr(755,apache,apache) %dir /var/www/cobbler/webui -- cgit From b3d798bd603d4c1fe6bb6740b9f95630ba4ee483 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 18:25:14 -0400 Subject: Abstract out sync code into it's constituent parts, plus some packaging changes that I left out earlier. --- MANIFEST.in | 1 + cobbler.spec | 1 + cobbler/action_litesync.py | 14 +- cobbler/action_sync.py | 495 ++------------------------------------------- cobbler/dhcpgen.py | 196 ++++++++++++++++++ cobbler/pxegen.py | 322 +++++++++++++++++++++++++++++ cobbler/webui/master.py | 4 +- cobbler/yumgen.py | 108 ++++++++++ 8 files changed, 655 insertions(+), 486 deletions(-) create mode 100644 cobbler/dhcpgen.py create mode 100644 cobbler/pxegen.py create mode 100644 cobbler/yumgen.py diff --git a/MANIFEST.in b/MANIFEST.in index 644c0c2..3216946 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include loaders/COPYING_ELILO include loaders/elilo-3.6-ia64.efi include loaders/menu.c32 include config/cobbler.conf +include config/cobbler_svc.conf include config/rsync.exclude include config/cobblerd include config/cobblerd_rotate diff --git a/cobbler.spec b/cobbler.spec index 5ebe403..2d0cc15 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -134,6 +134,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %{_mandir}/man1/cobbler.1.gz /etc/init.d/cobblerd %config(noreplace) /etc/httpd/conf.d/cobbler.conf +%config(noreplace) /etc/httpd/conf.d/cobbler_svc.conf %dir /var/log/cobbler/syslog %defattr(755,root,root) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index 89e24b4..c2029d4 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -57,7 +57,7 @@ class BootLiteSync: if distro is None: raise CX(_("error in distro lookup: %s") % name) # copy image files to images/$name in webdir & tftpboot: - self.sync.copy_single_distro_files(distro) + self.sync.pxegen.copy_single_distro_files(distro) # cascade sync kids = distro.get_children() for k in kids: @@ -78,7 +78,7 @@ class BootLiteSync: if profile is None: raise CX(_("error in profile lookup")) # rebuild the yum configuration files for any attached repos - self.sync.retemplate_yum_repos(profile,True) + self.sync.yumgen.retemplate_yum_repos(profile,True) # cascade sync kids = profile.get_children() for k in kids: @@ -97,7 +97,7 @@ class BootLiteSync: system = self.systems.find(name=name) if system is None: raise CX(_("error in system lookup for %s") % name) - self.sync.write_all_system_files(system) + self.sync.pxegen.write_all_system_files(system) def add_single_system(self, name): # get the system object: @@ -105,12 +105,12 @@ class BootLiteSync: if system is None: raise CX(_("error in system lookup for %s") % name) # rebuild system_list file in webdir - self.sync.regen_ethers() # /etc/ethers, for dnsmasq & rarpd - self.sync.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq + self.sync.dhcpgen.regen_ethers() # /etc/ethers, for dnsmasq & rarpd + self.sync.dhcpgen.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq # write the PXE files for the system - self.sync.write_all_system_files(system) + self.sync.pxegen.write_all_system_files(system) # per system kickstarts - self.sync.retemplate_yum_repos(system,False) + self.sync.yumgen.retemplate_yum_repos(system,False) def remove_single_system(self, name): bootloc = utils.tftpboot_location() diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index aa53d12..4156ea7 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -27,7 +27,9 @@ import errno import utils from cexceptions import * import templar -import kickgen +import pxegen +import dhcpgen +import yumgen import item_distro import item_profile @@ -57,7 +59,9 @@ class BootSync: self.settings = config.settings() self.repos = config.repos() self.templar = templar.Templar(config) - self.kickgen = kickgen.KickGen(config) + self.pxegen = pxegen.PXEGen(config) + self.dhcpgen = dhcpgen.DHCPGen(config) + self.yumgen = yumgen.YumGen(config) self.bootloc = utils.tftpboot_location() def run(self): @@ -71,195 +75,35 @@ class BootSync: # run pre-triggers... utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*") - # in case the pre-trigger modified any objects... + # (paranoid) in case the pre-trigger modified any objects... self.api.deserialize() self.distros = self.config.distros() self.profiles = self.config.profiles() self.systems = self.config.systems() self.settings = self.config.settings() self.repos = self.config.repos() + self.pxegen = pxegen.PXEGen(self.config) + self.dhcpgen = dhcpgen.DHCPGen(self.config) + self.yumgen = yumgen.YumGen(self.config) # execute the core of the sync operation self.clean_trees() - self.copy_bootloaders() - self.copy_distros() + self.pxegen.copy_bootloaders() + self.pxegen.copy_distros() for x in self.systems: - self.write_all_system_files(x) - self.retemplate_all_yum_repos() + self.pxegen.write_all_system_files(x) + self.yumgen.retemplate_all_yum_repos() if self.settings.manage_dhcp: # these functions DRT for ISC or dnsmasq - self.write_dhcp_file() - self.regen_ethers() - self.regen_hosts() - self.make_pxe_menu() + self.dhcpgen.write_dhcp_file() + self.dhcpgen.regen_ethers() + self.dhcpgen.regen_hosts() + self.pxegen.make_pxe_menu() # run post-triggers utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/post/*") return True - def copy_bootloaders(self): - """ - Copy bootloaders to the configured tftpboot directory - NOTE: we support different arch's if defined in - /var/lib/cobbler/settings. - """ - for loader in self.settings.bootloaders.keys(): - path = self.settings.bootloaders[loader] - newname = os.path.basename(path) - destpath = os.path.join(self.bootloc, newname) - utils.copyfile(path, destpath) - utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) - - # Copy memtest to tftpboot if package is installed on system - for memtest in glob.glob('/boot/memtest*'): - base = os.path.basename(memtest) - utils.copyfile(memtest,os.path.join(self.bootloc,"images",base)) - - def write_dhcp_file(self): - """ - DHCP files are written when manage_dhcp is set in - /var/lib/cobbler/settings. - """ - - settings_file = self.settings.dhcpd_conf - template_file = "/etc/cobbler/dhcp.template" - mode = self.settings.manage_dhcp_mode.lower() - if mode == "dnsmasq": - settings_file = self.settings.dnsmasq_conf - template_file = "/etc/cobbler/dnsmasq.template" - - try: - f2 = open(template_file,"r") - except: - raise CX(_("error writing template to file: %s") % template_file) - template_data = "" - template_data = f2.read() - f2.close() - - # build each per-system definition - # as configured, this only works for ISC, patches accepted - # from those that care about Itanium. elilo seems to be unmaintained - # so additional maintaince in other areas may be required to keep - # this working. - - elilo = os.path.basename(self.settings.bootloaders["ia64"]) - - system_definitions = {} - counter = 0 - - # we used to just loop through each system, but now we must loop - # through each network interface of each system. - - for system in self.systems: - profile = system.get_conceptual_parent() - distro = profile.get_conceptual_parent() - for (name, interface) in system.interfaces.iteritems(): - - mac = interface["mac_address"] - ip = interface["ip_address"] - host = interface["hostname"] - - if mac is None or mac == "": - # can't write a DHCP entry for this system - continue - - counter = counter + 1 - systxt = "" - - if mode == "isc": - - # the label the entry after the hostname if possible - if host is not None and host != "": - systxt = "\nhost %s {\n" % host - if self.settings.isc_set_host_name: - systxt = systxt + " option host-name = %s;\n" % host - else: - systxt = "\nhost generic%d {\n" % counter - - if distro.arch == "ia64": - # can't use pxelinux.0 anymore - systxt = systxt + " filename \"/%s\";\n" % elilo - systxt = systxt + " hardware ethernet %s;\n" % mac - if ip is not None and ip != "": - systxt = systxt + " fixed-address %s;\n" % ip - systxt = systxt + "}\n" - - else: - # dnsmasq. don't have to write IP and other info here, but we do tag - # each MAC based on the arch of it's distro, if it needs something other - # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq - # reload (full "cobbler sync") would be required after adding the system - # to cobbler, just to tag this relationship. - - if ip is not None and ip != "": - if distro.arch.lower() == "ia64": - systxt = "dhcp-host=net:ia64," + ip + "\n" - # support for other arches needs modifications here - else: - systxt = "" - - dhcp_tag = interface["dhcp_tag"] - if dhcp_tag == "": - dhcp_tag = "default" - - if not system_definitions.has_key(dhcp_tag): - system_definitions[dhcp_tag] = "" - system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt - - # we are now done with the looping through each interface of each system - - metadata = { - "insert_cobbler_system_definitions" : system_definitions.get("default",""), - "date" : time.asctime(time.gmtime()), - "cobbler_server" : self.settings.server, - "next_server" : self.settings.next_server, - "elilo" : elilo - } - - # now add in other DHCP expansions that are not tagged with "default" - for x in system_definitions.keys(): - if x == "default": - continue - metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] - - self.templar.render(template_data, metadata, settings_file, None) - - def regen_ethers(self): - # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date - # every time we add a system. - # read 'man ethers' for format info - fh = open("/etc/ethers","w+") - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - mac = interface["mac_address"] - ip = interface["ip_address"] - if mac is None or mac == "": - # can't write this w/o a MAC address - continue - if ip is not None and ip != "": - fh.write(mac.upper() + "\t" + ip + "\n") - fh.close() - - def regen_hosts(self): - # dnsmasq knows how to read this database for host info - # (other things may also make use of this later) - fh = open("/var/lib/cobbler/cobbler_hosts","w+") - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - mac = interface["mac_address"] - host = interface["hostname"] - ip = interface["ip_address"] - if mac is None or mac == "": - continue - if host is not None and host != "" and ip is not None and ip != "": - fh.write(ip + "\t" + host + "\n") - fh.close() - - - #def templatify(self, data, metadata, outfile): - # for x in metadata.keys(): - # template_data = template_data.replace("$%s" % x, metadata[x]) - def clean_trees(self): """ Delete any previously built pxelinux.cfg tree and virt tree info and then create @@ -288,307 +132,4 @@ class BootSync: utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) utils.rmtree_contents(os.path.join(self.bootloc, "images")) - def copy_distros(self): - """ - A distro is a kernel and an initrd. Copy all of them and error - out if any files are missing. The conf file was correct if built - via the CLI or API, though it's possible files have been moved - since or perhaps they reference NFS directories that are no longer - mounted. - - NOTE: this has to be done for both tftp and http methods - """ - # copy is a 4-letter word but tftpboot runs chroot, thus it's required. - for d in self.distros: - print _("sync distro: %s") % d.name - self.copy_single_distro_files(d) - - def copy_single_distro_files(self, d): - for dirtree in [self.bootloc, self.settings.webdir]: - distros = os.path.join(dirtree, "images") - distro_dir = os.path.join(distros,d.name) - utils.mkdir(distro_dir) - kernel = utils.find_kernel(d.kernel) # full path - initrd = utils.find_initrd(d.initrd) # full path - if kernel is None or not os.path.isfile(kernel): - raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name }) - if initrd is None or not os.path.isfile(initrd): - raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name }) - b_kernel = os.path.basename(kernel) - b_initrd = os.path.basename(initrd) - if kernel.startswith(dirtree): - utils.linkfile(kernel, os.path.join(distro_dir, b_kernel)) - else: - utils.copyfile(kernel, os.path.join(distro_dir, b_kernel)) - if initrd.startswith(dirtree): - utils.linkfile(initrd, os.path.join(distro_dir, b_initrd)) - else: - utils.copyfile(initrd, os.path.join(distro_dir, b_initrd)) - - def retemplate_all_yum_repos(self): - for p in self.profiles: - self.retemplate_yum_repos(p,True) - for system in self.systems: - self.retemplate_yum_repos(system,False) - - def retemplate_yum_repos(self,obj,is_profile): - """ - Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo - and also potentially in listed in the source_repos structure of the distro object, however - these files have server URLs in them that must be templated out. This function does this. - """ - blended = utils.blender(self.api, False, obj) - - if is_profile: - outseg = "repos_profile" - else: - outseg = "repos_system" - - input_files = [] - - # chance old versions from upgrade do not have a source_repos - # workaround for user bug - if not blended.has_key("source_repos"): - blended["source_repos"] = [] - - # tack on all the install source repos IF there is more than one. - # this is basically to support things like RHEL5 split trees - # if there is only one, then there is no need to do this. - - for r in blended["source_repos"]: - filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:]) - input_files.append(filename) - - for repo in blended["repos"]: - input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo")) - - for infile in input_files: - if infile.find("ks_mirror") == -1: - dispname = infile.split("/")[-2] - else: - dispname = infile.split("/")[-1].replace(".repo","") - confdir = os.path.join(self.settings.webdir, outseg) - outdir = os.path.join(confdir, blended["name"]) - utils.mkdir(outdir) - try: - infile_h = open(infile) - except: - print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname - continue - infile_data = infile_h.read() - infile_h.close() - outfile = os.path.join(outdir, "%s.repo" % (dispname)) - self.templar.render(infile_data, blended, outfile, None) - - - def write_all_system_files(self,system): - - profile = system.get_conceptual_parent() - if profile is None: - raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile}) - distro = profile.get_conceptual_parent() - if distro is None: - raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro}) - - # this used to just generate a single PXE config file, but now must - # generate one record for each described NIC ... - - counter = 0 - for (name,interface) in system.interfaces.iteritems(): - - ip = interface["ip_address"] - - f1 = utils.get_config_filename(system,interface=name) - - # for tftp only ... - if distro.arch in [ "x86", "x86_64", "standard"]: - # pxelinux wants a file named $name under pxelinux.cfg - f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1) - if distro.arch == "ia64": - # elilo expects files to be named "$name.conf" in the root - # and can not do files based on the MAC address - if ip is not None and ip != "": - print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name - - filename = "%s.conf" % utils.get_config_filename(system,interface=name) - f2 = os.path.join(self.bootloc, filename) - - f3 = os.path.join(self.settings.webdir, "systems", f1) - - if system.netboot_enabled and system.is_pxe_supported(): - if distro.arch in [ "x86", "x86_64", "standard"]: - self.write_pxe_file(f2,system,profile,distro,False) - if distro.arch == "ia64": - self.write_pxe_file(f2,system,profile,distro,True) - else: - # ensure the file doesn't exist - utils.rmfile(f2) - - counter = counter + 1 - - - def make_pxe_menu(self): - # only do this if there is NOT a system named default. - default = self.systems.find(name="default") - if default is not None: - return - - fname = os.path.join(self.bootloc, "pxelinux.cfg", "default") - - # read the default template file - template_src = open("/etc/cobbler/pxedefault.template") - template_data = template_src.read() - - # sort the profiles - profile_list = [profile for profile in self.profiles] - def sort_name(a,b): - return cmp(a.name,b.name) - profile_list.sort(sort_name) - - # build out the menu entries - pxe_menu_items = "" - for profile in profile_list: - distro = profile.get_conceptual_parent() - contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False) - if contents is not None: - pxe_menu_items = pxe_menu_items + contents + "\n" - - # if we have any memtest files in images, make entries for them - # after we list the profiles - memtests = glob.glob(self.bootloc + "/images/memtest*") - if len(memtests) > 0: - pxe_menu_items = pxe_menu_items + "\n\n" - for memtest in glob.glob(self.bootloc + '/memtest*'): - base = os.path.basename(memtest) - contents = self.write_memtest_pxe("/images/%s" % base) - pxe_menu_items = pxe_menu_items + contents + "\n" - - # save the template. - metadata = { "pxe_menu_items" : pxe_menu_items } - outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") - self.templar.render(template_data, metadata, outfile, None) - template_src.close() - - def write_memtest_pxe(self,filename): - """ - Write a configuration file for memtest - """ - - # just some random variables - template = None - metadata = {} - buffer = "" - - template = "/etc/cobbler/pxeprofile.template" - - # store variables for templating - metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename) - metadata["profile_name"] = os.path.basename(filename) - metadata["kernel_path"] = "/images/%s" % os.path.basename(filename) - metadata["initrd_path"] = "" - metadata["append_line"] = "" - - # get the template - template_fh = open(template) - template_data = template_fh.read() - template_fh.close() - - # return results - buffer = self.templar.render(template_data, metadata, None) - return buffer - - - - def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True): - """ - Write a configuration file for the boot loader(s). - More system-specific configuration may come in later, if so - that would appear inside the system object in api.py - - NOTE: relevant to tftp only - """ - - # --- - # system might have netboot_enabled set to False (see item_system.py), if so, - # don't do anything else and flag the error condition. - if system is not None and not system.netboot_enabled: - return None - - # --- - # just some random variables - template = None - metadata = {} - buffer = "" - - # --- - # find kernel and initrd - kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel)) - initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd)) - - # Find the kickstart if we inherit from another profile - kickstart_path = utils.blender(self.api, True, profile)["kickstart"] - - # --- - # choose a template - if system is None: - template = "/etc/cobbler/pxeprofile.template" - elif not is_ia64: - template = "/etc/cobbler/pxesystem.template" - else: - template = "/etc/cobbler/pxesystem_ia64.template" - - # now build the kernel command line - if system is not None: - blended = utils.blender(self.api, True, system) - else: - blended = utils.blender(self.api, True,profile) - kopts = blended["kernel_options"] - - # generate the append line - append_line = "append %s" % utils.hash_to_string(kopts) - if not is_ia64: - append_line = "%s initrd=%s" % (append_line, initrd_path) - if len(append_line) >= 255 + len("append "): - print _("warning: kernel option length exceeds 255") - - # kickstart path rewriting (get URLs for local files) - if kickstart_path is not None and kickstart_path != "": - - if system is not None and kickstart_path.startswith("/"): - kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) - elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: - kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) - - if distro.breed is None or distro.breed == "redhat": - append_line = "%s ks=%s" % (append_line, kickstart_path) - elif distro.breed == "suse": - append_line = "%s autoyast=%s" % (append_line, kickstart_path) - elif distro.breed == "debian": - append_line = "%s auto=true url=%s" % (append_line, kickstart_path) - append_line = append_line.replace("ksdevice","interface") - - # store variables for templating - metadata["menu_label"] = "" - if not is_ia64 and system is None: - metadata["menu_label"] = "MENU LABEL %s" % profile.name - metadata["profile_name"] = profile.name - metadata["kernel_path"] = kernel_path - metadata["initrd_path"] = initrd_path - metadata["append_line"] = append_line - - # get the template - template_fh = open(template) - template_data = template_fh.read() - template_fh.close() - - # save file and/or return results, depending on how called. - buffer = self.templar.render(template_data, metadata, None) - if filename is not None: - fd = open(filename, "w") - fd.write(buffer) - fd.close() - return buffer - - - diff --git a/cobbler/dhcpgen.py b/cobbler/dhcpgen.py new file mode 100644 index 0000000..0ec3dda --- /dev/null +++ b/cobbler/dhcpgen.py @@ -0,0 +1,196 @@ +""" +Builds out DHCP info +This is the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + +class DHCPGen: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config,verbose=False): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + + def write_dhcp_file(self): + """ + DHCP files are written when manage_dhcp is set in + /var/lib/cobbler/settings. + """ + + settings_file = self.settings.dhcpd_conf + template_file = "/etc/cobbler/dhcp.template" + mode = self.settings.manage_dhcp_mode.lower() + if mode == "dnsmasq": + settings_file = self.settings.dnsmasq_conf + template_file = "/etc/cobbler/dnsmasq.template" + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error writing template to file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + # build each per-system definition + # as configured, this only works for ISC, patches accepted + # from those that care about Itanium. elilo seems to be unmaintained + # so additional maintaince in other areas may be required to keep + # this working. + + elilo = os.path.basename(self.settings.bootloaders["ia64"]) + + system_definitions = {} + counter = 0 + + # we used to just loop through each system, but now we must loop + # through each network interface of each system. + + for system in self.systems: + profile = system.get_conceptual_parent() + distro = profile.get_conceptual_parent() + for (name, interface) in system.interfaces.iteritems(): + + mac = interface["mac_address"] + ip = interface["ip_address"] + host = interface["hostname"] + + if mac is None or mac == "": + # can't write a DHCP entry for this system + continue + + counter = counter + 1 + systxt = "" + + if mode == "isc": + + # the label the entry after the hostname if possible + if host is not None and host != "": + systxt = "\nhost %s {\n" % host + if self.settings.isc_set_host_name: + systxt = systxt + " option host-name = %s;\n" % host + else: + systxt = "\nhost generic%d {\n" % counter + + if distro.arch == "ia64": + # can't use pxelinux.0 anymore + systxt = systxt + " filename \"/%s\";\n" % elilo + systxt = systxt + " hardware ethernet %s;\n" % mac + if ip is not None and ip != "": + systxt = systxt + " fixed-address %s;\n" % ip + systxt = systxt + "}\n" + + else: + # dnsmasq. don't have to write IP and other info here, but we do tag + # each MAC based on the arch of it's distro, if it needs something other + # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq + # reload (full "cobbler sync") would be required after adding the system + # to cobbler, just to tag this relationship. + + if ip is not None and ip != "": + if distro.arch.lower() == "ia64": + systxt = "dhcp-host=net:ia64," + ip + "\n" + # support for other arches needs modifications here + else: + systxt = "" + + dhcp_tag = interface["dhcp_tag"] + if dhcp_tag == "": + dhcp_tag = "default" + + if not system_definitions.has_key(dhcp_tag): + system_definitions[dhcp_tag] = "" + system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt + + # we are now done with the looping through each interface of each system + + metadata = { + "insert_cobbler_system_definitions" : system_definitions.get("default",""), + "date" : time.asctime(time.gmtime()), + "cobbler_server" : self.settings.server, + "next_server" : self.settings.next_server, + "elilo" : elilo + } + + # now add in other DHCP expansions that are not tagged with "default" + for x in system_definitions.keys(): + if x == "default": + continue + metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] + + self.templar.render(template_data, metadata, settings_file, None) + + def regen_ethers(self): + # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date + # every time we add a system. + # read 'man ethers' for format info + fh = open("/etc/ethers","w+") + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + mac = interface["mac_address"] + ip = interface["ip_address"] + if mac is None or mac == "": + # can't write this w/o a MAC address + continue + if ip is not None and ip != "": + fh.write(mac.upper() + "\t" + ip + "\n") + fh.close() + + def regen_hosts(self): + # dnsmasq knows how to read this database for host info + # (other things may also make use of this later) + fh = open("/var/lib/cobbler/cobbler_hosts","w+") + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + mac = interface["mac_address"] + host = interface["hostname"] + ip = interface["ip_address"] + if mac is None or mac == "": + continue + if host is not None and host != "" and ip is not None and ip != "": + fh.write(ip + "\t" + host + "\n") + fh.close() + + diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py new file mode 100644 index 0000000..527b6a2 --- /dev/null +++ b/cobbler/pxegen.py @@ -0,0 +1,322 @@ +""" +Builds out filesystem trees/data based on the object tree. +This is the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +class PXEGen: + """ + Handles building out PXE stuff + """ + + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + self.bootloc = utils.tftpboot_location() + + def copy_bootloaders(self): + """ + Copy bootloaders to the configured tftpboot directory + NOTE: we support different arch's if defined in + /var/lib/cobbler/settings. + """ + for loader in self.settings.bootloaders.keys(): + path = self.settings.bootloaders[loader] + newname = os.path.basename(path) + destpath = os.path.join(self.bootloc, newname) + utils.copyfile(path, destpath) + utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) + + # Copy memtest to tftpboot if package is installed on system + for memtest in glob.glob('/boot/memtest*'): + base = os.path.basename(memtest) + utils.copyfile(memtest,os.path.join(self.bootloc,"images",base)) + + def copy_distros(self): + """ + A distro is a kernel and an initrd. Copy all of them and error + out if any files are missing. The conf file was correct if built + via the CLI or API, though it's possible files have been moved + since or perhaps they reference NFS directories that are no longer + mounted. + + NOTE: this has to be done for both tftp and http methods + """ + # copy is a 4-letter word but tftpboot runs chroot, thus it's required. + for d in self.distros: + print _("sync distro: %s") % d.name + self.copy_single_distro_files(d) + + def copy_single_distro_files(self, d): + for dirtree in [self.bootloc, self.settings.webdir]: + distros = os.path.join(dirtree, "images") + distro_dir = os.path.join(distros,d.name) + utils.mkdir(distro_dir) + kernel = utils.find_kernel(d.kernel) # full path + initrd = utils.find_initrd(d.initrd) # full path + if kernel is None or not os.path.isfile(kernel): + raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name }) + if initrd is None or not os.path.isfile(initrd): + raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name }) + b_kernel = os.path.basename(kernel) + b_initrd = os.path.basename(initrd) + if kernel.startswith(dirtree): + utils.linkfile(kernel, os.path.join(distro_dir, b_kernel)) + else: + utils.copyfile(kernel, os.path.join(distro_dir, b_kernel)) + if initrd.startswith(dirtree): + utils.linkfile(initrd, os.path.join(distro_dir, b_initrd)) + else: + utils.copyfile(initrd, os.path.join(distro_dir, b_initrd)) + + def write_all_system_files(self,system): + + profile = system.get_conceptual_parent() + if profile is None: + raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile}) + distro = profile.get_conceptual_parent() + if distro is None: + raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro}) + + # this used to just generate a single PXE config file, but now must + # generate one record for each described NIC ... + + counter = 0 + for (name,interface) in system.interfaces.iteritems(): + + ip = interface["ip_address"] + + f1 = utils.get_config_filename(system,interface=name) + + # for tftp only ... + if distro.arch in [ "x86", "x86_64", "standard"]: + # pxelinux wants a file named $name under pxelinux.cfg + f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1) + if distro.arch == "ia64": + # elilo expects files to be named "$name.conf" in the root + # and can not do files based on the MAC address + if ip is not None and ip != "": + print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name + + filename = "%s.conf" % utils.get_config_filename(system,interface=name) + f2 = os.path.join(self.bootloc, filename) + + f3 = os.path.join(self.settings.webdir, "systems", f1) + + if system.netboot_enabled and system.is_pxe_supported(): + if distro.arch in [ "x86", "x86_64", "standard"]: + self.write_pxe_file(f2,system,profile,distro,False) + if distro.arch == "ia64": + self.write_pxe_file(f2,system,profile,distro,True) + else: + # ensure the file doesn't exist + utils.rmfile(f2) + + counter = counter + 1 + + + def make_pxe_menu(self): + # only do this if there is NOT a system named default. + default = self.systems.find(name="default") + if default is not None: + return + + fname = os.path.join(self.bootloc, "pxelinux.cfg", "default") + + # read the default template file + template_src = open("/etc/cobbler/pxedefault.template") + template_data = template_src.read() + + # sort the profiles + profile_list = [profile for profile in self.profiles] + def sort_name(a,b): + return cmp(a.name,b.name) + profile_list.sort(sort_name) + + # build out the menu entries + pxe_menu_items = "" + for profile in profile_list: + distro = profile.get_conceptual_parent() + contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False) + if contents is not None: + pxe_menu_items = pxe_menu_items + contents + "\n" + + # if we have any memtest files in images, make entries for them + # after we list the profiles + memtests = glob.glob(self.bootloc + "/images/memtest*") + if len(memtests) > 0: + pxe_menu_items = pxe_menu_items + "\n\n" + for memtest in glob.glob(self.bootloc + '/memtest*'): + base = os.path.basename(memtest) + contents = self.write_memtest_pxe("/images/%s" % base) + pxe_menu_items = pxe_menu_items + contents + "\n" + + # save the template. + metadata = { "pxe_menu_items" : pxe_menu_items } + outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") + self.templar.render(template_data, metadata, outfile, None) + template_src.close() + + def write_memtest_pxe(self,filename): + """ + Write a configuration file for memtest + """ + + # just some random variables + template = None + metadata = {} + buffer = "" + + template = "/etc/cobbler/pxeprofile.template" + + # store variables for templating + metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename) + metadata["profile_name"] = os.path.basename(filename) + metadata["kernel_path"] = "/images/%s" % os.path.basename(filename) + metadata["initrd_path"] = "" + metadata["append_line"] = "" + + # get the template + template_fh = open(template) + template_data = template_fh.read() + template_fh.close() + + # return results + buffer = self.templar.render(template_data, metadata, None) + return buffer + + + + def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True): + """ + Write a configuration file for the boot loader(s). + More system-specific configuration may come in later, if so + that would appear inside the system object in api.py + + NOTE: relevant to tftp only + """ + + # --- + # system might have netboot_enabled set to False (see item_system.py), if so, + # don't do anything else and flag the error condition. + if system is not None and not system.netboot_enabled: + return None + + # --- + # just some random variables + template = None + metadata = {} + buffer = "" + + # --- + # find kernel and initrd + kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel)) + initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd)) + + # Find the kickstart if we inherit from another profile + kickstart_path = utils.blender(self.api, True, profile)["kickstart"] + + # --- + # choose a template + if system is None: + template = "/etc/cobbler/pxeprofile.template" + elif not is_ia64: + template = "/etc/cobbler/pxesystem.template" + else: + template = "/etc/cobbler/pxesystem_ia64.template" + + # now build the kernel command line + if system is not None: + blended = utils.blender(self.api, True, system) + else: + blended = utils.blender(self.api, True,profile) + kopts = blended["kernel_options"] + + # generate the append line + append_line = "append %s" % utils.hash_to_string(kopts) + if not is_ia64: + append_line = "%s initrd=%s" % (append_line, initrd_path) + if len(append_line) >= 255 + len("append "): + print _("warning: kernel option length exceeds 255") + + # kickstart path rewriting (get URLs for local files) + if kickstart_path is not None and kickstart_path != "": + + if system is not None and kickstart_path.startswith("/"): + kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) + elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: + kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) + + if distro.breed is None or distro.breed == "redhat": + append_line = "%s ks=%s" % (append_line, kickstart_path) + elif distro.breed == "suse": + append_line = "%s autoyast=%s" % (append_line, kickstart_path) + elif distro.breed == "debian": + append_line = "%s auto=true url=%s" % (append_line, kickstart_path) + append_line = append_line.replace("ksdevice","interface") + + # store variables for templating + metadata["menu_label"] = "" + if not is_ia64 and system is None: + metadata["menu_label"] = "MENU LABEL %s" % profile.name + metadata["profile_name"] = profile.name + metadata["kernel_path"] = kernel_path + metadata["initrd_path"] = initrd_path + metadata["append_line"] = append_line + + # get the template + template_fh = open(template) + template_data = template_fh.read() + template_fh.close() + + # save file and/or return results, depending on how called. + buffer = self.templar.render(template_data, metadata, None) + if filename is not None: + fd = open(filename, "w") + fd.write(buffer) + fd.close() + return buffer + + + + diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 0eec178..14f06b5 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1208545841.2024181 -__CHEETAH_genTimestamp__ = 'Fri Apr 18 15:10:41 2008' +__CHEETAH_genTime__ = 1208557454.7957201 +__CHEETAH_genTimestamp__ = 'Fri Apr 18 18:24:14 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' diff --git a/cobbler/yumgen.py b/cobbler/yumgen.py new file mode 100644 index 0000000..e39a72e --- /dev/null +++ b/cobbler/yumgen.py @@ -0,0 +1,108 @@ +""" +Builds out filesystem trees/data based on the object tree. +This is the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +class YumGen: + + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + + def retemplate_all_yum_repos(self): + for p in self.profiles: + self.retemplate_yum_repos(p,True) + for system in self.systems: + self.retemplate_yum_repos(system,False) + + def retemplate_yum_repos(self,obj,is_profile): + """ + Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo + and also potentially in listed in the source_repos structure of the distro object, however + these files have server URLs in them that must be templated out. This function does this. + """ + blended = utils.blender(self.api, False, obj) + + if is_profile: + outseg = "repos_profile" + else: + outseg = "repos_system" + + input_files = [] + + # chance old versions from upgrade do not have a source_repos + # workaround for user bug + if not blended.has_key("source_repos"): + blended["source_repos"] = [] + + # tack on all the install source repos IF there is more than one. + # this is basically to support things like RHEL5 split trees + # if there is only one, then there is no need to do this. + + for r in blended["source_repos"]: + filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:]) + input_files.append(filename) + + for repo in blended["repos"]: + input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo")) + + for infile in input_files: + if infile.find("ks_mirror") == -1: + dispname = infile.split("/")[-2] + else: + dispname = infile.split("/")[-1].replace(".repo","") + confdir = os.path.join(self.settings.webdir, outseg) + outdir = os.path.join(confdir, blended["name"]) + utils.mkdir(outdir) + try: + infile_h = open(infile) + except: + print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname + continue + infile_data = infile_h.read() + infile_h.close() + outfile = os.path.join(outdir, "%s.repo" % (dispname)) + self.templar.render(infile_data, blended, outfile, None) + + -- cgit From 91f390fbd37b6e185342bda84dfa5179d46bf7d8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 21 Apr 2008 10:49:35 -0400 Subject: Add isc_set_hostname to packaged settings file --- config/settings | 1 + 1 file changed, 1 insertion(+) diff --git a/config/settings b/config/settings index 10e06e2..0a3aaa4 100644 --- a/config/settings +++ b/config/settings @@ -16,6 +16,7 @@ dnsmasq_bin: /usr/sbin/dnsmasq dnsmasq_conf: /etc/dnsmasq.conf httpd_bin: /usr/sbin/httpd http_port: 80 +isc_set_host_name: 0 kerberos_realm: 'example.org' kernel_options: ksdevice: eth0 -- cgit From 6284e8477856ec3b84e06d5e01c05ea226e33a2f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 21 Apr 2008 11:03:42 -0400 Subject: Fix memtest location --- cobbler/pxegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index 527b6a2..e39fdc8 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -186,7 +186,7 @@ class PXEGen: memtests = glob.glob(self.bootloc + "/images/memtest*") if len(memtests) > 0: pxe_menu_items = pxe_menu_items + "\n\n" - for memtest in glob.glob(self.bootloc + '/memtest*'): + for memtest in glob.glob(self.bootloc + '/images/memtest*'): base = os.path.basename(memtest) contents = self.write_memtest_pxe("/images/%s" % base) pxe_menu_items = pxe_menu_items + contents + "\n" -- cgit From 54f590b14b728a7554a879870ef7c65b00d9ffb0 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 21 Apr 2008 14:42:38 -0400 Subject: Correct URLs in kickstart post --- cobbler/kickgen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index dce33f3..b0d0a62 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -96,8 +96,8 @@ class KickGen: nopxe = "\nwget \"http://%s/cblr/svc/?op=nopxe&system=%s\" -O /dev/null" saveks = "\nwget \"http://%s/cblr/svc/?op=ks&%s=%s\" -O /root/cobbler.ks" - runpost = "\nwget \"http://%s/cblr/srv/?op=trig&?mode=post&%s=%s\" -O /dev/null" - runpre = "\nwget \"http://%s/cblr/srv/?op=trig&?mode=pre&%s=%s\" -O /dev/null" + runpost = "\nwget \"http://%s/cblr/svc/?op=trig&?mode=post&%s=%s\" -O /dev/null" + runpre = "\nwget \"http://%s/cblr/svc/?op=trig&?mode=pre&%s=%s\" -O /dev/null" what = "profile" blend_this = profile -- cgit From 6e65a221533c36ffb8b3bb5bc5338944dd0523fd Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 21 Apr 2008 16:51:38 -0400 Subject: Send registration parameters at the right spot. --- cobbler/kickgen.py | 4 ++-- cobbler/pxegen.py | 2 ++ cobbler/remote.py | 2 +- config/settings | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index b0d0a62..1b1cfdd 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -96,8 +96,8 @@ class KickGen: nopxe = "\nwget \"http://%s/cblr/svc/?op=nopxe&system=%s\" -O /dev/null" saveks = "\nwget \"http://%s/cblr/svc/?op=ks&%s=%s\" -O /root/cobbler.ks" - runpost = "\nwget \"http://%s/cblr/svc/?op=trig&?mode=post&%s=%s\" -O /dev/null" - runpre = "\nwget \"http://%s/cblr/svc/?op=trig&?mode=pre&%s=%s\" -O /dev/null" + runpost = "\nwget \"http://%s/cblr/svc/?op=trig&mode=post&%s=%s\" -O /dev/null" + runpre = "\nwget \"http://%s/cblr/svc/?op=trig&mode=pre&%s=%s\" -O /dev/null" what = "profile" blend_this = profile diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index e39fdc8..a8a9af6 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -286,6 +286,8 @@ class PXEGen: kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) + if self.settings.register_new_installs: + kickstart_path = kickstart_path + "®=1" if distro.breed is None or distro.breed == "redhat": append_line = "%s ks=%s" % (append_line, kickstart_path) diff --git a/cobbler/remote.py b/cobbler/remote.py index a87355b..17be852 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -201,7 +201,7 @@ class CobblerXMLRPCInterface: READ: https://fedorahosted.org/cobbler/wiki/AutoRegistration """ - if not self.api.settings().allow_cgi_mac_registration: + if not self.api.settings().register_new_installs: return 1 system = self.api.find_system(mac_address=mac) diff --git a/config/settings b/config/settings index 0a3aaa4..10e06e2 100644 --- a/config/settings +++ b/config/settings @@ -16,7 +16,6 @@ dnsmasq_bin: /usr/sbin/dnsmasq dnsmasq_conf: /etc/dnsmasq.conf httpd_bin: /usr/sbin/httpd http_port: 80 -isc_set_host_name: 0 kerberos_realm: 'example.org' kernel_options: ksdevice: eth0 -- cgit From d02bc788cf637b3e5f8805c03d800170f7e22c32 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 21 Apr 2008 17:27:35 -0400 Subject: Modify services.py to send over registration bit. --- cobbler/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/services.py b/cobbler/services.py index 9ced0c6..fde62bb 100644 --- a/cobbler/services.py +++ b/cobbler/services.py @@ -66,7 +66,7 @@ class CobblerSvc(object): Generate kickstart files... """ self.__xmlrpc_setup() - return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC) + return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC,reg) def trig(self,mode="?",profile=None,system=None,REMOTE_ADDR=None,**rest): """ -- cgit From 912231cabd05f4b6ffebfde8bce77037281b0f20 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 21 Apr 2008 18:21:39 -0400 Subject: Move registration code to backend only, as users trying out the kickstarts will not be sending mac info and there is not a problem of them accidentally getting registered. --- cobbler/pxegen.py | 2 -- cobbler/remote.py | 79 +++-------------------------------------------------- cobbler/services.py | 4 +-- 3 files changed, 6 insertions(+), 79 deletions(-) diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index a8a9af6..e39fdc8 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -286,8 +286,6 @@ class PXEGen: kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) - if self.settings.register_new_installs: - kickstart_path = kickstart_path + "®=1" if distro.breed is None or distro.breed == "redhat": append_line = "%s ks=%s" % (append_line, kickstart_path) diff --git a/cobbler/remote.py b/cobbler/remote.py index 17be852..3ad2277 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -18,7 +18,7 @@ import socket import time import os import SimpleXMLRPCServer -from utils import _ +from rhpl.translate import _, N_, textdomain, utf8 import xmlrpclib import random import base64 @@ -158,10 +158,10 @@ class CobblerXMLRPCInterface: return self._fix_none(data) - def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,reg=None): + def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None): self.log("generate_kickstart") - if reg is not None and profile and not system: + if profile and not system: regrc = self.register_mac(REMOTE_MAC,profile) return self.api.generate_kickstart(profile,system) @@ -986,74 +986,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def remove_profile(self,name,token,recursive=1): """ Deletes a profile from a collection. Note that this just requires the name - of the profile, not a handle. - """ - self.log("remove_profile",name=name,token=token) - self.check_access(token, "remove_profile", name) - rc = self.api._config.profiles().remove(name,recursive=True) - return rc - - def remove_system(self,name,token): - """ - Deletes a system from a collection. Note that this just requires the name - of the system, not a handle. - """ - self.log("remove_system",name=name,token=token) - self.check_access(token, "remove_system", name) - rc = self.api._config.systems().remove(name) - return rc - - def remove_repo(self,name,token): - """ - Deletes a repo from a collection. Note that this just requires the name - of the repo, not a handle. - """ - self.log("remove_repo",name=name,token=token) - self.check_access(token, "remove_repo", name) - rc = self.api._config.repos().remove(name) - return rc - - def sync(self,token): - """ - Applies changes in Cobbler to the filesystem. - Editing a leaf-node object (like a system) does not require - this, but if updating a upper-level object or a kickstart file, - running sync at the end of operations is a good idea. A typical - cobbler sync may take anywhere between a few seconds and several - minutes, so user interfaces should be programmed accordingly. - Future versions of cobbler may understand how to do a cascade sync - on object edits making explicit calls to sync redundant. - """ - self.log("sync",token=token) - self.check_access(token, "sync") - return self.api.sync() - - def reposync(self,repos=[],token=None): - """ - Updates one or more mirrored yum repositories. - reposync is very slow and probably should not be used - through the XMLRPC API, setting up reposync on nightly cron is better. - """ - self.log("reposync",token=token,name=repos) - self.check_access(token, "reposync", repos) - return self.api.reposync(repos) - - def import_tree(self,mirror_url,mirror_name,network_root=None,token=None): - """ - I'm exposing this in the XMLRPC API for consistancy but as this - can be a very long running operation usage is /not/ recommended. - It would be better to use the CLI. See documentation in api.py. - This command may be removed from the API in a future release. - """ - self.log("import_tree",name=mirror_name,token=token) - self.check_access(token, "import_tree") - return self.api.import_tree(mirror_url,mirror_name,network_root) - - def get_kickstart_templates(self,token): - """ - Returns all of the kickstarts that are in use by the system. - """ - self.log("get_kickstart_templates",token=token) self.check_access(token, "get_kickstart_templates") files = {} for x in self.api.profiles(): @@ -1075,10 +1007,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ self.log("read_or_write_kickstart_template",name=kickstart_file,token=token) - if is_read: - self.check_access(token,"read_kickstart",kickstart_file) - else: - self.check_access(token,"modify_kickstart",kickstart_file) + self.check_access(token,"read_or_write_kickstart_templates",kickstart_file,is_read) if kickstart_file.find("..") != -1 or not kickstart_file.startswith("/"): raise CX(_("tainted file location")) diff --git a/cobbler/services.py b/cobbler/services.py index fde62bb..70153a0 100644 --- a/cobbler/services.py +++ b/cobbler/services.py @@ -61,12 +61,12 @@ class CobblerSvc(object): def index(self,**args): return "no mode specified" - def ks(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,reg=None,**rest): + def ks(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,**rest): """ Generate kickstart files... """ self.__xmlrpc_setup() - return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC,reg) + return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC) def trig(self,mode="?",profile=None,system=None,REMOTE_ADDR=None,**rest): """ -- cgit From 169be68abf25fa66d0a922377d2110d30fc7bc67 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 22 Apr 2008 13:14:40 -0400 Subject: Fix docstring --- cobbler/remote.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index 3ad2277..28041b4 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -986,6 +986,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def remove_profile(self,name,token,recursive=1): """ Deletes a profile from a collection. Note that this just requires the name + """ self.check_access(token, "get_kickstart_templates") files = {} for x in self.api.profiles(): @@ -998,7 +999,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def read_or_write_kickstart_template(self,kickstart_file,is_read,new_data,token): - """ + """ Allows the WebUI to be used as a kickstart file editor. For security reasons we will only allow kickstart files to be edited if they reside in /var/lib/cobbler/kickstarts/ or /etc/cobbler. This limits the damage -- cgit From c382a735c28c3453d181e304078e2f14a9df98c6 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 22 Apr 2008 17:27:48 -0400 Subject: Some fixes in reg code --- cobbler/item.py | 5 +++-- cobbler/remote.py | 9 ++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cobbler/item.py b/cobbler/item.py index 2549dce..bdd7c8b 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -198,8 +198,9 @@ class Item(serializable.Serializable): if key in [ "mac_address", "ip_address", "subnet", "gateway", "virt_bridge", "dhcp_tag", "hostname" ]: key_found_already = True for (name, interface) in data["interfaces"].iteritems(): - if interface[key].lower() == value.lower(): - return True + if value is not None: + if interface[key].lower() == value.lower(): + return True if not data.has_key(key): if not key_found_already: diff --git a/cobbler/remote.py b/cobbler/remote.py index 28041b4..d9ec275 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -201,14 +201,17 @@ class CobblerXMLRPCInterface: READ: https://fedorahosted.org/cobbler/wiki/AutoRegistration """ - if not self.api.settings().register_new_installs: + if mac is None: return 1 + if not self.api.settings().register_new_installs: + return 2 + system = self.api.find_system(mac_address=mac) if system is not None: - return 2 + return 3 - obj = server.new_system(token) + obj = self.api.new_system() obj.set_profile(profile) obj.set_name(mac.replace(":","_")) obj.set_mac_address(mac, "intf0") -- cgit From 11d6201018d14319f8227f67ccaed575e8ef35d8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 22 Apr 2008 17:47:39 -0400 Subject: Auto registration now tested and confirmed to work (if you have it enabled in settings) --- cobbler/item_system.py | 2 +- cobbler/remote.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 73eda71..1bcc111 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -157,7 +157,7 @@ class System(item.Item): raise CX(_("name must be a string")) for x in name: if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] : - raise CX(_("invalid characters in name")) + raise CX(_("invalid characters in name: %s") % x) if utils.is_mac(name): if intf["mac_address"] == "": diff --git a/cobbler/remote.py b/cobbler/remote.py index d9ec275..c25e700 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -192,7 +192,7 @@ class CobblerXMLRPCInterface: self.api.add_system(system) - def register_mac(self,mac,token=None): + def register_mac(self,mac,profile,token=None): """ If allow_cgi_register_mac is enabled in settings, this allows kickstarts to add new system records for per-profile-provisioned @@ -202,20 +202,30 @@ class CobblerXMLRPCInterface: """ if mac is None: + # don't go further if not being called by anaconda return 1 if not self.api.settings().register_new_installs: + # must be enabled in settings return 2 system = self.api.find_system(mac_address=mac) - if system is not None: + if system is not None: + # do not allow overwrites return 3 + # the MAC probably looks like "eth0 AA:BB:CC:DD:EE:FF" now, fix it + if mac.find(" ") != -1: + mac = mac.split()[-1] + + self.log("register mac for profile %s" % profile,token=token,name=mac) obj = self.api.new_system() obj.set_profile(profile) - obj.set_name(mac.replace(":","_")) + name = mac.replace(":","_") + obj.set_name(name) obj.set_mac_address(mac, "intf0") - systems.add(obj,save=True) + obj.set_netboot_enabled(False) + self.api.add_system(obj) return 0 def disable_netboot(self,name,token=None): -- cgit From dc8db8a8924503232860939eb2e8d151ebea5f9f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 12:23:44 -0400 Subject: Add "dumpvars" command. --- CHANGELOG | 1 + cobbler/api.py | 3 +++ cobbler/commands.py | 17 +++++++++++++-- cobbler/item.py | 8 +++++++ cobbler/modules/cli_distro.py | 37 +++++++++++++++++---------------- cobbler/modules/cli_profile.py | 47 +++++++++++++++++++++--------------------- cobbler/modules/cli_repo.py | 16 +++++++------- cobbler/modules/cli_system.py | 14 +++++++------ 8 files changed, 86 insertions(+), 57 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a97dd5e..4396f2d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ Cobbler CHANGELOG - kickstart templates are now evaluated dynamically - optional MAC registration is now built-in to requesting kickstarts - legacy static file generation from /var/www/cobbler removed +- implement "cobbler ___ dumpvars --name=X" feature to show template vars - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/api.py b/cobbler/api.py index ebe987e..1814c41 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -251,6 +251,9 @@ class BootAPI: def find_repo(self, name=None, return_list=False, **kargs): return self._config.repos().find(name=name, return_list=return_list, **kargs) + def dump_vars(self, obj, format=False): + return obj.dump_vars(format) + def auto_add_repos(self): """ Import any repos this server knows about and mirror them. diff --git a/cobbler/commands.py b/cobbler/commands.py index 7a6a25b..657934c 100644 --- a/cobbler/commands.py +++ b/cobbler/commands.py @@ -195,6 +195,14 @@ class CobblerFunction: Boilerplate for objects that offer add/edit/delete/remove/copy functionality. """ + if "dumpvars" in self.args: + if not self.options.name: + raise CX(_("name is required")) + obj = collect_fn().find(self.options.name) + if obj is None: + raise CX(_("object not found")) + return obj + if "remove" in self.args: recursive = False # only applies to distros/profiles and is not supported elsewhere @@ -219,11 +227,12 @@ class CobblerFunction: self.reporting_list_names2(collect_fn(),self.options.name) return None + if not self.options.name: + raise CX(_("name is required")) + if "add" in self.args: obj = new_fn(is_subobject=subobject) else: - if not self.options.name: - raise CX(_("name is required")) if "delete" in self.args: collect_fn().remove(self.options.name, with_delete=True) return None @@ -241,6 +250,10 @@ class CobblerFunction: Boilerplate for objects that offer add/edit/delete/remove/copy functionality. """ + if "dumpvars" in self.args: + print obj.dump_vars(True) + return True + clobber = False if "add" in self.args: clobber = options.clobber diff --git a/cobbler/item.py b/cobbler/item.py index bdd7c8b..992a18d 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -17,6 +17,7 @@ import serializable import utils from cexceptions import * from utils import _ +import pprint class Item(serializable.Serializable): @@ -212,3 +213,10 @@ class Item(serializable.Serializable): else: return False + def dump_vars(self,data,format=True): + raw = utils.blender(self.config.api, False, self) + if format: + return pprint.pformat(raw) + else: + return raw + diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py index 4d86448..b7a8fa7 100644 --- a/cobbler/modules/cli_distro.py +++ b/cobbler/modules/cli_distro.py @@ -33,16 +33,16 @@ class DistroFunction(commands.CobblerFunction): return "distro" def subcommands(self): - return [ "add", "edit", "copy", "rename", "remove", "list", "report" ] + return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ] def add_options(self, p, args): - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--arch", dest="arch", help="ex: x86, x86_64, ia64") p.add_option("--breed", dest="breed", help="ex: redhat, debian, suse") if self.matches_args(args,["add"]): p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--initrd", dest="initrd", help="absolute path to initrd.img (REQUIRED)") p.add_option("--kernel", dest="kernel", help="absolute path to vmlinuz (REQUIRED)") p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'") @@ -54,11 +54,11 @@ class DistroFunction(commands.CobblerFunction): if self.matches_args(args,["copy","rename"]): p.add_option("--newname", dest="newname", help="for copy/rename commands") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") - if not self.matches_args(args,["report","list"]): + if not self.matches_args(args,["dumpvars","report","list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") if self.matches_args(args,["remove"]): @@ -70,18 +70,19 @@ class DistroFunction(commands.CobblerFunction): if obj is None: return True - if self.options.kernel: - obj.set_kernel(self.options.kernel) - if self.options.initrd: - obj.set_initrd(self.options.initrd) - if self.options.kopts: - obj.set_kernel_options(self.options.kopts) - if self.options.ksmeta: - obj.set_ksmeta(self.options.ksmeta) - if self.options.breed: - obj.set_breed(self.options.breed) - if self.options.owners: - obj.set_owners(self.options.owners) + if not "dumpvars" in self.args: + if self.options.kernel: + obj.set_kernel(self.options.kernel) + if self.options.initrd: + obj.set_initrd(self.options.initrd) + if self.options.kopts: + obj.set_kernel_options(self.options.kopts) + if self.options.ksmeta: + obj.set_ksmeta(self.options.ksmeta) + if self.options.breed: + obj.set_breed(self.options.breed) + if self.options.owners: + obj.set_owners(self.options.owners) return self.object_manipulator_finish(obj, self.api.distros, self.options) diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py index b543b80..45b7b2e 100644 --- a/cobbler/modules/cli_profile.py +++ b/cobbler/modules/cli_profile.py @@ -33,7 +33,7 @@ class ProfileFunction(commands.CobblerFunction): return "profile" def subcommands(self): - return [ "add", "edit", "copy", "rename", "remove", "list", "report" ] + return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ] def add_options(self, p, args): @@ -41,7 +41,7 @@ class ProfileFunction(commands.CobblerFunction): p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--distro", dest="distro", help="ex: 'RHEL-5-i386' (REQUIRED)") p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configuration") @@ -55,16 +55,16 @@ class ProfileFunction(commands.CobblerFunction): if "copy" in args or "rename" in args: p.add_option("--newname", dest="newname") - if not self.matches_args(args,["remove","report", "list"]): + if not self.matches_args(args,["dumpvars","remove","report", "list"]): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") - if not self.matches_args(args,["report", "list"]): + if not self.matches_args(args,["dumpvars","report", "list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") if self.matches_args(args,["remove"]): p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--repos", dest="repos", help="names of cobbler repos") p.add_option("--server-override", dest="server_override", help="overrides value in settings file") p.add_option("--virt-bridge", dest="virt_bridge", help="ex: 'virbr0'") @@ -76,8 +76,7 @@ class ProfileFunction(commands.CobblerFunction): def run(self): - - if self.matches_args(self.args,["report","list","remove"]) or not self.options.inherit: + if self.matches_args(self.args,["report","list","remove","dumpvars"]) or not self.options.inherit: obj = self.object_manipulator_start(self.api.new_profile,self.api.profiles,subobject=False) else: obj = self.object_manipulator_start(self.api.new_profile,self.api.profiles,subobject=True) @@ -85,23 +84,23 @@ class ProfileFunction(commands.CobblerFunction): if obj is None: return True - if self.options.inherit: obj.set_parent(self.options.inherit) - if self.options.distro: obj.set_distro(self.options.distro) - if self.options.kickstart: obj.set_kickstart(self.options.kickstart) - if self.options.kopts: obj.set_kernel_options(self.options.kopts) - if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta) - if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size) - if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram) - if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge) - if self.options.virt_type: obj.set_virt_type(self.options.virt_type) - if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus) - if self.options.repos: obj.set_repos(self.options.repos) - if self.options.virt_path: obj.set_virt_path(self.options.virt_path) - if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag) - if self.options.server_override: obj.set_server(self.options.server) - - if self.options.owners: - obj.set_owners(self.options.owners) + if not self.matches_args(self.args,["dumpvars"]): + if self.options.inherit: obj.set_parent(self.options.inherit) + if self.options.distro: obj.set_distro(self.options.distro) + if self.options.kickstart: obj.set_kickstart(self.options.kickstart) + if self.options.kopts: obj.set_kernel_options(self.options.kopts) + if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta) + if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size) + if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram) + if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge) + if self.options.virt_type: obj.set_virt_type(self.options.virt_type) + if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus) + if self.options.repos: obj.set_repos(self.options.repos) + if self.options.virt_path: obj.set_virt_path(self.options.virt_path) + if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag) + if self.options.server_override: obj.set_server(self.options.server) + + if self.options.owners: obj.set_owners(self.options.owners) return self.object_manipulator_finish(obj, self.api.profiles, self.options) diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py index a567d2d..f31ab26 100644 --- a/cobbler/modules/cli_repo.py +++ b/cobbler/modules/cli_repo.py @@ -33,23 +33,23 @@ class RepoFunction(commands.CobblerFunction): return "repo" def subcommands(self): - return [ "add", "edit", "copy", "rename", "remove", "list", "report" ] + return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ] def add_options(self, p, args): - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--arch", dest="arch", help="overrides repo arch if required") if self.matches_args(args,["add"]): p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--createrepo-flags", dest="createrepo_flags", help="additional flags for createrepo") p.add_option("--keep-updated", dest="keep_updated", help="update on each reposync, yes/no") p.add_option("--name", dest="name", help="ex: 'Fedora-8-updates-i386' (REQUIRED)") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--mirror", dest="mirror", help="source to mirror (REQUIRED)") p.add_option("--priority", dest="priority", help="set priority") p.add_option("--rpm-list", dest="rpm_list", help="just mirror these rpms") @@ -59,11 +59,11 @@ class RepoFunction(commands.CobblerFunction): p.add_option("--newname", dest="newname", help="used for copy/edit") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") - if not self.matches_args(args,["report","list"]): + if not self.matches_args(args,["dumpvars","report","list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") @@ -72,6 +72,8 @@ class RepoFunction(commands.CobblerFunction): obj = self.object_manipulator_start(self.api.new_repo,self.api.repos) if obj is None: return True + if self.matches_args(self.args,["dumpvars"]): + return self.object_manipulator_finish(obj, self.api.profiles, self.options) if self.options.arch: obj.set_arch(self.options.arch) if self.options.createrepo_flags: obj.set_createrepo_flags(self.options.createrepo_flags) diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 976c380..0c9a5b8 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -33,14 +33,14 @@ class SystemFunction(commands.CobblerFunction): return "system" def subcommands(self): - return [ "add", "edit", "copy", "rename", "remove", "report", "list" ] + return [ "add", "edit", "copy", "rename", "remove", "report", "list", "dumpvars" ] def add_options(self, p, args): if self.matches_args(args,["add"]): p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configurations") p.add_option("--gateway", dest="gateway", help="for static IP / templating usage") p.add_option("--hostname", dest="hostname", help="ex: server.example.org") @@ -53,19 +53,19 @@ class SystemFunction(commands.CobblerFunction): p.add_option("--name", dest="name", help="a name for the system (REQUIRED)") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--netboot-enabled", dest="netboot_enabled", help="PXE on (1) or off (0)") if self.matches_args(args,["copy","rename"]): p.add_option("--newname", dest="newname", help="for use with copy/edit") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed") - if not self.matches_args(args,["report","list"]): + if not self.matches_args(args,["dumpvars","report","list"]): p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution") - if not self.matches_args(args,["remove","report","list"]): + if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module") p.add_option("--profile", dest="profile", help="name of cobbler profile (REQUIRED)") p.add_option("--server-override", dest="server_override", help="overrides server value in settings file") @@ -80,6 +80,8 @@ class SystemFunction(commands.CobblerFunction): obj = self.object_manipulator_start(self.api.new_system,self.api.systems) if obj is None: return True + if self.matches_args(self.args,["dumpvars"]): + return self.object_manipulator_finish(obj, self.api.profiles, self.options) if self.options.profile: obj.set_profile(self.options.profile) if self.options.kopts: obj.set_kernel_options(self.options.kopts) -- cgit From 7ca984b22725d19890c60423e5156aaa179809ad Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 12:36:10 -0400 Subject: Fix exception processing bug for older broken Python --- cobbler/cobbler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index c164244..5cd0c2e 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -56,7 +56,7 @@ def main(): except SystemExit: pass # probably exited from optparse, nothing extra to print except Exception, exc2: - if str(type(exc2)).find("CX") == -1: + if str(type(exc2)).find("CX") != -1: traceback.print_exc() else: print str(exc2)[1:-1] # remove framing air quotes -- cgit From a3583f38b6bf136a683b14394ee24988a8f206ca Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 13:16:55 -0400 Subject: validateks now works against all URLs, not just local files --- CHANGELOG | 1 + cobbler/action_validate.py | 46 +++++++++++++++++++++++++++++----------------- cobbler/cobbler.py | 2 +- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4396f2d..2e92fea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ Cobbler CHANGELOG - optional MAC registration is now built-in to requesting kickstarts - legacy static file generation from /var/www/cobbler removed - implement "cobbler ___ dumpvars --name=X" feature to show template vars +- validateks now works against all URLs as opposed to rendered local files - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_validate.py b/cobbler/action_validate.py index 8ede60c..44f3a9a 100644 --- a/cobbler/action_validate.py +++ b/cobbler/action_validate.py @@ -16,6 +16,7 @@ import os import re import sub_process from utils import _ +import utils class Validate: @@ -39,16 +40,10 @@ class Validate: failed = False for x in self.config.profiles(): - distro = x.get_conceptual_parent() - if distro.breed != "redhat": - continue - if not self.checkfile(x.name, "%s/kickstarts/%s/ks.cfg" % (self.settings.webdir, x.name)): + if not self.checkfile(x, True): failed = True for x in self.config.systems(): - distro = x.get_conceptual_parent().get_conceptual_parent() - if distro.breed != "redhat": - continue - if not self.checkfile(x.name, "%s/kickstarts_sys/%s/ks.cfg" % (self.settings.webdir, x.name)): + if not self.checkfile(x, False): failed = True if failed: @@ -58,15 +53,32 @@ class Validate: return failed - def checkfile(self,name,file): - # print _("scanning rendered kickstart template: %s" % file) - if not os.path.exists(file): - print _("kickstart file does not exist for: %s") % name - return False - rc = os.system("/usr/bin/ksvalidator %s" % file) - if not rc == 0: - print _("ksvalidator detected a possible problem for: %s") % name - print _(" rendered kickstart template at: %s" % file) + def checkfile(self,obj,is_profile): + blended = utils.blender(self.config.api, False, obj) + ks = blended["kickstart"] + breed = blended["breed"] + if breed != "redhat": + print "%s has a breed of %s, skipping" % (obj.name, breed) + return True + if ks is None or ks == "": + print "%s has no kickstart, skipping" % obj.name + return True + + server = blended["server"] + if not ks.startswith("/"): + url = self.kickstart + elif is_profile: + url = "http://%s/cblr/svc/?op=ks;profile=%s" % (server,obj.name) + else: + url = "http://%s/cblr/svc/?op=ks;system=%s" % (server,obj.name) + + print "----------------------------" + print "checking url: %s" % url + + + rc = os.system("/usr/bin/ksvalidator \"%s\"" % url) + if rc != 0: return False + return True diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 5cd0c2e..c164244 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -56,7 +56,7 @@ def main(): except SystemExit: pass # probably exited from optparse, nothing extra to print except Exception, exc2: - if str(type(exc2)).find("CX") != -1: + if str(type(exc2)).find("CX") == -1: traceback.print_exc() else: print str(exc2)[1:-1] # remove framing air quotes -- cgit From 5a53dfb37bbd95aa5c3ae5d67c493c2b1879c8f7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 13:43:16 -0400 Subject: Fix instance checking --- cobbler/cobbler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index c164244..5db40ce 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -56,7 +56,7 @@ def main(): except SystemExit: pass # probably exited from optparse, nothing extra to print except Exception, exc2: - if str(type(exc2)).find("CX") == -1: + if isinstance(exc2, CX) or isinstance(exc2, CobblerException) traceback.print_exc() else: print str(exc2)[1:-1] # remove framing air quotes -- cgit From b93705dc092d8e675bcb0690b29c20dbd2d58b68 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 13:47:58 -0400 Subject: Typo --- cobbler/cobbler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 5db40ce..e8a2ef5 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -56,7 +56,7 @@ def main(): except SystemExit: pass # probably exited from optparse, nothing extra to print except Exception, exc2: - if isinstance(exc2, CX) or isinstance(exc2, CobblerException) + if isinstance(exc2, CX) or isinstance(exc2, CobblerException): traceback.print_exc() else: print str(exc2)[1:-1] # remove framing air quotes -- cgit From aba4879418ece5fb1c1126d45dfefcca73b4abc7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 14:14:54 -0400 Subject: Services operations are now happy with unicode data. --- cobbler/services.py | 5 +++-- scripts/services.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cobbler/services.py b/cobbler/services.py index 70153a0..adaf6fc 100644 --- a/cobbler/services.py +++ b/cobbler/services.py @@ -66,8 +66,9 @@ class CobblerSvc(object): Generate kickstart files... """ self.__xmlrpc_setup() - return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC) - + data = self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC) + return u"%s" % data + def trig(self,mode="?",profile=None,system=None,REMOTE_ADDR=None,**rest): """ Hook to call install triggers. diff --git a/scripts/services.py b/scripts/services.py index 07243ae..2486a4a 100755 --- a/scripts/services.py +++ b/scripts/services.py @@ -60,8 +60,9 @@ def handler(req): content = func( **form ) # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form))) - req.content_type = "text/plain" - req.write(content) + req.content_type = "text/plain;charset=utf-8" + content = unicode(content) + req.write(content.encode('utf-8')) return apache.OK -- cgit From 16359a0f6cef98adf4da1dcd4a402fbdb6751e5d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 14:19:00 -0400 Subject: Apply unicode changes to webui also --- scripts/index.py | 4 ++-- scripts/services.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/index.py b/scripts/index.py index 281e36e..8d10129 100755 --- a/scripts/index.py +++ b/scripts/index.py @@ -122,8 +122,8 @@ def handler(req): content = func( "Invalid Mode: \"%s\"" % mode ) # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form))) - req.content_type = "text/html" - req.write(content) + req.content_type = "text/html;charset=utf-8" + req.write(unicode(content).encode('utf-8')) return apache.OK diff --git a/scripts/services.py b/scripts/services.py index 2486a4a..b41d6a0 100755 --- a/scripts/services.py +++ b/scripts/services.py @@ -61,8 +61,8 @@ def handler(req): # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form))) req.content_type = "text/plain;charset=utf-8" - content = unicode(content) - req.write(content.encode('utf-8')) + content = unicode(content).encode('utf-8') + req.write(content) return apache.OK -- cgit From 5cde562c195f159c460cd2ef181a402eadad3bf2 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 14:19:56 -0400 Subject: changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 2e92fea..ce085f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ Cobbler CHANGELOG - fix for dnsmasq template file host config path - fix dnsmasq template to point at the correct hosts file - force all names to be alphanumeric +- all mod python pieces now happy with Unicode output * Fri Feb 22 2008 - 0.8.2 - fix to webui to allow repos to be edited there on profile page -- cgit From 899a61bdf1a1369c656a9e34513b17f558e864bc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 14:49:47 -0400 Subject: Support "fake" query string URLs to support xend's esoteric parser that doesn't like query strings on the kernel options line. --- scripts/services.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scripts/services.py b/scripts/services.py index b41d6a0..8fcb8f8 100755 --- a/scripts/services.py +++ b/scripts/services.py @@ -33,6 +33,8 @@ def handler(req): """ my_uri = req.uri + + # apache.log_error("cannot load /var/lib/cobbler/web.ss") req.add_common_vars() @@ -42,6 +44,26 @@ def handler(req): form = {} for x in fs.keys(): form[x] = str(fs.get(x,'default')) + + if my_uri.find("?") == -1: + # support fake query strings + # something log /cobbler/web/op/ks/server/foo + # which is needed because of xend parser errors + # not tolerating ";" and also libvirt on 5.1 not + # tolerating "&" (nor "&"). + + tokens = my_uri.split("/") + tokens = tokens[3:] + label = True + field = "" + for t in tokens: + if label: + field = t + apache.log_error("field %s" % field) + else: + form[field] = t + apache.log_error("adding %s to %s" % (field,t)) + label = not label form["REMOTE_ADDR"] = req.subprocess_env.get("REMOTE_ADDR",None) form["REMOTE_MAC"] = req.subprocess_env.get("HTTP_X_RHN_PROVISIONING_MAC_0",None) -- cgit From 7e7533b8011e43f9d380275d91d2b69b2ed36162 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 15:12:33 -0400 Subject: Modify URLs --- cobbler/kickgen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index 1b1cfdd..a5e540e 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -94,10 +94,10 @@ class KickGen: * end: save the original kickstart file for debug """ - nopxe = "\nwget \"http://%s/cblr/svc/?op=nopxe&system=%s\" -O /dev/null" - saveks = "\nwget \"http://%s/cblr/svc/?op=ks&%s=%s\" -O /root/cobbler.ks" - runpost = "\nwget \"http://%s/cblr/svc/?op=trig&mode=post&%s=%s\" -O /dev/null" - runpre = "\nwget \"http://%s/cblr/svc/?op=trig&mode=pre&%s=%s\" -O /dev/null" + nopxe = "\nwget \"http://%s/cblr/svc/op/nopxe/system/%s\" -O /dev/null" + saveks = "\nwget \"http://%s/cblr/svc/op/ks/%s/%s\" -O /root/cobbler.ks" + runpost = "\nwget \"http://%s/cblr/svc/op/trig/mode/post/%s/%s\" -O /dev/null" + runpre = "\nwget \"http://%s/cblr/svc/op/trig/mode/pre/%s/%s\" -O /dev/null" what = "profile" blend_this = profile -- cgit From 7fe5d10a7387fb8f7d972567db54b3e0927081c5 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 23 Apr 2008 15:13:49 -0400 Subject: apply URL changes to PXE also --- cobbler/pxegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index e39fdc8..8cb8c93 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -283,9 +283,9 @@ class PXEGen: if kickstart_path is not None and kickstart_path != "": if system is not None and kickstart_path.startswith("/"): - kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) + kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (blended["http_server"], system.name) elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: - kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) + kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name) if distro.breed is None or distro.breed == "redhat": append_line = "%s ks=%s" % (append_line, kickstart_path) -- cgit From 9f314143b39edfa0943c68158d1ae954af4f4f86 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 24 Apr 2008 13:46:04 -0400 Subject: It's now possible to create new kickstart templates in /var/lib/cobbler/kickstarts/ from the WebUI, as well as delete ones that are no longer being used while on the edit page for that template. --- CHANGELOG | 1 + cobbler/remote.py | 43 ++++++++++++++++++++++++++++++++--- cobbler/webui/CobblerWeb.py | 29 +++++++++++++++++++++--- cobbler/webui/master.py | 16 +++++++++----- setup.py | 1 + webui_templates/ksfile_edit.tmpl | 39 ++++++++++++++++++++++++-------- webui_templates/ksfile_list.tmpl | 3 ++- webui_templates/ksfile_new.tmpl | 48 ++++++++++++++++++++++++++++++++++++++++ webui_templates/master.tmpl | 1 + 9 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 webui_templates/ksfile_new.tmpl diff --git a/CHANGELOG b/CHANGELOG index ce085f1..ed91270 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ Cobbler CHANGELOG - legacy static file generation from /var/www/cobbler removed - implement "cobbler ___ dumpvars --name=X" feature to show template vars - validateks now works against all URLs as opposed to rendered local files +- now possible to create new kickstarts in webui, and delete unused ones - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/remote.py b/cobbler/remote.py index c25e700..a7e056b 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -24,6 +24,7 @@ import random import base64 import string import traceback +import glob import api as cobbler_api import utils @@ -158,6 +159,35 @@ class CobblerXMLRPCInterface: return self._fix_none(data) + def get_kickstart_templates(self,token): + """ + Returns all of the kickstarts that are in use by the system. + """ + self.log("get_kickstart_templates",token=token) + self.check_access(token, "get_kickstart_templates") + files = {} + for x in self.api.profiles(): + if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": + files[x.kickstart] = 1 + for x in self.api.systems(): + if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": + files[x.kickstart] = 1 + for x in glob.glob("/var/lib/cobbler/kickstarts/*"): + files[x] = 1 + + return files.keys() + + def is_kickstart_in_use(self,ks,token): + self.log("is_kickstart_in_use",token=token) + self.check_access(token, "is_kickstart_in_use") + for x in self.api.profiles(): + if x.kickstart is not None and x.kickstart == ks: + return True + for x in self.api.systems(): + if x.kickstart is not None and x.kickstart == ks: + return True + return False + def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None): self.log("generate_kickstart") @@ -1042,9 +1072,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): fileh.close() return data else: - fileh = open(kickstart_file,"w+") - fileh.write(new_data) - fileh.close() + if new_data == -1: + # delete requested + if not self.is_kickstart_in_use(kickstart_file,token): + os.remove(kickstart_file) + else: + raise CX(_("attempt to delete in-use file")) + else: + fileh = open(kickstart_file,"w+") + fileh.write(new_data) + fileh.close() return True diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 628d776..2eeb1a3 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -712,6 +712,20 @@ class CobblerWeb(object): 'ksfiles': self.remote.get_kickstart_templates(self.token) } ) + def ksfile_new(self, name=None,**spam): + + + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + + can_edit = self.remote.check_access_no_fail(self.token,"add_kickstart",name) + return self.__render( 'ksfile_new.tmpl', { + 'editable' : can_edit, + 'ksdata': '' + } ) + + + def ksfile_edit(self, name=None,**spam): @@ -721,18 +735,26 @@ class CobblerWeb(object): can_edit = self.remote.check_access_no_fail(self.token,"modify_kickstart",name) return self.__render( 'ksfile_edit.tmpl', { 'name': name, + 'deleteable' : not self.remote.is_kickstart_in_use(name,self.token), 'editable' : can_edit, 'ksdata': self.remote.read_or_write_kickstart_template(name,True,"",self.token) } ) - def ksfile_save(self, name=None, ksdata=None, **args): + def ksfile_save(self, name=None, ksdata=None, delete1=None, delete2=None, isnew=None, **args): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() + + try: - self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token) + if delete1 and delete2: + self.remote.read_or_write_kickstart_template(name,False,-1,self.token) + if isnew is not None: + name = "/var/lib/cobbler/kickstarts/" + name + if not delete1 and not delete2: + self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token) except Exception, e: return self.error_page("An error occurred while trying to save kickstart file %s:

    %s" % (name,str(e))) - return self.ksfile_edit(name=name) + return self.ksfile_list() # ------------------------------------------------------------------------ # # Miscellaneous @@ -813,6 +835,7 @@ class CobblerWeb(object): settings_view.exposed = True ksfile_edit.exposed = True + ksfile_new.exposed = True ksfile_save.exposed = True ksfile_list.exposed = True diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 14f06b5..22391eb 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,10 +33,10 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1208557454.7957201 -__CHEETAH_genTimestamp__ = 'Fri Apr 18 18:24:14 2008' +__CHEETAH_genTime__ = 1209057202.737108 +__CHEETAH_genTimestamp__ = 'Thu Apr 24 13:13:22 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' -__CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' +__CHEETAH_srcLastModified__ = 'Thu Apr 24 12:59:37 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: @@ -69,7 +69,7 @@ class master(Template): - ## CHEETAH: generated from #block body at line 53, col 1. + ## CHEETAH: generated from #block body at line 54, col 1. trans = KWS.get("trans") if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)): trans = self.transaction # is None unless self.awake() was called @@ -196,11 +196,15 @@ class master(Template):
  • Kickstart
  • +
  • Repo


  • Sync
  • diff --git a/setup.py b/setup.py index eb969d1..91eabed 100644 --- a/setup.py +++ b/setup.py @@ -162,6 +162,7 @@ if __name__ == "__main__": # Web UI kickstart file editing (wwwtmpl, ['webui_templates/ksfile_edit.tmpl']), + (wwwtmpl, ['webui_templates/ksfile_new.tmpl']), (wwwtmpl, ['webui_templates/ksfile_list.tmpl']), # Web UI support files diff --git a/webui_templates/ksfile_edit.tmpl b/webui_templates/ksfile_edit.tmpl index 87c9f41..6303442 100644 --- a/webui_templates/ksfile_edit.tmpl +++ b/webui_templates/ksfile_edit.tmpl @@ -20,25 +20,46 @@ if you need to resolve this.
    - Edit Kickstart File + Edit Kickstart Template + + + + + + + + #if $deleteable + + + #else + + #end if + + +
    + +

    +
    + + + Yes + Really +

    Check both buttons and click save to delete this object

    +
    + NOTE: This kickstart template is currently in-use. +
    #if $editable == True + #end if +
    -#if $editable == True -
    -
    -NOTE: Run a cobbler sync to after making changes here in order -for kickstart files to be regenerated. -
    -#end if - #end block body diff --git a/webui_templates/ksfile_list.tmpl b/webui_templates/ksfile_list.tmpl index dcfaa0a..6ae1a9c 100644 --- a/webui_templates/ksfile_list.tmpl +++ b/webui_templates/ksfile_list.tmpl @@ -3,7 +3,7 @@ #block body - + @@ -12,6 +12,7 @@ #set $evenodd = 1 #for $ksfile in $ksfiles + #if $evenodd % 2 == 0 #set $tr_class = "roweven" #else diff --git a/webui_templates/ksfile_new.tmpl b/webui_templates/ksfile_new.tmpl new file mode 100644 index 0000000..c68bbdd --- /dev/null +++ b/webui_templates/ksfile_new.tmpl @@ -0,0 +1,48 @@ +#extends cobbler.webui.master + +#block body + +#if $editable != True +
    +NOTE: You do not have permission to create new kickstart templates. +
    +
    +#end if + + +
    + Create New Kickstart Template + + + +
    Cobbler Kickstart FilesCobbler Kickstart Templates
    File Edit/View
    + + + + + + + + + +
    + + + Example: foo.ks (to be saved in /var/lib/cobbler/kickstarts/)

    +
    + +
    +
    + +
    + #if $editable == True + + + #end if +
    + +
    + + +#end block body diff --git a/webui_templates/master.tmpl b/webui_templates/master.tmpl index 96fbe75..5e9438f 100644 --- a/webui_templates/master.tmpl +++ b/webui_templates/master.tmpl @@ -43,6 +43,7 @@
  • Profile
  • Subprofile
  • System
  • +
  • Kickstart
  • Repo


  • Sync
  • -- cgit From dd381ffc395bd6c6405e15aa5baeb3d3645dec70 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 28 Apr 2008 09:05:44 -0400 Subject: Quote dhcp hostnames --- cobbler/dhcpgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/dhcpgen.py b/cobbler/dhcpgen.py index 0ec3dda..2d6facf 100644 --- a/cobbler/dhcpgen.py +++ b/cobbler/dhcpgen.py @@ -110,7 +110,7 @@ class DHCPGen: if host is not None and host != "": systxt = "\nhost %s {\n" % host if self.settings.isc_set_host_name: - systxt = systxt + " option host-name = %s;\n" % host + systxt = systxt + " option host-name = \"%s\";\n" % host else: systxt = "\nhost generic%d {\n" % counter -- cgit From 964b3328298dba0d8ea500e3349f5aa650344c20 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 28 Apr 2008 11:15:25 -0400 Subject: Update website with new footer. --- website/new/css/style.css | 143 ++++++++++++++++++++++++++++++++++--- website/new/footer.html | 38 +++++++++- website/new/img/et_logo.png | Bin 0 -> 5194 bytes website/new/img/footer_corner.png | Bin 0 -> 2359 bytes website/new/img/footer_pattern.png | Bin 0 -> 817 bytes website/push.sh | 3 +- 6 files changed, 170 insertions(+), 14 deletions(-) create mode 100644 website/new/img/et_logo.png create mode 100644 website/new/img/footer_corner.png create mode 100644 website/new/img/footer_pattern.png diff --git a/website/new/css/style.css b/website/new/css/style.css index 1d43324..b02cb02 100644 --- a/website/new/css/style.css +++ b/website/new/css/style.css @@ -40,7 +40,7 @@ ul#nav { } ul#nav li a { - color: #59cbe1; + color: #c98b07; text-decoration: none; } @@ -49,7 +49,7 @@ ul#nav li#active { } ul#nav li#active a, ul#nav li#active a:link, ul#nav li#active a:visited { - color: white; + color: #c98b07; } div#feed { @@ -73,7 +73,7 @@ div#feed ul { } div#main { - background-color: #212121; + /** background-color: #212121; **/ border-top: 1px solid #59cbe1; border-bottom: 1px solid #59cbe1; overflow: auto; @@ -108,7 +108,7 @@ div#content p { } a:link { - color: #59cbe1; + color: #c98b07; } a:hover { @@ -174,11 +174,134 @@ tt { padding-left: 30px; } + #footer { - width: 100%; - font-size: x-small; - color: #aaa; - text-align: center; - padding-top: 16px; - padding-bottom: 16px; + clear: both; + position: relative; + margin: 0px; + padding: 0px; + border: 0px; + width: 100%; + background: #757575; + height: 180px; +} + +/* This is hidden from IE <= 6 because it can't do transparency */ +body > #footer { + background: #757575 url(img/footer_pattern.png) repeat-x; +} + +#footer p { + position: absolute; + top: 0px; + left: 0px; + margin: 0px; + border: 0px; + width: 220px; + text-align: center; +} + +#footer p a img { + border: 0px; } + +#projects { + margin: 0px; + border: 0px; + position: absolute; + top: 0px; + left: 0px; + width: 100%; +} + + +#projects dl { + margin: 0px; + border: 0px solid white; + height: 180px; + position: absolute; + top: 0px; + left: 0px; +} +/* This is hidden from IE <= 6 because it can't do transparency */ +head:first-child+body #projects dl { + background: url(img/footer_corner.png) no-repeat; +} + + +#projects #p1 { + margin-left: 25%; + width: 75%; +} + +#projects #p2 { + margin-left: 50%; + width: 50%; +} + +#projects #p3 { + margin-left: 75%; + width: 25%; +} + +#projects dt, #projects dd { + padding: 0px; + margin: 0px; +} + +#projects #p1 dt, #projects #p1 dd { + width: 33%; + } +#projects #p2 dt, #projects #p2 dd { + width: 50%; +} +#projects #p3 dt, #projects #p3 dd { + width: 99%; +} + +#projects { +} + +#projects span { + font-size: 0.8em; + display: block; + padding-left: 1em; + padding-top: 0.5em; +} + +#projects a { + font-size: 0.8em; + display: block; + padding-left: 0.8em; + padding-top: 1em; +} + +#projects a { + color: white; + text-decoration: inherit; +} + +#projects span { + color: #ccc; +} + + +.screenshot img { + border: 0px; +} +.screenshot { + padding: 1em; + background: #6a7178; + color: #dddddd; + text-align: center; + width: 70%; + margin-left: auto ! important; + margin-right: auto ! important; + margin-top: 1em; + margin-bottom: 3em; +} + +.screenshot a { + color: inherit; +} + diff --git a/website/new/footer.html b/website/new/footer.html index 35be552..cf18ff8 100644 --- a/website/new/footer.html +++ b/website/new/footer.html @@ -1,6 +1,38 @@ -

    Copyright © 2007 Red Hat, Inc. and others. -A project from the Red Hat Emerging Technologies Group.
    -Website design is Creative Commons Attribution-Share Alike 3.0. +

    diff --git a/website/new/img/et_logo.png b/website/new/img/et_logo.png new file mode 100644 index 0000000..e094b59 Binary files /dev/null and b/website/new/img/et_logo.png differ diff --git a/website/new/img/footer_corner.png b/website/new/img/footer_corner.png new file mode 100644 index 0000000..090bfce Binary files /dev/null and b/website/new/img/footer_corner.png differ diff --git a/website/new/img/footer_pattern.png b/website/new/img/footer_pattern.png new file mode 100644 index 0000000..647c52a Binary files /dev/null and b/website/new/img/footer_pattern.png differ diff --git a/website/push.sh b/website/push.sh index 594c50b..9623250 100644 --- a/website/push.sh +++ b/website/push.sh @@ -1,2 +1,3 @@ #!/bin/sh -scp -r new/* et.redhat.com:/var/www/sites/cobbler.et.redhat.com +#scp -r new/* et.redhat.com:/var/www/sites/cobbler.et.redhat.com +cp -r new/* /var/www/html/stage -- cgit From 97a4085adb0d83efd1008614b2b7da5546016ea6 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 1 May 2008 17:50:15 -0400 Subject: Working on adding a "cobbler buildiso" command to generate non-live CD's. Menu does not work yet, but getting there. Also made registration check for duplicate macs, but needs testing. --- cobbler/action_buildiso.py | 168 ++++++++++++++++++++++++++++++++++++++++++++ cobbler/api.py | 7 ++ cobbler/cobbler.py | 19 +++-- cobbler/modules/cli_misc.py | 28 ++++++-- cobbler/remote.py | 4 ++ 5 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 cobbler/action_buildiso.py diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py new file mode 100644 index 0000000..0a2edac --- /dev/null +++ b/cobbler/action_buildiso.py @@ -0,0 +1,168 @@ +""" +Builds non-live bootable CD's that have PXE-equivalent behavior +for all cobbler profiles currently in memory. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import sub_process +import sys +import traceback +import shutil +import sub_process + +import utils +from cexceptions import * +from utils import _ + +# FIXME: lots of overlap with pxegen.py, should consolidate +# FIXME: disable timeouts and remove local boot for this? +HEADER = """ + +DEFAULT menu +PROMPT 0 +MENU TITLE Cobbler | http://cobbler.et.redhat.com +TIMEOUT 200 +TOTALTIMEOUT 6000 +ONTIMEOUT local + +LABEL local + MENU LABEL (local) + MENU DEFAULT + LOCALBOOT 0 + +""" + +class BuildIso: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config,verbose=False): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + + def run(self,iso=None,tempdir=None,profiles=None): + + # verify we can find isolinux.bin + + if iso is None: + iso = "kickstart.iso" + + isolinuxbin = "/usr/lib/syslinux/isolinux.bin" + if not os.path.exists(isolinuxbin): + raise CX(_("Required file not found: %s") % isolinuxbin) + + # if iso is none, create it in . as "cobbler.iso" + if tempdir is None: + tempdir = os.path.join(os.getcwd(), "buildiso") + print _("- using/creating tempdir: %s") % tempdir + if not os.path.exists(tempdir): + os.makedirs(tempdir) + + # if base of tempdir does not exist, fail + # create all profiles unless filtered by "profiles" + imagesdir = os.path.join(tempdir, "images") + isolinuxdir = os.path.join(tempdir, "isolinux") + + print _("- building tree for isolinux") + if not os.path.exists(imagesdir): + os.makedirs(imagesdir) + if not os.path.exists(isolinuxdir): + os.makedirs(isolinuxdir) + + print _("- copying miscellaneous files") + utils.copyfile(isolinuxbin, os.path.join(isolinuxdir, "isolinux.bin")) + menu = "/var/lib/cobbler/menu.c32" + files = [ isolinuxbin, menu ] + for f in files: + if not os.path.exists(f): + raise CX(_("Required file not found: %s") % f) + utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f))) + + print _("- copying kernels and initrds") + # copy all images in included profiles to images dir + for x in self.api.profiles(): + use_this = True + if profiles is not None: + which_profiles = profiles.split(",") + if not use_this in which_profiles: + use_this = False + dist = x.get_conceptual_parent() + distdir = os.path.join(isolinuxdir, x.name) + if not os.path.exists(distdir): + os.makedirs(distdir) + # tempdir/isolinux/$distro/vmlinuz, initrd.img + # FIXME: this will likely crash on non-Linux breeds + shutil.copyfile(dist.kernel, os.path.join(distdir, "vmlinuz")) + shutil.copyfile(dist.initrd, os.path.join(distdir, "initrd.img")) + + # generate isolinux.cfg + print _("- generating a isolinux.cfg") + isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg") + cfg = open(isolinuxcfg, "w+") + cfg.write(HEADER) # fixme, use template + + for x in self.api.profiles(): + # FIXME + use_this = True + if profiles is not None: + which_profiles = profiles.split(",") + if not use_this in which_profiles: + use_this = False + if use_this: + dist = x.get_conceptual_parent() + data = utils.blender(self.api, True, x) + + cfg.write("\n") + cfg.write("LABEL %s\n" % x.name) + cfg.write(" MENU LABEL %s\n" % x.name) + cfg.write(" kernel /%s/vmlinuz\n" % dist.name) + + if data["kickstart"].startswith("/"): + data["kickstart"] = "http://%s/cblr/svc/op/ks/profile/%s" % ( + data["server"], + x.name + ) + + append_line = " append %s/initrd.img" % dist.name + append_line = append_line + " ks=%s " % data["kickstart"] + append_line = append_line + " %s\n" % data["kernel_options"] + cfg.write(append_line) + + print _("- done writing config") + cfg.write("\n") + cfg.write("MENU END\n") + cfg.close() + + cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin" % iso + cmd = cmd + " -no-emul-boot -boot-load-size 4 " + cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir + + print _("- running: %s") % cmd + rc = sub_process.call(cmd, shell=True) + if rc: + raise CX(_("mkisofs failed")) + + print _("ISO build complete") + print _("You may wish to delete: %s") % tempdir + print _("The output file is: %s") % iso + + diff --git a/cobbler/api.py b/cobbler/api.py index 1814c41..7bcd4fa 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -22,6 +22,7 @@ import action_import import action_reposync import action_status import action_validate +import action_buildiso from cexceptions import * import sub_process import module_loader @@ -417,3 +418,9 @@ class BootAPI: self.log("authorize",[user,resource,arg1,arg2,rc],debug=True) return rc + def build_iso(self,iso=None,profiles=None,tempdir=None): + builder = action_buildiso.BuildIso(self._config) + return builder.run( + iso=iso, profiles=profiles, tempdir=tempdir + ) + diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index e8a2ef5..627e398 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -21,6 +21,7 @@ import os.path import traceback import optparse import commands +import cexceptions from cexceptions import * from utils import _ @@ -49,20 +50,16 @@ def main(): """ exitcode = 0 try: - # FIXME: redo locking code? return BootCLI().run(sys.argv) - except CX, exc: - print str(exc)[1:-1] # remove framing air quotes - except SystemExit: - pass # probably exited from optparse, nothing extra to print - except Exception, exc2: - if isinstance(exc2, CX) or isinstance(exc2, CobblerException): - traceback.print_exc() + except Exception, exc: + if isinstance(exc, cexceptions.CobblerException) or \ + isinstance(exc, cexceptions.CX) or \ + str(type(exc)).find("CX") != -1 or \ + str(type(exc)).find("CobblerException") != -1: + print str(exc)[1:-1] # remove framing air quotes else: - print str(exc2)[1:-1] # remove framing air quotes + traceback.print_exc() return 1 - return 1 - if __name__ == "__main__": sys.exit(main()) diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index af2f6b2..6ca14a2 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -184,11 +184,6 @@ class ReportFunction(commands.CobblerFunction): self.reporting_print_sorted(self.api.repos()) return True -## FIXME: add legacy command translator to keep things simple -## cobbler system report foo --> cobbler report --what=systems --name=foo -## cobbler system report --> cobbler report --what=systems -## ditto for "cobbler list" - ######################################################## class StatusFunction(commands.CobblerFunction): @@ -244,6 +239,28 @@ class ValidateKsFunction(commands.CobblerFunction): def run(self): return self.api.validateks() +######################################################## + +class BuildIsoFunction(commands.CobblerFunction): + + def add_options(self,p,args): + p.add_option("--iso", dest="isoname", help="(OPTIONAL) output ISO to this path") + p.add_option("--profiles", dest="profiles", help="(OPTIONAL) use these profiles only") + p.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) working directory") + + def help_me(self): + return HELP_FORMAT % ("cobbler buildiso","") + + def command_name(self): + return "buildiso" + + def run(self): + return self.api.build_iso( + iso=self.options.isoname, + profiles=self.options.profiles, + tempdir=self.options.tempdir + ) + ######################################################## # MODULE HOOKS @@ -255,6 +272,7 @@ def register(): def cli_functions(api): return [ + BuildIsoFunction(api), CheckFunction(api), ImportFunction(api), ReserializeFunction(api), ListFunction(api), ReportFunction(api), StatusFunction(api), SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api) diff --git a/cobbler/remote.py b/cobbler/remote.py index a7e056b..18c3947 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -248,6 +248,10 @@ class CobblerXMLRPCInterface: if mac.find(" ") != -1: mac = mac.split()[-1] + dup = self.api.find_system(mac_address=mac) + if dup is not None: + return 4 + self.log("register mac for profile %s" % profile,token=token,name=mac) obj = self.api.new_system() obj.set_profile(profile) -- cgit From 5cc622d9683da05055e8ee93dc31fb8beabd3213 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 1 May 2008 18:09:07 -0400 Subject: Fix some paths and parameters. More to do. --- cobbler/action_buildiso.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py index 0a2edac..d11bee4 100644 --- a/cobbler/action_buildiso.py +++ b/cobbler/action_buildiso.py @@ -134,7 +134,7 @@ class BuildIso: cfg.write("\n") cfg.write("LABEL %s\n" % x.name) cfg.write(" MENU LABEL %s\n" % x.name) - cfg.write(" kernel /%s/vmlinuz\n" % dist.name) + cfg.write(" kernel %s/vmlinuz\n" % dist.name) if data["kickstart"].startswith("/"): data["kickstart"] = "http://%s/cblr/svc/op/ks/profile/%s" % ( @@ -142,7 +142,7 @@ class BuildIso: x.name ) - append_line = " append %s/initrd.img" % dist.name + append_line = " append initrd=%s/initrd.img" % dist.name append_line = append_line + " ks=%s " % data["kickstart"] append_line = append_line + " %s\n" % data["kernel_options"] cfg.write(append_line) -- cgit From 2f6b73def7068ee401d37b7eeee44a9fc064c9a1 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 10:29:50 -0400 Subject: Build iso completion --- cobbler/action_buildiso.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py index d11bee4..639bd6b 100644 --- a/cobbler/action_buildiso.py +++ b/cobbler/action_buildiso.py @@ -40,7 +40,7 @@ ONTIMEOUT local LABEL local MENU LABEL (local) MENU DEFAULT - LOCALBOOT 0 + LOCALBOOT -1 """ @@ -58,6 +58,16 @@ class BuildIso: self.api = config.api self.distros = config.distros() self.profiles = config.profiles() + self.distmap = {} + self.distctr = 0 + + def make_shorter(self,distname): + if self.distmap.has_key(distname): + return self.distmap[distname] + else: + self.distctr = self.distctr + 1 + self.distmap[distname] = str(self.distctr) + return str(self.distctr) def run(self,iso=None,tempdir=None,profiles=None): @@ -106,13 +116,13 @@ class BuildIso: if not use_this in which_profiles: use_this = False dist = x.get_conceptual_parent() - distdir = os.path.join(isolinuxdir, x.name) - if not os.path.exists(distdir): - os.makedirs(distdir) + if dist.name.find("-xen") != -1: + continue + distname = self.make_shorter(dist.name) # tempdir/isolinux/$distro/vmlinuz, initrd.img # FIXME: this will likely crash on non-Linux breeds - shutil.copyfile(dist.kernel, os.path.join(distdir, "vmlinuz")) - shutil.copyfile(dist.initrd, os.path.join(distdir, "initrd.img")) + shutil.copyfile(dist.kernel, os.path.join(isolinuxdir, "%s.krn" % distname)) + shutil.copyfile(dist.initrd, os.path.join(isolinuxdir, "%s.img" % distname)) # generate isolinux.cfg print _("- generating a isolinux.cfg") @@ -129,12 +139,15 @@ class BuildIso: use_this = False if use_this: dist = x.get_conceptual_parent() + if dist.name.find("-xen") != -1: + continue data = utils.blender(self.api, True, x) + distname = self.make_shorter(dist.name) cfg.write("\n") cfg.write("LABEL %s\n" % x.name) cfg.write(" MENU LABEL %s\n" % x.name) - cfg.write(" kernel %s/vmlinuz\n" % dist.name) + cfg.write(" kernel %s.krn\n" % distname) if data["kickstart"].startswith("/"): data["kickstart"] = "http://%s/cblr/svc/op/ks/profile/%s" % ( @@ -142,7 +155,7 @@ class BuildIso: x.name ) - append_line = " append initrd=%s/initrd.img" % dist.name + append_line = " append initrd=%s.img" % distname append_line = append_line + " ks=%s " % data["kickstart"] append_line = append_line + " %s\n" % data["kernel_options"] cfg.write(append_line) @@ -152,7 +165,7 @@ class BuildIso: cfg.write("MENU END\n") cfg.close() - cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin" % iso + cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso cmd = cmd + " -no-emul-boot -boot-load-size 4 " cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir -- cgit From 7eb0fa0be305d1d682b29aaff6dde267402a9e6a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 11:51:33 -0400 Subject: Apply Pablo's patch to add OMAPI support to dhcp config generation. --- cobbler/dhcpgen.py | 82 +++++++++++++++++++++++++++++++++++++-- cobbler/settings.py | 2 + config/settings | 2 + templates/dhcp.template | 1 + triggers/restart-services.trigger | 10 ++++- 5 files changed, 93 insertions(+), 4 deletions(-) diff --git a/cobbler/dhcpgen.py b/cobbler/dhcpgen.py index 2d6facf..cc6d410 100644 --- a/cobbler/dhcpgen.py +++ b/cobbler/dhcpgen.py @@ -22,6 +22,9 @@ import sys import glob import traceback import errno +import popen2 +from shlex import shlex + import utils from cexceptions import * @@ -53,6 +56,50 @@ class DHCPGen: self.repos = config.repos() self.templar = templar.Templar(config) + def writeDHCPLease(self,port,host,ip,mac): + """writeDHCPLease(port,host,ip,mac) + Use DHCP's API to create a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ + #Code from http://svn.osgdc.org/browse/kusu/kusu/trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025 + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("set ip-address = %s\n" % ip) + tochild.flush() + tochild.write("set hardware-address = %s\n" % mac) + tochild.flush() + tochild.write("set hardware-type = 1\n") + tochild.flush() + tochild.write("create\n") + tochild.flush() + tochild.close() + fromchild.close() + + def removeDHCPLease(self,port,host): + """removeDHCPLease(port,host) + Use DHCP's API to delete a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("open\n") # opens register with host information + tochild.flush() + tochild.write("remove\n") + tochild.flush() + tochild.close() + fromchild.close() + + def write_dhcp_file(self): """ DHCP files are written when manage_dhcp is set in @@ -84,10 +131,28 @@ class DHCPGen: system_definitions = {} counter = 0 + + + # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in + # case to avoid conflicts with the hosts we're defining and to clean + # possible removed hosts (only if using OMAPI) + # + # Pablo Iranzo Gómez (Pablo.Iranzo@redhat.com) + if self.settings.omapi and self.settings.omapi_port: + file = open('/var/lib/dhcpd/dhcpd.leases') + item = shlex(file) + while 1: + elem = item.get_token() + if not elem: + break + if elem == 'host': + hostremove = item.get_token() + self.removeDHCPLease(self.settings.omapi_port,hostremove) + # we used to just loop through each system, but now we must loop # through each network interface of each system. - + for system in self.systems: profile = system.get_conceptual_parent() distro = profile.get_conceptual_parent() @@ -121,6 +186,19 @@ class DHCPGen: if ip is not None and ip != "": systxt = systxt + " fixed-address %s;\n" % ip systxt = systxt + "}\n" + + # If we have all values defined and we're using omapi, + # we will just create entries dinamically into DHCPD + # without requiring a restart (but file will be written + # as usual for having it working after restart) + + if ip is not None and ip != "": + if mac is not None and mac != "": + if host is not None and host != "": + if self.settings.omapi and self.settings.omapi_port: + self.removeDHCPLease(self.settings.omapi_port,host) + self.writeDHCPLease(self.settings.omapi_port,host,ip,mac) + else: # dnsmasq. don't have to write IP and other info here, but we do tag @@ -192,5 +270,3 @@ class DHCPGen: if host is not None and host != "" and ip is not None and ip != "": fh.write(ip + "\t" + host + "\n") fh.close() - - diff --git a/cobbler/settings.py b/cobbler/settings.py index 40ed571..491de75 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -58,6 +58,8 @@ DEFAULTS = { "manage_dhcp" : 0, "manage_dhcp_mode" : "isc", "next_server" : "127.0.0.1", + "omapi" : 1, + "omapi_port" : 647, "pxe_just_once" : 0, "register_new_installs" : 0, "run_install_triggers" : 1, diff --git a/config/settings b/config/settings index 10e06e2..724f419 100644 --- a/config/settings +++ b/config/settings @@ -32,6 +32,8 @@ ldap_search_prefix: 'uid=' manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' +omapi: 1 +omapi_port: 647 pxe_just_once: 0 register_new_installs: 0 run_install_triggers: 1 diff --git a/templates/dhcp.template b/templates/dhcp.template index 705cc77..19afcad 100644 --- a/templates/dhcp.template +++ b/templates/dhcp.template @@ -9,6 +9,7 @@ ddns-update-style interim; allow booting; allow bootp; +omapi-port 647; ignore client-updates; set vendorclass = option vendor-class-identifier; diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger index b65b825..6a0e320 100644 --- a/triggers/restart-services.trigger +++ b/triggers/restart-services.trigger @@ -8,11 +8,19 @@ bootapi = capi.BootAPI() settings = bootapi.settings() manage_dhcp = str(settings.manage_dhcp).lower() manage_dhcp_mode = str(settings.manage_dhcp_mode).lower() +omapi = settings.omapi +omapi_port = settings.omapi_port + + + +# We're just going to restart DHCPD if using ISC and if not using OMAPI at all rc = 0 if manage_dhcp != "0": if manage_dhcp_mode == "isc": - rc = os.system("/sbin/service dhcpd restart") + if not omapi: + if not omapi_port: + rc = os.system("/sbin/service dhcpd restart") elif manage_dhcp_mode == "dnsmasq": rc = os.system("/sbin/service dnsmasq restart") else: -- cgit From 82f4bb814835a8aadcfe5cbba8bfbc4359c34b39 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 11:58:36 -0400 Subject: Apply John Eckersberg's patch for BIND management. --- AUTHORS | 1 + cobbler/action_sync.py | 4 ++++ cobbler/settings.py | 4 ++++ cobbler/webui/master.py | 1 - config/settings | 1 + docs/cobbler.pod | 14 ++++++++++++-- setup.py | 4 +++- triggers/restart-services.trigger | 9 ++++++++- website/new/docs/cobbler.html | 14 ++++++++++++++ 9 files changed, 47 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 5d2c797..cb26afc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,6 +11,7 @@ Patches and other contributions from: James Bowes C. Daniel Chase Máirín Duffy + John Eckersberg Tru Huynh Matt Hyclak Pablo Iranzo Gómez diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 4156ea7..98faa0c 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -29,6 +29,7 @@ from cexceptions import * import templar import pxegen import dhcpgen +import dnsgen import yumgen import item_distro @@ -84,6 +85,7 @@ class BootSync: self.repos = self.config.repos() self.pxegen = pxegen.PXEGen(self.config) self.dhcpgen = dhcpgen.DHCPGen(self.config) + self.dnsgen = dnsgen.DNSGen(self.config) self.yumgen = yumgen.YumGen(self.config) # execute the core of the sync operation @@ -98,6 +100,8 @@ class BootSync: self.dhcpgen.write_dhcp_file() self.dhcpgen.regen_ethers() self.dhcpgen.regen_hosts() + if self.settings.manage_dns: + self.dnsgen.write_bind_files() self.pxegen.make_pxe_menu() # run post-triggers diff --git a/cobbler/settings.py b/cobbler/settings.py index 491de75..a661768 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -57,6 +57,10 @@ DEFAULTS = { }, "manage_dhcp" : 0, "manage_dhcp_mode" : "isc", + "manage_dns" : 0, + "manage_forward_zones" : [], + "manage_reverse_zones" : [], + "named_conf" : "/etc/named.conf", "next_server" : "127.0.0.1", "omapi" : 1, "omapi_port" : 647, diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 22391eb..7e54a2d 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -35,7 +35,6 @@ __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) __CHEETAH_genTime__ = 1209057202.737108 __CHEETAH_genTimestamp__ = 'Thu Apr 24 13:13:22 2008' -__CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Thu Apr 24 12:59:37 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' diff --git a/config/settings b/config/settings index 724f419..6f2aff6 100644 --- a/config/settings +++ b/config/settings @@ -31,6 +31,7 @@ ldap_search_passwd: '' ldap_search_prefix: 'uid=' manage_dhcp: 0 manage_dhcp_mode: isc +manage_dns: 0 next_server: '127.0.0.1' omapi: 1 omapi_port: 647 diff --git a/docs/cobbler.pod b/docs/cobbler.pod index d7a4550..21ec78e 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -403,8 +403,8 @@ Cobbler sync is used to repair or rebuild the contents /tftpboot or /var/www/cob Sync should be run whenever files in /var/lib/cobbler are manually edited (which is not recommended except for the settings file) or when making changes to kickstart files. In practice, this should not happen often, though running sync too many times does not cause any adverse effects. -If using cobbler to manage a DHCP server (see the advanced section of this manpage), sync does need to be -run after systems are added to regenerate and reload the DHCP configuration. +If using cobbler to manage a DHCP and/or DNS server (see the advanced section of this manpage), sync does need to be +run after systems are added to regenerate and reload the DHCP/DNS configuration. =head1 EXAMPLES @@ -565,6 +565,16 @@ Itanium systems names also need to be assigned to a distro that was created with The dhcpd.conf file will be updated each time "cobbler sync" is run, and not until then, so it is important to remember to use "cobbler sync" when using this feature. +=head2 BIND CONFIGURATION MANAGEMENT + +Cobbler can optionally manage DNS configuration using BIND. + +This feature is off by default and must be turned on by setting 'manage_dns' to 1 in /var/lib/cobbler/settings. You may restrict the scope of zones managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. + +Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding. + +The named.conf file as well as all zone files will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature. + =head2 SERVICE DISCOVERY (AVAHI) If the avahi-tools package is installed, cobblerd will broadcast it's presence on the network, allowing it to be discovered by koan with the koan --server=DISCOVER parameter. diff --git a/setup.py b/setup.py index 91eabed..442da5b 100644 --- a/setup.py +++ b/setup.py @@ -94,13 +94,15 @@ if __name__ == "__main__": (etcpath, ['kickstarts/sample_end.ks']), (etcpath, ['kickstarts/default.ks']), - # templates for DHCP and syslinux configs + # templates for DHCP, DNS, and syslinux configs (etcpath, ['templates/dhcp.template']), (etcpath, ['templates/dnsmasq.template']), + (etcpath, ['templates/named.template']), (etcpath, ['templates/pxedefault.template']), (etcpath, ['templates/pxesystem.template']), (etcpath, ['templates/pxesystem_ia64.template']), (etcpath, ['templates/pxeprofile.template']), + (etcpath, ['templates/zone.template']), # kickstart dir (vl_kick, []), diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger index 6a0e320..24fc033 100644 --- a/triggers/restart-services.trigger +++ b/triggers/restart-services.trigger @@ -8,13 +8,13 @@ bootapi = capi.BootAPI() settings = bootapi.settings() manage_dhcp = str(settings.manage_dhcp).lower() manage_dhcp_mode = str(settings.manage_dhcp_mode).lower() +manage_dns = str(settings.manage_dns).lower() omapi = settings.omapi omapi_port = settings.omapi_port # We're just going to restart DHCPD if using ISC and if not using OMAPI at all - rc = 0 if manage_dhcp != "0": if manage_dhcp_mode == "isc": @@ -27,4 +27,11 @@ if manage_dhcp != "0": print "- error: unknown DHCP engine: %s" % manage_dhcp_mode rc = 411 +if rc != 0: + sys.exit(rc) + +if manage_dns != "0": + rc = os.system("/sbin/service named restart") + sys.exit(rc) + diff --git a/website/new/docs/cobbler.html b/website/new/docs/cobbler.html index 36ad3e0..06a8b14 100644 --- a/website/new/docs/cobbler.html +++ b/website/new/docs/cobbler.html @@ -661,6 +661,20 @@ the setting to 'isc'.

    The dhcpd.conf file will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature.

    +

    BIND CONFIGURATION MANAGEMENT

    +

    Cobbler can optionally manage DNS configuration using BIND.

    +

    This feature is off by default and must be turned on by setting 'manage_dns' to 1 in +/var/lib/cobbler/settings. You may restrict the scope of zones +managed with the options 'manage_forward_zones' and +'manage_reverse_zones'. +

    Cobbler will use /etc/cobbler/bind.template and + /etc/cobbler/zone.template as a starting point for the named.conf + and individual zone files, respectively. These files must be user + edited for the user's particular networking environment. Read the + file and understand how BIND works before proceeding.

    +

    The named.conf file as well as all zone files will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature.

    +

    +

    SERVICE DISCOVERY (AVAHI)

    If the avahi-tools package is installed, cobblerd will broadcast it's presence on the network, allowing it to be discovered by koan with the koan --server=DISCOVER parameter.

    -- cgit From 13eff0d1bb7e36569a00bcd291d73781ab2351e4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 12:41:20 -0400 Subject: Adjusting the dhcp patch some, prior to moving it all into modules/ --- cobbler/action_sync.py | 2 +- cobbler/api.py | 15 +++++- cobbler/dhcpgen.py | 106 +++++++++++++++++++++----------------- cobbler/settings.py | 2 +- config/settings | 2 +- templates/dhcp.template | 4 +- tests/tests.py | 4 +- triggers/restart-services.trigger | 4 +- 8 files changed, 82 insertions(+), 57 deletions(-) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 98faa0c..53ef3a7 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -47,7 +47,7 @@ class BootSync: Handles conversion of internal state to the tftpboot tree layout """ - def __init__(self,config,verbose=False): + def __init__(self,config,verbose=False,dhcp=None,dns=None): """ Constructor """ diff --git a/cobbler/api.py b/cobbler/api.py index 7bcd4fa..29a854d 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -81,6 +81,16 @@ class BootAPI: "module", "authz_allowall" ) + self.dhcp = self.get_module_from_file( + "dhcp_management", + "module", + "dhcp_isc" + ) + self.dns = self.get_module_from_file( + "dns_management", + "module", + "dns_bind" + ) self.kickgen = kickgen.KickGen(self._config) self.logger.debug("API handle initialized") @@ -332,9 +342,12 @@ class BootAPI: saved with serialize() will NOT be synchronized with this command. """ self.log("sync") - sync = action_sync.BootSync(self._config) + sync = self.get_sync() return sync.run() + def get_sync(self): + return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns) + def reposync(self, name=None): """ Take the contents of /var/lib/cobbler/repos and update them -- diff --git a/cobbler/dhcpgen.py b/cobbler/dhcpgen.py index cc6d410..5dd981c 100644 --- a/cobbler/dhcpgen.py +++ b/cobbler/dhcpgen.py @@ -60,44 +60,54 @@ class DHCPGen: """writeDHCPLease(port,host,ip,mac) Use DHCP's API to create a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ #Code from http://svn.osgdc.org/browse/kusu/kusu/trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025 - fromchild, tochild = popen2.popen2("/usr/bin/omshell") - tochild.write("port %s\n" % port) - tochild.flush() - tochild.write("connect\n") - tochild.flush() - tochild.write("new host\n") - tochild.flush() - tochild.write('set name = \"%s\"\n' % host) - tochild.flush() - tochild.write("set ip-address = %s\n" % ip) - tochild.flush() - tochild.write("set hardware-address = %s\n" % mac) - tochild.flush() - tochild.write("set hardware-type = 1\n") - tochild.flush() - tochild.write("create\n") - tochild.flush() - tochild.close() - fromchild.close() - + # FIXME: should use subprocess + try: + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("set ip-address = %s\n" % ip) + tochild.flush() + tochild.write("set hardware-address = %s\n" % mac) + tochild.flush() + tochild.write("set hardware-type = 1\n") + tochild.flush() + tochild.write("create\n") + tochild.flush() + tochild.close() + fromchild.close() + except IOError: + # FIXME: just catch 32 (broken pipe) and show a warning + pass + def removeDHCPLease(self,port,host): """removeDHCPLease(port,host) Use DHCP's API to delete a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ fromchild, tochild = popen2.popen2("/usr/bin/omshell") - tochild.write("port %s\n" % port) - tochild.flush() - tochild.write("connect\n") - tochild.flush() - tochild.write("new host\n") - tochild.flush() - tochild.write('set name = \"%s\"\n' % host) - tochild.flush() - tochild.write("open\n") # opens register with host information - tochild.flush() - tochild.write("remove\n") - tochild.flush() - tochild.close() - fromchild.close() + try: + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("open\n") # opens register with host information + tochild.flush() + tochild.write("remove\n") + tochild.flush() + tochild.close() + fromchild.close() + except IOError: + # FIXME: convert this to subprocess. + # FIXME: catch specific errors only (32/broken pipe) + pass def write_dhcp_file(self): @@ -136,19 +146,17 @@ class DHCPGen: # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in # case to avoid conflicts with the hosts we're defining and to clean # possible removed hosts (only if using OMAPI) - # - # Pablo Iranzo Gómez (Pablo.Iranzo@redhat.com) - if self.settings.omapi and self.settings.omapi_port: - file = open('/var/lib/dhcpd/dhcpd.leases') - item = shlex(file) - while 1: - elem = item.get_token() - if not elem: - break - if elem == 'host': - hostremove = item.get_token() - self.removeDHCPLease(self.settings.omapi_port,hostremove) - + if self.settings.omapi_enabled and self.settings.omapi_port: + if os.path.exists("/var/lib/dhcpd/dhcpd.leases"): + file = open('/var/lib/dhcpd/dhcpd.leases') + item = shlex(file) + while 1: + elem = item.get_token() + if not elem: + break + if elem == 'host': + hostremove = item.get_token() + self.removeDHCPLease(self.settings.omapi_port,hostremove) # we used to just loop through each system, but now we must loop # through each network interface of each system. @@ -195,7 +203,7 @@ class DHCPGen: if ip is not None and ip != "": if mac is not None and mac != "": if host is not None and host != "": - if self.settings.omapi and self.settings.omapi_port: + if self.settings.omapi_enabled and self.settings.omapi_port: self.removeDHCPLease(self.settings.omapi_port,host) self.writeDHCPLease(self.settings.omapi_port,host,ip,mac) @@ -225,6 +233,8 @@ class DHCPGen: # we are now done with the looping through each interface of each system metadata = { + "omapi_enabled" : self.settings.omapi_enabled, + "omapi_port" : self.settings.omapi_port, "insert_cobbler_system_definitions" : system_definitions.get("default",""), "date" : time.asctime(time.gmtime()), "cobbler_server" : self.settings.server, diff --git a/cobbler/settings.py b/cobbler/settings.py index a661768..70b2ac8 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -62,7 +62,7 @@ DEFAULTS = { "manage_reverse_zones" : [], "named_conf" : "/etc/named.conf", "next_server" : "127.0.0.1", - "omapi" : 1, + "omapi_enabled" : 0, "omapi_port" : 647, "pxe_just_once" : 0, "register_new_installs" : 0, diff --git a/config/settings b/config/settings index 6f2aff6..8b39707 100644 --- a/config/settings +++ b/config/settings @@ -33,7 +33,7 @@ manage_dhcp: 0 manage_dhcp_mode: isc manage_dns: 0 next_server: '127.0.0.1' -omapi: 1 +omapi_enabled: 1 omapi_port: 647 pxe_just_once: 0 register_new_installs: 0 diff --git a/templates/dhcp.template b/templates/dhcp.template index 19afcad..344b108 100644 --- a/templates/dhcp.template +++ b/templates/dhcp.template @@ -9,7 +9,9 @@ ddns-update-style interim; allow booting; allow bootp; -omapi-port 647; +#if $omapi_enabled +omapi-port $omapi_port; +#end if ignore client-updates; set vendorclass = option vendor-class-identifier; diff --git a/tests/tests.py b/tests/tests.py index bb64dd1..02d53be 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -743,7 +743,7 @@ class SyncContents(BootTest): fh = open("/tftpboot/pxelinux.cfg/%s" % converted) data = fh.read() - self.assertTrue(data.find("op=ks") != -1) + self.assertTrue(data.find("/op/ks/") != -1) fh.close() # ensure that after sync is applied, the blender cache still allows @@ -753,7 +753,7 @@ class SyncContents(BootTest): self.api.sync() fh = open("/tftpboot/pxelinux.cfg/%s" % converted) data = fh.read() - self.assertTrue(data.find("op=ks") != -1) + self.assertTrue(data.find("/op/ks/") != -1) fh.close() diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger index 24fc033..bc7c36e 100644 --- a/triggers/restart-services.trigger +++ b/triggers/restart-services.trigger @@ -9,7 +9,7 @@ settings = bootapi.settings() manage_dhcp = str(settings.manage_dhcp).lower() manage_dhcp_mode = str(settings.manage_dhcp_mode).lower() manage_dns = str(settings.manage_dns).lower() -omapi = settings.omapi +omapi_enabled = settings.omapi_enabled omapi_port = settings.omapi_port @@ -18,7 +18,7 @@ omapi_port = settings.omapi_port rc = 0 if manage_dhcp != "0": if manage_dhcp_mode == "isc": - if not omapi: + if not omapi_enabled: if not omapi_port: rc = os.system("/sbin/service dhcpd restart") elif manage_dhcp_mode == "dnsmasq": -- cgit From 34652b616c1adda6379c47886caed3b219fc29d1 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 12:47:18 -0400 Subject: Do not add profiles that have -xen distros to PXE menu entries as there is no chance of them booting there. --- cobbler/pxegen.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index 8cb8c93..639c30b 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -177,6 +177,9 @@ class PXEGen: pxe_menu_items = "" for profile in profile_list: distro = profile.get_conceptual_parent() + # xen distros can be ruled out as they won't boot + if distro.name.find("-xen") != -1: + continue contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False) if contents is not None: pxe_menu_items = pxe_menu_items + contents + "\n" -- cgit From e4fce2b7321c880f8c1f1adbdf137fc4d66563de Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 13:02:10 -0400 Subject: Changelog and doc cleanup (note: manpages reference future behavior WRT dns/dhcp behavior, to be implemented soon). --- CHANGELOG | 4 ++++ docs/cobbler.pod | 40 +++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed91270..278b8fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,6 +33,10 @@ Cobbler CHANGELOG - implement "cobbler ___ dumpvars --name=X" feature to show template vars - validateks now works against all URLs as opposed to rendered local files - now possible to create new kickstarts in webui, and delete unused ones +- support for OMAPI for avoid dhcp restarts +- support for managing BIND +- xen kernel (PV) distros do not get added to PXE menus as they won't boot there +- cobbler buildiso command to build non live ISOs - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 21ec78e..311a94c 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -537,43 +537,37 @@ Anywhere a kickstart template mentions SNIPPET::snippet_name, the file named /va To check for potential errors in kickstarts, prior to installation, use "cobbler validateks". This function will check all profile and system kickstarts for detectable errors. Since pykickstart is not future-Anaconda-version aware, there may be some false positives. It should be noted that "cobbler validateks" runs on the rendered kickstart output, not kickstart templates themselves. -=head2 DHCP CONFIGURATION MANAGEMENT +=head2 DHCP MANAGEMENT -Cobbler can optionally help you manage DHCP and (depending on how used) DNS as it relates -to systems you wish to provision/control. This allows cobbler to essentially maintain a database -of all of your installed systems, and be a central point of control for aspects related to setting -up those systems. +Cobbler can optionally help you manage DHCP server. This feature is off by default. -This feature is off by default and must be turned on by setting 'manage_dhcp' to 1 in -/var/lib/cobbler/settings. Choices include ISC dhcpd (default), or DNSmasq, which can be chosen -by setting manage_dhcp_mode to 'dnsmasq'. If you choose dnsmasq and want to revert to ISC, change -the setting to 'isc'. +Choose either "management = isc_and_bind" in /etc/cobbler/dhcp.template or "management = "dnsmasq" in /etc/cobbler/modules.conf. Then set "manage_dhcp" to 1 in /var/lib/cobbler/settings. + +This allows DHCP to be managed via "cobbler system add" commands, when you specify the mac address and IP address for systems you add into cobbler. Depending on your choice, cobbler will use /etc/cobbler/dhcpd.template or /etc/cobbler/dnsmasq.template as a starting point. This file must be user edited for the user's particular networking environment. Read the file and understand how the particular app (ISC dhcpd or dnsmasq) work before proceeding. If you already have DHCP configuration data that you would like to preserve (say DHCP was manually configured earlier), insert the relevant portions of it into the template file, as running "cobbler sync" will overwrite your previous configuration. -In summary, if this manage_dhcp bit is enabled, the following features are enabled: +NOTE: Itanium systems names also need to be assigned to a distro that was created with the "--arch=ia64" parameter. If you have Itanium systems, you must (for now) choose 'dhcp_isc' for /etc/cobbler/modules.conf and manage_dhcp in the /var/lib/cobbler/settings file, and are required to use --ip when creating the system object in order for those systems to PXE. This is due to an elilo limitation. -(A) pinning dhcp hostnames to MAC addresses automatically. -(B) relatively seamless mixing of Itanium and x86/x86_64 machines in a PXE environment (ISC only) -(C) assigning hostnames to MAC addresses using DNS (dnsmasq only). +By default, the DHCP configuration file will be updated each time "cobbler sync" is run, and not until then, so it is important to remember to use "cobbler sync" when using this feature. -These options are all enabled by using the --hostname and --ip options when using the "cobbler system add" command. +If omapi_enabled is set to 1 in /var/lib/cobbler/settings, the need to sync when adding new system records can be eliminated. -Itanium systems names also need to be assigned to a distro that was created with the "--arch=ia64" parameter. If you have Itanium systems, you must (for now) choose 'isc' for 'manage_dhcp_mode' in the /var/lib/cobbler/settings file, and are required to use --ip when creating the system object in order for those systems to PXE. +=head2 DNS CONFIGURATION MANAGEMENT -The dhcpd.conf file will be updated each time "cobbler sync" is run, and not until then, so it is important to remember to use "cobbler sync" when using this feature. +Cobbler can optionally manage DNS configuration using BIND and dnsmasq. -=head2 BIND CONFIGURATION MANAGEMENT +Choose either "management = isc_and_bind" or "management = dnsmasq" in /etc/cobbler/modules.conf and then enable manage_dns in /var/lib/cobbler/settings. -Cobbler can optionally manage DNS configuration using BIND. +This feature is off by default. If using BIND, you may restrict the scope of zones managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. (See the Wiki for more information on this). -This feature is off by default and must be turned on by setting 'manage_dns' to 1 in /var/lib/cobbler/settings. You may restrict the scope of zones managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. +If using BIND, Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding. -Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding. +If using dnsmasq, the template is /etc/cobbler/dnsmasq.template. Read this file and understand how dnsmasq works before proceeding. -The named.conf file as well as all zone files will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature. +All managed files (whether zone files and named.conf for BIND, or dnsmasq.conf for dnsmasq) will be updated each time ``cobbler sync'' is run, and not until then, so it is important to remember to use ``cobbler sync'' when using this feature. =head2 SERVICE DISCOVERY (AVAHI) @@ -684,6 +678,10 @@ Most of the day-to-day actions in cobbler's command line can be performed in Cob https://hosted.fedoraproject.org/projects/cobbler/wiki/CobblerWebUi +=head2 BOOT CD + +Cobbler can build all of it's profiles into a bootable CD image using the "cobbler buildiso" command. This allows for PXE-menu like bringup of bare metal in evnvironments where PXE is not possible. Another more advanced method is described in the koan manpage, though this method is easier and sufficient for most applications. + =head1 EXIT_STATUS cobbler's command line returns a zero for success and non-zero for failure. -- cgit From a6fd395b51a624e4aa7d09b4960382784cefa8e8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 16:05:32 -0400 Subject: Working on moving DNS/DHCP management to cobbler/modules (uses modules.conf) In progress. --- cobbler/action_check.py | 8 +- cobbler/action_litesync.py | 24 +- cobbler/action_sync.py | 18 +- cobbler/api.py | 17 +- cobbler/dhcpgen.py | 282 ---------------------- cobbler/manage_ctrl.py | 76 ++++++ cobbler/modules/manage_dnsmasq.py | 191 +++++++++++++++ cobbler/modules/manage_isc_and_bind.py | 425 +++++++++++++++++++++++++++++++++ config/modules.conf | 5 + config/settings | 1 - 10 files changed, 736 insertions(+), 311 deletions(-) delete mode 100644 cobbler/dhcpgen.py create mode 100644 cobbler/manage_ctrl.py create mode 100644 cobbler/modules/manage_dnsmasq.py create mode 100644 cobbler/modules/manage_isc_and_bind.py diff --git a/cobbler/action_check.py b/cobbler/action_check.py index e91396f..588a65a 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -37,8 +37,8 @@ class BootCheck: status = [] self.check_name(status) if self.settings.manage_dhcp: - mode = self.settings.manage_dhcp_mode.lower() - if mode == "isc": + mode = self.config.api.get_sync().manager.what() + if mode == "isc_and_bind": self.check_dhcpd_bin(status) self.check_dhcpd_conf(status) self.check_service(status,"dhcpd") @@ -46,7 +46,9 @@ class BootCheck: self.check_dnsmasq_bin(status) self.check_service(status,"dnsmasq") else: - status.append(_("manage_dhcp_mode in /var/lib/cobbler/settings should be 'isc' or 'dnsmasq'")) + status.append(_("configured management mode in modules.conf is unknown")) + # FIXME: add in checks for bind config + self.check_service(status, "cobblerd") self.check_bootloaders(status) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index c2029d4..ba0a256 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -49,7 +49,7 @@ class BootLiteSync: self.systems = config.systems() self.settings = config.settings() self.repos = config.repos() - self.sync = action_sync.BootSync(self.config) + self.sync = config.api.get_sync() def add_single_distro(self, name): # get the distro record @@ -105,12 +105,22 @@ class BootLiteSync: if system is None: raise CX(_("error in system lookup for %s") % name) # rebuild system_list file in webdir - self.sync.dhcpgen.regen_ethers() # /etc/ethers, for dnsmasq & rarpd - self.sync.dhcpgen.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq + self.sync.manager.regen_ethers() + self.sync.manager.regen_hosts() # write the PXE files for the system self.sync.pxegen.write_all_system_files(system) # per system kickstarts self.sync.yumgen.retemplate_yum_repos(system,False) + if self.settings.manage_dhcp: + if self.settings.omapi_enabled: + for (name,interface) in system.interfaces.iteritems(): + self.sync.manager.write_dhcp_lease( + self.settings.omapi_port, + interface["hostname"], + interface["mac-address"], + interface["ip-address"] + ) + def remove_single_system(self, name): bootloc = utils.tftpboot_location() @@ -122,6 +132,14 @@ class BootLiteSync: filename = utils.get_config_filename(system_record,interface=name) utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename)) + if self.settings.manage_dhcp: + if self.settings.omapi_enabled: + for (name,interface) in system_record.interfaces.iteritems(): + self.sync.manager.remove_dhcp_lease( + self.settings.omapi_port, + interface["hostname"] + ) + # unneeded #if not system_record.is_pxe_supported(): # # no need to go any further with PXE cleanup diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 53ef3a7..fbc9402 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -28,8 +28,7 @@ import utils from cexceptions import * import templar import pxegen -import dhcpgen -import dnsgen +import manage_ctrl import yumgen import item_distro @@ -47,7 +46,7 @@ class BootSync: Handles conversion of internal state to the tftpboot tree layout """ - def __init__(self,config,verbose=False,dhcp=None,dns=None): + def __init__(self,config,verbose=False,manage=None): """ Constructor """ @@ -61,7 +60,7 @@ class BootSync: self.repos = config.repos() self.templar = templar.Templar(config) self.pxegen = pxegen.PXEGen(config) - self.dhcpgen = dhcpgen.DHCPGen(config) + self.manager = manage self.yumgen = yumgen.YumGen(config) self.bootloc = utils.tftpboot_location() @@ -84,8 +83,6 @@ class BootSync: self.settings = self.config.settings() self.repos = self.config.repos() self.pxegen = pxegen.PXEGen(self.config) - self.dhcpgen = dhcpgen.DHCPGen(self.config) - self.dnsgen = dnsgen.DNSGen(self.config) self.yumgen = yumgen.YumGen(self.config) # execute the core of the sync operation @@ -96,12 +93,11 @@ class BootSync: self.pxegen.write_all_system_files(x) self.yumgen.retemplate_all_yum_repos() if self.settings.manage_dhcp: - # these functions DRT for ISC or dnsmasq - self.dhcpgen.write_dhcp_file() - self.dhcpgen.regen_ethers() - self.dhcpgen.regen_hosts() + self.manager.write_dhcp_file() + self.manager.regen_ethers() + self.manager.regen_hosts() if self.settings.manage_dns: - self.dnsgen.write_bind_files() + self.manager.write_dns_files() self.pxegen.make_pxe_menu() # run post-triggers diff --git a/cobbler/api.py b/cobbler/api.py index 29a854d..1f5a246 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -81,16 +81,6 @@ class BootAPI: "module", "authz_allowall" ) - self.dhcp = self.get_module_from_file( - "dhcp_management", - "module", - "dhcp_isc" - ) - self.dns = self.get_module_from_file( - "dns_management", - "module", - "dns_bind" - ) self.kickgen = kickgen.KickGen(self._config) self.logger.debug("API handle initialized") @@ -346,7 +336,12 @@ class BootAPI: return sync.run() def get_sync(self): - return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns) + self.manage = self.get_module_from_file( + "management", + "module", + "manage_isc_and_bind" + ).get_manager(self._config) + return action_sync.BootSync(self._config,manage=self.manage) def reposync(self, name=None): """ diff --git a/cobbler/dhcpgen.py b/cobbler/dhcpgen.py deleted file mode 100644 index 5dd981c..0000000 --- a/cobbler/dhcpgen.py +++ /dev/null @@ -1,282 +0,0 @@ -""" -Builds out DHCP info -This is the code behind 'cobbler sync'. - -Copyright 2006-2008, Red Hat, Inc -Michael DeHaan - -This software may be freely redistributed under the terms of the GNU -general public license. - -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., 675 Mass Ave, Cambridge, MA 02139, USA. -""" - -import os -import os.path -import shutil -import time -import sub_process -import sys -import glob -import traceback -import errno -import popen2 -from shlex import shlex - - -import utils -from cexceptions import * -import templar - -import item_distro -import item_profile -import item_repo -import item_system - -from utils import _ - -class DHCPGen: - """ - Handles conversion of internal state to the tftpboot tree layout - """ - - def __init__(self,config,verbose=False): - """ - Constructor - """ - self.verbose = verbose - self.config = config - self.api = config.api - self.distros = config.distros() - self.profiles = config.profiles() - self.systems = config.systems() - self.settings = config.settings() - self.repos = config.repos() - self.templar = templar.Templar(config) - - def writeDHCPLease(self,port,host,ip,mac): - """writeDHCPLease(port,host,ip,mac) - Use DHCP's API to create a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ - #Code from http://svn.osgdc.org/browse/kusu/kusu/trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025 - # FIXME: should use subprocess - try: - fromchild, tochild = popen2.popen2("/usr/bin/omshell") - tochild.write("port %s\n" % port) - tochild.flush() - tochild.write("connect\n") - tochild.flush() - tochild.write("new host\n") - tochild.flush() - tochild.write('set name = \"%s\"\n' % host) - tochild.flush() - tochild.write("set ip-address = %s\n" % ip) - tochild.flush() - tochild.write("set hardware-address = %s\n" % mac) - tochild.flush() - tochild.write("set hardware-type = 1\n") - tochild.flush() - tochild.write("create\n") - tochild.flush() - tochild.close() - fromchild.close() - except IOError: - # FIXME: just catch 32 (broken pipe) and show a warning - pass - - def removeDHCPLease(self,port,host): - """removeDHCPLease(port,host) - Use DHCP's API to delete a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ - fromchild, tochild = popen2.popen2("/usr/bin/omshell") - try: - tochild.write("port %s\n" % port) - tochild.flush() - tochild.write("connect\n") - tochild.flush() - tochild.write("new host\n") - tochild.flush() - tochild.write('set name = \"%s\"\n' % host) - tochild.flush() - tochild.write("open\n") # opens register with host information - tochild.flush() - tochild.write("remove\n") - tochild.flush() - tochild.close() - fromchild.close() - except IOError: - # FIXME: convert this to subprocess. - # FIXME: catch specific errors only (32/broken pipe) - pass - - - def write_dhcp_file(self): - """ - DHCP files are written when manage_dhcp is set in - /var/lib/cobbler/settings. - """ - - settings_file = self.settings.dhcpd_conf - template_file = "/etc/cobbler/dhcp.template" - mode = self.settings.manage_dhcp_mode.lower() - if mode == "dnsmasq": - settings_file = self.settings.dnsmasq_conf - template_file = "/etc/cobbler/dnsmasq.template" - - try: - f2 = open(template_file,"r") - except: - raise CX(_("error writing template to file: %s") % template_file) - template_data = "" - template_data = f2.read() - f2.close() - - # build each per-system definition - # as configured, this only works for ISC, patches accepted - # from those that care about Itanium. elilo seems to be unmaintained - # so additional maintaince in other areas may be required to keep - # this working. - - elilo = os.path.basename(self.settings.bootloaders["ia64"]) - - system_definitions = {} - counter = 0 - - - # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in - # case to avoid conflicts with the hosts we're defining and to clean - # possible removed hosts (only if using OMAPI) - if self.settings.omapi_enabled and self.settings.omapi_port: - if os.path.exists("/var/lib/dhcpd/dhcpd.leases"): - file = open('/var/lib/dhcpd/dhcpd.leases') - item = shlex(file) - while 1: - elem = item.get_token() - if not elem: - break - if elem == 'host': - hostremove = item.get_token() - self.removeDHCPLease(self.settings.omapi_port,hostremove) - - # we used to just loop through each system, but now we must loop - # through each network interface of each system. - - for system in self.systems: - profile = system.get_conceptual_parent() - distro = profile.get_conceptual_parent() - for (name, interface) in system.interfaces.iteritems(): - - mac = interface["mac_address"] - ip = interface["ip_address"] - host = interface["hostname"] - - if mac is None or mac == "": - # can't write a DHCP entry for this system - continue - - counter = counter + 1 - systxt = "" - - if mode == "isc": - - # the label the entry after the hostname if possible - if host is not None and host != "": - systxt = "\nhost %s {\n" % host - if self.settings.isc_set_host_name: - systxt = systxt + " option host-name = \"%s\";\n" % host - else: - systxt = "\nhost generic%d {\n" % counter - - if distro.arch == "ia64": - # can't use pxelinux.0 anymore - systxt = systxt + " filename \"/%s\";\n" % elilo - systxt = systxt + " hardware ethernet %s;\n" % mac - if ip is not None and ip != "": - systxt = systxt + " fixed-address %s;\n" % ip - systxt = systxt + "}\n" - - # If we have all values defined and we're using omapi, - # we will just create entries dinamically into DHCPD - # without requiring a restart (but file will be written - # as usual for having it working after restart) - - if ip is not None and ip != "": - if mac is not None and mac != "": - if host is not None and host != "": - if self.settings.omapi_enabled and self.settings.omapi_port: - self.removeDHCPLease(self.settings.omapi_port,host) - self.writeDHCPLease(self.settings.omapi_port,host,ip,mac) - - - else: - # dnsmasq. don't have to write IP and other info here, but we do tag - # each MAC based on the arch of it's distro, if it needs something other - # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq - # reload (full "cobbler sync") would be required after adding the system - # to cobbler, just to tag this relationship. - - if ip is not None and ip != "": - if distro.arch.lower() == "ia64": - systxt = "dhcp-host=net:ia64," + ip + "\n" - # support for other arches needs modifications here - else: - systxt = "" - - dhcp_tag = interface["dhcp_tag"] - if dhcp_tag == "": - dhcp_tag = "default" - - if not system_definitions.has_key(dhcp_tag): - system_definitions[dhcp_tag] = "" - system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt - - # we are now done with the looping through each interface of each system - - metadata = { - "omapi_enabled" : self.settings.omapi_enabled, - "omapi_port" : self.settings.omapi_port, - "insert_cobbler_system_definitions" : system_definitions.get("default",""), - "date" : time.asctime(time.gmtime()), - "cobbler_server" : self.settings.server, - "next_server" : self.settings.next_server, - "elilo" : elilo - } - - # now add in other DHCP expansions that are not tagged with "default" - for x in system_definitions.keys(): - if x == "default": - continue - metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] - - self.templar.render(template_data, metadata, settings_file, None) - - def regen_ethers(self): - # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date - # every time we add a system. - # read 'man ethers' for format info - fh = open("/etc/ethers","w+") - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - mac = interface["mac_address"] - ip = interface["ip_address"] - if mac is None or mac == "": - # can't write this w/o a MAC address - continue - if ip is not None and ip != "": - fh.write(mac.upper() + "\t" + ip + "\n") - fh.close() - - def regen_hosts(self): - # dnsmasq knows how to read this database for host info - # (other things may also make use of this later) - fh = open("/var/lib/cobbler/cobbler_hosts","w+") - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - mac = interface["mac_address"] - host = interface["hostname"] - ip = interface["ip_address"] - if mac is None or mac == "": - continue - if host is not None and host != "" and ip is not None and ip != "": - fh.write(ip + "\t" + host + "\n") - fh.close() diff --git a/cobbler/manage_ctrl.py b/cobbler/manage_ctrl.py new file mode 100644 index 0000000..a01910a --- /dev/null +++ b/cobbler/manage_ctrl.py @@ -0,0 +1,76 @@ +""" +Builds out DHCP info +This is the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno +import popen2 +from shlex import shlex + + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + +class ManageCtrl: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config,verbose=False,dns=None,dhcp=None): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + self.dns = dns + + def write_dhcp_lease(self,port,host,ip,mac): + return dhcp.write_dhcp_lease(port,host,ip,mac) + + def remove_dhcp_lease(self,port,host): + return dhcp.remove_dhcp_lease(port,host) + + def write_dhcp_file(self): + return dhcp.write_dhcp_file() + + def regen_ethers(self): + return dhcp.regen_ethers() + + def regen_hosts(self): + return dns.regen_hosts() + + def write_dns_files(self): + return dns.write_bind_files() diff --git a/cobbler/modules/manage_dnsmasq.py b/cobbler/modules/manage_dnsmasq.py new file mode 100644 index 0000000..d37a2d4 --- /dev/null +++ b/cobbler/modules/manage_dnsmasq.py @@ -0,0 +1,191 @@ + +""" +This is some of the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan +John Eckersberg + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno +import popen2 +from shlex import shlex +import utils +from cexceptions import * +import templar +import item_distro +import item_profile +import item_repo +import item_system +from utils import _ + +def register(): + return "manage" + +class DnsmasqManager: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config,verbose=False,dhcp=None): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + self.dns = dns + + def what(self): + return "dnsmasq" + + def write_dhcp_lease(self,port,host,ip,mac): + pass + + def remove_dhcp_lease(self,port,host): + pass + + + def write_dhcp_file(self): + """ + DHCP files are written when manage_dhcp is set in + /var/lib/cobbler/settings. + """ + + settings_file = self.settings.dnsmasq_conf + template_file = "/etc/cobbler/dnsmasq.template" + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error writing template to file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + # build each per-system definition + # as configured, this only works for ISC, patches accepted + # from those that care about Itanium. elilo seems to be unmaintained + # so additional maintaince in other areas may be required to keep + # this working. + + elilo = os.path.basename(self.settings.bootloaders["ia64"]) + + system_definitions = {} + counter = 0 + + # we used to just loop through each system, but now we must loop + # through each network interface of each system. + + for system in self.systems: + profile = system.get_conceptual_parent() + distro = profile.get_conceptual_parent() + for (name, interface) in system.interfaces.iteritems(): + + mac = interface["mac_address"] + ip = interface["ip_address"] + host = interface["hostname"] + + if mac is None or mac == "": + # can't write a DHCP entry for this system + continue + + counter = counter + 1 + systxt = "" + + # dnsmasq. don't have to write IP and other info here, but we do tag + # each MAC based on the arch of it's distro, if it needs something other + # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq + # reload (full "cobbler sync") would be required after adding the system + # to cobbler, just to tag this relationship. + + if ip is not None and ip != "": + if distro.arch.lower() == "ia64": + systxt = "dhcp-host=net:ia64," + ip + "\n" + # support for other arches needs modifications here + else: + systxt = "" + + dhcp_tag = interface["dhcp_tag"] + if dhcp_tag == "": + dhcp_tag = "default" + + if not system_definitions.has_key(dhcp_tag): + system_definitions[dhcp_tag] = "" + system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt + + # we are now done with the looping through each interface of each system + + metadata = { + "omapi_enabled" : self.settings.omapi_enabled, + "omapi_port" : self.settings.omapi_port, + "insert_cobbler_system_definitions" : system_definitions.get("default",""), + "date" : time.asctime(time.gmtime()), + "cobbler_server" : self.settings.server, + "next_server" : self.settings.next_server, + "elilo" : elilo + } + + # now add in other DHCP expansions that are not tagged with "default" + for x in system_definitions.keys(): + if x == "default": + continue + metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] + + self.templar.render(template_data, metadata, settings_file, None) + + def regen_ethers(self): + # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date + # every time we add a system. + # read 'man ethers' for format info + fh = open("/etc/ethers","w+") + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + mac = interface["mac_address"] + ip = interface["ip_address"] + if mac is None or mac == "": + # can't write this w/o a MAC address + continue + if ip is not None and ip != "": + fh.write(mac.upper() + "\t" + ip + "\n") + fh.close() + + def regen_hosts(self): + # dnsmasq knows how to read this database for host info + # (other things may also make use of this later) + fh = open("/var/lib/cobbler/cobbler_hosts","w+") + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + mac = interface["mac_address"] + host = interface["hostname"] + ip = interface["ip_address"] + if mac is None or mac == "": + continue + if host is not None and host != "" and ip is not None and ip != "": + fh.write(ip + "\t" + host + "\n") + fh.close() + +def get_manager(config): + return DnsmasqManager(config) + diff --git a/cobbler/modules/manage_isc_and_bind.py b/cobbler/modules/manage_isc_and_bind.py new file mode 100644 index 0000000..bc3a338 --- /dev/null +++ b/cobbler/modules/manage_isc_and_bind.py @@ -0,0 +1,425 @@ +""" +This is some of the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan +John Eckersberg + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno +import popen2 +from shlex import shlex + + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "manage" + + +class IscAndBindManager: + + def what(self): + return "isc_and_bind" + + def __init__(self,config,verbose=False): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + + def write_dhcp_lease(self,port,host,ip,mac): + """ + Use DHCP's API to create a DHCP entry in the + /var/lib/dhcpd/dhcpd.leases file + #Code from http://svn.osgdc.org/browse/kusu/kusu + # /trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025 + # FIXME: should use subprocess + """ + try: + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("set ip-address = %s\n" % ip) + tochild.flush() + tochild.write("set hardware-address = %s\n" % mac) + tochild.flush() + tochild.write("set hardware-type = 1\n") + tochild.flush() + tochild.write("create\n") + tochild.flush() + tochild.close() + fromchild.close() + except IOError: + # FIXME: just catch 32 (broken pipe) and show a warning + pass + + def remove_dhcp_lease(self,port,host): + """ + removeDHCPLease(port,host) + Use DHCP's API to delete a DHCP entry in + the /var/lib/dhcpd/dhcpd.leases file + """ + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + try: + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("open\n") # opens register with host information + tochild.flush() + tochild.write("remove\n") + tochild.flush() + tochild.close() + fromchild.close() + except IOError: + # FIXME: convert this to subprocess. + # FIXME: catch specific errors only (32/broken pipe) + pass + + def write_dhcp_file(self): + """ + DHCP files are written when manage_dhcp is set in + /var/lib/cobbler/settings. + """ + + settings_file = self.settings.dhcpd_conf + template_file = "/etc/cobbler/dhcp.template" + mode = self.settings.manage_dhcp_mode.lower() + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error writing template to file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + # build each per-system definition + # as configured, this only works for ISC, patches accepted + # from those that care about Itanium. elilo seems to be unmaintained + # so additional maintaince in other areas may be required to keep + # this working. + + elilo = os.path.basename(self.settings.bootloaders["ia64"]) + + system_definitions = {} + counter = 0 + + + # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in + # case to avoid conflicts with the hosts we're defining and to clean + # possible removed hosts (only if using OMAPI) + if self.settings.omapi_enabled and self.settings.omapi_port: + if os.path.exists("/var/lib/dhcpd/dhcpd.leases"): + file = open('/var/lib/dhcpd/dhcpd.leases') + item = shlex(file) + while 1: + elem = item.get_token() + if not elem: + break + if elem == 'host': + hostremove = item.get_token() + self.removeDHCPLease(self.settings.omapi_port,hostremove) + + # we used to just loop through each system, but now we must loop + # through each network interface of each system. + + for system in self.systems: + profile = system.get_conceptual_parent() + distro = profile.get_conceptual_parent() + for (name, interface) in system.interfaces.iteritems(): + + mac = interface["mac_address"] + ip = interface["ip_address"] + host = interface["hostname"] + + if mac is None or mac == "": + # can't write a DHCP entry for this system + continue + + counter = counter + 1 + systxt = "" + + + # the label the entry after the hostname if possible + if host is not None and host != "": + systxt = "\nhost %s {\n" % host + if self.settings.isc_set_host_name: + systxt = systxt + " option host-name = \"%s\";\n" % host + else: + systxt = "\nhost generic%d {\n" % counter + + if distro.arch == "ia64": + # can't use pxelinux.0 anymore + systxt = systxt + " filename \"/%s\";\n" % elilo + systxt = systxt + " hardware ethernet %s;\n" % mac + if ip is not None and ip != "": + systxt = systxt + " fixed-address %s;\n" % ip + systxt = systxt + "}\n" + + # If we have all values defined and we're using omapi, + # we will just create entries dinamically into DHCPD + # without requiring a restart (but file will be written + # as usual for having it working after restart) + + if ip is not None and ip != "": + if mac is not None and mac != "": + if host is not None and host != "": + if self.settings.omapi_enabled and self.settings.omapi_port: + self.removeDHCPLease(self.settings.omapi_port,host) + self.writeDHCPLease(self.settings.omapi_port,host,ip,mac) + + dhcp_tag = interface["dhcp_tag"] + if dhcp_tag == "": + dhcp_tag = "default" + + if not system_definitions.has_key(dhcp_tag): + system_definitions[dhcp_tag] = "" + system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt + + # we are now done with the looping through each interface of each system + + metadata = { + "omapi_enabled" : self.settings.omapi_enabled, + "omapi_port" : self.settings.omapi_port, + "insert_cobbler_system_definitions" : system_definitions.get("default",""), + "date" : time.asctime(time.gmtime()), + "cobbler_server" : self.settings.server, + "next_server" : self.settings.next_server, + "elilo" : elilo + } + + # now add in other DHCP expansions that are not tagged with "default" + for x in system_definitions.keys(): + if x == "default": + continue + metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] + + self.templar.render(template_data, metadata, settings_file, None) + + def regen_ethers(self): + pass # ISC/BIND do not use this + + + def regen_hosts(self): + pass # ISC/BIND do not use this + + + def __forward_zones(self): + """ + Returns ALL forward zones for all systems + """ + zones = {} + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + host = interface["hostname"] + ip = interface["ip_address"] + if not host or not ip: + # gotsta have some hostname and ip or else! + continue + tokens = host.split('.') + zone = '.'.join(tokens[1:]) + if zones.has_key(zone): + zones[zone].append((host.split('.')[0], ip)) + else: + zones[zone] = [(host.split('.')[0], ip)] + return zones + + def __reverse_zones(self): + """ + Returns ALL reverse zones for all systems + """ + zones = {} + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + host = interface["hostname"] + ip = interface["ip_address"] + if not host or not ip: + # gotsta have some hostname and ip or else! + continue + tokens = ip.split('.') + zone = '.'.join(tokens[0:3]) + if zones.has_key(zone): + zones[zone].append((tokens[3], host + '.')) + else: + zones[zone] = [(tokens[3], host + '.')] + return zones + + def __config_forward_zones(self): + """ + Returns only the forward zones which have systems and are defined + in the option manage_forward_zones + + Alternatively if manage_forward_zones is empty, return all systems + """ + all = self.__forward_zones() + want = self.settings.manage_forward_zones + if want == []: return all + ret = {} + for zone in all.keys(): + if zone in want: + ret[zone] = all[zone] + return ret + + def __config_reverse_zones(self): + """ + Returns only the reverse zones which have systems and are defined + in the option manage_reverse_zones + + Alternatively if manage_reverse_zones is empty, return all systems + """ + all = self.__reverse_zones() + want = self.settings.manage_reverse_zones + if want == []: return all + ret = {} + for zone in all.keys(): + if zone in want: + ret[zone] = all[zone] + return ret + + def __write_named_conf(self): + """ + Write out the named.conf main config file from the template. + """ + settings_file = self.settings.named_conf + template_file = "/etc/cobbler/named.template" + forward_zones = self.settings.manage_forward_zones + reverse_zones = self.settings.manage_reverse_zones + + metadata = {'zone_include': ''} + for zone in self.__config_forward_zones().keys(): + txt = """ +zone "%(zone)s." { + type master; + file "%(zone)s"; +}; +""" % {'zone': zone} + metadata['zone_include'] = metadata['zone_include'] + txt + + for zone in self.__config_reverse_zones().keys(): + tokens = zone.split('.') + tokens.reverse() + arpa = '.'.join(tokens) + '.in-addr.arpa' + txt = """ +zone "%(arpa)s." { + type master; + file "%(zone)s"; +}; +""" % {'arpa': arpa, 'zone': zone} + metadata['zone_include'] = metadata['zone_include'] + txt + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error reading template from file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + self.templar.render(template_data, metadata, settings_file, None) + + def __write_zone_files(self): + """ + Write out the forward and reverse zone files for all the zones + defined in manage_forward_zones and manage_reverse_zones + """ + template_file = "/etc/cobbler/zone.template" + cobbler_server = self.settings.server + serial = int(time.time()) + forward = self.__config_forward_zones() + reverse = self.__config_reverse_zones() + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error reading template from file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + for (zone, hosts) in forward.iteritems(): + metadata = { + 'cobbler_server': cobbler_server, + 'serial': serial, + 'host_record': '' + } + + for host in hosts: + txt = '%s\tIN\tA\t%s\n' % host + metadata['host_record'] = metadata['host_record'] + txt + + self.templar.render(template_data, metadata, '/var/named/' + zone, None) + + for (zone, hosts) in reverse.iteritems(): + metadata = { + 'cobbler_server': cobbler_server, + 'serial': serial, + 'host_record': '' + } + + for host in hosts: + txt = '%s\tIN\tPTR\t%s\n' % host + metadata['host_record'] = metadata['host_record'] + txt + + self.templar.render(template_data, metadata, '/var/named/' + zone, None) + + + def write_dns_files(self): + """ + BIND files are written when manage_dns is set in + /var/lib/cobbler/settings. + """ + + self.__write_named_conf() + self.__write_zone_files() + +def get_manager(config): + return IscAndBindManager(config) + diff --git a/config/modules.conf b/config/modules.conf index 88aa134..c16385e 100644 --- a/config/modules.conf +++ b/config/modules.conf @@ -10,3 +10,8 @@ module = authn_denyall [authorization] module = authz_allowall + +[manage] +module = manage_isc_and_bind + + diff --git a/config/settings b/config/settings index 8b39707..5c4d7fa 100644 --- a/config/settings +++ b/config/settings @@ -30,7 +30,6 @@ ldap_search_bind_dn: '' ldap_search_passwd: '' ldap_search_prefix: 'uid=' manage_dhcp: 0 -manage_dhcp_mode: isc manage_dns: 0 next_server: '127.0.0.1' omapi_enabled: 1 -- cgit From 6ffd4eca30766153aa920888411f6437948757b2 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 16:38:22 -0400 Subject: Add some missing files and do some more work on dns/dhcp refactoring --- cobbler/action_check.py | 8 +++++++- cobbler/action_litesync.py | 4 ++-- cobbler/modules/manage_dnsmasq.py | 5 ++++- cobbler/modules/manage_isc_and_bind.py | 4 ++-- templates/named.template | 18 ++++++++++++++++++ templates/zone.template | 13 +++++++++++++ 6 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 templates/named.template create mode 100644 templates/zone.template diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 588a65a..2004c9f 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -48,7 +48,13 @@ class BootCheck: else: status.append(_("configured management mode in modules.conf is unknown")) # FIXME: add in checks for bind config - + if self.settings.manage_dns: + mode = self.config.api.get_sync().manager.what() + if mode == "isc_and_bind": + #self.check_bind_bin(status) + #self.check_service(status,"dhcpd") + pass + self.check_service(status, "cobblerd") self.check_bootloaders(status) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index ba0a256..9df99cc 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -117,8 +117,8 @@ class BootLiteSync: self.sync.manager.write_dhcp_lease( self.settings.omapi_port, interface["hostname"], - interface["mac-address"], - interface["ip-address"] + interface["mac_address"], + interface["ip_address"] ) diff --git a/cobbler/modules/manage_dnsmasq.py b/cobbler/modules/manage_dnsmasq.py index d37a2d4..465995f 100644 --- a/cobbler/modules/manage_dnsmasq.py +++ b/cobbler/modules/manage_dnsmasq.py @@ -55,7 +55,6 @@ class DnsmasqManager: self.settings = config.settings() self.repos = config.repos() self.templar = templar.Templar(config) - self.dns = dns def what(self): return "dnsmasq" @@ -186,6 +185,10 @@ class DnsmasqManager: fh.write(ip + "\t" + host + "\n") fh.close() + def write_dns_files(self): + # already taken care of by the regen_hosts() + pass + def get_manager(config): return DnsmasqManager(config) diff --git a/cobbler/modules/manage_isc_and_bind.py b/cobbler/modules/manage_isc_and_bind.py index bc3a338..3927c69 100644 --- a/cobbler/modules/manage_isc_and_bind.py +++ b/cobbler/modules/manage_isc_and_bind.py @@ -213,8 +213,8 @@ class IscAndBindManager: if mac is not None and mac != "": if host is not None and host != "": if self.settings.omapi_enabled and self.settings.omapi_port: - self.removeDHCPLease(self.settings.omapi_port,host) - self.writeDHCPLease(self.settings.omapi_port,host,ip,mac) + self.remove_dhcp_lease(self.settings.omapi_port,host) + self.write_dhcp_lease(self.settings.omapi_port,host,ip,mac) dhcp_tag = interface["dhcp_tag"] if dhcp_tag == "": diff --git a/templates/named.template b/templates/named.template new file mode 100644 index 0000000..f77eadc --- /dev/null +++ b/templates/named.template @@ -0,0 +1,18 @@ +options { + listen-on port 53 { 127.0.0.1; }; + directory "/var/named"; + dump-file "/var/named/data/cache_dump.db"; + statistics-file "/var/named/data/named_stats.txt"; + memstatistics-file "/var/named/data/named_mem_stats.txt"; + allow-query { localhost; }; + recursion yes; +}; + +logging { + channel default_debug { + file "data/named.run"; + severity dynamic; + }; +}; + +$zone_include diff --git a/templates/zone.template b/templates/zone.template new file mode 100644 index 0000000..5426f78 --- /dev/null +++ b/templates/zone.template @@ -0,0 +1,13 @@ +\$TTL 300 +@ IN SOA $cobbler_server. nobody.example.com. ( + $serial ; Serial + 600 ; Refresh + 1800 ; Retry + 604800 ; Expire + 300 ; TTL + ) + + IN NS $cobbler_server. + + +$host_record -- cgit From 35fd760f0fc069906da78aebf4a1ecb3e2414291 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 16:50:33 -0400 Subject: Added some check code for BIND. --- cobbler/action_check.py | 16 ++++++++++++---- cobbler/settings.py | 2 ++ config/settings | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 2004c9f..e81e66c 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -51,8 +51,8 @@ class BootCheck: if self.settings.manage_dns: mode = self.config.api.get_sync().manager.what() if mode == "isc_and_bind": - #self.check_bind_bin(status) - #self.check_service(status,"dhcpd") + self.check_bind_bin(status) + self.check_service(status,"named") pass self.check_service(status, "cobblerd") @@ -118,14 +118,22 @@ class BootCheck: Check if dhcpd is installed """ if not os.path.exists(self.settings.dhcpd_bin): - status.append(_("dhcpd isn't installed, but is enabled in /var/lib/cobbler/settings")) + status.append(_("dhcpd isn't installed, but management is enabled in /var/lib/cobbler/settings")) def check_dnsmasq_bin(self,status): """ Check if dnsmasq is installed """ if not os.path.exists(self.settings.dnsmasq_bin): - status.append(_("dnsmasq isn't installed, but is enabled in /var/lib/cobbler/settings")) + status.append(_("dnsmasq isn't installed, but management is enabled in /var/lib/cobbler/settings")) + + def check_bind_bin(self,status): + """ + Check if bind is installed. + """ + if not os.path.exists(self.settings.bind_bin): + status.append(_("bind isn't installed, but management is enabled in /var/lib/cobbler/settings")) + def check_bootloaders(self,status): """ diff --git a/cobbler/settings.py b/cobbler/settings.py index 70b2ac8..bf12545 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -24,6 +24,7 @@ TESTMODE = False DEFAULTS = { "allow_duplicate_macs" : 0, "allow_duplicate_ips" : 0, + "bind_bin" : "/usr/sbin/named", "bootloaders" : { "standard" : "/usr/lib/syslinux/pxelinux.0", "ia64" : "/var/lib/cobbler/elilo-3.6-ia64.efi" @@ -64,6 +65,7 @@ DEFAULTS = { "next_server" : "127.0.0.1", "omapi_enabled" : 0, "omapi_port" : 647, + "omshell_bin" : "/usr/bin/omshell", "pxe_just_once" : 0, "register_new_installs" : 0, "run_install_triggers" : 1, diff --git a/config/settings b/config/settings index 5c4d7fa..2f6b08d 100644 --- a/config/settings +++ b/config/settings @@ -1,6 +1,7 @@ --- allow_duplicate_macs: 0 allow_duplicate_ips: 0 +bind_bin: /usr/sbin/named bootloaders: ia64: /var/lib/cobbler/elilo-3.6-ia64.efi standard: /usr/lib/syslinux/pxelinux.0 @@ -34,6 +35,7 @@ manage_dns: 0 next_server: '127.0.0.1' omapi_enabled: 1 omapi_port: 647 +omshell_bin: /usr/bin/omshell pxe_just_once: 0 register_new_installs: 0 run_install_triggers: 1 -- cgit From ca041e7ed57a4818ed490b9fd9429db20d9d8860 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 2 May 2008 17:31:46 -0400 Subject: Various DNS/DHCP work. --- cobbler/action_check.py | 16 +- cobbler/action_litesync.py | 10 +- cobbler/action_sync.py | 14 +- cobbler/api.py | 13 +- cobbler/modules/manage_bind.py | 242 +++++++++++++++++++ cobbler/modules/manage_isc.py | 253 ++++++++++++++++++++ cobbler/modules/manage_isc_and_bind.py | 425 --------------------------------- config/modules.conf | 7 +- 8 files changed, 529 insertions(+), 451 deletions(-) create mode 100644 cobbler/modules/manage_bind.py create mode 100644 cobbler/modules/manage_isc.py delete mode 100644 cobbler/modules/manage_isc_and_bind.py diff --git a/cobbler/action_check.py b/cobbler/action_check.py index e81e66c..9fe0543 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -37,23 +37,23 @@ class BootCheck: status = [] self.check_name(status) if self.settings.manage_dhcp: - mode = self.config.api.get_sync().manager.what() - if mode == "isc_and_bind": + mode = self.config.api.get_sync().dhcp.what() + if mode == "isc": self.check_dhcpd_bin(status) self.check_dhcpd_conf(status) self.check_service(status,"dhcpd") elif mode == "dnsmasq": self.check_dnsmasq_bin(status) self.check_service(status,"dnsmasq") - else: - status.append(_("configured management mode in modules.conf is unknown")) - # FIXME: add in checks for bind config + if self.settings.manage_dns: - mode = self.config.api.get_sync().manager.what() - if mode == "isc_and_bind": + mode = self.config.api.get_sync().dns.what() + if mode == "bind": self.check_bind_bin(status) self.check_service(status,"named") - pass + elif mode == "dnsmasq" and not self.settings.manage_dhcp: + self.check_dnsmasq_bin(status) + self.check_service(status,"dnsmasq") self.check_service(status, "cobblerd") diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index 9df99cc..ba3e3ee 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -105,8 +105,10 @@ class BootLiteSync: if system is None: raise CX(_("error in system lookup for %s") % name) # rebuild system_list file in webdir - self.sync.manager.regen_ethers() - self.sync.manager.regen_hosts() + if self.settings.manage_dhcp: + self.sync.dns.regen_ethers() + if self.settings.manage_dns: + self.sync.dhcp.regen_hosts() # write the PXE files for the system self.sync.pxegen.write_all_system_files(system) # per system kickstarts @@ -114,7 +116,7 @@ class BootLiteSync: if self.settings.manage_dhcp: if self.settings.omapi_enabled: for (name,interface) in system.interfaces.iteritems(): - self.sync.manager.write_dhcp_lease( + self.sync.dhcp.write_dhcp_lease( self.settings.omapi_port, interface["hostname"], interface["mac_address"], @@ -135,7 +137,7 @@ class BootLiteSync: if self.settings.manage_dhcp: if self.settings.omapi_enabled: for (name,interface) in system_record.interfaces.iteritems(): - self.sync.manager.remove_dhcp_lease( + self.sync.dhcp.remove_dhcp_lease( self.settings.omapi_port, interface["hostname"] ) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index fbc9402..55cdad5 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -28,7 +28,6 @@ import utils from cexceptions import * import templar import pxegen -import manage_ctrl import yumgen import item_distro @@ -46,7 +45,7 @@ class BootSync: Handles conversion of internal state to the tftpboot tree layout """ - def __init__(self,config,verbose=False,manage=None): + def __init__(self,config,verbose=False,dhcp=None,dns=None): """ Constructor """ @@ -60,7 +59,8 @@ class BootSync: self.repos = config.repos() self.templar = templar.Templar(config) self.pxegen = pxegen.PXEGen(config) - self.manager = manage + self.dns = dns + self.dhcp = dhcp self.yumgen = yumgen.YumGen(config) self.bootloc = utils.tftpboot_location() @@ -93,11 +93,11 @@ class BootSync: self.pxegen.write_all_system_files(x) self.yumgen.retemplate_all_yum_repos() if self.settings.manage_dhcp: - self.manager.write_dhcp_file() - self.manager.regen_ethers() - self.manager.regen_hosts() + self.dhcp.write_dhcp_file() + self.dhcp.regen_ethers() if self.settings.manage_dns: - self.manager.write_dns_files() + self.dns.regen_hosts() + self.dns.write_dns_files() self.pxegen.make_pxe_menu() # run post-triggers diff --git a/cobbler/api.py b/cobbler/api.py index 1f5a246..20c7364 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -336,12 +336,17 @@ class BootAPI: return sync.run() def get_sync(self): - self.manage = self.get_module_from_file( - "management", + self.dhcp = self.get_module_from_file( + "dhcp", "module", - "manage_isc_and_bind" + "manage_isc" ).get_manager(self._config) - return action_sync.BootSync(self._config,manage=self.manage) + self.dns = self.get_module_from_file( + "dns", + "module", + "manage_bind" + ).get_manager(self._config) + return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns) def reposync(self, name=None): """ diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py new file mode 100644 index 0000000..900a832 --- /dev/null +++ b/cobbler/modules/manage_bind.py @@ -0,0 +1,242 @@ +""" +This is some of the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan +John Eckersberg + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno +import popen2 +from shlex import shlex + + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "manage" + + +class BindManager: + + def what(self): + return "isc_and_bind" + + def __init__(self,config,verbose=False): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + + def regen_hosts(self): + pass # not used + + def __forward_zones(self): + """ + Returns ALL forward zones for all systems + """ + zones = {} + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + host = interface["hostname"] + ip = interface["ip_address"] + if not host or not ip: + # gotsta have some hostname and ip or else! + continue + if host.find(".") == -1: + continue + tokens = host.split('.') + zone = '.'.join(tokens[1:]) + if zones.has_key(zone): + zones[zone].append((host.split('.')[0], ip)) + else: + zones[zone] = [(host.split('.')[0], ip)] + return zones + + def __reverse_zones(self): + """ + Returns ALL reverse zones for all systems + """ + zones = {} + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + host = interface["hostname"] + ip = interface["ip_address"] + if not host or not ip: + # gotsta have some hostname and ip or else! + continue + tokens = ip.split('.') + zone = '.'.join(tokens[0:3]) + if zones.has_key(zone): + zones[zone].append((tokens[3], host + '.')) + else: + zones[zone] = [(tokens[3], host + '.')] + return zones + + def __config_forward_zones(self): + """ + Returns only the forward zones which have systems and are defined + in the option manage_forward_zones + + Alternatively if manage_forward_zones is empty, return all systems + """ + all = self.__forward_zones() + want = self.settings.manage_forward_zones + if want == []: return all + ret = {} + for zone in all.keys(): + if zone in want: + ret[zone] = all[zone] + return ret + + def __config_reverse_zones(self): + """ + Returns only the reverse zones which have systems and are defined + in the option manage_reverse_zones + + Alternatively if manage_reverse_zones is empty, return all systems + """ + all = self.__reverse_zones() + want = self.settings.manage_reverse_zones + if want == []: return all + ret = {} + for zone in all.keys(): + if zone in want: + ret[zone] = all[zone] + return ret + + def __write_named_conf(self): + """ + Write out the named.conf main config file from the template. + """ + settings_file = self.settings.named_conf + template_file = "/etc/cobbler/named.template" + forward_zones = self.settings.manage_forward_zones + reverse_zones = self.settings.manage_reverse_zones + + metadata = {'zone_include': ''} + for zone in self.__config_forward_zones().keys(): + txt = """ +zone "%(zone)s." { + type master; + file "%(zone)s"; +}; +""" % {'zone': zone} + metadata['zone_include'] = metadata['zone_include'] + txt + + for zone in self.__config_reverse_zones().keys(): + tokens = zone.split('.') + tokens.reverse() + arpa = '.'.join(tokens) + '.in-addr.arpa' + txt = """ +zone "%(arpa)s." { + type master; + file "%(zone)s"; +}; +""" % {'arpa': arpa, 'zone': zone} + metadata['zone_include'] = metadata['zone_include'] + txt + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error reading template from file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + self.templar.render(template_data, metadata, settings_file, None) + + def __write_zone_files(self): + """ + Write out the forward and reverse zone files for all the zones + defined in manage_forward_zones and manage_reverse_zones + """ + template_file = "/etc/cobbler/zone.template" + cobbler_server = self.settings.server + serial = int(time.time()) + forward = self.__config_forward_zones() + reverse = self.__config_reverse_zones() + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error reading template from file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + for (zone, hosts) in forward.iteritems(): + metadata = { + 'cobbler_server': cobbler_server, + 'serial': serial, + 'host_record': '' + } + + for host in hosts: + txt = '%s\tIN\tA\t%s\n' % host + metadata['host_record'] = metadata['host_record'] + txt + + self.templar.render(template_data, metadata, '/var/named/' + zone, None) + + for (zone, hosts) in reverse.iteritems(): + metadata = { + 'cobbler_server': cobbler_server, + 'serial': serial, + 'host_record': '' + } + + for host in hosts: + txt = '%s\tIN\tPTR\t%s\n' % host + metadata['host_record'] = metadata['host_record'] + txt + + self.templar.render(template_data, metadata, '/var/named/' + zone, None) + + + def write_dns_files(self): + """ + BIND files are written when manage_dns is set in + /var/lib/cobbler/settings. + """ + + self.__write_named_conf() + self.__write_zone_files() + +def get_manager(config): + return BindManager(config) + diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py new file mode 100644 index 0000000..bc88412 --- /dev/null +++ b/cobbler/modules/manage_isc.py @@ -0,0 +1,253 @@ +""" +This is some of the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan +John Eckersberg + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno +import popen2 +from shlex import shlex + + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "manage" + + +class IscManager: + + def what(self): + return "isc_and_bind" + + def __init__(self,config,verbose=False): + """ + Constructor + """ + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + + def write_dhcp_lease(self,port,host,ip,mac): + """ + Use DHCP's API to create a DHCP entry in the + /var/lib/dhcpd/dhcpd.leases file + #Code from http://svn.osgdc.org/browse/kusu/kusu + # /trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025 + # FIXME: should use subprocess + """ + try: + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("set ip-address = %s\n" % ip) + tochild.flush() + tochild.write("set hardware-address = %s\n" % mac) + tochild.flush() + tochild.write("set hardware-type = 1\n") + tochild.flush() + tochild.write("create\n") + tochild.flush() + tochild.close() + fromchild.close() + except IOError: + # FIXME: just catch 32 (broken pipe) and show a warning + pass + + def remove_dhcp_lease(self,port,host): + """ + removeDHCPLease(port,host) + Use DHCP's API to delete a DHCP entry in + the /var/lib/dhcpd/dhcpd.leases file + """ + fromchild, tochild = popen2.popen2("/usr/bin/omshell") + try: + tochild.write("port %s\n" % port) + tochild.flush() + tochild.write("connect\n") + tochild.flush() + tochild.write("new host\n") + tochild.flush() + tochild.write('set name = \"%s\"\n' % host) + tochild.flush() + tochild.write("open\n") # opens register with host information + tochild.flush() + tochild.write("remove\n") + tochild.flush() + tochild.close() + fromchild.close() + except IOError: + # FIXME: convert this to subprocess. + # FIXME: catch specific errors only (32/broken pipe) + pass + + def write_dhcp_file(self): + """ + DHCP files are written when manage_dhcp is set in + /var/lib/cobbler/settings. + """ + + settings_file = self.settings.dhcpd_conf + template_file = "/etc/cobbler/dhcp.template" + mode = self.settings.manage_dhcp_mode.lower() + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error writing template to file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + # build each per-system definition + # as configured, this only works for ISC, patches accepted + # from those that care about Itanium. elilo seems to be unmaintained + # so additional maintaince in other areas may be required to keep + # this working. + + elilo = os.path.basename(self.settings.bootloaders["ia64"]) + + system_definitions = {} + counter = 0 + + + # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in + # case to avoid conflicts with the hosts we're defining and to clean + # possible removed hosts (only if using OMAPI) + if self.settings.omapi_enabled and self.settings.omapi_port: + if os.path.exists("/var/lib/dhcpd/dhcpd.leases"): + file = open('/var/lib/dhcpd/dhcpd.leases') + item = shlex(file) + while 1: + elem = item.get_token() + if not elem: + break + if elem == 'host': + hostremove = item.get_token() + self.removeDHCPLease(self.settings.omapi_port,hostremove) + + # we used to just loop through each system, but now we must loop + # through each network interface of each system. + + for system in self.systems: + profile = system.get_conceptual_parent() + distro = profile.get_conceptual_parent() + for (name, interface) in system.interfaces.iteritems(): + + mac = interface["mac_address"] + ip = interface["ip_address"] + host = interface["hostname"] + + if mac is None or mac == "": + # can't write a DHCP entry for this system + continue + + counter = counter + 1 + systxt = "" + + + # the label the entry after the hostname if possible + if host is not None and host != "": + systxt = "\nhost %s {\n" % host + if self.settings.isc_set_host_name: + systxt = systxt + " option host-name = \"%s\";\n" % host + else: + systxt = "\nhost generic%d {\n" % counter + + if distro.arch == "ia64": + # can't use pxelinux.0 anymore + systxt = systxt + " filename \"/%s\";\n" % elilo + systxt = systxt + " hardware ethernet %s;\n" % mac + if ip is not None and ip != "": + systxt = systxt + " fixed-address %s;\n" % ip + systxt = systxt + "}\n" + + # If we have all values defined and we're using omapi, + # we will just create entries dinamically into DHCPD + # without requiring a restart (but file will be written + # as usual for having it working after restart) + + if ip is not None and ip != "": + if mac is not None and mac != "": + if host is not None and host != "": + if self.settings.omapi_enabled and self.settings.omapi_port: + self.remove_dhcp_lease(self.settings.omapi_port,host) + self.write_dhcp_lease(self.settings.omapi_port,host,ip,mac) + + dhcp_tag = interface["dhcp_tag"] + if dhcp_tag == "": + dhcp_tag = "default" + + if not system_definitions.has_key(dhcp_tag): + system_definitions[dhcp_tag] = "" + system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt + + # we are now done with the looping through each interface of each system + + metadata = { + "omapi_enabled" : self.settings.omapi_enabled, + "omapi_port" : self.settings.omapi_port, + "insert_cobbler_system_definitions" : system_definitions.get("default",""), + "date" : time.asctime(time.gmtime()), + "cobbler_server" : self.settings.server, + "next_server" : self.settings.next_server, + "elilo" : elilo + } + + # now add in other DHCP expansions that are not tagged with "default" + for x in system_definitions.keys(): + if x == "default": + continue + metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] + + self.templar.render(template_data, metadata, settings_file, None) + + def regen_ethers(self): + pass # ISC/BIND do not use this + + +def get_manager(config): + return IscManager(config) + diff --git a/cobbler/modules/manage_isc_and_bind.py b/cobbler/modules/manage_isc_and_bind.py deleted file mode 100644 index 3927c69..0000000 --- a/cobbler/modules/manage_isc_and_bind.py +++ /dev/null @@ -1,425 +0,0 @@ -""" -This is some of the code behind 'cobbler sync'. - -Copyright 2006-2008, Red Hat, Inc -Michael DeHaan -John Eckersberg - -This software may be freely redistributed under the terms of the GNU -general public license. - -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., 675 Mass Ave, Cambridge, MA 02139, USA. -""" - -import os -import os.path -import shutil -import time -import sub_process -import sys -import glob -import traceback -import errno -import popen2 -from shlex import shlex - - -import utils -from cexceptions import * -import templar - -import item_distro -import item_profile -import item_repo -import item_system - -from utils import _ - - -def register(): - """ - The mandatory cobbler module registration hook. - """ - return "manage" - - -class IscAndBindManager: - - def what(self): - return "isc_and_bind" - - def __init__(self,config,verbose=False): - """ - Constructor - """ - self.verbose = verbose - self.config = config - self.api = config.api - self.distros = config.distros() - self.profiles = config.profiles() - self.systems = config.systems() - self.settings = config.settings() - self.repos = config.repos() - self.templar = templar.Templar(config) - - def write_dhcp_lease(self,port,host,ip,mac): - """ - Use DHCP's API to create a DHCP entry in the - /var/lib/dhcpd/dhcpd.leases file - #Code from http://svn.osgdc.org/browse/kusu/kusu - # /trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025 - # FIXME: should use subprocess - """ - try: - fromchild, tochild = popen2.popen2("/usr/bin/omshell") - tochild.write("port %s\n" % port) - tochild.flush() - tochild.write("connect\n") - tochild.flush() - tochild.write("new host\n") - tochild.flush() - tochild.write('set name = \"%s\"\n' % host) - tochild.flush() - tochild.write("set ip-address = %s\n" % ip) - tochild.flush() - tochild.write("set hardware-address = %s\n" % mac) - tochild.flush() - tochild.write("set hardware-type = 1\n") - tochild.flush() - tochild.write("create\n") - tochild.flush() - tochild.close() - fromchild.close() - except IOError: - # FIXME: just catch 32 (broken pipe) and show a warning - pass - - def remove_dhcp_lease(self,port,host): - """ - removeDHCPLease(port,host) - Use DHCP's API to delete a DHCP entry in - the /var/lib/dhcpd/dhcpd.leases file - """ - fromchild, tochild = popen2.popen2("/usr/bin/omshell") - try: - tochild.write("port %s\n" % port) - tochild.flush() - tochild.write("connect\n") - tochild.flush() - tochild.write("new host\n") - tochild.flush() - tochild.write('set name = \"%s\"\n' % host) - tochild.flush() - tochild.write("open\n") # opens register with host information - tochild.flush() - tochild.write("remove\n") - tochild.flush() - tochild.close() - fromchild.close() - except IOError: - # FIXME: convert this to subprocess. - # FIXME: catch specific errors only (32/broken pipe) - pass - - def write_dhcp_file(self): - """ - DHCP files are written when manage_dhcp is set in - /var/lib/cobbler/settings. - """ - - settings_file = self.settings.dhcpd_conf - template_file = "/etc/cobbler/dhcp.template" - mode = self.settings.manage_dhcp_mode.lower() - - try: - f2 = open(template_file,"r") - except: - raise CX(_("error writing template to file: %s") % template_file) - template_data = "" - template_data = f2.read() - f2.close() - - # build each per-system definition - # as configured, this only works for ISC, patches accepted - # from those that care about Itanium. elilo seems to be unmaintained - # so additional maintaince in other areas may be required to keep - # this working. - - elilo = os.path.basename(self.settings.bootloaders["ia64"]) - - system_definitions = {} - counter = 0 - - - # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in - # case to avoid conflicts with the hosts we're defining and to clean - # possible removed hosts (only if using OMAPI) - if self.settings.omapi_enabled and self.settings.omapi_port: - if os.path.exists("/var/lib/dhcpd/dhcpd.leases"): - file = open('/var/lib/dhcpd/dhcpd.leases') - item = shlex(file) - while 1: - elem = item.get_token() - if not elem: - break - if elem == 'host': - hostremove = item.get_token() - self.removeDHCPLease(self.settings.omapi_port,hostremove) - - # we used to just loop through each system, but now we must loop - # through each network interface of each system. - - for system in self.systems: - profile = system.get_conceptual_parent() - distro = profile.get_conceptual_parent() - for (name, interface) in system.interfaces.iteritems(): - - mac = interface["mac_address"] - ip = interface["ip_address"] - host = interface["hostname"] - - if mac is None or mac == "": - # can't write a DHCP entry for this system - continue - - counter = counter + 1 - systxt = "" - - - # the label the entry after the hostname if possible - if host is not None and host != "": - systxt = "\nhost %s {\n" % host - if self.settings.isc_set_host_name: - systxt = systxt + " option host-name = \"%s\";\n" % host - else: - systxt = "\nhost generic%d {\n" % counter - - if distro.arch == "ia64": - # can't use pxelinux.0 anymore - systxt = systxt + " filename \"/%s\";\n" % elilo - systxt = systxt + " hardware ethernet %s;\n" % mac - if ip is not None and ip != "": - systxt = systxt + " fixed-address %s;\n" % ip - systxt = systxt + "}\n" - - # If we have all values defined and we're using omapi, - # we will just create entries dinamically into DHCPD - # without requiring a restart (but file will be written - # as usual for having it working after restart) - - if ip is not None and ip != "": - if mac is not None and mac != "": - if host is not None and host != "": - if self.settings.omapi_enabled and self.settings.omapi_port: - self.remove_dhcp_lease(self.settings.omapi_port,host) - self.write_dhcp_lease(self.settings.omapi_port,host,ip,mac) - - dhcp_tag = interface["dhcp_tag"] - if dhcp_tag == "": - dhcp_tag = "default" - - if not system_definitions.has_key(dhcp_tag): - system_definitions[dhcp_tag] = "" - system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt - - # we are now done with the looping through each interface of each system - - metadata = { - "omapi_enabled" : self.settings.omapi_enabled, - "omapi_port" : self.settings.omapi_port, - "insert_cobbler_system_definitions" : system_definitions.get("default",""), - "date" : time.asctime(time.gmtime()), - "cobbler_server" : self.settings.server, - "next_server" : self.settings.next_server, - "elilo" : elilo - } - - # now add in other DHCP expansions that are not tagged with "default" - for x in system_definitions.keys(): - if x == "default": - continue - metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] - - self.templar.render(template_data, metadata, settings_file, None) - - def regen_ethers(self): - pass # ISC/BIND do not use this - - - def regen_hosts(self): - pass # ISC/BIND do not use this - - - def __forward_zones(self): - """ - Returns ALL forward zones for all systems - """ - zones = {} - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - host = interface["hostname"] - ip = interface["ip_address"] - if not host or not ip: - # gotsta have some hostname and ip or else! - continue - tokens = host.split('.') - zone = '.'.join(tokens[1:]) - if zones.has_key(zone): - zones[zone].append((host.split('.')[0], ip)) - else: - zones[zone] = [(host.split('.')[0], ip)] - return zones - - def __reverse_zones(self): - """ - Returns ALL reverse zones for all systems - """ - zones = {} - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - host = interface["hostname"] - ip = interface["ip_address"] - if not host or not ip: - # gotsta have some hostname and ip or else! - continue - tokens = ip.split('.') - zone = '.'.join(tokens[0:3]) - if zones.has_key(zone): - zones[zone].append((tokens[3], host + '.')) - else: - zones[zone] = [(tokens[3], host + '.')] - return zones - - def __config_forward_zones(self): - """ - Returns only the forward zones which have systems and are defined - in the option manage_forward_zones - - Alternatively if manage_forward_zones is empty, return all systems - """ - all = self.__forward_zones() - want = self.settings.manage_forward_zones - if want == []: return all - ret = {} - for zone in all.keys(): - if zone in want: - ret[zone] = all[zone] - return ret - - def __config_reverse_zones(self): - """ - Returns only the reverse zones which have systems and are defined - in the option manage_reverse_zones - - Alternatively if manage_reverse_zones is empty, return all systems - """ - all = self.__reverse_zones() - want = self.settings.manage_reverse_zones - if want == []: return all - ret = {} - for zone in all.keys(): - if zone in want: - ret[zone] = all[zone] - return ret - - def __write_named_conf(self): - """ - Write out the named.conf main config file from the template. - """ - settings_file = self.settings.named_conf - template_file = "/etc/cobbler/named.template" - forward_zones = self.settings.manage_forward_zones - reverse_zones = self.settings.manage_reverse_zones - - metadata = {'zone_include': ''} - for zone in self.__config_forward_zones().keys(): - txt = """ -zone "%(zone)s." { - type master; - file "%(zone)s"; -}; -""" % {'zone': zone} - metadata['zone_include'] = metadata['zone_include'] + txt - - for zone in self.__config_reverse_zones().keys(): - tokens = zone.split('.') - tokens.reverse() - arpa = '.'.join(tokens) + '.in-addr.arpa' - txt = """ -zone "%(arpa)s." { - type master; - file "%(zone)s"; -}; -""" % {'arpa': arpa, 'zone': zone} - metadata['zone_include'] = metadata['zone_include'] + txt - - try: - f2 = open(template_file,"r") - except: - raise CX(_("error reading template from file: %s") % template_file) - template_data = "" - template_data = f2.read() - f2.close() - - self.templar.render(template_data, metadata, settings_file, None) - - def __write_zone_files(self): - """ - Write out the forward and reverse zone files for all the zones - defined in manage_forward_zones and manage_reverse_zones - """ - template_file = "/etc/cobbler/zone.template" - cobbler_server = self.settings.server - serial = int(time.time()) - forward = self.__config_forward_zones() - reverse = self.__config_reverse_zones() - - try: - f2 = open(template_file,"r") - except: - raise CX(_("error reading template from file: %s") % template_file) - template_data = "" - template_data = f2.read() - f2.close() - - for (zone, hosts) in forward.iteritems(): - metadata = { - 'cobbler_server': cobbler_server, - 'serial': serial, - 'host_record': '' - } - - for host in hosts: - txt = '%s\tIN\tA\t%s\n' % host - metadata['host_record'] = metadata['host_record'] + txt - - self.templar.render(template_data, metadata, '/var/named/' + zone, None) - - for (zone, hosts) in reverse.iteritems(): - metadata = { - 'cobbler_server': cobbler_server, - 'serial': serial, - 'host_record': '' - } - - for host in hosts: - txt = '%s\tIN\tPTR\t%s\n' % host - metadata['host_record'] = metadata['host_record'] + txt - - self.templar.render(template_data, metadata, '/var/named/' + zone, None) - - - def write_dns_files(self): - """ - BIND files are written when manage_dns is set in - /var/lib/cobbler/settings. - """ - - self.__write_named_conf() - self.__write_zone_files() - -def get_manager(config): - return IscAndBindManager(config) - diff --git a/config/modules.conf b/config/modules.conf index c16385e..8e6b8be 100644 --- a/config/modules.conf +++ b/config/modules.conf @@ -11,7 +11,8 @@ module = authn_denyall [authorization] module = authz_allowall -[manage] -module = manage_isc_and_bind - +[dns] +module = manage_isc +[dhcp] +module = manage_bind -- cgit From 110e28b4296e7118c3a6886afdcb6542c85ef54c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 12:11:14 -0400 Subject: Website cleanup, add link to other et projects, remove some code we no longer need, consolidate docs on FPO for stuff that frequently changes. --- website/new/about.html | 11 +- website/new/cobbler-dhcp.php | 44 -------- website/new/cobbler-import.php | 44 -------- website/new/cobbler-manpage.php | 44 -------- website/new/cobbler-repos.php | 44 -------- website/new/communicate.html | 27 ----- website/new/communicate.php | 48 --------- website/new/css/style.css | 2 +- website/new/download.html | 2 +- website/new/faq.html | 66 ------------ website/new/faq.php | 48 --------- website/new/feed.php | 31 ------ website/new/footer.html | 9 +- website/new/koan-manpage.php | 44 -------- website/new/nav.php | 41 +------- website/new/news-feed.php | 0 website/new/news.html | 1 - website/new/rss-aggregator.php | 73 ------------- website/new/rss-parser.php | 221 ---------------------------------------- 19 files changed, 13 insertions(+), 787 deletions(-) delete mode 100755 website/new/cobbler-dhcp.php delete mode 100755 website/new/cobbler-import.php delete mode 100755 website/new/cobbler-manpage.php delete mode 100755 website/new/cobbler-repos.php delete mode 100644 website/new/communicate.html delete mode 100755 website/new/communicate.php delete mode 100644 website/new/faq.html delete mode 100755 website/new/faq.php delete mode 100755 website/new/feed.php delete mode 100755 website/new/koan-manpage.php delete mode 100644 website/new/news-feed.php delete mode 100644 website/new/news.html delete mode 100755 website/new/rss-aggregator.php delete mode 100755 website/new/rss-parser.php diff --git a/website/new/about.html b/website/new/about.html index 2d8370a..b9dfc1a 100644 --- a/website/new/about.html +++ b/website/new/about.html @@ -1,12 +1,15 @@

    About Cobbler

    -

    Cobbler is a Linux boot server that allows for rapid setup of network installation environments. With a simple series of commands, network installs can be configured for PXE, reinstallations, and virtualized installs using Xen or KVM. Cobbler uses a helper program called 'Koan' (which interacts with Cobbler) for reinstallation and virtualization support.

    +

    Cobbler is a Linux provisioning server that allows for rapid setup of network installation environments. With a simple series of commands, network installs can be configured for PXE, reinstallations, and virtualized installs using Xen or KVM. Cobbler uses a helper program called 'Koan' (which interacts with Cobbler) for reinstallation and virtualization support.

    -

    Setting up Cobbler is simple. Installation trees can be imported directly from media you already have (or copied from a mirror location), and turned into network install sources within minutes. RHEL, Fedora, and Centos are all supported for both the boot server and installation targets.

    +

    Setting up a new Cobbler server is trivial as Cobbler eliminates much of the complexity of configuring the underlying install components. Simply install cobbler via yum, import media you may already have from mirrors or DVD ISOs, and start adding new records into cobbler using the command line or web interface. Extensive documentation is provided on the Cobbler Wiki.

    -

    Cobbler can, if desired, also assist in managing of DHCP infrastructure for provisioned systems (using ISC dhcp or dnsmasq), mirroring install trees, templating kickstart files, automatically creating PXE menus, and locally mirroring repositories used by systems on your network. Lots of additional information on advanced features can be found on the Cobbler Wiki.

    +

    When creating deployment infrastructure, many components must be dealt with. Cobbler glues together all of these diverse components and makes them easy to manage. As low-level bringup often involves adding records to DHCP and DNS, cobbler "system add" commands can (optionally) be configured to help manage DHCP and DNS using templates. For static IP networks, cobbler still provides solid deployment solutions, including the ability to build DVD network install CDs and reinstall existing Linux systems via the koan command line.

    -

    In short, Cobbler helps build and maintain network installation infrastructure really easily. It's highly customizable to your particular methods of operation through a wide variety of options, a pluggable extension mechanism, and (for developers) its own Python API. Cobbler lets administrators forget how software gets installed and delivered and lets them concentrate instead on what they want to install where.

    +

    Cobbler also contains a very powerful kickstart templating system that can help you manage all of the differences in the answer files that drive your automated deployments. Additionally, Cobbler can help mirror packages via yum and rsync, and associate those local mirrors with profiles in cobbler, so they are available to all installed systems.

    + +

    In short, Cobbler helps build and maintain network installation infrastructure really easily. It's highly customizable to your particular methods of operation through a wide variety of options, a powerful command line, a Web interface, a pluggable extension mechanism, and (for developers) its own Python API. Cobbler lets administrators forget how software gets installed and delivered and lets them concentrate instead on what they want to install where.

    Whether you run a large datacenter, a campus lab, or just have a handfull of machines on a home network, cobbler can help you perform installations and updates faster.

    + diff --git a/website/new/cobbler-dhcp.php b/website/new/cobbler-dhcp.php deleted file mode 100755 index 08f7ea4..0000000 --- a/website/new/cobbler-dhcp.php +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - -Cobbler - - - - - -
    - - -
    - - -
    - - -
    -
    - -
    - - diff --git a/website/new/cobbler-import.php b/website/new/cobbler-import.php deleted file mode 100755 index 11310fb..0000000 --- a/website/new/cobbler-import.php +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - -Cobbler - - - - - -
    - - -
    - - -
    - - -
    -
    - -
    - - diff --git a/website/new/cobbler-manpage.php b/website/new/cobbler-manpage.php deleted file mode 100755 index ebbc56c..0000000 --- a/website/new/cobbler-manpage.php +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - -Cobbler - - - - - -
    - - -
    - - -
    - - -
    -
    - -
    - - diff --git a/website/new/cobbler-repos.php b/website/new/cobbler-repos.php deleted file mode 100755 index a5a81db..0000000 --- a/website/new/cobbler-repos.php +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - -Cobbler - - - - - -
    - - -
    - - -
    - - -
    -
    - -
    - - diff --git a/website/new/communicate.html b/website/new/communicate.html deleted file mode 100644 index 6274b16..0000000 --- a/website/new/communicate.html +++ /dev/null @@ -1,27 +0,0 @@ -

    Mailing List

    - -

    Send comments, questions, patches, and suggestions to the et-mgmt-tools mailing list. You can send mail even if you aren't a list member. This is a community project, so if you feel like contributing, download the source, check it out, and join the list.

    - - - -

    Internet Relay Chat (IRC)

    -

    Cobbler has a chat room on irc.freenode.net ... #cobbler:

    - -

    If no one seems to be responding to your requests in IRC, feel free to ask your questions on the mailing list. (It's quite common for IRC users to 'idle' their nicks in a channel so they can catch up on any conversation they missed later on.)

    -

    Note: irc.freenode.net has an IRC 'nick' (nickname, the name you go by in chat) registration system. Some irc.freenode.net channels require that you identify with the server every time you log in. This identification is also required for private messages on the network (no, they're not ignoring you; you probably just haven't identified with the registration system!) For more information, please consult the Freenode Frequently-Asked Questions page on user registration.

    - -

    User Submitted Content

    - -As mentioned in the documentation section, Cobbler has a Wiki for user submitted content. - -

    Bugs/Features

    - -Bugs and Features for all distributions can be filed on the Cobbler and Koan Trac pages. Cobbler and koan also have bugzilla components if you are using Fedora and prefer to post items there. Regardless, the mailing list and the IRC channel are very active -- so if you need help, you do not need to file a bug report unless you want to do so. - - - - diff --git a/website/new/communicate.php b/website/new/communicate.php deleted file mode 100755 index 59af7ae..0000000 --- a/website/new/communicate.php +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - -Cobbler - - - - - -
    - - -
    - - -
    - -

    Communicate

    - - - -
    -
    - -
    - - diff --git a/website/new/css/style.css b/website/new/css/style.css index b02cb02..d3bfe13 100644 --- a/website/new/css/style.css +++ b/website/new/css/style.css @@ -183,7 +183,7 @@ tt { border: 0px; width: 100%; background: #757575; - height: 180px; + height: 290px; } /* This is hidden from IE <= 6 because it can't do transparency */ diff --git a/website/new/download.html b/website/new/download.html index 977fb1b..ddf3978 100644 --- a/website/new/download.html +++ b/website/new/download.html @@ -1,4 +1,4 @@ -

    Cobbler is licensed under the General Public License (GPL).

    +

    Cobbler is licensed under the General Public License (GPL).

    Download Cobbler

    diff --git a/website/new/faq.html b/website/new/faq.html deleted file mode 100644 index 9147adb..0000000 --- a/website/new/faq.html +++ /dev/null @@ -1,66 +0,0 @@ - - -

    Questions

    -
    - - -

    Answers

    - - -
    Who Uses Cobbler?
    -
    -
    Cobbler is in use in a lot of diverse configurations. Large companies, hosting datacenters, college labs, consultants, ISVs, developers, and home Linux users all use cobbler and koan in different ways. It is intended to cover the large configurations just as easily as the smaller ones -- and be non-complex for all classes of users. -
    - Back to top -
    - -
    What Operating Systems Are Supported?
    -
    -
    -Cobbler runs on RHEL 4 and later, Fedora 5 and later, and Centos 4 and later. Koan, cobbler's helper program, also works with RHEL 3. -
    - Back to top -
    - -
    What are my non-PXE install options?
    -
    -
    Cobbler's helper program koan can be used on the target system to reinstall it's operating system with another operating system. This solution is ideal for those users who can not take advantage of PXE due to hardware, network, or policy constraints. For more information, see the koan manpage documentation. -
    - Back to top -
    - -
    How are virtual systems installed?
    -
    -
    Cobbler's helper program, koan, when invoked on the remote host system, pulls down information from the remote cobbler server to begin a fully automated installation of a virtual guest. This works out of the box for any distribution that contains a Xen kernel. For more information, see the koan manpage documentation. -
    - Back to top -
    - - -
    How does Cobbler work internally?
    -
    -
    -When the administrator on the Cobbler server runs Cobbler commands, cobbler updates a configuration tree, which is stored in /var/lib/cobbler. Data from this configuration database is used to update various entities on the target operating system. Cobbler will restart services, as well as create trees of files in /tftpboot and /var/www/cobbler -- for use with PXE and koan, respectively. -
    -Koan interacts with cobbler by retrieving data over XMLRPC. Cobbler serves up XMLRPC using "cobblerd", which also has the dual purpose of logging syslog data from kickstart -- which is used in Cobbler's kickstart tracking feature. For a better understanding of how this works, see the Cobbler manpage documentation. -Back to top -
    - - -
    How can I contribute?
    -
    -
    -Send in bug reports, patches, ideas, or comments. We're interested in hearing about your real-world provisioning challenges and how we can solve them for system administrators everywhere. -
    - Back to top -
    - - - diff --git a/website/new/faq.php b/website/new/faq.php deleted file mode 100755 index 0fbd05e..0000000 --- a/website/new/faq.php +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - -Cobbler - - - - - -
    - - -
    - - -
    - -

    Frequently-Asked Questions

    - - - -
    -
    - -
    - - diff --git a/website/new/feed.php b/website/new/feed.php deleted file mode 100755 index a92f985..0000000 --- a/website/new/feed.php +++ /dev/null @@ -1,31 +0,0 @@ -
    -

    Below are the latest 3 message subjects from the et-mgmt-tools list:

    -
      -items; - $item = $item_array[$i]; - $href = $item['link']; - $title = $item['title']; - $body = $item['description']; - $author = $item['dc']['creator']; - $raw_timestamp = $item['dc']['date']; - $unixtime = strtotime($raw_timestamp); - $timestamp = date('g:i A T j F Y', $unixtime); - - echo "
    • $title
    • "; - } - - -?> -
    -

    -[ View More ... ]

    -
    diff --git a/website/new/footer.html b/website/new/footer.html index cf18ff8..68993c8 100644 --- a/website/new/footer.html +++ b/website/new/footer.html @@ -6,8 +6,8 @@
    libvirt
    The open source virtualization API
    - +
    Func
    +
    A secure, scriptable remote control framework & API
    Cobbler
    @@ -15,8 +15,6 @@
    oVirt
    Virtualization management across the data center
    -
    FreeIPA
    @@ -24,8 +22,7 @@
    Virtual Machine Manager
    Virtualization management from the desktop
    - +

    diff --git a/website/new/koan-manpage.php b/website/new/koan-manpage.php deleted file mode 100755 index ad400df..0000000 --- a/website/new/koan-manpage.php +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - -Cobbler - - - - - -

    - - -
    - - -
    - - -
    -
    - -
    - - diff --git a/website/new/nav.php b/website/new/nav.php index b093a75..8f4ee69 100755 --- a/website/new/nav.php +++ b/website/new/nav.php @@ -7,59 +7,20 @@ echo ""; break; case "download": echo ""; break; - case "faq": - echo ""; - break; - case "communicate": - echo ""; - break; - case "documentation": - echo ""; - break; default: echo " "; break; diff --git a/website/new/news-feed.php b/website/new/news-feed.php deleted file mode 100644 index e69de29..0000000 diff --git a/website/new/news.html b/website/new/news.html deleted file mode 100644 index 2981fd1..0000000 --- a/website/new/news.html +++ /dev/null @@ -1 +0,0 @@ -news goes here. diff --git a/website/new/rss-aggregator.php b/website/new/rss-aggregator.php deleted file mode 100755 index 9cbb837..0000000 --- a/website/new/rss-aggregator.php +++ /dev/null @@ -1,73 +0,0 @@ -

    Simple RSS agregator

    - - -
    - - -get($url)) { - echo "$rs[title]
    \n"; - echo "$rs[description]
    \n"; - - echo "
      \n"; - foreach ($rs['items'] as $item) { - echo "\t
    • $item[title]
    • \n"; - } - if ($rs['items_count'] <= 0) { echo "
    • Sorry, no items found in the RSS file :-(
    • "; } - echo "
    \n"; - } - else { - echo "Sorry: It's not possible to reach RSS file $url\n
    "; - // you will probably hide this message in a live version - } -} - -// =============================================================================== - -// include lastRSS -include "./rss-parser.php"; - -// List of RSS URLs -$rss_left = array( - 'http://freshmeat.net/backend/fm.rdf', - 'http://slashdot.org/slashdot.rdf' -); -$rss_right = array( - 'http://www.freshfolder.com/rss.php', - 'http://phpbuilder.com/rss_feed.php' -); - -// Create lastRSS object -$rss = new lastRSS; - -// Set cache dir and cache time limit (5 seconds) -// (don't forget to chmod cahce dir to 777 to allow writing) -$rss->cache_dir = './temp'; -$rss->cache_time = 1200; - - -// Show all rss files -echo "
    "; -foreach ($rss_left as $url) { - ShowOneRSS($url); -} -echo ""; -foreach ($rss_right as $url) { - ShowOneRSS($url); -} -echo "
    "; -?> - diff --git a/website/new/rss-parser.php b/website/new/rss-parser.php deleted file mode 100755 index cfe1637..0000000 --- a/website/new/rss-parser.php +++ /dev/null @@ -1,221 +0,0 @@ -cache_dir != '') { - $cache_file = $this->cache_dir . '/rsscache_' . md5($rss_url); - $timedif = @(time() - filemtime($cache_file)); - if ($timedif < $this->cache_time) { - // cached file is fresh enough, return cached array - $result = unserialize(join('', file($cache_file))); - // set 'cached' to 1 only if cached file is correct - if ($result) $result['cached'] = 1; - } else { - // cached file is too old, create new - $result = $this->Parse($rss_url); - $serialized = serialize($result); - if ($f = @fopen($cache_file, 'w')) { - fwrite ($f, $serialized, strlen($serialized)); - fclose($f); - } - if ($result) $result['cached'] = 0; - } - } - // If CACHE DISABLED >> load and parse the file directly - else { - $result = $this->Parse($rss_url); - if ($result) $result['cached'] = 0; - } - // return result - return $result; - } - - // ------------------------------------------------------------------- - // Modification of preg_match(); return trimed field with index 1 - // from 'classic' preg_match() array output - // ------------------------------------------------------------------- - function my_preg_match ($pattern, $subject) { - // start regullar expression - preg_match($pattern, $subject, $out); - - // if there is some result... process it and return it - if(isset($out[1])) { - // Process CDATA (if present) - if ($this->CDATA == 'content') { // Get CDATA content (without CDATA tag) - $out[1] = strtr($out[1], array(''', ']]>'=>'')); - } elseif ($this->CDATA == 'strip') { // Strip CDATA - $out[1] = strtr($out[1], array(''', ']]>'=>'')); - } - - // If code page is set convert character encoding to required - if ($this->cp != '') - //$out[1] = $this->MyConvertEncoding($this->rsscp, $this->cp, $out[1]); - $out[1] = iconv($this->rsscp, $this->cp.'//TRANSLIT', $out[1]); - // Return result - return trim($out[1]); - } else { - // if there is NO result, return empty string - return ''; - } - } - - // ------------------------------------------------------------------- - // Replace HTML entities &something; by real characters - // ------------------------------------------------------------------- - function unhtmlentities ($string) { - // Get HTML entities table - $trans_tbl = get_html_translation_table (HTML_ENTITIES, ENT_QUOTES); - // Flip keys<==>values - $trans_tbl = array_flip ($trans_tbl); - // Add support for ' entity (missing in HTML_ENTITIES) - $trans_tbl += array(''' => "'"); - // Replace entities by values - return strtr ($string, $trans_tbl); - } - - // ------------------------------------------------------------------- - // Parse() is private method used by Get() to load and parse RSS file. - // Don't use Parse() in your scripts - use Get($rss_file) instead. - // ------------------------------------------------------------------- - function Parse ($rss_url) { - // Open and load RSS file - if ($f = @fopen($rss_url, 'r')) { - $rss_content = ''; - while (!feof($f)) { - $rss_content .= fgets($f, 4096); - } - fclose($f); - - // Parse document encoding - $result['encoding'] = $this->my_preg_match("'encoding=[\'\"](.*?)[\'\"]'si", $rss_content); - // if document codepage is specified, use it - if ($result['encoding'] != '') - { $this->rsscp = $result['encoding']; } // This is used in my_preg_match() - // otherwise use the default codepage - else - { $this->rsscp = $this->default_cp; } // This is used in my_preg_match() - - // Parse CHANNEL info - preg_match("'(.*?)'si", $rss_content, $out_channel); - foreach($this->channeltags as $channeltag) - { - $temp = $this->my_preg_match("'<$channeltag.*?>(.*?)'si", $out_channel[1]); - if ($temp != '') $result[$channeltag] = $temp; // Set only if not empty - } - // If date_format is specified and lastBuildDate is valid - if ($this->date_format != '' && ($timestamp = strtotime($result['lastBuildDate'])) !==-1) { - // convert lastBuildDate to specified date format - $result['lastBuildDate'] = date($this->date_format, $timestamp); - } - - // Parse TEXTINPUT info - preg_match("']*[^/])>(.*?)'si", $rss_content, $out_textinfo); - // This a little strange regexp means: - // Look for tag with or without any attributes, but skip truncated version (it's not beggining tag) - if (isset($out_textinfo[2])) { - foreach($this->textinputtags as $textinputtag) { - $temp = $this->my_preg_match("'<$textinputtag.*?>(.*?)'si", $out_textinfo[2]); - if ($temp != '') $result['textinput_'.$textinputtag] = $temp; // Set only if not empty - } - } - // Parse IMAGE info - preg_match("'(.*?)'si", $rss_content, $out_imageinfo); - if (isset($out_imageinfo[1])) { - foreach($this->imagetags as $imagetag) { - $temp = $this->my_preg_match("'<$imagetag.*?>(.*?)'si", $out_imageinfo[1]); - if ($temp != '') $result['image_'.$imagetag] = $temp; // Set only if not empty - } - } - // Parse ITEMS - preg_match_all("'(.*?)'si", $rss_content, $items); - $rss_items = $items[2]; - $i = 0; - $result['items'] = array(); // create array even if there are no items - foreach($rss_items as $rss_item) { - // If number of items is lower then limit: Parse one item - if ($i < $this->items_limit || $this->items_limit == 0) { - foreach($this->itemtags as $itemtag) { - $temp = $this->my_preg_match("'<$itemtag.*?>(.*?)'si", $rss_item); - if ($temp != '') $result['items'][$i][$itemtag] = $temp; // Set only if not empty - } - // Strip HTML tags and other bullshit from DESCRIPTION - if ($this->stripHTML && $result['items'][$i]['description']) - $result['items'][$i]['description'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['description']))); - // Strip HTML tags and other bullshit from TITLE - if ($this->stripHTML && $result['items'][$i]['title']) - $result['items'][$i]['title'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['title']))); - // If date_format is specified and pubDate is valid - if ($this->date_format != '' && ($timestamp = strtotime($result['items'][$i]['pubDate'])) !==-1) { - // convert pubDate to specified date format - $result['items'][$i]['pubDate'] = date($this->date_format, $timestamp); - } - // Item counter - $i++; - } - } - - $result['items_count'] = $i; - return $result; - } - else // Error in opening return False - { - return False; - } - } -} - -?> - -- cgit From 17e2579f3bd7c14dab0e14ed2a808fe9546f556d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 12:12:09 -0400 Subject: Fix typo in config file. --- config/modules.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/modules.conf b/config/modules.conf index 8e6b8be..8863e02 100644 --- a/config/modules.conf +++ b/config/modules.conf @@ -12,7 +12,7 @@ module = authn_denyall module = authz_allowall [dns] -module = manage_isc +module = manage_bind [dhcp] -module = manage_bind +module = manage_isc -- cgit From 3b447a659ddd9c8ee97ece470c876dd36aa32914 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 14:36:36 -0400 Subject: Apply jeckersb's patch to allow per-zone templates --- cobbler/modules/manage_bind.py | 29 ++++++++++++++++++++++------- cobbler/webui/master.py | 7 ++++--- docs/cobbler.pod | 2 +- setup.py | 4 ++++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py index 900a832..e14122a 100644 --- a/cobbler/modules/manage_bind.py +++ b/cobbler/modules/manage_bind.py @@ -184,21 +184,20 @@ zone "%(arpa)s." { def __write_zone_files(self): """ - Write out the forward and reverse zone files for all the zones - defined in manage_forward_zones and manage_reverse_zones + Write out the forward and reverse zone files for all configured zones """ - template_file = "/etc/cobbler/zone.template" + default_template_file = "/etc/cobbler/zone.template" cobbler_server = self.settings.server serial = int(time.time()) forward = self.__config_forward_zones() reverse = self.__config_reverse_zones() try: - f2 = open(template_file,"r") + f2 = open(default_template_file,"r") except: - raise CX(_("error reading template from file: %s") % template_file) - template_data = "" - template_data = f2.read() + raise CX(_("error reading template from file: %s") % default_template_file) + default_template_data = "" + default_template_data = f2.read() f2.close() for (zone, hosts) in forward.iteritems(): @@ -208,6 +207,14 @@ zone "%(arpa)s." { 'host_record': '' } + # grab zone-specific template if it exists + try: + fd = open('/etc/cobbler/zone_templates/%s' % zone) + template_data = fd.read() + fd.close() + except: + template_data = default_template_data + for host in hosts: txt = '%s\tIN\tA\t%s\n' % host metadata['host_record'] = metadata['host_record'] + txt @@ -221,6 +228,14 @@ zone "%(arpa)s." { 'host_record': '' } + # grab zone-specific template if it exists + try: + fd = open('/etc/cobbler/zone_templates/%s' % zone) + template_data = fd.read() + fd.close() + except: + template_data = default_template_data + for host in hosts: txt = '%s\tIN\tPTR\t%s\n' % host metadata['host_record'] = metadata['host_record'] + txt diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 7e54a2d..cdde14d 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,9 +33,10 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1209057202.737108 -__CHEETAH_genTimestamp__ = 'Thu Apr 24 13:13:22 2008' -__CHEETAH_srcLastModified__ = 'Thu Apr 24 12:59:37 2008' +__CHEETAH_genTime__ = 1209998447.8930521 +__CHEETAH_genTimestamp__ = 'Mon May 5 10:40:47 2008' +__CHEETAH_src__ = 'webui_templates/master.tmpl' +__CHEETAH_srcLastModified__ = 'Thu May 1 13:51:29 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 311a94c..018c3f4 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -563,7 +563,7 @@ Choose either "management = isc_and_bind" or "management = dnsmasq" in /etc/cobb This feature is off by default. If using BIND, you may restrict the scope of zones managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. (See the Wiki for more information on this). -If using BIND, Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding. +If using BIND, Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. You may drop zone-specific template files in /etc/cobbler/zone_templates/name-of-zone which will override the default. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding. If using dnsmasq, the template is /etc/cobbler/dnsmasq.template. Read this file and understand how dnsmasq works before proceeding. diff --git a/setup.py b/setup.py index 442da5b..07d9880 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ if __name__ == "__main__": vw_systems = "/var/www/cobbler/systems" vw_profiles = "/var/www/cobbler/profiles" vw_links = "/var/www/cobbler/links" + zone_templates = "/etc/cobbler/zone_templates" tftp_cfg = "/tftpboot/pxelinux.cfg" tftp_images = "/tftpboot/images" rotpath = "/etc/logrotate.d" @@ -134,6 +135,9 @@ if __name__ == "__main__": (vw_profiles, []), (vw_links, []), + # zone-specific templates directory + (zone_templates, []), + # tftp directories that we own (tftp_cfg, []), (tftp_images, []), -- cgit From 5a3883b847007ba11e8058441c5bcc401b36fd1f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 14:38:09 -0400 Subject: Remove typo in function --- cobbler/modules/manage_isc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py index bc88412..5bbe2fa 100644 --- a/cobbler/modules/manage_isc.py +++ b/cobbler/modules/manage_isc.py @@ -98,7 +98,6 @@ class IscManager: def remove_dhcp_lease(self,port,host): """ - removeDHCPLease(port,host) Use DHCP's API to delete a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ @@ -166,7 +165,7 @@ class IscManager: break if elem == 'host': hostremove = item.get_token() - self.removeDHCPLease(self.settings.omapi_port,hostremove) + self.remove_dhcp_lease(self.settings.omapi_port,hostremove) # we used to just loop through each system, but now we must loop # through each network interface of each system. -- cgit From 3c4bb08f300527952067287fa205120eb0de3933 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 14:39:04 -0400 Subject: Change default PXE behavior for local to "-1" which means next step in boot process as opposed to picking a specific drive. --- templates/pxedefault.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/pxedefault.template b/templates/pxedefault.template index bb09893..206c028 100644 --- a/templates/pxedefault.template +++ b/templates/pxedefault.template @@ -8,7 +8,7 @@ ONTIMEOUT local LABEL local MENU LABEL (local) MENU DEFAULT - LOCALBOOT 0 + LOCALBOOT -1 $pxe_menu_items -- cgit From 97cb9acc37c4b230f5ac914044d0425cf73a11d0 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 14:40:44 -0400 Subject: Get omshell path from config file --- cobbler/modules/manage_isc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py index 5bbe2fa..895eadc 100644 --- a/cobbler/modules/manage_isc.py +++ b/cobbler/modules/manage_isc.py @@ -73,7 +73,7 @@ class IscManager: # FIXME: should use subprocess """ try: - fromchild, tochild = popen2.popen2("/usr/bin/omshell") + fromchild, tochild = popen2.popen2(self.settings.omshell_bin) tochild.write("port %s\n" % port) tochild.flush() tochild.write("connect\n") @@ -101,7 +101,7 @@ class IscManager: Use DHCP's API to delete a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ - fromchild, tochild = popen2.popen2("/usr/bin/omshell") + fromchild, tochild = popen2.popen2(self.settings.omshell_bin) try: tochild.write("port %s\n" % port) tochild.flush() -- cgit From 19e53e8ad1a6889d020c6029d2d34273dacdc750 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 14:46:37 -0400 Subject: Always restart dhcp seperate from dns. --- triggers/restart-services.trigger | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger index bc7c36e..d586f46 100644 --- a/triggers/restart-services.trigger +++ b/triggers/restart-services.trigger @@ -4,6 +4,8 @@ import cobbler.api as capi import os import sys +#!/usr/bin/python + bootapi = capi.BootAPI() settings = bootapi.settings() manage_dhcp = str(settings.manage_dhcp).lower() @@ -12,14 +14,10 @@ manage_dns = str(settings.manage_dns).lower() omapi_enabled = settings.omapi_enabled omapi_port = settings.omapi_port - - -# We're just going to restart DHCPD if using ISC and if not using OMAPI at all rc = 0 if manage_dhcp != "0": if manage_dhcp_mode == "isc": if not omapi_enabled: - if not omapi_port: rc = os.system("/sbin/service dhcpd restart") elif manage_dhcp_mode == "dnsmasq": rc = os.system("/sbin/service dnsmasq restart") @@ -27,9 +25,6 @@ if manage_dhcp != "0": print "- error: unknown DHCP engine: %s" % manage_dhcp_mode rc = 411 -if rc != 0: - sys.exit(rc) - if manage_dns != "0": rc = os.system("/sbin/service named restart") -- cgit From e38519a052ae0c5aa472856621de15e45e008845 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 5 May 2008 17:02:48 -0400 Subject: omshell wants lowercase macs --- cobbler/modules/manage_isc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py index 895eadc..c398270 100644 --- a/cobbler/modules/manage_isc.py +++ b/cobbler/modules/manage_isc.py @@ -84,7 +84,7 @@ class IscManager: tochild.flush() tochild.write("set ip-address = %s\n" % ip) tochild.flush() - tochild.write("set hardware-address = %s\n" % mac) + tochild.write("set hardware-address = %s\n" % mac.lower()) tochild.flush() tochild.write("set hardware-type = 1\n") tochild.flush() -- cgit From 6aeaebf293c899a05ff64fb35bf992a0e100d057 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 6 May 2008 11:39:39 -0400 Subject: Revert to previous local boot PXE policy --- templates/pxedefault.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/pxedefault.template b/templates/pxedefault.template index 206c028..bb09893 100644 --- a/templates/pxedefault.template +++ b/templates/pxedefault.template @@ -8,7 +8,7 @@ ONTIMEOUT local LABEL local MENU LABEL (local) MENU DEFAULT - LOCALBOOT -1 + LOCALBOOT 0 $pxe_menu_items -- cgit From 66d034de6a272f24c4afe023c211c4857e64dc72 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 6 May 2008 11:54:35 -0400 Subject: Swap dhcp and dns module references --- cobbler/action_litesync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index ba3e3ee..ad669be 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -106,9 +106,9 @@ class BootLiteSync: raise CX(_("error in system lookup for %s") % name) # rebuild system_list file in webdir if self.settings.manage_dhcp: - self.sync.dns.regen_ethers() + self.sync.dhcp.regen_ethers() if self.settings.manage_dns: - self.sync.dhcp.regen_hosts() + self.sync.dns.regen_hosts() # write the PXE files for the system self.sync.pxegen.write_all_system_files(system) # per system kickstarts -- cgit From 31645710cbb926f6859f38e5feb11e3263925478 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 6 May 2008 12:30:03 -0400 Subject: Redo exception handling code for older python to eliminate tracebacks that should not be printed. --- cobbler/cexceptions.py | 3 +++ cobbler/cobbler.py | 12 +++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cobbler/cexceptions.py b/cobbler/cexceptions.py index a78f7f2..dba2857 100644 --- a/cobbler/cexceptions.py +++ b/cobbler/cexceptions.py @@ -20,6 +20,9 @@ class CobblerException(exceptions.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_cobbler = 1 def __str__(self): return repr(self.value) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 627e398..160dbc5 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -52,13 +52,11 @@ def main(): try: return BootCLI().run(sys.argv) except Exception, exc: - if isinstance(exc, cexceptions.CobblerException) or \ - isinstance(exc, cexceptions.CX) or \ - str(type(exc)).find("CX") != -1 or \ - str(type(exc)).find("CobblerException") != -1: - print str(exc)[1:-1] # remove framing air quotes - else: - traceback.print_exc() + try: + getattr(exc, "from_cobbler") + print str(exc)[1:-1] + except: + traceback.print_exc() return 1 if __name__ == "__main__": -- cgit From ae4a1c0b3551aadba6ea09bd6211e5c617d034a4 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 6 May 2008 15:50:54 -0400 Subject: More exception handling. --- cobbler/cobbler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 160dbc5..0e15175 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -20,6 +20,7 @@ import os import os.path import traceback import optparse +import string import commands import cexceptions from cexceptions import * @@ -52,11 +53,12 @@ def main(): try: return BootCLI().run(sys.argv) except Exception, exc: + (t, v, tb) = sys.exc_info() try: getattr(exc, "from_cobbler") print str(exc)[1:-1] except: - traceback.print_exc() + print string.join(traceback.format_list(traceback.extract_tb(tb))) return 1 if __name__ == "__main__": -- cgit From 4f5d4161611ba4189c3e36a67bd00cae21afa562 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 6 May 2008 16:09:01 -0400 Subject: Ignore system exit --- cobbler/cobbler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 0e15175..5081009 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -52,6 +52,8 @@ def main(): exitcode = 0 try: return BootCLI().run(sys.argv) + except SystemExit, ex: + return 1 except Exception, exc: (t, v, tb) = sys.exc_info() try: -- cgit From e9cba0c5d8ca7356621a737a09b6a22d8b00e9fc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 7 May 2008 15:17:18 -0400 Subject: Improve tracebacks. --- cobbler/cobbler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 5081009..970a06a 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -60,6 +60,8 @@ def main(): getattr(exc, "from_cobbler") print str(exc)[1:-1] except: + print t + print v print string.join(traceback.format_list(traceback.extract_tb(tb))) return 1 -- cgit From 072204fb8d3e351c2c6e67beb52de175cad0c979 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 8 May 2008 10:09:08 -0400 Subject: Apply John Eckersberg's patch to allow random mac usage from the command line. --- cobbler/modules/cli_system.py | 8 ++++++-- cobbler/remote.py | 20 ++++---------------- cobbler/utils.py | 21 +++++++++++++++++++++ cobbler/webui/master.py | 4 ++-- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 0c9a5b8..419b561 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) -from utils import _ +from utils import _, get_random_mac import commands import cexceptions @@ -98,7 +98,11 @@ class SystemFunction(commands.CobblerFunction): my_interface = "intf0" if self.options.hostname: obj.set_hostname(self.options.hostname, my_interface) - if self.options.mac: obj.set_mac_address(self.options.mac, my_interface) + if self.options.mac: + if self.options.mac.lower() == 'random': + obj.set_mac_address(get_random_mac(self.api), my_interface) + else: + obj.set_mac_address(self.options.mac, my_interface) if self.options.ip: obj.set_ip_address(self.options.ip, my_interface) if self.options.subnet: obj.set_subnet(self.options.subnet, my_interface) if self.options.gateway: obj.set_gateway(self.options.gateway, my_interface) diff --git a/cobbler/remote.py b/cobbler/remote.py index 18c3947..749298e 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -475,25 +475,13 @@ class CobblerXMLRPCInterface: def get_random_mac(self,token=None): """ - Generate a random MAC address. - 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 + Wrapper for utils.get_random_mac + + Used in the webui """ self.log("get_random_mac",token=None) self._refresh() - mac = [ 0x00, 0x16, 0x3e, - random.randint(0x00, 0x7f), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff) ] - mac = ':'.join(map(lambda x: "%02x" % x, mac)) - systems = self.api.systems() - while ( systems.find(mac_address=mac) ): - mac = self.get_random_mac() - - return mac + return utils.get_random_mac(self.api) def _fix_none(self,data): """ diff --git a/cobbler/utils.py b/cobbler/utils.py index 7bd37ad..14019e8 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -17,6 +17,7 @@ import os import re import socket import glob +import random import sub_process import shutil import string @@ -111,6 +112,26 @@ def is_mac(strdata): return True return False +def get_random_mac(api_handle): + """ + Generate a random MAC address. + 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) ] + mac = ':'.join(map(lambda x: "%02x" % x, mac)) + systems = api_handle.systems() + while ( systems.find(mac_address=mac) ): + mac = get_random_mac(api_handle) + + return mac + def resolve_ip(strdata): """ diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index cdde14d..50f9fd1 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1209998447.8930521 -__CHEETAH_genTimestamp__ = 'Mon May 5 10:40:47 2008' +__CHEETAH_genTime__ = 1210208830.681746 +__CHEETAH_genTimestamp__ = 'Wed May 7 21:07:10 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Thu May 1 13:51:29 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' -- cgit From 7ee964ea8bc64284409c7ac053eb0c12b449aed6 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 8 May 2008 13:41:07 -0400 Subject: Apply scott henson's replicate patch. --- AUTHORS | 1 + CHANGELOG | 1 + Makefile | 2 +- cobbler/api.py | 5 + cobbler/modules/cli_misc.py | 21 +++- cobbler/settings.py | 1 + cobbler/webui/master.py | 266 -------------------------------------------- 7 files changed, 29 insertions(+), 268 deletions(-) delete mode 100644 cobbler/webui/master.py diff --git a/AUTHORS b/AUTHORS index cb26afc..9230de5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ Patches and other contributions from: C. Daniel Chase Máirín Duffy John Eckersberg + Scott Henson Tru Huynh Matt Hyclak Pablo Iranzo Gómez diff --git a/CHANGELOG b/CHANGELOG index 278b8fa..eec038e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ Cobbler CHANGELOG - support for managing BIND - xen kernel (PV) distros do not get added to PXE menus as they won't boot there - cobbler buildiso command to build non live ISOs +- cobbler replicate command - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/Makefile b/Makefile index 6287f5d..af01e9f 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ messages: cobbler/*.py sed -i'~' -e 's/SOME DESCRIPTIVE TITLE/cobbler/g' -e 's/YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/2007 Red Hat, Inc. /g' -e 's/FIRST AUTHOR , YEAR/Michael DeHaan , 2007/g' -e 's/PACKAGE VERSION/cobbler $(VERSION)-$(RELEASE)/g' -e 's/PACKAGE/cobbler/g' $(MESSAGESPOT) -rpms: clean manpage sdist +rpms: clean updatewui manpage sdist mkdir -p rpm-build cp dist/*.gz rpm-build/ rpmbuild --define "_topdir %(pwd)/rpm-build" \ diff --git a/cobbler/api.py b/cobbler/api.py index 20c7364..1b73acd 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -23,6 +23,7 @@ import action_reposync import action_status import action_validate import action_buildiso +import action_replicate from cexceptions import * import sub_process import module_loader @@ -437,3 +438,7 @@ class BootAPI: iso=iso, profiles=profiles, tempdir=tempdir ) + def replicate(self, cobbler_master = None): + replicator = action_replicate.Replicate(self._config) + return replicator.run(cobbler_master = cobbler_master) + diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index 6ca14a2..c72b11d 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -261,6 +261,24 @@ class BuildIsoFunction(commands.CobblerFunction): tempdir=self.options.tempdir ) +######################################################## + +class ReplicateFunction(commands.CobblerFunction): + + def help_me(self): + return HELP_FORMAT % ("cobbler replicate","[ARGS|--help]") + + def command_name(self): + return "replicate" + + def add_options(self, p, args): + p.add_option("--master", dest="master", help="Cobbler server to replicate from.") + + def run(self): + return self.api.replicate(cobbler_master = self.options.master) + + + ######################################################## # MODULE HOOKS @@ -275,7 +293,8 @@ def cli_functions(api): BuildIsoFunction(api), CheckFunction(api), ImportFunction(api), ReserializeFunction(api), ListFunction(api), ReportFunction(api), StatusFunction(api), - SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api) + SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api), + ReplicateFunction(api) ] return [] diff --git a/cobbler/settings.py b/cobbler/settings.py index bf12545..d147d4b 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -29,6 +29,7 @@ DEFAULTS = { "standard" : "/usr/lib/syslinux/pxelinux.0", "ia64" : "/var/lib/cobbler/elilo-3.6-ia64.efi" }, + "cobbler_master" : '', "default_kickstart" : "/etc/cobbler/default.ks", "default_virt_bridge" : "xenbr0", "default_virt_type" : "auto", diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py deleted file mode 100644 index 50f9fd1..0000000 --- a/cobbler/webui/master.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python - - - - -################################################## -## DEPENDENCIES -import sys -import os -import os.path -from os.path import getmtime, exists -import time -import types -import __builtin__ -from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion -from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple -from Cheetah.Template import Template -from Cheetah.DummyTransaction import DummyTransaction -from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList -from Cheetah.CacheRegion import CacheRegion -import Cheetah.Filters as Filters -import Cheetah.ErrorCatchers as ErrorCatchers - -################################################## -## MODULE CONSTANTS -try: - True, False -except NameError: - True, False = (1==1), (1==0) -VFFSL=valueFromFrameOrSearchList -VFSL=valueFromSearchList -VFN=valueForName -currentTime=time.time -__CHEETAH_version__ = '2.0.1' -__CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1210208830.681746 -__CHEETAH_genTimestamp__ = 'Wed May 7 21:07:10 2008' -__CHEETAH_src__ = 'webui_templates/master.tmpl' -__CHEETAH_srcLastModified__ = 'Thu May 1 13:51:29 2008' -__CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' - -if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: - raise AssertionError( - 'This template was compiled with Cheetah version' - ' %s. Templates compiled before version %s must be recompiled.'%( - __CHEETAH_version__, RequiredCheetahVersion)) - -################################################## -## CLASSES - -class master(Template): - - ################################################## - ## CHEETAH GENERATED METHODS - - - def __init__(self, *args, **KWs): - - Template.__init__(self, *args, **KWs) - if not self._CHEETAH__instanceInitialized: - cheetahKWArgs = {} - allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split() - for k,v in KWs.items(): - if k in allowedKWs: cheetahKWArgs[k] = v - self._initCheetahInstance(**cheetahKWArgs) - - - def body(self, **KWS): - - - - ## CHEETAH: generated from #block body at line 54, col 1. - trans = KWS.get("trans") - if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)): - trans = self.transaction # is None unless self.awake() was called - if not trans: - trans = DummyTransaction() - _dummyTrans = True - else: _dummyTrans = False - write = trans.response().write - SL = self._CHEETAH__searchList - _filter = self._CHEETAH__currentFilter - - ######################################## - ## START - generated method body - - write(''' -

    Template Failure

    - -''') - - ######################################## - ## END - generated method body - - return _dummyTrans and trans.response().getvalue() or "" - - - def respond(self, trans=None): - - - - ## CHEETAH: main method generated for this template - if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)): - trans = self.transaction # is None unless self.awake() was called - if not trans: - trans = DummyTransaction() - _dummyTrans = True - else: _dummyTrans = False - write = trans.response().write - SL = self._CHEETAH__searchList - _filter = self._CHEETAH__currentFilter - - ######################################## - ## START - generated method body - - write(''' - - - ''') - _v = VFFSL(SL,"title",True) # '$title' on line 5, col 12 - if _v is not None: write(_filter(_v, rawExpr='$title')) # from line 5, col 12. - write(''' - - - - - - - - - - - - -
    -

    - - Cobbler Logo - -

    -
    - -
    - - - -
    -''') - self.body(trans=trans) - write('''
    -
    - - - -''') - - ######################################## - ## END - generated method body - - return _dummyTrans and trans.response().getvalue() or "" - - ################################################## - ## CHEETAH GENERATED ATTRIBUTES - - - _CHEETAH__instanceInitialized = False - - _CHEETAH_version = __CHEETAH_version__ - - _CHEETAH_versionTuple = __CHEETAH_versionTuple__ - - _CHEETAH_genTime = __CHEETAH_genTime__ - - _CHEETAH_genTimestamp = __CHEETAH_genTimestamp__ - - _CHEETAH_src = __CHEETAH_src__ - - _CHEETAH_srcLastModified = __CHEETAH_srcLastModified__ - - title = "Cobbler Web Interface" - - _mainCheetahMethod_for_master= 'respond' - -## END CLASS DEFINITION - -if not hasattr(master, '_initCheetahAttributes'): - templateAPIClass = getattr(master, '_CHEETAH_templateClass', Template) - templateAPIClass._addCheetahPlumbingCodeToClass(master) - - -# CHEETAH was developed by Tavis Rudd and Mike Orr -# with code, advice and input from many other volunteers. -# For more information visit http://www.CheetahTemplate.org/ - -################################################## -## if run from command line: -if __name__ == '__main__': - from Cheetah.TemplateCmdLineIface import CmdLineIface - CmdLineIface(templateObj=master()).run() - - -- cgit From 6ae4029a0c0021cb9151cb5a22a68c4b72ecf8cc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 8 May 2008 16:57:33 -0400 Subject: Added --mirror-locally option to repo add/edit and WebUI for specifying that a cobbler repo object is to be used directly, rather than mirrored into /var/www/cobbler by reposync/rsync. This can be usable when network connectivity outside is certain, and there are no performance or bandwidth needs on a local mirror. This supports http:// and ftp:// only, rsync:// is not natively understood by yum. --- CHANGELOG | 1 + cobbler/action_reposync.py | 37 +++++++++++++++++++++++++++---------- cobbler/item_repo.py | 18 ++++++++++++++++-- cobbler/kickgen.py | 7 ++++++- cobbler/modules/cli_repo.py | 2 ++ cobbler/remote.py | 5 +++++ cobbler/services.py | 1 + cobbler/webui/CobblerWeb.py | 7 ++++++- docs/cobbler.pod | 6 +++++- webui_templates/repo_edit.tmpl | 30 +++++++++++++++++++++--------- 10 files changed, 90 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index eec038e..2a70394 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ Cobbler CHANGELOG - xen kernel (PV) distros do not get added to PXE menus as they won't boot there - cobbler buildiso command to build non live ISOs - cobbler replicate command +- added cobbler repo option --mirror-locally to reference external repos without mirroring - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index d6b97a0..33bfe30 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -57,8 +57,10 @@ class RepoSync: self.verbose = verbose for repo in self.repos: if name is not None and repo.name != name: + # invoked to sync only a specific repo, this is not the one continue elif name is None and not repo.keep_updated: + # invoked to run against all repos, but this one is off print _("- %s is set to not be updated") % repo.name continue @@ -71,6 +73,8 @@ class RepoSync: if repo.is_rsync_mirror(): self.do_rsync(repo) else: + # which may actually NOT reposync if the repo is set to not mirror locally + # but that's a technicality self.do_reposync(repo) self.update_permissions(repo_path) @@ -105,7 +109,7 @@ class RepoSync: store_path = os.path.join(self.settings.webdir, "repo_mirror") dest_path = os.path.join(store_path, repo.name) temp_path = os.path.join(store_path, ".origin") - if not os.path.isdir(temp_path): + if not os.path.isdir(temp_path) and repo.mirror_locally: # FIXME: there's a chance this might break the RHN D/L case os.makedirs(temp_path) @@ -118,18 +122,21 @@ class RepoSync: # this is the simple non-RHN case. # create the config file that yum will use for the copying - temp_file = self.create_local_file(repo, temp_path, output=False) + if repo.mirror_locally: + temp_file = self.create_local_file(repo, temp_path, output=False) - if not has_rpm_list: + if not has_rpm_list and repo.mirror_locally: # if we have not requested only certain RPMs, use reposync cmd = "/usr/bin/reposync --config=%s --repoid=%s --download_path=%s" % (temp_file, repo.name, store_path) if repo.arch != "": + if repo.arch == "x86": + repo.arch = "i386" # FIX potential arch errors cmd = "%s -a %s" % (cmd, repo.arch) print _("- %s") % cmd cmds.append(cmd) - else: + elif repo.mirror_locally: # create the output directory if it doesn't exist if not os.path.exists(dest_path): @@ -149,6 +156,8 @@ class RepoSync: # this is the somewhat more-complex RHN case. # NOTE: this requires that you have entitlements for the server and you give the mirror as rhn://$channelname + if not repo.mirror_locally: + raise CX(_("rhn:// repos do not work with --mirror-locally=1")) if has_rpm_list: print _("- warning: --rpm-list is not supported for RHN content") @@ -161,7 +170,6 @@ class RepoSync: if repo.arch != "": cmd = "%s -a %s" % (cmd, repo.arch) - print _("- %s") % cmd cmds.append(cmd) # now regardless of whether we're doing yumdownloader or reposync @@ -169,9 +177,10 @@ class RepoSync: # commands here. Any failure at any point stops the operation. for cmd in cmds: - rc = sub_process.call(cmd, shell=True) - if rc !=0: - raise CX(_("cobbler reposync failed")) + if repo.mirror_locally: + rc = sub_process.call(cmd, shell=True) + if rc !=0: + raise CX(_("cobbler reposync failed")) # some more special case handling for RHN. # create the config file now, because the directory didn't exist earlier @@ -181,7 +190,8 @@ class RepoSync: # now run createrepo to rebuild the index - os.path.walk(dest_path, self.createrepo_walker, repo) + if repo.mirror_locally: + os.path.walk(dest_path, self.createrepo_walker, repo) # create the config file the hosts will use to access the repository. @@ -196,6 +206,9 @@ class RepoSync: Handle copying of rsync:// and rsync-over-ssh repos. """ + if not repo.mirror_locally: + raise CX(_("rsync:// urls must be mirrored locally, yum cannot access them directly")) + if repo.rpm_list != "": print _("- warning: --rpm-list is not supported for rsync'd repositories") dest_path = os.path.join(self.settings.webdir, "repo_mirror", repo.name) @@ -238,7 +251,11 @@ class RepoSync: optenabled = False optgpgcheck = False if output: - line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name) + if repo.mirror_locally: + line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name) + else: + line = "baseurl=%s" % (repo.mirror) + config_file.write(line) # user may have options specific to certain yum plugins # add them to the file diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index b87528d..ba32a07 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -31,6 +31,7 @@ class Repo(item.Item): def clear(self,is_subobject=False): self.parent = None self.name = None + # FIXME: subobject code does not really make sense for repos self.mirror = (None, '<>')[is_subobject] self.keep_updated = ('y', '<>')[is_subobject] self.priority = (99, '<>')[is_subobject] @@ -40,6 +41,7 @@ class Repo(item.Item): self.arch = "" # use default arch self.yumopts = {} self.owners = self.settings.default_ownership + self.mirror_locally = 1 def from_datastruct(self,seed_data): self.parent = self.load_item(seed_data, 'parent') @@ -53,6 +55,7 @@ class Repo(item.Item): self.depth = self.load_item(seed_data, 'depth', 2) self.yumopts = self.load_item(seed_data, 'yumopts', {}) self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership) + self.mirror_locally = self.load_item(seed_data, 'mirror_locally', '1') # coerce types from input file self.set_keep_updated(self.keep_updated) @@ -70,7 +73,7 @@ class Repo(item.Item): if mirror.find("x86_64") != -1: self.set_arch("x86_64") elif mirror.find("x86") != -1 or mirror.find("i386") != -1: - self.set_arch("x86") + self.set_arch("i386") elif mirror.find("ia64") != -1: self.set_arch("ia64") return True @@ -165,6 +168,7 @@ class Repo(item.Item): 'name' : self.name, 'owners' : self.owners, 'mirror' : self.mirror, + 'mirror_locally' : self.mirror_locally, 'keep_updated' : self.keep_updated, 'priority' : self.priority, 'rpm_list' : self.rpm_list, @@ -175,10 +179,19 @@ class Repo(item.Item): 'yumopts' : self.yumopts } + def set_mirror_locally(self,value): + value = str(value).lower() + if value in [ "yes", "y", "1", "on", "true" ]: + self.mirror_locally = 1 + else: + self.mirror_locally = 0 + return True + def printable(self): buf = _("repo : %s\n") % self.name buf = buf + _("owners : %s\n") % self.owners buf = buf + _("mirror : %s\n") % self.mirror + buf = buf + _("mirror locally : %s\n") % self.mirror_locally buf = buf + _("keep updated : %s\n") % self.keep_updated buf = buf + _("priority : %s\n") % self.priority buf = buf + _("rpm list : %s\n") % self.rpm_list @@ -215,6 +228,7 @@ class Repo(item.Item): 'rpm-list' : self.set_rpm_list, 'createrepo-flags' : self.set_createrepo_flags, 'yumopts' : self.set_yumopts, - 'owners' : self.set_owners + 'owners' : self.set_owners, + 'mirror-locally' : self.set_mirror_locally } diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index a5e540e..a9bc095 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -146,13 +146,18 @@ class KickGen: configs = self.get_repo_filenames(obj,is_profile) repos = self.repos + # FIXME: this really should be dynamically generated as with the kickstarts for c in configs: name = c.split("/")[-1].replace(".repo","") (is_core, baseurl) = self.analyze_repo_config(c) for repo in repos: if repo.name == name: if not repo.yumopts.has_key('enabled') or repo.yumopts['enabled'] == '1': - buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl) + if repo.mirror_locally: + buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl) + else: + buf = buf + "repo --name=%s --baseurl=%s\n" % (name, repo.mirror) + return buf def analyze_repo_config(self, filename): diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py index f31ab26..af6a9d1 100644 --- a/cobbler/modules/cli_repo.py +++ b/cobbler/modules/cli_repo.py @@ -51,6 +51,7 @@ class RepoFunction(commands.CobblerFunction): if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--mirror", dest="mirror", help="source to mirror (REQUIRED)") + p.add_option("--mirror-locally", dest="mirror_locally", help="mirror or use external directly? (default 1)") p.add_option("--priority", dest="priority", help="set priority") p.add_option("--rpm-list", dest="rpm_list", help="just mirror these rpms") p.add_option("--yumopts", dest="yumopts", help="ex: pluginvar=abcd") @@ -81,6 +82,7 @@ class RepoFunction(commands.CobblerFunction): if self.options.keep_updated: obj.set_keep_updated(self.options.keep_updated) if self.options.priority: obj.set_priority(self.options.priority) if self.options.mirror: obj.set_mirror(self.options.mirror) + if self.options.mirror_locally: obj.set_mirror_locally(self.options.mirror_locally) if self.options.yumopts: obj.set_yumopts(self.options.yumopts) if self.options.owners: diff --git a/cobbler/remote.py b/cobbler/remote.py index 749298e..87695d2 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -65,6 +65,11 @@ class CobblerXMLRPCInterface: def ping(self): return True + def update(self,token=None): + # ensure the config is up to date as of /now/ + self.api.deserialize() + return True + def get_user_from_token(self,token): if not TOKEN_CACHE.has_key(token): raise CX(_("invalid token: %s") % token) diff --git a/cobbler/services.py b/cobbler/services.py index adaf6fc..1cb3864 100644 --- a/cobbler/services.py +++ b/cobbler/services.py @@ -45,6 +45,7 @@ class CobblerSvc(object): This is the version that does not require logins. """ self.remote = xmlrpclib.Server(self.server, allow_none=True) + self.remote.update() def modes(self): """ diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 2eeb1a3..0951633 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -61,6 +61,8 @@ class CobblerWeb(object): try: self.remote.token_check(self.token) self.username = self.remote.get_user_from_token(self.token) + # ensure config is up2date + self.remote.update(self.token) return True except Exception, e: if str(e).find("invalid token") != -1: @@ -79,6 +81,8 @@ class CobblerWeb(object): log_exc(self.apache) return False self.password = None # don't need it anymore, get rid of it + # ensure configuration is up2date + self.remote.update(self.token) return True # login failed @@ -631,7 +635,7 @@ class CobblerWeb(object): } ) def repo_save(self,name=None,oldname=None,new_or_edit=None,editmode="edit", - mirror=None,owners=None,keep_updated=None,priority=99, + mirror=None,owners=None,keep_updated=None,mirror_locally=0,priority=99, rpm_list=None,createrepo_flags=None,arch=None,yumopts=None, delete1=None,delete2=None,**args): if not self.__xmlrpc_setup(): @@ -675,6 +679,7 @@ class CobblerWeb(object): self.remote.modify_repo(repo, 'mirror', mirror, self.token) self.remote.modify_repo(repo, 'keep-updated', keep_updated, self.token) self.remote.modify_repo(repo, 'priority', priority, self.token) + self.remote.modify_repo(repo, 'mirror-locally', mirror_locally, self.token) if rpm_list: self.remote.modify_repo(repo, 'rpm-list', rpm_list, self.token) diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 018c3f4..9f5154e 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -280,7 +280,7 @@ on your network will result in faster, more up-to-date installations and faster are only provisioning a home setup, this will probably be overkill, though it can be very useful for larger setups (labs, datacenters, etc). -B +B =over @@ -335,6 +335,10 @@ Specifies optional flags to feed into the createrepo tool, which is called when Specifies that the named repository should not be updated during a normal "cobbler reposync". The repo may still be updated by name. See "cobbler reposync" below. +=item mirror-locally + +When true, specifies that this yum repo is to be referenced directly via kickstarts and not mirrored locally on the cobbler server. Only http:// and ftp:// mirror urls are supported when using --mirror-locally=1. + =item priority Specifies the priority of the repository (the lower the number, the higher the priority), which applies to installed machines using the repositories that also have the yum priorities plugin installed. The default priority for the plugin is 99, as is that of all cobbler mirrored repositories. diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index 30d516d..c259a13 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -90,10 +90,24 @@ function disablename(value) checked="true" #end if /> -

    Disable to prevent the mirror from being updated.

    +

    Uncheck to prevent the mirror from being updated again.

    +
    + + + + + + +

    Uncheck to reference the repository directly instead of mirroring.

    + +
    @@ -130,7 +144,7 @@ function disablename(value) - + - + - +
    -Note: Newly added repos contain no package content until -"cobbler reposync" is run from the command line, which means -that profiles relying on these repositories will not install. -Placing "cobbler reposync" on a crontab to ensure frequent -updates is recommended procedure. +Note: Newly added repo definitions will not be usable until +"cobbler reposync" is run from the command line on this system. +Placing "cobbler reposync" on a crontab is recommended procedure.

    -- cgit From a2800a1cb6dfc7c3b4a0feeacd265c147c977110 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 8 May 2008 18:20:28 -0400 Subject: Adding --virt overrides for many more additional fields that were found in the profiles objects but not in the system objects. This needs testing in both the webui and command line. --- CHANGELOG | 1 + cobbler/item_profile.py | 143 ++++---------------------------------- cobbler/item_system.py | 90 +++++++++++++++--------- cobbler/modules/cli_system.py | 18 +++-- cobbler/utils.py | 146 +++++++++++++++++++++++++++++++++++++++ cobbler/webui/CobblerWeb.py | 16 ++++- webui_templates/system_edit.tmpl | 106 ++++++++++++++++++++++++++-- 7 files changed, 347 insertions(+), 173 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2a70394..6f46185 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ Cobbler CHANGELOG - cobbler buildiso command to build non live ISOs - cobbler replicate command - added cobbler repo option --mirror-locally to reference external repos without mirroring +- all virt parameters on profiles can now be overriden on cobbler profile objects - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index a352ce1..b472b48 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -138,46 +138,7 @@ class Profile(item.Item): self.server = server return True - def set_repos(self,repos): - - # WARNING: hack - repos = utils.fix_mod_python_select_submission(repos) - - - # allow the magic inherit string to persist - if repos == "<>": - # FIXME: this is not inheritable in the WebUI presently ? - self.repos = "<>" - return - - # store as an array regardless of input type - if repos is None: - repolist = [] - elif type(repos) != list: - # allow backwards compatibility support of string input - repolist = repos.split(None) - else: - repolist = repos - - - # make sure there are no empty strings - try: - repolist.remove('') - except: - pass - - self.repos = [] - - # if any repos don't exist, fail the operation - ok = True - for r in repolist: - if self.config.repos().find(name=r) is not None: - self.repos.append(r) - else: - print _("warning: repository not found: %s" % r) - - return True - + def set_kickstart(self,kickstart): """ Sets the kickstart. This must be a NFS, HTTP, or FTP URL. @@ -192,108 +153,27 @@ class Profile(item.Item): raise CX(_("kickstart not found")) def set_virt_cpus(self,num): - """ - For Virt only. Set the number of virtual CPUs to give to the - virtual machine. This is fed to virtinst RAW, so cobbler - will not yelp if you try to feed it 9999 CPUs. No formatting - like 9,999 please :) - """ - if num == "<>": - self.virt_cpus = "<>" - return True - - try: - num = int(str(num)) - except: - raise CX(_("invalid number of virtual CPUs")) - - self.virt_cpus = num - return True + return utils.set_virt_cpus(self,num) def set_virt_file_size(self,num): - """ - For Virt only. - Specifies the size of the virt image in gigabytes. - Older versions of koan (x<0.6.3) interpret 0 as "don't care" - Newer versions (x>=0.6.4) interpret 0 as "no disks" - """ - # num is a non-negative integer (0 means default) - # can also be a comma seperated list -- for usage with multiple disks - - if num == "<>": - self.virt_file_size = "<>" - return True - - if type(num) == str and num.find(",") != -1: - tokens = num.split(",") - for t in tokens: - # hack to run validation on each - self.set_virt_file_size(t) - # if no exceptions raised, good enough - self.virt_file_size = num - return True - - try: - inum = int(num) - if inum != float(num): - return CX(_("invalid virt file size")) - if inum >= 0: - self.virt_file_size = inum - return True - raise CX(_("invalid virt file size")) - except: - raise CX(_("invalid virt file size")) - + return utils.set_virt_file_size(self,num) + def set_virt_ram(self,num): - """ - For Virt only. - Specifies the size of the Virt RAM in MB. - 0 tells Koan to just choose a reasonable default. - """ - - if num == "<>": - self.virt_ram = "<>" - return True - - # num is a non-negative integer (0 means default) - try: - inum = int(num) - if inum != float(num): - return CX(_("invalid virt ram size")) - if inum >= 0: - self.virt_ram = inum - return True - return CX(_("invalid virt ram size")) - except: - return CX(_("invalid virt ram size")) + return utils.set_virt_ram(self,num) def set_virt_type(self,vtype): - """ - Virtualization preference, can be overridden by koan. - """ - - if vtype == "<>": - self.virt_type == "<>" - return True - - if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]: - raise CX(_("invalid virt type")) - self.virt_type = vtype - return True + return utils.set_virt_Type(self,vtype) def set_virt_bridge(self,vbridge): - """ - The default bridge for all virtual interfaces under this profile. - """ self.virt_bridge = vbridge return True def set_virt_path(self,path): - """ - Virtual storage location suggestion, can be overriden by koan. - """ - self.virt_path = path - return True + return utils.set_virt_path(self,path) + + def set_repos(self,repos): + return utils.set_repos(self,repos) + def get_parent(self): """ @@ -375,6 +255,7 @@ class Profile(item.Item): buf = buf + _("owners : %s\n") % self.owners return buf + def remote_methods(self): return { 'name' : self.set_name, diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 1bcc111..a97cdf6 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -38,9 +38,15 @@ class System(item.Item): self.netboot_enabled = True self.depth = 2 self.kickstart = "<>" # use value in profile + self.server = "<>" # "" (or settings) self.virt_path = "<>" # "" self.virt_type = "<>" # "" - self.server = "<>" # "" (or settings) + self.virt_cpus = "<>" # "" + self.virt_file_size = "<>" # "" + self.virt_ram = "<>" # "" + self.virt_type = "<>" # "" + self.virt_path = "<>" # "" + self.virt_bridge = "<>" # "" def delete_interface(self,name): """ @@ -87,11 +93,19 @@ class System(item.Item): self.ks_meta = self.load_item(seed_data, 'ks_meta', {}) self.depth = self.load_item(seed_data, 'depth', 2) self.kickstart = self.load_item(seed_data, 'kickstart', '<>') - self.virt_path = self.load_item(seed_data, 'virt_path', '<>') - self.virt_type = self.load_item(seed_data, 'virt_type', '<>') self.netboot_enabled = self.load_item(seed_data, 'netboot_enabled', True) self.server = self.load_item(seed_data, 'server', '<>') + # virt specific + self.virt_path = self.load_item(seed_data, 'virt_path', '<>') + self.virt_type = self.load_item(seed_data, 'virt_type', '<>') + self.virt_ram = self.load_item(seed_data,'virt_ram','<>') + self.virt_file_size = self.load_item(seed_data,'virt_file_size','<>') + self.virt_path = self.load_item(seed_data,'virt_path','<>') + self.virt_type = self.load_item(seed_data,'virt_type','<>') + self.virt_bridge = self.load_item(seed_data,'virt_bridge','<>') + self.virt_cpus = self.load_item(seed_data,'virt_cpus','<>') + # backwards compat, these settings are now part of the interfaces data structure # and will contain data only in upgrade scenarios. @@ -276,21 +290,20 @@ class System(item.Item): return True raise CX(_("invalid profile name")) - def set_virt_path(self,path): - """ - Virtual storage location suggestion, can be overriden by koan. - """ - self.virt_path = path - return True + def set_virt_cpus(self,num): + return utils.set_virt_cpus(self,num) + + def set_virt_file_size(self,num): + return utils.set_virt_file_size(self,num) + + def set_virt_ram(self,num): + return utils.set_virt_ram(self,num) def set_virt_type(self,vtype): - """ - Virtualization preference, can be overridden by koan. - """ - if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]: - raise CX(_("invalid virt type")) - self.virt_type = vtype - return True + return utils.set_virt_Type(self,vtype) + + def set_virt_path(self,path): + return utils.set_virt_path(self,path) def set_netboot_enabled(self,netboot_enabled): """ @@ -347,19 +360,24 @@ class System(item.Item): def to_datastruct(self): return { - 'name' : self.name, - 'owners' : self.owners, - 'profile' : self.profile, - 'kernel_options' : self.kernel_options, - 'ks_meta' : self.ks_meta, - 'netboot_enabled' : self.netboot_enabled, - 'parent' : self.parent, - 'depth' : self.depth, - 'kickstart' : self.kickstart, - 'virt_type' : self.virt_type, - 'virt_path' : self.virt_path, - 'interfaces' : self.interfaces, - 'server' : self.server + 'name' : self.name, + 'kernel_options' : self.kernel_options, + 'depth' : self.depth, + 'interfaces' : self.interfaces, + 'ks_meta' : self.ks_meta, + 'kickstart' : self.kickstart, + 'netboot_enabled' : self.netboot_enabled, + 'owners' : self.owners, + 'parent' : self.parent, + 'profile' : self.profile, + 'server' : self.server, + 'virt_cpus' : self.virt_cpus, + 'virt_bridge' : self.virt_bridge, + 'virt_file_size' : self.virt_file_size, + 'virt_path' : self.virt_path, + 'virt_ram' : self.virt_ram, + 'virt_type' : self.virt_type + } def printable(self): @@ -370,8 +388,14 @@ class System(item.Item): buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled buf = buf + _("kickstart : %s\n") % self.kickstart - buf = buf + _("virt type : %s\n") % self.virt_type - buf = buf + _("virt path : %s\n") % self.virt_path + + buf = buf + _("virt file size : %s\n") % self.virt_file_size + buf = buf + _("virt ram : %s\n") % self.virt_ram + buf = buf + _("virt type : %s\n") % self.virt_type + buf = buf + _("virt path : %s\n") % self.virt_path + buf = buf + _("virt bridge : %s\n") % self.virt_bridge + buf = buf + _("virt cpus : %s\n") % self.virt_cpus + buf = buf + _("server : %s\n") % self.server buf = buf + _("owners : %s\n") % self.owners @@ -419,6 +443,10 @@ class System(item.Item): 'virt-type' : self.set_virt_type, 'modify-interface' : self.modify_interface, 'delete-interface' : self.delete_interface, + 'virt-path' : self.set_virt_path, + 'virt-type' : self.set_virt_type, + 'virt-bridge' : self.set_virt_bridge, + 'virt-cpus' : self.set_virt_cpus, 'server' : self.set_server, 'owners' : self.set_owners } diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 419b561..6e301d1 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -70,9 +70,13 @@ class SystemFunction(commands.CobblerFunction): p.add_option("--profile", dest="profile", help="name of cobbler profile (REQUIRED)") p.add_option("--server-override", dest="server_override", help="overrides server value in settings file") p.add_option("--subnet", dest="subnet", help="for static IP / templating usage") - p.add_option("--virt-bridge", dest="virt_bridge", help="ex: virbr0") - p.add_option("--virt-path", dest="virt_path", help="path, partition, or volume") - p.add_option("--virt-type", dest="virt_type", help="ex: xenpv, qemu, xenfv") + + p.add_option("--virt-bridge", dest="virt_bridge", help="ex: 'virbr0'") + p.add_option("--virt-cpus", dest="virt_cpus", help="integer (default: 1)") + p.add_option("--virt-file-size", dest="virt_file_size", help="size in GB") + p.add_option("--virt-path", dest="virt_path", help="path, partition, or volume") + p.add_option("--virt-ram", dest="virt_ram", help="size in MB") + p.add_option("--virt-type", dest="virt_type", help="ex: 'xenpv', 'qemu'") def run(self): @@ -89,8 +93,14 @@ class SystemFunction(commands.CobblerFunction): if self.options.kickstart: obj.set_kickstart(self.options.kickstart) if self.options.netboot_enabled: obj.set_netboot_enabled(self.options.netboot_enabled) if self.options.server_override: obj.set_server(self.options.server_override) - if self.options.virt_path: obj.set_virt_path(self.options.virt_path) + + if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size) + if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram) + if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge) if self.options.virt_type: obj.set_virt_type(self.options.virt_type) + if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus) + if self.options.virt_path: obj.set_virt_path(self.options.virt_path) + if self.options.interface: my_interface = "intf%s" % self.options.interface diff --git a/cobbler/utils.py b/cobbler/utils.py index 14019e8..1b4b30a 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -631,6 +631,152 @@ def mkdir(path,mode=0777): print oe.errno raise CX(_("Error creating") % path) +def set_repos(self,repos): + # WARNING: hack + repos = fix_mod_python_select_submission(repos) + + # allow the magic inherit string to persist + if repos == "<>": + # FIXME: this is not inheritable in the WebUI presently ? + self.repos = "<>" + return + + # store as an array regardless of input type + if repos is None: + repolist = [] + elif type(repos) != list: + # allow backwards compatibility support of string input + repolist = repos.split(None) + else: + repolist = repos + + + # make sure there are no empty strings + try: + repolist.remove('') + except: + pass + + self.repos = [] + + # if any repos don't exist, fail the operation + ok = True + for r in repolist: + if self.config.repos().find(name=r) is not None: + self.repos.append(r) + else: + print _("warning: repository not found: %s" % r) + + return True + +def set_virt_file_size(self,num): + """ + For Virt only. + Specifies the size of the virt image in gigabytes. + Older versions of koan (x<0.6.3) interpret 0 as "don't care" + Newer versions (x>=0.6.4) interpret 0 as "no disks" + """ + # num is a non-negative integer (0 means default) + # can also be a comma seperated list -- for usage with multiple disks + + if num == "<>": + self.virt_file_size = "<>" + return True + + if type(num) == str and num.find(",") != -1: + tokens = num.split(",") + for t in tokens: + # hack to run validation on each + self.set_virt_file_size(t) + # if no exceptions raised, good enough + self.virt_file_size = num + return True + + try: + inum = int(num) + if inum != float(num): + return CX(_("invalid virt file size")) + if inum >= 0: + self.virt_file_size = inum + return True + raise CX(_("invalid virt file size")) + except: + raise CX(_("invalid virt file size")) + return True + +def set_virt_ram(self,num): + """ + For Virt only. + Specifies the size of the Virt RAM in MB. + 0 tells Koan to just choose a reasonable default. + """ + + if num == "<>": + self.virt_ram = "<>" + return True + + # num is a non-negative integer (0 means default) + try: + inum = int(num) + if inum != float(num): + return CX(_("invalid virt ram size")) + if inum >= 0: + self.virt_ram = inum + return True + return CX(_("invalid virt ram size")) + except: + return CX(_("invalid virt ram size")) + return True + +def set_virt_type(self,vtype): + """ + Virtualization preference, can be overridden by koan. + """ + + if vtype == "<>": + self.virt_type == "<>" + return True + + if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]: + raise CX(_("invalid virt type")) + self.virt_type = vtype + return True + +def set_virt_bridge(self,vbridge): + """ + The default bridge for all virtual interfaces under this profile. + """ + self.virt_bridge = vbridge + return True + +def set_virt_path(self,path): + """ + Virtual storage location suggestion, can be overriden by koan. + """ + self.virt_path = path + return True + +def set_virt_cpus(self,num): + """ + For Virt only. Set the number of virtual CPUs to give to the + virtual machine. This is fed to virtinst RAW, so cobbler + will not yelp if you try to feed it 9999 CPUs. No formatting + like 9,999 please :) + """ + if num == "<>": + self.virt_cpus = "<>" + return True + + try: + num = int(str(num)) + except: + raise CX(_("invalid number of virtual CPUs")) + + self.virt_cpus = num + return True + + + if __name__ == "__main__": # print redhat_release() print tftpboot_location() diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 0951633..1b52b80 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -313,7 +313,8 @@ class CobblerWeb(object): def system_save(self,name=None,oldname=None,editmode="edit",profile=None, new_or_edit=None, kopts=None, ksmeta=None, owners=None, server_override=None, netboot='n', - delete1=None, delete2=None, **args): + virtpath=None,virtram=None,virttype=None,virtcpus=None,virtfilesize=None,delete1=None, delete2=None, **args): + if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -363,6 +364,19 @@ class CobblerWeb(object): if server_override: self.remote.modify_system(system, 'server', server_override, self.token) + if virtfilesize: + self.remote.modify_system(system, 'virt-file-size', virtfilesize, self.token) + if virtcpus: + self.remote.modify_system(system, 'virt-cpus', virtcpus, self.token) + if virtram: + self.remote.modify_system(system, 'virt-ram', virtram, self.token) + if virttype: + self.remote.modify_system(system, 'virt-type', virtype, self.token) + + if virtpath: + self.remote.modify_system(system, 'virt-path', virtpath, self.token) + + for x in range(0,7): interface = "intf%s" % x macaddress = args.get("macaddress-%s" % interface, "") diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 684c6d1..b996520 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -236,6 +236,106 @@ function page_onload() { + + + + + + +

    For virtual installs only, require this disk size in GB.

    + + + + + + + + + +

    For virtual installs only, allocate this amount of RAM, in MB.

    + + + + + + + + + + #if $system and $system.virt_type == "<>" + Inherit + #else + #if $system + Inherit + #else + Inherit + #end if + #end if + + + #if $system and $system.virt_type == "auto" + Any + #else + #if $system + Any + #else + Any + #end if + #end if + + #if $system and $system.virt_type == "xenpv" + Xen (pv) + #else + Xen (pv) + #end if + + #if $system and $system.virt_type == "qemu" + qemu/KVM + #else + qemu/KVM + #end if +

    What virtualization technology should koan use?

    + + + + + + + + + +

    Sets koan's storage preferences, read manpage or leave blank.

    + + + + + + + + + +

    How many virtual CPUs? This is an integer.

    + + + + + ## ====================================== start of looping through interfaces @@ -349,8 +449,6 @@ function page_onload() { - ## FIXME: add virt_bridge editing (like above) - @@ -363,8 +461,6 @@ function page_onload() { - ## FIXME: add subnet editing (like above) - @@ -377,8 +473,6 @@ function page_onload() { - ## FIXME: add gateway editing (like above) - -- cgit From 1467682551da147ff0064881c8429e3036a2f1a9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 9 May 2008 11:02:11 -0400 Subject: Add missing file. --- cobbler/action_replicate.py | 158 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 cobbler/action_replicate.py diff --git a/cobbler/action_replicate.py b/cobbler/action_replicate.py new file mode 100644 index 0000000..139f18f --- /dev/null +++ b/cobbler/action_replicate.py @@ -0,0 +1,158 @@ +""" +Replicate from a cobbler master. + +Copyright 2007-2008, Red Hat, Inc +Michael DeHaan +Scott Henson + +This software may be freely redistributed under the terms of the GNU +general public license. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import xmlrpclib +import api as cobbler_api + +class Replicate: + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.settings = config.settings() + self.api = config.api + self.remote = None + self.uri = None + + # ------------------------------------------------------- + + def link_distro(self, distro): + """ + Create the distro links + """ + # find the tree location + dirname = os.path.dirname(distro.kernel) + tokens = dirname.split("/") + tokens = tokens[:-2] + base = "/".join(tokens) + dest_link = os.path.join(self.settings.webdir, "links", distro.name) + + # create the links directory only if we are mirroring because with + # SELinux Apache can't symlink to NFS (without some doing) + + if not os.path.exists(dest_link): + try: + os.symlink(base, dest_link) + except: + # this shouldn't happen but I've seen it ... debug ... + print _("- symlink creation failed: %(base)s, %(dest)s") % { "base" : base, "dest" : dest_link } + + # ------------------------------------------------------- + + def add_distro(self, distro): + """ + Add a distro that has been found + """ + #Register the distro + if os.path.exists(distro['kernel']): + new_distro = self.api.new_distro() + new_distro.from_datastruct(distro) + #create the symlinks + self.link_distro(new_distro) + #Add the distro permanently + self.api.distros().add(new_distro, save=True) + print 'Added distro %s. Creating Links.' % distro['name'] + else: + print 'Distro %s not here yet.' % distro['name'] + + # ------------------------------------------------------- + + def add_profile(self, profile): + """ + Add a profile that has been found + """ + #Register the new profile + new_profile = self.api.new_profile() + new_profile.from_datastruct(profile) + self.api.profiles().add(new_profile, save=True) + print 'Added profile %s.' % profile['name'] + + + # ------------------------------------------------------- + + def check_profile(self, profile): + """ + Check that a profile belongs to a distro + """ + profiles = self.api.profiles().to_datastruct() + if profile not in profiles: + for distro in self.api.distros().to_datastruct(): + if distro['name'] == profile['name']: + return True + return False + + + # ------------------------------------------------------- + + def sync_distros(self): + """ + Sync distros from master + """ + local_distros = self.api.distros() + remote_distros = self.remote.get_distros() + + needsync = False + for distro in remote_distros: + if distro not in local_distros.to_datastruct(): + print 'Found distro %s.' % distro['name'] + self.add_distro(distro) + needsync = True + + self.call_sync(needsync) + + + # ------------------------------------------------------- + + def sync_profiles(self): + """ + Sync profiles from master + """ + local_profiles = self.api.profiles() + remote_profiles = self.remote.get_profiles() + + needsync = False + for profile in remote_profiles: + if self.check_profile(profile): + print 'Found profile %s.' % profilew['name'] + self.add_profile(profile) + needsync = True + self.call_sync(needsync) + + + # ------------------------------------------------------- + + def call_sync(self, needsync): + if needsync: + self.api.sync() + + # ------------------------------------------------------- + + def run(self, cobbler_master=None): + """ + Get remote profiles and distros and sync them locally + """ + if cobbler_master is not None: + self.uri = 'http://%s/cobbler_api' % cobbler_master + elif len(self.settings.cobbler_master) > 0: + self.uri = 'http://%s/cobbler_api' % self.settings.cobbler_master + + if self.uri is not None: + self.remote = xmlrpclib.Server(self.uri) + self.sync_distros() + self.sync_profiles() + -- cgit From cb19415d6574f91410cab56d50bbff9871edb4ce Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 9 May 2008 11:10:34 -0400 Subject: Apply patch to rework bind zone handling. --- cobbler/modules/manage_bind.py | 120 +++++++++++++++++++++++++---------------- docs/cobbler.pod | 2 +- 2 files changed, 74 insertions(+), 48 deletions(-) diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py index e14122a..339fbf0 100644 --- a/cobbler/modules/manage_bind.py +++ b/cobbler/modules/manage_bind.py @@ -23,6 +23,7 @@ import glob import traceback import errno import popen2 +import re from shlex import shlex @@ -69,9 +70,13 @@ class BindManager: def __forward_zones(self): """ - Returns ALL forward zones for all systems + Returns a map of zones and the records that belong + in them """ zones = {} + for zone in self.settings.manage_forward_zones: + zones[zone] = [] + for sys in self.systems: for (name, interface) in sys.interfaces.iteritems(): host = interface["hostname"] @@ -81,19 +86,45 @@ class BindManager: continue if host.find(".") == -1: continue - tokens = host.split('.') - zone = '.'.join(tokens[1:]) - if zones.has_key(zone): - zones[zone].append((host.split('.')[0], ip)) - else: - zones[zone] = [(host.split('.')[0], ip)] + + # match the longest zone! + # e.g. if you have a host a.b.c.d.e + # if manage_forward_zones has: + # - c.d.e + # - b.c.d.e + # then a.b.c.d.e should go in b.c.d.e + best_match = '' + for zone in zones.keys(): + if re.search('\.%s$' % zone, host) and len(zone) > len(best_match): + best_match = zone + + if best_match == '': # no match + continue + + # strip the zone off the hostname and append the + # remainder + ip to the zone list + host = host.replace(best_match, '') + if host[-1] == '.': # strip trailing '.' if it's there + host = host[:-1] + zones[best_match].append((host, ip)) + + # axe zones that are defined in manage_forward_zones + # but don't actually match any hosts + for (k,v) in zones.items(): + if v == []: + zones.pop(k) + return zones def __reverse_zones(self): """ - Returns ALL reverse zones for all systems + Returns a map of zones and the records that belong + in them """ zones = {} + for zone in self.settings.manage_reverse_zones: + zones[zone] = [] + for sys in self.systems: for (name, interface) in sys.interfaces.iteritems(): host = interface["hostname"] @@ -101,45 +132,40 @@ class BindManager: if not host or not ip: # gotsta have some hostname and ip or else! continue + + # match the longest zone! + # e.g. if you have an ip 1.2.3.4 + # if manage_reverse_zones has: + # - 1.2 + # - 1.2.3 + # then 1.2.3.4 should go in 1.2.3 + best_match = '' + for zone in zones.keys(): + if re.search('^%s\.' % zone, ip) and len(zone) > len(best_match): + best_match = zone + + if best_match == '': # no match + continue + + # strip the zone off the front of the ip + # reverse the rest of the octets + # append the remainder + hostname + ip = ip.replace(best_match, '', 1) + if ip[0] == '.': # strip leading '.' if it's there + ip = ip[1:] tokens = ip.split('.') - zone = '.'.join(tokens[0:3]) - if zones.has_key(zone): - zones[zone].append((tokens[3], host + '.')) - else: - zones[zone] = [(tokens[3], host + '.')] - return zones + tokens.reverse() + ip = '.'.join(tokens) + zones[best_match].append((ip, host + '.')) - def __config_forward_zones(self): - """ - Returns only the forward zones which have systems and are defined - in the option manage_forward_zones + # axe zones that are defined in manage_forward_zones + # but don't actually match any hosts + for (k,v) in zones.items(): + if v == []: + zones.pop(k) - Alternatively if manage_forward_zones is empty, return all systems - """ - all = self.__forward_zones() - want = self.settings.manage_forward_zones - if want == []: return all - ret = {} - for zone in all.keys(): - if zone in want: - ret[zone] = all[zone] - return ret - - def __config_reverse_zones(self): - """ - Returns only the reverse zones which have systems and are defined - in the option manage_reverse_zones + return zones - Alternatively if manage_reverse_zones is empty, return all systems - """ - all = self.__reverse_zones() - want = self.settings.manage_reverse_zones - if want == []: return all - ret = {} - for zone in all.keys(): - if zone in want: - ret[zone] = all[zone] - return ret def __write_named_conf(self): """ @@ -151,7 +177,7 @@ class BindManager: reverse_zones = self.settings.manage_reverse_zones metadata = {'zone_include': ''} - for zone in self.__config_forward_zones().keys(): + for zone in self.__forward_zones().keys(): txt = """ zone "%(zone)s." { type master; @@ -160,7 +186,7 @@ zone "%(zone)s." { """ % {'zone': zone} metadata['zone_include'] = metadata['zone_include'] + txt - for zone in self.__config_reverse_zones().keys(): + for zone in self.__reverse_zones().keys(): tokens = zone.split('.') tokens.reverse() arpa = '.'.join(tokens) + '.in-addr.arpa' @@ -189,8 +215,8 @@ zone "%(arpa)s." { default_template_file = "/etc/cobbler/zone.template" cobbler_server = self.settings.server serial = int(time.time()) - forward = self.__config_forward_zones() - reverse = self.__config_reverse_zones() + forward = self.__forward_zones() + reverse = self.__reverse_zones() try: f2 = open(default_template_file,"r") diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 9f5154e..72836f0 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -565,7 +565,7 @@ Cobbler can optionally manage DNS configuration using BIND and dnsmasq. Choose either "management = isc_and_bind" or "management = dnsmasq" in /etc/cobbler/modules.conf and then enable manage_dns in /var/lib/cobbler/settings. -This feature is off by default. If using BIND, you may restrict the scope of zones managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. (See the Wiki for more information on this). +This feature is off by default. If using BIND, you must define the zones to be managed with the options 'manage_forward_zones' and 'manage_reverse_zones'. (See the Wiki for more information on this). If using BIND, Cobbler will use /etc/cobbler/bind.template and /etc/cobbler/zone.template as a starting point for the named.conf and individual zone files, respectively. You may drop zone-specific template files in /etc/cobbler/zone_templates/name-of-zone which will override the default. These files must be user edited for the user's particular networking environment. Read the file and understand how BIND works before proceeding. -- cgit From 9c5b5e8502c3350f358129e0c1983efa5efdd9cc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 9 May 2008 11:54:24 -0400 Subject: Fixes for virt overrides on system objects. --- cobbler/item_distro.py | 6 +++--- cobbler/item_profile.py | 16 ++++++++-------- cobbler/item_repo.py | 8 ++++---- cobbler/item_system.py | 19 +++++++++---------- cobbler/modules/cli_system.py | 1 - 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index 1e392a5..1a4c296 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -180,12 +180,12 @@ class Distro(item.Item): kstr = utils.find_kernel(self.kernel) istr = utils.find_initrd(self.initrd) buf = _("distro : %s\n") % self.name - buf = buf + _("kernel : %s\n") % kstr + buf = buf + _("breed : %s\n") % self.breed + buf = buf + _("architecture : %s\n") % self.arch buf = buf + _("initrd : %s\n") % istr + buf = buf + _("kernel : %s\n") % kstr buf = buf + _("kernel options : %s\n") % self.kernel_options - buf = buf + _("architecture : %s\n") % self.arch buf = buf + _("ks metadata : %s\n") % self.ks_meta - buf = buf + _("breed : %s\n") % self.breed buf = buf + _("owners : %s\n") % self.owners return buf diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index b472b48..019f01a 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -240,19 +240,19 @@ class Profile(item.Item): buf = buf + _("parent : %s\n") % self.parent else: buf = buf + _("distro : %s\n") % self.distro - buf = buf + _("kickstart : %s\n") % self.kickstart + buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag buf = buf + _("kernel options : %s\n") % self.kernel_options + buf = buf + _("kickstart : %s\n") % self.kickstart buf = buf + _("ks metadata : %s\n") % self.ks_meta + buf = buf + _("owners : %s\n") % self.owners + buf = buf + _("repos : %s\n") % self.repos + buf = buf + _("server : %s\n") % self.server + buf = buf + _("virt bridge : %s\n") % self.virt_bridge + buf = buf + _("virt cpus : %s\n") % self.virt_cpus buf = buf + _("virt file size : %s\n") % self.virt_file_size + buf = buf + _("virt path : %s\n") % self.virt_path buf = buf + _("virt ram : %s\n") % self.virt_ram buf = buf + _("virt type : %s\n") % self.virt_type - buf = buf + _("virt path : %s\n") % self.virt_path - buf = buf + _("virt bridge : %s\n") % self.virt_bridge - buf = buf + _("virt cpus : %s\n") % self.virt_cpus - buf = buf + _("repos : %s\n") % self.repos - buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag - buf = buf + _("server : %s\n") % self.server - buf = buf + _("owners : %s\n") % self.owners return buf diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index ba32a07..3b9b839 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -189,14 +189,14 @@ class Repo(item.Item): def printable(self): buf = _("repo : %s\n") % self.name - buf = buf + _("owners : %s\n") % self.owners + buf = buf + _("arch : %s\n") % self.arch + buf = buf + _("createrepo_flags : %s\n") % self.createrepo_flags + buf = buf + _("keep updated : %s\n") % self.keep_updated buf = buf + _("mirror : %s\n") % self.mirror buf = buf + _("mirror locally : %s\n") % self.mirror_locally - buf = buf + _("keep updated : %s\n") % self.keep_updated + buf = buf + _("owners : %s\n") % self.owners buf = buf + _("priority : %s\n") % self.priority buf = buf + _("rpm list : %s\n") % self.rpm_list - buf = buf + _("createrepo_flags : %s\n") % self.createrepo_flags - buf = buf + _("arch : %s\n") % self.arch buf = buf + _("yum options : %s\n") % self.yumopts return buf diff --git a/cobbler/item_system.py b/cobbler/item_system.py index a97cdf6..6effc22 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -300,7 +300,7 @@ class System(item.Item): return utils.set_virt_ram(self,num) def set_virt_type(self,vtype): - return utils.set_virt_Type(self,vtype) + return utils.set_virt_type(self,vtype) def set_virt_path(self,path): return utils.set_virt_path(self,path) @@ -384,20 +384,19 @@ class System(item.Item): buf = _("system : %s\n") % self.name buf = buf + _("profile : %s\n") % self.profile buf = buf + _("kernel options : %s\n") % self.kernel_options + buf = buf + _("kickstart : %s\n") % self.kickstart buf = buf + _("ks metadata : %s\n") % self.ks_meta buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled - buf = buf + _("kickstart : %s\n") % self.kickstart + buf = buf + _("owners : %s\n") % self.owners + buf = buf + _("server : %s\n") % self.server - buf = buf + _("virt file size : %s\n") % self.virt_file_size - buf = buf + _("virt ram : %s\n") % self.virt_ram - buf = buf + _("virt type : %s\n") % self.virt_type - buf = buf + _("virt path : %s\n") % self.virt_path - buf = buf + _("virt bridge : %s\n") % self.virt_bridge - buf = buf + _("virt cpus : %s\n") % self.virt_cpus + buf = buf + _("virt cpus : %s\n") % self.virt_cpus + buf = buf + _("virt file size : %s\n") % self.virt_file_size + buf = buf + _("virt path : %s\n") % self.virt_path + buf = buf + _("virt ram : %s\n") % self.virt_ram + buf = buf + _("virt type : %s\n") % self.virt_type - buf = buf + _("server : %s\n") % self.server - buf = buf + _("owners : %s\n") % self.owners counter = 0 for (name,x) in self.interfaces.iteritems(): diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 6e301d1..d8f8edf 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -96,7 +96,6 @@ class SystemFunction(commands.CobblerFunction): if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size) if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram) - if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge) if self.options.virt_type: obj.set_virt_type(self.options.virt_type) if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus) if self.options.virt_path: obj.set_virt_path(self.options.virt_path) -- cgit From f4a1f5f2f62b5b75a0a327056ea9f34e72e7a50f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 9 May 2008 12:32:55 -0400 Subject: Fixes for webapp new fields --- cobbler/item_system.py | 3 ++- cobbler/webui/CobblerWeb.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 6effc22..e7d6ed6 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -443,9 +443,10 @@ class System(item.Item): 'modify-interface' : self.modify_interface, 'delete-interface' : self.delete_interface, 'virt-path' : self.set_virt_path, + 'virt-ram' : self.set_virt_ram, 'virt-type' : self.set_virt_type, - 'virt-bridge' : self.set_virt_bridge, 'virt-cpus' : self.set_virt_cpus, + 'virt-file-size' : self.set_virt_file_size, 'server' : self.set_server, 'owners' : self.set_owners } diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 1b52b80..d272281 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -371,7 +371,7 @@ class CobblerWeb(object): if virtram: self.remote.modify_system(system, 'virt-ram', virtram, self.token) if virttype: - self.remote.modify_system(system, 'virt-type', virtype, self.token) + self.remote.modify_system(system, 'virt-type', virttype, self.token) if virtpath: self.remote.modify_system(system, 'virt-path', virtpath, self.token) -- cgit From 34fc9d39389ed2a949b0efbeea246f30a10c0265 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 9 May 2008 13:11:07 -0400 Subject: Added some additional links for kickstart viewing/editing to the profile/system web pages --- CHANGELOG | 1 + cobbler/action_reposync.py | 2 +- cobbler/item_profile.py | 2 +- cobbler/kickgen.py | 5 ++++- webui_templates/profile_list.tmpl | 19 ++++++++++++++++++- webui_templates/system_list.tmpl | 24 +++++++++++++++++++++--- 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6f46185..79b35d5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ Cobbler CHANGELOG - cobbler replicate command - added cobbler repo option --mirror-locally to reference external repos without mirroring - all virt parameters on profiles can now be overriden on cobbler profile objects +- added some additional links for kickstart viewing/editing to the web page - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index 33bfe30..d6b30bc 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -254,7 +254,7 @@ class RepoSync: if repo.mirror_locally: line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name) else: - line = "baseurl=%s" % (repo.mirror) + line = "baseurl=%s\n" % (repo.mirror) config_file.write(line) # user may have options specific to certain yum plugins diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index 019f01a..c5c9bdb 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -162,7 +162,7 @@ class Profile(item.Item): return utils.set_virt_ram(self,num) def set_virt_type(self,vtype): - return utils.set_virt_Type(self,vtype) + return utils.set_virt_type(self,vtype) def set_virt_bridge(self,vbridge): self.virt_bridge = vbridge diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index a9bc095..ba8bfd2 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -170,7 +170,10 @@ class KickGen: if line.find("ks_mirror") != -1: ret = True if line.find("baseurl") != -1: - first, baseurl = line.split("=") + try: + first, baseurl = line.split("=",1) + except: + raise CX(_("error scanning repo: %s" % filename)) fd.close() return (ret, baseurl) diff --git a/webui_templates/profile_list.tmpl b/webui_templates/profile_list.tmpl index f213775..fefe753 100644 --- a/webui_templates/profile_list.tmpl +++ b/webui_templates/profile_list.tmpl @@ -38,7 +38,24 @@ $profile.parent #end if - $profile.kickstart + + #if $profile.kickstart and $profile.kickstart !="" + #set $kick = $profile.kickstart + #if $kick.startswith("http://") or $kick.startswith("ftp://") or $kick.startswith("nfs://") + #if not $kick.startswith("nfs://") + $kick + #else + $kick + #end if + #else + #set $name = $profile.name + (view rendered)  + #if $kick != "" and $kick != "<>": + (edit template) + #end if + #end if + #end if + #end for diff --git a/webui_templates/system_list.tmpl b/webui_templates/system_list.tmpl index 5c70ffc..6ab5fac 100644 --- a/webui_templates/system_list.tmpl +++ b/webui_templates/system_list.tmpl @@ -14,9 +14,7 @@ Name Profile - ## FIXME: how to handle for multiple interface listing? MAC - ## IP - ## Hostname + Kickstart @@ -39,7 +37,27 @@ ## ${system.mac_address} ## ${system.ip_address} ## ${system.hostname} + + + #set $kick = $system.kickstart + #if $kick.startswith("http://") or $kick.startswith("ftp://") or $kick.startswith("nfs://") + #if not $kick.startswith("nfs://") + $kick + #else + $kick + #end if + #else + #set $name = $system.name + #set $pname = $system.profile + (view rendered)  + #if $kick != "<>" and $kick != "" + (edit template) + #end if + #end if + + + #end for -- cgit From 402ff49f5dd70671575e25d5224792f59a0c8408 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 9 May 2008 15:12:55 -0400 Subject: Release bump for test release --- CHANGELOG | 2 +- cobbler.spec | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 79b35d5..f08b74a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ Cobbler CHANGELOG (all entries mdehaan@redhat.com unless noted otherwise) -- Mon Mar 10 2008 - 0.9.0 +- Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck - applied patch to send hostname from ISC - added patch to allow --kopts/--ksmeta items to be cleared with --kopts=delete diff --git a/cobbler.spec b/cobbler.spec index 2d0cc15..5b23ec2 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -2,7 +2,7 @@ Summary: Boot server configurator Name: cobbler AutoReq: no -Version: 0.9.0 +Version: 0.9.1 Release: 1%{?dist} Source0: %{name}-%{version}.tar.gz License: GPLv2+ @@ -188,7 +188,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -* Tue Apr 08 2008 Michael DeHaan - 0.9.0-1 +* Fri May 09 2008 Michael DeHaan - 0.9.1-1 - Upstream changes (see CHANGELOG) - packaged /etc/cobbler/users.conf - remaining CGI replaced with mod_python diff --git a/setup.py b/setup.py index 07d9880..57c28ad 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys from distutils.core import setup, Extension import string -VERSION = "0.9.0" +VERSION = "0.9.1" SHORT_DESC = "Network Boot and Update Server" LONG_DESC = """ Cobbler is a network boot and update server. Cobbler supports PXE, provisioning virtualized images, and reinstalling existing Linux machines. The last two modes require a helper tool called 'koan' that integrates with cobbler. Cobbler's advanced features include importing distributions from DVDs and rsync mirrors, kickstart templating, integrated yum mirroring, and built-in DHCP Management. Cobbler has a Python API for integration with other GPL systems management applications. -- cgit From bdcdc5b244ea5deb35ff92787e1fb4068968d9dd Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 12 May 2008 14:15:40 -0400 Subject: Apply patch to remove anti-createrepo conditions, bump version. --- CHANGELOG | 3 +++ cobbler.spec | 5 ++++- cobbler/action_reposync.py | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f08b74a..049ead8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Cobbler CHANGELOG (all entries mdehaan@redhat.com unless noted otherwise) +- Mon May 12 2008 - 0.9.2 +- run createrepo with less preconditions during cobbler reposync + - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck - applied patch to send hostname from ISC diff --git a/cobbler.spec b/cobbler.spec index 5b23ec2..51ea3aa 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -2,7 +2,7 @@ Summary: Boot server configurator Name: cobbler AutoReq: no -Version: 0.9.1 +Version: 0.9.2 Release: 1%{?dist} Source0: %{name}-%{version}.tar.gz License: GPLv2+ @@ -188,6 +188,9 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog +* Mon May 12 2008 Michael DeHaan - 0.9.2-1 +- Upstream changes (see CHANGELOG) + * Fri May 09 2008 Michael DeHaan - 0.9.1-1 - Upstream changes (see CHANGELOG) - packaged /etc/cobbler/users.conf diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index d6b30bc..495aa3a 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -285,7 +285,7 @@ class RepoSync: """ Used to run createrepo on a copied mirror. """ - if os.path.exists(os.path.join(dirname,"RPMS")) or repo.is_rsync_mirror(): + if os.path.exists(dirname) or repo.is_rsync_mirror(): utils.remove_yum_olddata(dirname) try: cmd = "createrepo %s %s" % (repo.createrepo_flags, dirname) diff --git a/setup.py b/setup.py index 57c28ad..7b11c78 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys from distutils.core import setup, Extension import string -VERSION = "0.9.1" +VERSION = "0.9.2" SHORT_DESC = "Network Boot and Update Server" LONG_DESC = """ Cobbler is a network boot and update server. Cobbler supports PXE, provisioning virtualized images, and reinstalling existing Linux machines. The last two modes require a helper tool called 'koan' that integrates with cobbler. Cobbler's advanced features include importing distributions from DVDs and rsync mirrors, kickstart templating, integrated yum mirroring, and built-in DHCP Management. Cobbler has a Python API for integration with other GPL systems management applications. -- cgit From aaf52ab85b518d8c6b4ed0211b69d6be531ae5a9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 12 May 2008 14:27:06 -0400 Subject: Patch for replicate error handling + docs --- CHANGELOG | 1 + cobbler/action_replicate.py | 4 +++- docs/cobbler.pod | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 049ead8..95156a5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Cobbler CHANGELOG - Mon May 12 2008 - 0.9.2 - run createrepo with less preconditions during cobbler reposync +- doc upgrades and error handling for "cobbler replicate" - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/action_replicate.py b/cobbler/action_replicate.py index 139f18f..ae3adc8 100644 --- a/cobbler/action_replicate.py +++ b/cobbler/action_replicate.py @@ -17,6 +17,7 @@ import os import os.path import xmlrpclib import api as cobbler_api +from utils import _ class Replicate: def __init__(self,config): @@ -150,7 +151,8 @@ class Replicate: self.uri = 'http://%s/cobbler_api' % cobbler_master elif len(self.settings.cobbler_master) > 0: self.uri = 'http://%s/cobbler_api' % self.settings.cobbler_master - + else: + print _('No cobbler master found to replicate from, try --master.') if self.uri is not None: self.remote = xmlrpclib.Server(self.uri) self.sync_distros() diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 72836f0..ce5cb0d 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -399,6 +399,14 @@ Objects can also be renamed, as long as other objects don't reference them. B +=head2 REPLICATING + +Cobbler can replicate distro and profile data from a master cobbler server. + +B + +This will bring over all distro data for which it can find data in /var/www/cobbler/ks_mirror can be found. It will also bring over any default profiles for those distros. A default cobbler master can be set in the settings file. Tree data must still be rsync'd (or otherwise mirrored) manually. + =head2 REBUILDING CONFIGURATIONS B -- cgit From b3e6aa54f75dba69ea58797c04751b104b406136 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 12 May 2008 16:12:29 -0400 Subject: Make duplicate mac checking work in more places, nicer error when encountering rootsquash. --- CHANGELOG | 1 + cobbler/commands.py | 11 ++++++----- cobbler/utils.py | 11 +++++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 95156a5..729ec7f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ Cobbler CHANGELOG - Mon May 12 2008 - 0.9.2 - run createrepo with less preconditions during cobbler reposync - doc upgrades and error handling for "cobbler replicate" +- improved error message that occurs when copying from nfs w/ rootsquash - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/commands.py b/cobbler/commands.py index 657934c..705249d 100644 --- a/cobbler/commands.py +++ b/cobbler/commands.py @@ -295,11 +295,12 @@ class CobblerFunction: else: rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=False) else: - # editing or copying (but not renaming), so duplicate netinfo - # CAN be bad, duplicate names are already handled, though - # we need to clean up checks around duplicate netinfo here - # (FIXME) so they are made and work. - rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers) + check_dup = False + if not "copy" in self.args: + check_dup = True + # FIXME: this ensures duplicate prevention on copy, but not + # rename? + rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_netinfo=check_dup) else: # we are renaming here, so duplicate netinfo checks also diff --git a/cobbler/utils.py b/cobbler/utils.py index 1b4b30a..a9b374f 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -590,10 +590,13 @@ def copyfile(src,dst): try: return shutil.copyfile(src,dst) except: - if not os.path.samefile(src,dst): - # accomodate for the possibility that we already copied - # the file as a symlink/hardlink - raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) + try: + if not os.path.samefile(src,dst): + # accomodate for the possibility that we already copied + # the file as a symlink/hardlink + raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) + except: + raise CX(_("Problems reading %(src)s") % { "src" : src}) def rmfile(path): try: -- cgit From 044efca218e1a05206fa659bb6a15597cb074bd9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 12 May 2008 16:13:10 -0400 Subject: keep changelog up to date --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 729ec7f..f1d3164 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ Cobbler CHANGELOG - run createrepo with less preconditions during cobbler reposync - doc upgrades and error handling for "cobbler replicate" - improved error message that occurs when copying from nfs w/ rootsquash +- mac duplication checking improvements for CLI - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck -- cgit From 6cc83a05f80d7f3c703aa8743ccacab2aa2ac86d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 12 May 2008 17:20:22 -0400 Subject: Added code to cobbler check to look for httpd_can_network_connect boolean if SELinux is enabled. --- CHANGELOG | 1 + cobbler/action_check.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f1d3164..e28d6eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ Cobbler CHANGELOG - doc upgrades and error handling for "cobbler replicate" - improved error message that occurs when copying from nfs w/ rootsquash - mac duplication checking improvements for CLI +- add warning to cobbler check if selinux is on and Apache boolean not set - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 9fe0543..29b39be 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -36,6 +36,7 @@ class BootCheck: """ status = [] self.check_name(status) + self.check_selinux(status) if self.settings.manage_dhcp: mode = self.config.api.get_sync().dhcp.what() if mode == "isc": @@ -106,6 +107,19 @@ class BootCheck: if self.settings.next_server == "127.0.0.1": status.append(_("For PXE to be functional, the 'next_server' field in /var/lib/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network.")) + def check_selinux(self,status): + prc = sub_process.Popen("/usr/sbin/getenforce",shell=True,stdout=sub_process.PIPE) + data = prc.communicate()[0] + if data.lower().find("disabled") == -1: + # permissive or enforcing or something else + prc2 = sub_process.Popen("/usr/sbin/getsebool -a",shell=True,stdout=sub_process.PIPE) + data2 = prc2.communicate()[0] + for line in data2.split("\n"): + if line.find("httpd_can_network_connect ") != -1: + if line.find("off") != -1: + status.append(_("Must enable selinux boolean to enable Apache and web services components, run: setsebool -P httpd_can_network_connect true")) + + def check_httpd(self,status): """ Check if Apache is installed. -- cgit From 5daab278a734ed9679ef1e7aaa51a62e82292b85 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 May 2008 11:59:22 -0400 Subject: Added code to cobbler check to see if any templates are still using the default password, and if so, to warn about them. --- CHANGELOG | 1 + cobbler/action_check.py | 14 ++++++++++++++ cobbler/api.py | 3 +++ cobbler/remote.py | 12 +----------- cobbler/utils.py | 15 +++++++++++++++ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e28d6eb..500ff77 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ Cobbler CHANGELOG - improved error message that occurs when copying from nfs w/ rootsquash - mac duplication checking improvements for CLI - add warning to cobbler check if selinux is on and Apache boolean not set +- added warning to cobbler check if templates use the default password - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 29b39be..5691d60 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -65,6 +65,7 @@ class BootCheck: self.check_httpd(status) self.check_iptables(status) self.check_yum(status) + self.check_for_default_password(status) return status @@ -120,6 +121,19 @@ class BootCheck: status.append(_("Must enable selinux boolean to enable Apache and web services components, run: setsebool -P httpd_can_network_connect true")) + def check_for_default_password(self,status): + templates = utils.get_kickstart_templates(self.config.api) + files = [] + for t in templates: + fd = open(t) + data = fd.read() + fd.close() + if data.find("\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac."): + files.append(t) + if len(files) > 0: + status.append(_("One or more kickstart templates references default password 'cobbler' and should be changed for security reasons: %s") % ", ".join(files)) + + def check_httpd(self,status): """ Check if Apache is installed. diff --git a/cobbler/api.py b/cobbler/api.py index 1b73acd..a2aa881 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -442,3 +442,6 @@ class BootAPI: replicator = action_replicate.Replicate(self._config) return replicator.run(cobbler_master = cobbler_master) + def get_kickstart_templates(self): + return utils.get_kickstar_templates(self) + diff --git a/cobbler/remote.py b/cobbler/remote.py index 87695d2..8cf9ba3 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -170,17 +170,7 @@ class CobblerXMLRPCInterface: """ self.log("get_kickstart_templates",token=token) self.check_access(token, "get_kickstart_templates") - files = {} - for x in self.api.profiles(): - if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": - files[x.kickstart] = 1 - for x in self.api.systems(): - if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": - files[x.kickstart] = 1 - for x in glob.glob("/var/lib/cobbler/kickstarts/*"): - files[x] = 1 - - return files.keys() + return utils.get_kickstart_templates(self.api) def is_kickstart_in_use(self,ks,token): self.log("is_kickstart_in_use",token=token) diff --git a/cobbler/utils.py b/cobbler/utils.py index a9b374f..112d94b 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -778,6 +778,21 @@ def set_virt_cpus(self,num): self.virt_cpus = num return True +def get_kickstart_templates(api): + files = {} + for x in api.profiles(): + if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": + files[x.kickstart] = 1 + for x in api.systems(): + if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": + files[x.kickstart] = 1 + for x in glob.glob("/var/lib/cobbler/kickstarts/*"): + files[x] = 1 + for x in glob.glob("/etc/cobbler/*.ks"): + files[x] = 1 + + return files.keys() + if __name__ == "__main__": -- cgit From 58d126f8e2430d2d47e6e204f7866b9ee60426bf Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 May 2008 12:00:14 -0400 Subject: Fix find test. --- cobbler/action_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 5691d60..044b56b 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -128,7 +128,7 @@ class BootCheck: fd = open(t) data = fd.read() fd.close() - if data.find("\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac."): + if data.find("\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.") != -1: files.append(t) if len(files) > 0: status.append(_("One or more kickstart templates references default password 'cobbler' and should be changed for security reasons: %s") % ", ".join(files)) -- cgit From 045957209b62b2ab40a1bcdee0549ea54074bc48 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 May 2008 12:21:29 -0400 Subject: Allows cobbler system kickstart to be reset by setting them to "", "delete", or None (the last via the API). This allows the system to use the profile's kickstart setting as expected. --- CHANGELOG | 1 + cobbler/item_system.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 500ff77..48fe825 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ Cobbler CHANGELOG - mac duplication checking improvements for CLI - add warning to cobbler check if selinux is on and Apache boolean not set - added warning to cobbler check if templates use the default password +- setting per-system kickstart template to "" or "delete" restores inheritance - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/item_system.py b/cobbler/item_system.py index e7d6ed6..94ec392 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -352,6 +352,9 @@ class System(item.Item): abstraction layer -- assigning systems to defined and repeatable roles. """ + if kickstart is None or kickstart == "" or kickstart == "delete": + self.kickstart = "<>" + return True if utils.find_kickstart(kickstart): self.kickstart = kickstart return True -- cgit From 480798fdccd1d0bb5c4b160333f0933dc463053e Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 May 2008 15:45:13 -0400 Subject: Consolidate various repo related warnings under cobbler check and clean up prints elsewhere. --- CHANGELOG | 2 ++ cobbler/action_check.py | 29 +++++++++++++++++++++++++++++ cobbler/action_reposync.py | 2 +- cobbler/item_profile.py | 11 +++++++---- cobbler/serializer.py | 4 ---- cobbler/utils.py | 18 +++++++++++------- cobbler/yumgen.py | 4 +++- 7 files changed, 53 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 48fe825..1992994 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Cobbler CHANGELOG - add warning to cobbler check if selinux is on and Apache boolean not set - added warning to cobbler check if templates use the default password - setting per-system kickstart template to "" or "delete" restores inheritance +- if repos in profiles no longer exist, remove noisy warning, move to "check" +- move warning about reposync to check also (check is more useful at runtime now) - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 044b56b..f5f86f0 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -66,6 +66,8 @@ class BootCheck: self.check_iptables(status) self.check_yum(status) self.check_for_default_password(status) + self.check_for_unreferenced_repos(status) + self.check_for_unsynced_repos(status) return status @@ -134,6 +136,33 @@ class BootCheck: status.append(_("One or more kickstart templates references default password 'cobbler' and should be changed for security reasons: %s") % ", ".join(files)) + def check_for_unreferenced_repos(self,status): + repos = [] + referenced = [] + not_found = [] + for r in self.config.api.repos(): + repos.append(r.name) + for p in self.config.api.profiles(): + my_repos = p.repos + referenced.extend(my_repos) + for r in referenced: + if r not in repos: + not_found.append(r) + if len(not_found) > 0: + status.append(_("One or more repos referenced by profile objects is no longer defined in cobbler: %s") % ", ".join(not_found)) + + def check_for_unsynced_repos(self,status): + need_sync = [] + for r in self.config.repos(): + if r.mirror_locally == 1: + lookfor = os.path.join(self.settings.webdir, "repo_mirror", r.name) + print "DEBUG: looking for: %s" % lookfor + if not os.path.exists(lookfor): + need_sync.append(r.name) + if len(need_sync) > 0: + status.append(_("One or more repos need to be processed by cobbler reposync for the first time before kickstarting against them: %s") % ", ".join(need_sync)) + + def check_httpd(self,status): """ Check if Apache is installed. diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index 495aa3a..57c2d08 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -314,7 +314,7 @@ class RepoSync: if os.path.exists(getenforce): data = sub_process.Popen(getenforce, shell=True, stdout=sub_process.PIPE).communicate()[0] if data.lower().find("disabled") == -1: - cmd3 = "chcon --reference /var/www %s" % repo_path + cmd3 = "chcon --reference /var/www %s >/dev/null 2>/dev/null" % repo_path sub_process.call(cmd3, shell=True) diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index c5c9bdb..0e16f46 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -70,7 +70,10 @@ class Profile(item.Item): # backwards compatibility if type(self.repos) != list: - self.set_repos(self.repos) + # ensure we are formatted correctly though if some repo + # defs don't exist on this side, don't fail as we need + # to convert everything -- cobbler check can report it + self.set_repos(self.repos,bypass_check=True) self.set_parent(self.parent) # virt specific @@ -87,7 +90,7 @@ class Profile(item.Item): if self.ks_meta != "<>" and type(self.ks_meta) != dict: self.set_ksmeta(self.ks_meta) if self.repos != "<>" and type(self.ks_meta) != list: - self.set_repos(self.repos) + self.set_repos(self.repos,bypass_check=True) self.set_owners(self.owners) @@ -171,8 +174,8 @@ class Profile(item.Item): def set_virt_path(self,path): return utils.set_virt_path(self,path) - def set_repos(self,repos): - return utils.set_repos(self,repos) + def set_repos(self,repos,bypass_check=False): + return utils.set_repos(self,repos,bypass_check) def get_parent(self): diff --git a/cobbler/serializer.py b/cobbler/serializer.py index 25d0d60..ac98412 100644 --- a/cobbler/serializer.py +++ b/cobbler/serializer.py @@ -59,10 +59,8 @@ def serialize_item(collection, item): storage_module = __get_storage_module(collection.collection_type()) save_fn = getattr(storage_module, "serialize_item", None) if save_fn is None: - # print "DEBUG: WARNING: full serializer" rc = storage_module.serialize(collection) else: - # print "DEBUG: partial serializer" rc = save_fn(collection,item) __release_lock() return rc @@ -75,10 +73,8 @@ def serialize_delete(collection, item): storage_module = __get_storage_module(collection.collection_type()) delete_fn = getattr(storage_module, "serialize_delete", None) if delete_fn is None: - # print "DEBUG: full delete" rc = storage_module.serialize(collection) else: - # print "DEBUG: partial delete" rc = delete_fn(collection,item) __release_lock() return rc diff --git a/cobbler/utils.py b/cobbler/utils.py index 112d94b..c62ec6c 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -634,7 +634,7 @@ def mkdir(path,mode=0777): print oe.errno raise CX(_("Error creating") % path) -def set_repos(self,repos): +def set_repos(self,repos,bypass_check=False): # WARNING: hack repos = fix_mod_python_select_submission(repos) @@ -653,7 +653,6 @@ def set_repos(self,repos): else: repolist = repos - # make sure there are no empty strings try: repolist.remove('') @@ -662,13 +661,18 @@ def set_repos(self,repos): self.repos = [] - # if any repos don't exist, fail the operation + # if any repos don't exist, fail the set operation + # unless called from the deserializer stage in which + # case we have a soft error that check can report ok = True for r in repolist: - if self.config.repos().find(name=r) is not None: - self.repos.append(r) - else: - print _("warning: repository not found: %s" % r) + if bypass_check: + self.repos.append(r) + else: + if self.config.repos().find(name=r) is not None: + self.repos.append(r) + else: + raise CX(_("repo %s is not defined") % r) return True diff --git a/cobbler/yumgen.py b/cobbler/yumgen.py index e39a72e..8c10be3 100644 --- a/cobbler/yumgen.py +++ b/cobbler/yumgen.py @@ -98,7 +98,9 @@ class YumGen: try: infile_h = open(infile) except: - print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname + # file does not exist and the user needs to run reposync + # before we will use this, cobbler check will mention + # this problem continue infile_data = infile_h.read() infile_h.close() -- cgit From 0f60b1a19ef32c63c591905b9640ebb60684f442 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 May 2008 16:04:34 -0400 Subject: If for some reason the user has defined a system where the first interface record is blank and the second is not, still build the PXE tree based on the second record. --- CHANGELOG | 1 + cobbler/item_system.py | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1992994..079a790 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ Cobbler CHANGELOG - setting per-system kickstart template to "" or "delete" restores inheritance - if repos in profiles no longer exist, remove noisy warning, move to "check" - move warning about reposync to check also (check is more useful at runtime now) +- build pxe trees for systems even if interface0 is undefined - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 94ec392..2318ced 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -229,11 +229,12 @@ class System(item.Item): """ if self.name == "default": return True - mac = self.get_mac_address(interface) - ip = self.get_ip_address(interface) - if mac is None and ip is None: - return False - return True + for (name,x) in self.interfaces.iteritems(): + mac = x.get("mac_address",None) + ip = x.get("ip_address",None) + if mac is not None or ip is not None: + return True + return False def set_dhcp_tag(self,dhcp_tag,interface="intf0"): intf = self.__get_interface(interface) -- cgit From b7217f4730ed54abaec95855a682c5c9f3b73870 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 May 2008 17:18:16 -0400 Subject: Change the way error checking works around NFS read errors (root squash) --- CHANGELOG | 1 + cobbler/remote.py | 10 ++++++++++ cobbler/utils.py | 13 ++++++------- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 079a790..32ff3e0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ Cobbler CHANGELOG - if repos in profiles no longer exist, remove noisy warning, move to "check" - move warning about reposync to check also (check is more useful at runtime now) - build pxe trees for systems even if interface0 is undefined +- add sync() back into XMLRPC API, missing in 0.9.1 - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/remote.py b/cobbler/remote.py index 8cf9ba3..69754a3 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -747,6 +747,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): return self.object_cache[object_id][0] raise CX(_("No such object for ID: %s") % object_id) + def sync(self,token): + """ + Run sync code, which should complete before XMLRPC timeout. We can't + do reposync this way. Would be nice to send output over AJAX/other + later. + """ + self.log("sync",token=token) + self.check_access(token,"sync") + return self.api.sync() + def new_distro(self,token): """ Creates a new (unconfigured) distro object. It works something like diff --git a/cobbler/utils.py b/cobbler/utils.py index c62ec6c..6e756c9 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -590,13 +590,12 @@ def copyfile(src,dst): try: return shutil.copyfile(src,dst) except: - try: - if not os.path.samefile(src,dst): - # accomodate for the possibility that we already copied - # the file as a symlink/hardlink - raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) - except: - raise CX(_("Problems reading %(src)s") % { "src" : src}) + if not os.access(src,os.R_OK): + raise CX(_("Cannot read: %s") % src) + if not os.path.samefile(src,dst): + # accomodate for the possibility that we already copied + # the file as a symlink/hardlink + raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) def rmfile(path): try: -- cgit From da5afb37f3d5f8ce4e4ad0508206eb68c8db25c9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 14 May 2008 15:52:03 -0400 Subject: Since it's common to want to reference the name of the profile in a template, and that changes whether the rendering is a per-profile or per-system kickstart, I've added three new variables to make things easier: "distro_name", "profile_name", and "system_name" which show up in the templates automatically in addition to the existing "distro", "profile", and "name" you get for a system, or "distro" and "name" for profile. This is more consistant and easier to use in the templating language. --- CHANGELOG | 1 + cobbler/pxegen.py | 1 - cobbler/utils.py | 13 +++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 32ff3e0..c34ffff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ Cobbler CHANGELOG - move warning about reposync to check also (check is more useful at runtime now) - build pxe trees for systems even if interface0 is undefined - add sync() back into XMLRPC API, missing in 0.9.1 +- added 'distro_name', 'profile_name', and 'system_name' to generated template vars - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index 639c30b..d847569 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -84,7 +84,6 @@ class PXEGen: """ # copy is a 4-letter word but tftpboot runs chroot, thus it's required. for d in self.distros: - print _("sync distro: %s") % d.name self.copy_single_distro_files(d) def copy_single_distro_files(self, d): diff --git a/cobbler/utils.py b/cobbler/utils.py index 6e756c9..a63f7af 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -370,6 +370,19 @@ def blender(api_handle,remove_hashes, root_obj): # sanitize output for koan and kernel option lines, etc if remove_hashes: results = flatten(results) + + # add in some variables for easier templating + # as these variables change based on object type + if results.has_key("interfaces"): + results["system_name"] = results["name"] + results["profile_name"] = results["profile"] + results["distro_name"] = results["distro"] + elif results.has_key("distro"): + results["profile_name"] = results["name"] + results["distro_name"] = results["distro"] + elif results.has_key("kernel"): + results["distro_name"] = results["name"] + return results def flatten(data): -- cgit From 9d5b494aaac2d2b0be4b598d6049eb869ef4b49c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 14 May 2008 15:58:44 -0400 Subject: Make template finder code (used by check and webapp) ignore kickstarts that are not on the filesystem and are therefore real kickstarts or URLs that generate them, not actual templates. --- cobbler/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cobbler/utils.py b/cobbler/utils.py index a63f7af..007b96c 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -798,10 +798,12 @@ def get_kickstart_templates(api): files = {} for x in api.profiles(): if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": - files[x.kickstart] = 1 + if os.path.exists(x.kickstart): + files[x.kickstart] = 1 for x in api.systems(): if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<>": - files[x.kickstart] = 1 + if os.path.exists(x.kickstart): + files[x.kickstart] = 1 for x in glob.glob("/var/lib/cobbler/kickstarts/*"): files[x] = 1 for x in glob.glob("/etc/cobbler/*.ks"): -- cgit From 7b6922a1da2654874d14ba97340e95ab88eff202 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 14 May 2008 16:15:21 -0400 Subject: Added the ability to undefine a symbol that is defined in a parent object. This means that if a kernel option is set for a profile, and you want it unset in a child system, you can say --kopts='!foo' to undefine the foo. Similarly you can add symbols and undefine others at the same time... --kopts='bar !foo baz=3' and so on. This is relatively fringe usage but was previously not possible. --- CHANGELOG | 1 + cobbler/utils.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c34ffff..c291335 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ Cobbler CHANGELOG - build pxe trees for systems even if interface0 is undefined - add sync() back into XMLRPC API, missing in 0.9.1 - added 'distro_name', 'profile_name', and 'system_name' to generated template vars +- it's now possible to undefine a --ksmeta or kopts symbol defined in a parent with "!foo" - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/utils.py b/cobbler/utils.py index 007b96c..455a7e3 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -446,6 +446,24 @@ def __consolidate(node,results): else: results[field] = data_item + # now if we have any "!foo" results in the list, delete corresponding + # key entry "foo", and also the entry "!foo", allowing for removal + # of kernel options set in a distro later in a profile, etc. + + hash_removals(results,"kernel_options") + hash_removals(results,"ks_meta") + +def hash_removals(results,subkey): + if not results.has_key(subkey): + return + scan = results[subkey].keys() + for k in scan: + if k.startswith("!") and k != "!": + remove_me = k[1:] + if results[subkey].has_key(remove_me): + del results[subkey][remove_me] + del results[subkey][k] + def hash_to_string(hash): """ Convert a hash to a printable string. -- cgit From 57828b2e7ad347e7ffe76c58f210e6b5b0dedeca Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 15 May 2008 14:36:30 -0400 Subject: Checked in architecture diagram source, run "make graphviz" to build it. --- Makefile | 4 ++ docs/cobbler.dot | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 docs/cobbler.dot diff --git a/Makefile b/Makefile index af01e9f..3407721 100644 --- a/Makefile +++ b/Makefile @@ -100,3 +100,7 @@ eraseconfig: -rm /var/lib/cobbler/profiles* -rm /var/lib/cobbler/systems* -rm /var/lib/cobbler/repos* + +graphviz: + dot -Tpdf docs/cobbler.dot -o cobbler.pdf + diff --git a/docs/cobbler.dot b/docs/cobbler.dot new file mode 100644 index 0000000..e4eb2fb --- /dev/null +++ b/docs/cobbler.dot @@ -0,0 +1,118 @@ +graph arch { + + +webui -- apache + +cobblerd +mgmt [label="mgmt/sync"] +// yum stuff + +node [color="brown"] + +api +misc -- replication +misc -- buildiso +i_dvd [label="DVD"] +i_other [label="rsync/ssh/filesystem"] +misc -- import +import -- i_dvd +import -- i_other +misc -- check + +api -- mgmt +api -- misc + +api -- yum_mirroring +api -- triggers +y_rsync [label="rsync/ssh/local"] +y_http_ftp [label="http/ftp"] +yum_mirroring -- y_rsync +yum_mirroring -- y_http_ftp + +node [color="black"] + +cli -- api +cli +cobblerd -- api + +node [color="red"] + +// triggers stuff +triggers_add [label="add/remove"] +triggers_sync [label="sync"] +triggers_install [label="install"] +triggers -- triggers_add +triggers -- triggers_sync +triggers -- triggers_install + +// mgmt stuff + +node [color="grey"] + +dns +dhcp +mgmt -- tftpboot +mgmt -- dns +mgmt -- dhcp +dns -- bind +dns -- dnsmasq +dhcp -- isc +dhcp -- dnsmasq +yumconfigs +mgmt -- yumconfigs +tftpboot -- templating +isc -- templating +dnsmasq -- templating +bind -- templating +yumconfigs -- templating +templating -- snippets + +node [color="blue"] + +api -- configs +configs -- settings +configs -- objects +objects -- distros +objects -- systems +objects -- profiles +objects -- repos +configs -- modules_conf +configs -- cobbler_conf +configs -- services_conf +apache -- cobbler_conf +apache -- services_conf + + +node [color="green"] + +cobblerd -- security +cobblerd -- webui [label="xmlrpc"] +cobblerd -- avahi +cobblerd -- mod_python [label="xmlrpc"] +mod_python -- services [label="http"] +services -- kickgen +kickgen -- templating +services -- registration +services -- triggers + +services -- apache + +security -- authn +security -- authz + +node [color="orange"] + +cobblerd -- koan [label="xmlrpc"] +koan -- avahi +koan -- replaceself +replaceself -- livecd +koan -- virt +koan -- apache [label="http"] +xen [label="xen fv/pv"] +virt -- xen +qemu_kvm [label="qemu/KVM"] +virt -- qemu_kvm +virt -- vmware + + +} -- cgit From eb9fd952a3ea964d4e1aa1b0f7e6a0a208d12b40 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 15 May 2008 15:01:37 -0400 Subject: Add extra logging if an error is encountered while rendering a kickstart --- CHANGELOG | 1 + cobbler/api.py | 12 +----------- cobbler/kickgen.py | 3 +-- cobbler/utils.py | 13 +++++++++++++ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c291335..72bba8a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ Cobbler CHANGELOG - add sync() back into XMLRPC API, missing in 0.9.1 - added 'distro_name', 'profile_name', and 'system_name' to generated template vars - it's now possible to undefine a --ksmeta or kopts symbol defined in a parent with "!foo" +- log errors while rendering kickstarts - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/cobbler/api.py b/cobbler/api.py index a2aa881..cafc216 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -86,17 +86,7 @@ class BootAPI: self.logger.debug("API handle initialized") def __setup_logger(self,name): - logger = logging.getLogger(name) - logger.setLevel(logging.INFO) - try: - ch = logging.FileHandler("/var/log/cobbler/cobbler.log") - except: - raise CX(_("No write permissions on log file. Are you root?")) - ch.setLevel(logging.INFO) - formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s") - ch.setFormatter(formatter) - logger.addHandler(ch) - return logger + return utils.setup_logger(name) def log(self,msg,args=None,debug=False): if debug: diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index ba8bfd2..9f0235d 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -81,8 +81,7 @@ class KickGen: kfile.close() return data except: - traceback.print_exc() # leave this in, for now... - msg = "err_kickstart2" + utils.log_exc(self.api.logger) raise CX(_("Error while rendering kickstart file")) def generate_kickstart_signal(self, is_pre=0, profile=None, system=None): diff --git a/cobbler/utils.py b/cobbler/utils.py index 455a7e3..de00150 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -23,6 +23,7 @@ import shutil import string import traceback import errno +import logging from cexceptions import * #placeholder for translation @@ -37,6 +38,18 @@ MODULE_CACHE = {} _re_kernel = re.compile(r'vmlinuz(.*)') _re_initrd = re.compile(r'initrd(.*).img') +def setup_logger(name): + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + try: + ch = logging.FileHandler("/var/log/cobbler/cobbler.log") + except: + raise CX(_("No write permissions on log file. Are you root?")) + ch.setLevel(logging.INFO) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s") + ch.setFormatter(formatter) + logger.addHandler(ch) + return logger def log_exc(logger): """ -- cgit From 825db5f84fc039330fdb09b7975a6b4e6bbc8b2e Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 15 May 2008 15:08:20 -0400 Subject: Cleaner error messages on kickstart rendering problems. --- cobbler/kickgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py index 9f0235d..f302d00 100644 --- a/cobbler/kickgen.py +++ b/cobbler/kickgen.py @@ -82,7 +82,7 @@ class KickGen: return data except: utils.log_exc(self.api.logger) - raise CX(_("Error while rendering kickstart file")) + return _("# Error while rendering kickstart file, see /var/log/cobbler/cobbler.log for details") def generate_kickstart_signal(self, is_pre=0, profile=None, system=None): """ -- cgit From 17f0ac054ef5bf160909095aaf119931edd5c39c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 16 May 2008 13:51:49 -0400 Subject: Found out our config file supports comments (hooray) and added a bunch explaining what all of the settings do. Should have done this long ago but glad to have it! --- CHANGELOG | 1 + config/settings | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 72bba8a..8e01199 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ Cobbler CHANGELOG - added 'distro_name', 'profile_name', and 'system_name' to generated template vars - it's now possible to undefine a --ksmeta or kopts symbol defined in a parent with "!foo" - log errors while rendering kickstarts +- comments added to the config file, neat! - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/config/settings b/config/settings index 2f6b08d..b1f7bd7 100644 --- a/config/settings +++ b/config/settings @@ -1,27 +1,72 @@ --- +# cobbler settings file +# run "cobbler sync" after making changes +# (it's a good idea to make backups too) +# +# if 1, cobbler will allow insertions of system records that duplicate +# the mac address information of other system records. In general, +# this is undesirable. allow_duplicate_macs: 0 + +# if 1, cobbler will allow insertions of system records that duplicate +# the ip address information of other system records. In general, +# this is undesirable. allow_duplicate_ips: 0 + +# the path to BIND's executable for this distribution. bind_bin: /usr/sbin/named + +# where to find various bootloaders on the filesystem bootloaders: ia64: /var/lib/cobbler/elilo-3.6-ia64.efi standard: /usr/lib/syslinux/pxelinux.0 + +# if no kickstart is specified, use this template (FIXME) default_kickstart: /etc/cobbler/default.ks + +# for libvirt based installs in koan, if no virt bridge +# is specified, which bridge do we try? default_virt_bridge: xenbr0 -default_virt_type: auto + +# if koan is invoked without --virt-type and no virt-type +# is set on the profile/system, what virtualization type +# should be assumed? Values: xenpv, xenfv, qemu, vmware +default_virt_type: xenpv + +# use this as the default disk size for virt guests (GB) default_virt_file_size: 5 + +# use this as the default memory size for virt guests (MB) default_virt_ram: 512 + +# if using the authz_ownership module (see the Wiki), objects +# created without specifying an owner are assigned to this +# owner and/or group. Can be a comma seperated list. default_ownership: "admin" + +# location for some important binaries and config files +# that can vary based on the distribution. dhcpd_bin: /usr/sbin/dhcpd dhcpd_conf: /etc/dhcpd.conf dnsmasq_bin: /usr/sbin/dnsmasq dnsmasq_conf: /etc/dnsmasq.conf httpd_bin: /usr/sbin/httpd + +# change this port if Apache is not running plaintext on port +# 80. Most people can leave this alone. http_port: 80 -kerberos_realm: 'example.org' + +# kernel options that should be present in every cobbler installation. +# kernel options can also be applied at the distro/profile/system +# level. kernel_options: ksdevice: eth0 lang: ' ' text: ~ + +# configuration options if using the authn_ldap module. See the +# the Wiki for details. This can be ignored if you are not using +# LDAP for WebUI/XMLRPC authentication. ldap_server: "ldap.example.com" ldap_base_dn: "DC=example,DC=com" ldap_port: 389 @@ -30,23 +75,112 @@ ldap_anonymous_bind: 1 ldap_search_bind_dn: '' ldap_search_passwd: '' ldap_search_prefix: 'uid=' + +# set to 1 to enable Cobbler's DHCP management features. +# the choice of DHCP management engine is in /etc/cobbler/modules.conf manage_dhcp: 0 + +# set to 1 to enable Cobbler's DNS management features. +# the choice of DNS mangement engine is in /etc/cobbler/modules.conf manage_dns: 0 + +# if using cobbler with manage_dhcp, put the IP address +# of the cobbler server here so that PXE booting guests can find it next_server: '127.0.0.1' + +# if using cobbler with manage_dhcp and ISC, omapi allows realtime DHCP +# updates without restarting ISC dhcpd. omapi_enabled: 1 omapi_port: 647 omshell_bin: /usr/bin/omshell + +# if this setting is set to 1, cobbler systems that pxe boot +# will request at the end of their installation to toggle the +# --netboot-enabled record in the cobbler system record. This eliminates +# the potential for a PXE boot loop if the system is set to PXE +# first in it's BIOS order. Enable this if PXE is first in your BIOS +# boot order, otherwise leave this disabled. See the manpage +# for --netboot-enabled. pxe_just_once: 0 + +# if set to 1, new systems doing profile based installations will +# contact cobbler to have system records created for them containing +# the mac address information that they have requested for install. +# this effectively allows for registration of new hardware via PXE +# without having to manually enter in all of the mac addresses for +# every machine on your network register_new_installs: 0 + +# install triggers are scripts in /var/lib/cobbler/triggers/install +# that are triggered in kickstart pre and post sections. Any +# executable script in those directories is run. They can be used +# to send email or perform other actions. They are currently +# run as root so if you do not need this functionality you can +# disable it, though this will also disable "cobbler status" which +# uses a logging trigger to audit install progress. run_install_triggers: 1 + +# this is the address of the cobbler server -- as it is used +# by systems during the install process, it must be the address +# or hostname of the system as those systems can see the server. +# if you have a server that appears differently to different subnets +# (dual homed, etc), you need to read the --server-override section +# of the manpage for how that works. server: '127.0.0.1' + +# this is a directory of files that cobbler uses to make +# templating easier. See the Wiki for more information. Changing +# this directory should not be required. snippetsdir: /var/lib/cobbler/snippets + +# by default, installs are set to send syslog traffic on this port +# and cobblerd will listen on this port. syslog data (for installs +# that support it... RHEL 5 and later, etc) is logged in /var/log/cobbler +# and can be used to help debug problematic installations. Syslog +# is UDP and may not be available depending on network/firewall configuration. syslog_port: 25150 + +# locations of the TFTP binary and config file tftpd_bin: /usr/sbin/in.tftpd tftpd_conf: /etc/xinetd.d/tftp + +# cobbler's web directory. Don't change this setting -- see the +# Wiki on "relocating your cobbler install" if your /var partition +# is not large enough. webdir: /var/www/cobbler + +# cobbler's public XMLRPC listens on this port. Change this only +# if absolutely needed, as you'll have to start supplying a new +# port option to koan if it is not the default. xmlrpc_port: 25151 + +# cobbler's read write XMLRPC is the version of XMLRPC +# used by the WebUI and some features like system registration. +# XMLRPC connections here require login information to access. +# this feature can be disabled to gain increased security but +# will disable the WebUI, registration, and potentially other +# cobbler features. Most users should leave XMLRPC RW +# enabled. The port can be relocated if needed. xmlrpc_rw_enabled: 1 xmlrpc_rw_port: 25152 + +# "cobbler repo add" commands set cobbler up with repository +# information that can be used during kickstart and is automatically +# set up in the cobbler kickstart templates. By default, these +# are only available at install time. To make these repositories +# usable on installed systems (since cobbler makes a very convient) +# mirror, set this to 1. Most users can safely set this to 1. Users +# who have a dual homed cobbler server, or are installing laptops that +# will not always have access to the cobbler server may wish to leave +# this as 0. In that case, the cobbler mirrored yum repos are still +# accessable at http://cobbler.example.org/cblr/repo_mirror and yum +# configuration can still be done manually. This is just a shortcut. yum_post_install_mirror: 0 + +# "cobbler repo" support normally uses rsync or reposync. If --rpm-list +# is used, it's possible to download only a certain package list, plus +# dependencies, but --resolve and other flags are not supported in +# all versions of yumdownloader. This is a list of what flags +# to pass to it. Only change this if you are experiencing problems +# with "cobbler reposync" and are using --rpm-list. yumdownloader_flags: "--resolve" -- cgit From 90666a4a0a3c918a31f7cdfd9fa9fd2a6ad48705 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 16 May 2008 14:42:37 -0400 Subject: The settings file is now /etc/cobbler/settings, and cobbler's command line will warn the user the old file is no longer in use and ask them to delete it before proceeding. --- CHANGELOG | 1 + Makefile | 16 ++++++++-------- cobbler.spec | 5 +++-- cobbler/action_check.py | 10 +++++----- cobbler/api.py | 2 +- cobbler/cobbler.py | 11 +++++++++++ cobbler/item_system.py | 2 +- cobbler/pxegen.py | 2 +- cobbler/remote.py | 2 +- cobbler/settings.py | 8 -------- setup.py | 2 +- 11 files changed, 33 insertions(+), 28 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8e01199..d43d434 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ Cobbler CHANGELOG - it's now possible to undefine a --ksmeta or kopts symbol defined in a parent with "!foo" - log errors while rendering kickstarts - comments added to the config file, neat! +- settings file is now /etc/cobbler/settings - Fri May 09 2008 - 0.9.1 - patch to allow yumopts to override gpgcheck diff --git a/Makefile b/Makefile index 3407721..d7fd78c 100644 --- a/Makefile +++ b/Makefile @@ -36,17 +36,17 @@ install: clean manpage python setup.py install -f devinstall: - cp /var/lib/cobbler/settings /tmp/cobbler_settings - cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf - cp /etc/httpd/conf.d/cobbler.conf /tmp/cobbler_http.conf - cp /etc/cobbler/users.conf /tmp/cobbler_users.conf + -cp /etc/cobbler/settings /tmp/cobbler_settings + -cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf + -cp /etc/httpd/conf.d/cobbler.conf /tmp/cobbler_http.conf + -cp /etc/cobbler/users.conf /tmp/cobbler_users.conf -cp /etc/cobbler/users.digest /tmp/cobbler_users.digest make install - cp /tmp/cobbler_settings /var/lib/cobbler/settings - cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf - cp /tmp/cobbler_users.conf /etc/cobbler/users.conf + -cp /tmp/cobbler_settings /etc/cobbler/settings + -cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf + -cp /tmp/cobbler_users.conf /etc/cobbler/users.conf -cp /tmp/cobbler_users.digest /etc/cobbler/users.digest - cp /tmp/cobbler_http.conf /etc/httpd/conf.d/cobbler.conf + -cp /tmp/cobbler_http.conf /etc/httpd/conf.d/cobbler.conf find /var/lib/cobbler/triggers | xargs chmod +x chown -R apache /var/www/cobbler chown -R apache /var/www/cgi-bin/cobbler diff --git a/cobbler.spec b/cobbler.spec index 51ea3aa..c4284f3 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -169,7 +169,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /var/lib/cobbler/triggers/install/post/status_post.trigger %defattr(664,root,root) -%config(noreplace) /var/lib/cobbler/settings +%config(noreplace) /etc/cobbler/settings %config(noreplace) /var/lib/cobbler/snippets/partition_select /var/lib/cobbler/elilo-3.6-ia64.efi /var/lib/cobbler/menu.c32 @@ -188,8 +188,9 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -* Mon May 12 2008 Michael DeHaan - 0.9.2-1 +* Fri May 16 2008 Michael DeHaan - 0.9.2-1 - Upstream changes (see CHANGELOG) +- moved /var/lib/cobbler/settings to /etc/cobbler/settings * Fri May 09 2008 Michael DeHaan - 0.9.1-1 - Upstream changes (see CHANGELOG) diff --git a/cobbler/action_check.py b/cobbler/action_check.py index f5f86f0..d38603b 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -106,9 +106,9 @@ class BootCheck: parameters. """ if self.settings.server == "127.0.0.1": - status.append(_("The 'server' field in /var/lib/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it.")) + status.append(_("The 'server' field in /etc/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it.")) if self.settings.next_server == "127.0.0.1": - status.append(_("For PXE to be functional, the 'next_server' field in /var/lib/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network.")) + status.append(_("For PXE to be functional, the 'next_server' field in /etc/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network.")) def check_selinux(self,status): prc = sub_process.Popen("/usr/sbin/getenforce",shell=True,stdout=sub_process.PIPE) @@ -175,21 +175,21 @@ class BootCheck: Check if dhcpd is installed """ if not os.path.exists(self.settings.dhcpd_bin): - status.append(_("dhcpd isn't installed, but management is enabled in /var/lib/cobbler/settings")) + status.append(_("dhcpd isn't installed, but management is enabled in /etc/cobbler/settings")) def check_dnsmasq_bin(self,status): """ Check if dnsmasq is installed """ if not os.path.exists(self.settings.dnsmasq_bin): - status.append(_("dnsmasq isn't installed, but management is enabled in /var/lib/cobbler/settings")) + status.append(_("dnsmasq isn't installed, but management is enabled in /etc/cobbler/settings")) def check_bind_bin(self,status): """ Check if bind is installed. """ if not os.path.exists(self.settings.bind_bin): - status.append(_("bind isn't installed, but management is enabled in /var/lib/cobbler/settings")) + status.append(_("bind isn't installed, but management is enabled in /etc/cobbler/settings")) def check_bootloaders(self,status): diff --git a/cobbler/api.py b/cobbler/api.py index cafc216..9f4a636 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -41,7 +41,7 @@ DEBUG = 5 # notes on locking: # BootAPI is a singleton object # the XMLRPC variants allow 1 simultaneous request -# therefore we flock on /var/lib/cobbler/settings for now +# therefore we flock on /etc/cobbler/settings for now # on a request by request basis. class BootAPI: diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 970a06a..2aed672 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -45,12 +45,23 @@ class BootCLI: #################################################### +def run_upgrade_checks(): + """ + Cobbler tries to make manual upgrade steps unneeded, though + this function serves to inform users of manual steps when they /are/ + needed. + """ + # for users running pre-1.0 upgrading to 1.0 + if os.path.exists("/var/lib/cobbler/settings"): + raise CX(_("/var/lib/cobbler/settings is no longer in use, remove this file to acknowledge you have migrated your configuration to /etc/cobbler/settings. Do not simply copy the file over or you will lose new configuration entries. Run 'cobbler check' and then 'cobbler sync' after making changes.")) + def main(): """ CLI entry point """ exitcode = 0 try: + run_upgrade_checks() return BootCLI().run(sys.argv) except SystemExit, ex: return 1 diff --git a/cobbler/item_system.py b/cobbler/item_system.py index 2318ced..09f169d 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -249,7 +249,7 @@ class System(item.Item): def set_ip_address(self,address,interface="intf0"): """ Assign a IP or hostname in DHCP when this MAC boots. - Only works if manage_dhcp is set in /var/lib/cobbler/settings + Only works if manage_dhcp is set in /etc/cobbler/settings """ intf = self.__get_interface(interface) if address == "" or utils.is_ip(address): diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index d847569..a438583 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -58,7 +58,7 @@ class PXEGen: """ Copy bootloaders to the configured tftpboot directory NOTE: we support different arch's if defined in - /var/lib/cobbler/settings. + /etc/cobbler/settings. """ for loader in self.settings.bootloaders.keys(): path = self.settings.bootloaders[loader] diff --git a/cobbler/remote.py b/cobbler/remote.py index 69754a3..1a414f3 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -193,7 +193,7 @@ class CobblerXMLRPCInterface: def get_settings(self,token=None): """ - Return the contents of /var/lib/cobbler/settings, which is a hash. + Return the contents of /etc/cobbler/settings, which is a hash. """ self.log("get_settings",token=token) return self.__get_all("settings") diff --git a/cobbler/settings.py b/cobbler/settings.py index d147d4b..4670ab0 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -140,11 +140,3 @@ class Settings(serializable.Serializable): else: raise AttributeError, name -if __name__ == "__main__": - # used to save a settings file to /var/lib/cobbler/settings, for purposes of - # including a new updated settings file in the RPM without remembering how - # to format lots of YAML. - import yaml - print yaml.dump(DEFAULTS) - - diff --git a/setup.py b/setup.py index 7b11c78..c659f9a 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,7 @@ if __name__ == "__main__": (etcpath, ['config/rsync.exclude']), (etcpath, ['config/users.conf']), (initpath, ['config/cobblerd']), - (cobpath, ['config/settings']), + (etcpath, ['config/settings']), # backups for upgrades (backpath, []), -- cgit From 6f5ad0b0fe3d43f5cf5efaa465f8eff4c92eb642 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 16 May 2008 16:26:52 -0400 Subject: Fix to make serializer look in the right place, also removed a debug print. --- cobbler.spec | 4 ++-- cobbler/action_check.py | 1 - cobbler/modules/serializer_yaml.py | 5 ++++- website/codepush.sh | 3 --- 4 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 website/codepush.sh diff --git a/cobbler.spec b/cobbler.spec index c4284f3..523485c 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -3,7 +3,7 @@ Summary: Boot server configurator Name: cobbler AutoReq: no Version: 0.9.2 -Release: 1%{?dist} +Release: 2%{?dist} Source0: %{name}-%{version}.tar.gz License: GPLv2+ Group: Applications/System @@ -188,7 +188,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -* Fri May 16 2008 Michael DeHaan - 0.9.2-1 +* Fri May 16 2008 Michael DeHaan - 0.9.2-2 - Upstream changes (see CHANGELOG) - moved /var/lib/cobbler/settings to /etc/cobbler/settings diff --git a/cobbler/action_check.py b/cobbler/action_check.py index d38603b..74af0c6 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -156,7 +156,6 @@ class BootCheck: for r in self.config.repos(): if r.mirror_locally == 1: lookfor = os.path.join(self.settings.webdir, "repo_mirror", r.name) - print "DEBUG: looking for: %s" % lookfor if not os.path.exists(lookfor): need_sync.append(r.name) if len(need_sync) > 0: diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py index 724265d..c4bd359 100644 --- a/cobbler/modules/serializer_yaml.py +++ b/cobbler/modules/serializer_yaml.py @@ -66,7 +66,10 @@ def get_filename(collection_type): ending = collection_type if not ending.endswith("s"): ending = ending + "s" - return "/var/lib/cobbler/%s" % ending + if ending != "settings": + return "/var/lib/cobbler/%s" % ending + else: + return "/etc/cobbler/settings" def deserialize_raw(collection_type): filename = get_filename(collection_type) diff --git a/website/codepush.sh b/website/codepush.sh deleted file mode 100644 index e997938..0000000 --- a/website/codepush.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -scp ../rpm-build/*.src.rpm et.redhat.com:/var/www/sites/cobbler.et.redhat.com/download -scp ../rpm-build/*.tar.gz et.redhat.com:/var/www/sites/cobbler.et.redhat.com/download -- cgit From 3ee3e2052886af701e6f14b74d3463dbf9f9b069 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 27 May 2008 12:00:24 -0400 Subject: doc item --- docs/cobbler.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cobbler.pod b/docs/cobbler.pod index ce5cb0d..da3e58e 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -48,7 +48,7 @@ in network booting via PXE and just want to use koan to install virtual systems =head2 ADDING A DISTRIBUTION -This first step towards configurating what you want to provision is to add a distribution to the cobbler's configuration. +This first step towards configurating what you want to provision is to add a distribution to cobbler's configuration. If there is an rsync mirror, DVD, NFS, or filesystem tree available that you would rather import instead, skip down to the documentation about the "import" command. It's really a lot easier, and it only requires waiting for the mirror content to be copied and/or scanned. Imported mirrors also save time during install since they don't have to hit external install sources. -- cgit From 08d7f84030b1a709603568134c357fdd4813730a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 27 May 2008 12:00:52 -0400 Subject: doc item --- docs/cobbler.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cobbler.pod b/docs/cobbler.pod index da3e58e..93b184c 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -218,7 +218,7 @@ MAC addresses have the format AA:BB:CC:DD:EE:FF. If cobbler is configured to generate a DHCP configuratition (see advanced section), use this setting to define a specific IP for this system in DHCP. Leaving off this parameter will result in no DHCP management for this particular system. -Example: ---ip=192.168.1.50 +Example: --ip=192.168.1.50 Note for Itanium users: this setting is always required for IA64 regardless of whether DHCP management is enabled. -- cgit From 2963016f5c143a6f3c3bc711a2b6d1ed07d65abe Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 27 May 2008 12:09:44 -0400 Subject: doc item --- docs/cobbler.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cobbler.pod b/docs/cobbler.pod index 93b184c..eb7000f 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -612,7 +612,7 @@ Note that all of the import commands will mirror install tree content into /var/ For import methods using rsync, additional flags can be passed to rsync with the option --rsync-flags. -Should you want to force the usage of a specific cobbler kickstart template for all profiles created by an import, you can feed the option --kicksart to import, to bypass the built-in kickstart autodetection. +Should you want to force the usage of a specific cobbler kickstart template for all profiles created by an import, you can feed the option --kickstart to import, to bypass the built-in kickstart auto-detection. =head2 DEFAULT PXE BOOT BEHAVIOR -- cgit