summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--CHANGELOG59
-rw-r--r--MANIFEST.in8
-rw-r--r--Makefile11
-rw-r--r--cobbler.spec164
-rw-r--r--cobbler/action_import.py88
-rw-r--r--cobbler/action_litesync.py19
-rw-r--r--cobbler/action_reposync.py41
-rw-r--r--cobbler/action_sync.py57
-rw-r--r--cobbler/api.py215
-rwxr-xr-xcobbler/cobbler.py780
-rw-r--r--cobbler/cobblerd.py46
-rw-r--r--cobbler/collection.py97
-rw-r--r--cobbler/collection_distros.py30
-rw-r--r--cobbler/collection_profiles.py34
-rw-r--r--cobbler/collection_repos.py11
-rw-r--r--cobbler/collection_systems.py16
-rw-r--r--cobbler/commands.py328
-rw-r--r--cobbler/demo_connect.py43
-rw-r--r--cobbler/item.py2
-rw-r--r--cobbler/item_distro.py9
-rw-r--r--cobbler/item_profile.py20
-rw-r--r--cobbler/item_repo.py43
-rw-r--r--cobbler/item_system.py2
-rw-r--r--cobbler/module_loader.py17
-rw-r--r--cobbler/modules/authn_configfile.py76
-rw-r--r--cobbler/modules/authn_kerberos.py81
-rw-r--r--cobbler/modules/authz_allowall.py41
-rw-r--r--cobbler/modules/cli_distro.py95
-rw-r--r--cobbler/modules/cli_misc.py259
-rw-r--r--cobbler/modules/cli_profile.py114
-rw-r--r--cobbler/modules/cli_repo.py97
-rw-r--r--cobbler/modules/cli_system.py120
-rw-r--r--cobbler/remote.py448
-rw-r--r--cobbler/serializer.py47
-rw-r--r--cobbler/server/__init__.py0
-rw-r--r--cobbler/server/xmlrpcclient.py72
-rw-r--r--cobbler/server/xmlrpclib2.py236
-rw-r--r--cobbler/settings.py7
-rw-r--r--cobbler/utils.py12
-rw-r--r--cobbler/webui/CobblerWeb.py248
-rw-r--r--cobbler/webui/master.py30
-rw-r--r--config/.htaccess8
-rw-r--r--config/.htpasswd1
-rw-r--r--config/cobbler.conf16
-rw-r--r--config/cobblerd_rotate10
-rw-r--r--config/modules.conf5
-rw-r--r--config/settings5
-rw-r--r--config/users.digest1
-rw-r--r--config/webui-cherrypy.cfg9
-rw-r--r--docs/cobbler.pod75
-rw-r--r--kickstarts/kickstart_fc6_domU.ks41
-rw-r--r--kickstarts/legacy.ks (renamed from kickstarts/kickstart_fc5.ks)0
-rw-r--r--kickstarts/sample.ks (renamed from kickstarts/kickstart_fc6.ks)0
-rwxr-xr-xscripts/cobbler3
-rw-r--r--scripts/cobbler_auth_help55
-rwxr-xr-xscripts/cobblerd20
-rwxr-xr-xscripts/findks.cgi2
-rwxr-xr-xscripts/index.py160
-rwxr-xr-xscripts/nopxe.cgi2
-rw-r--r--scripts/post_install_trigger.cgi72
-rwxr-xr-xscripts/webui.cgi108
-rw-r--r--setup.py23
-rw-r--r--tests/performance.py75
-rw-r--r--tests/tests.py20
-rwxr-xr-xwebsite/new/communicate.php3
-rw-r--r--website/new/documentation.html3
-rw-r--r--website/new/download.html10
-rw-r--r--website/new/faq.html6
-rw-r--r--website/new/magpierss/extlib/Snoopy.class.inc900
-rw-r--r--website/new/magpierss/rss_cache.inc200
-rw-r--r--website/new/magpierss/rss_fetch.inc458
-rw-r--r--website/new/magpierss/rss_parse.inc605
-rw-r--r--website/new/magpierss/rss_utils.inc67
-rwxr-xr-xwebsite/new/nav.php17
-rw-r--r--webui_templates/distro_edit.tmpl11
-rw-r--r--webui_templates/distro_list.tmpl2
-rw-r--r--webui_templates/ksfile_edit.tmpl2
-rw-r--r--webui_templates/ksfile_list.tmpl4
-rw-r--r--webui_templates/master.tmpl24
-rw-r--r--webui_templates/paginate.tmpl6
-rw-r--r--webui_templates/profile_edit.tmpl10
-rw-r--r--webui_templates/profile_list.tmpl6
-rw-r--r--webui_templates/repo_edit.tmpl34
-rw-r--r--webui_templates/repo_list.tmpl2
-rw-r--r--webui_templates/system_edit.tmpl18
-rw-r--r--webui_templates/system_list.tmpl4
87 files changed, 3226 insertions, 4002 deletions
diff --git a/AUTHORS b/AUTHORS
index 440032b..0f9d456 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -20,8 +20,10 @@ Patches and other contributions from:
Jack Neely <jjneely@ncsu.edu>
Ben Riggs <rigg0022@umn.edu>
Adam Rosenwald <thestrider@gmail.com>
+ Christophe Sahut <csahut@nogoa.org>
Scott Seago <sseago@redhat.com>
Al Tobey <tobert@gmail.com>
+ Tim Verhoeven <tim.verhoeven.be@gmail.com>
[...send patches to get your name here...]
diff --git a/CHANGELOG b/CHANGELOG
index f45079b..2106d55 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,10 +1,43 @@
Cobbler CHANGELOG
(all entries mdehaan@redhat.com unless noted otherwise)
-* Thu Jan 10 2007 - 0.6.5
-- NOTE: 0.7.X devel branch in progress at this time, see changelog there
-- Fix for f8 comps.xml name change
-- Fix for reposync permissions during rsync
+* XXX Feb XX 2008 - 0.8.0 (TBD)
+- stable release of 0.7.* branch plus ...
+- fixed potential user problem with source_repos in upgrade scenario
+- additional higher level API functions for find, fixes for other higher level API functions
+- better messaging when insufficient permissions on needed files
+- update permissions on reposync fixes
+
+* Thu Jan 31 2008 - 0.7.2 (0.8 rc)
+- default_virt_file_size and default_virt_ram added to settings
+- enforce permissions/selinux context after reposync
+- better API for copying/renames, API consistancy cleanup
+- support for renames that resolve dependencies, inclusion in CLI+webapp
+- remove leading newline in rendered template files, which apparently breaks AutoYAST?
+- recursive syncs automatically sync all subobjects when editing parent objects (default behavior)
+- deletes can now be done recursively (optional --recursive on distro/profile remove)
+- 'cobbler list' is now (re)sorted
+
+* Wed Jan 09 2008 - 0.7.1
+- allow imports to force usage of a specific kickstart template with --kickstart
+- added --yumopts parameter to repos (works just like --kopts/--ksmeta)
+- minor doc fixes
+- fix for name of F8 comps.xml file
+- added option --rsync-flags to import command
+- added http_port to settings to run Apache on non-80
+- rsync during createrepo now keeps filesystem permissions/groups
+- ...
+
+* Mon Dec 10 2007 - 0.7.0
+- Testing branch
+- Fix bug related to <<inherit>> and kickstart args
+- Make CLI functions modular and use optparse
+- Quote wget args to avoid creating stray files on target system
+- Support Xen FV as virt type (requires F8+)
+- Implemented fully pluggable authn/authz system
+- WebUI is now mod_python based
+- Greatly enhanced logging (goes to /var/log/cobbler/cobbler.log)
+- ...
* Wed Nov 14 2007 - 0.6.4
- Changed permissions of auth.conf
@@ -225,6 +258,24 @@ Cobbler CHANGELOG
older releases (for now). The CLI still takes the --xen options
as well as the new --virt options, as they are aliased. The API
now exclusively just uses methods with "virt" in them, however.
+- ...
+
+* Thu Dec 14 2007 - 0.7.0
+- Testing branch
+- Fix bug related to <<inherit>> and kickstart args
+- Make CLI functions modular and use optparse
+- Quote wget args to avoid creating stray files on target system
+- Support Xen FV as virt type (requires F8+)
+- Implemented fully pluggable authn/authz system
+- WebUI is now mod_python based
+- Greatly enhanced logging (goes to /var/log/cobbler/cobbler.log)
+- New --no-triggers and --no-sync on "adds" for performance and other reasons
+- pxe_just_once is now much faster.
+- performance testing scripts (in source checkout)
+- webui now uses Apache logging
+- misc webui fixes
+- remove -b from wgets since busybox doesn't have -b in wget
+- rename default/sample kickstarts to avoid confusion
- Fixed some bugs related to kickstart templating
* Tue Oct 24 2006 - 0.3.0
diff --git a/MANIFEST.in b/MANIFEST.in
index 7375351..357cdcc 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,10 +8,8 @@ include config/cobblerd_rotate
include config/cobbler_hosts
include config/modules.conf
include config/auth.conf
-include config/webui-cherrypy.cfg
include config/settings
-include config/.htaccess
-include config/.htpasswd
+include config/users.digest
recursive-include templates *.template
recursive-include kickstarts *.ks
include docs/cobbler.1.gz
@@ -19,10 +17,14 @@ 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/webui.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 ca8f57c..c40b4ed 100644
--- a/Makefile
+++ b/Makefile
@@ -37,17 +37,20 @@ install: clean manpage
devinstall:
cp /var/lib/cobbler/settings /tmp/cobbler_settings
- cp /etc/cobbler/auth.conf /tmp/cobbler_auth.conf
cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf
- -cp /var/www/cgi-bin/cobbler/.htpasswd /tmp/cobbler_htpasswd
+ -cp /etc/cobbler/users.digest /tmp/cobbler_users.digest
make install
cp /tmp/cobbler_settings /var/lib/cobbler/settings
- cp /tmp/cobbler_auth.conf /etc/cobbler/auth.conf
cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf
- -cp /tmp/cobbler_htpasswd /var/www/cgi-bin/cobbler/.htpasswd
+ -cp /tmp/cobbler_users.digest /etc/cobbler/users.digest
find /var/lib/cobbler/triggers | xargs chmod +x
chown -R apache /var/www/cobbler
chown -R apache /var/www/cgi-bin/cobbler
+ chmod -R +x /var/www/cobbler/web
+
+webtest: devinstall
+ /sbin/service cobblerd restart
+ /sbin/service httpd restart
sdist: clean messages updatewui
python setup.py sdist
diff --git a/cobbler.spec b/cobbler.spec
index 5989399..13873a8 100644
--- a/cobbler.spec
+++ b/cobbler.spec
@@ -1,8 +1,9 @@
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
Summary: Boot server configurator
Name: cobbler
-Version: 0.6.5
-Release: 3%{?dist}
+AutoReq: no
+Version: 0.8.0
+Release: 2%{?dist}
Source0: %{name}-%{version}.tar.gz
License: GPLv2+
Group: Applications/System
@@ -14,9 +15,7 @@ Requires: createrepo
Requires: mod_python
Requires: python-cheetah
Requires: rhpl
-%ifarch i386 i686 x86_64
-Requires: syslinux
-%endif
+Requires: rsync
Requires(post): /sbin/chkconfig
Requires(preun): /sbin/chkconfig
Requires(preun): /sbin/service
@@ -83,13 +82,10 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%files
%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/findks.cgi
-/var/www/cgi-bin/cobbler/nopxe.cgi
-/var/www/cgi-bin/cobbler/webui.cgi
-%defattr(660,apache,apache)
-%config(noreplace) /var/www/cgi-bin/cobbler/.htaccess
-%config(noreplace) /var/www/cgi-bin/cobbler/.htpasswd
+/var/www/cgi-bin/cobbler/*.cgi
%defattr(755,apache,apache)
%dir /usr/share/cobbler/webui_templates
@@ -125,27 +121,20 @@ 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/default.ks
-%config(noreplace) /etc/cobbler/kickstart_fc5.ks
-%config(noreplace) /etc/cobbler/kickstart_fc6.ks
-%config(noreplace) /etc/cobbler/kickstart_fc6_domU.ks
-%config(noreplace) /etc/cobbler/dhcp.template
-%config(noreplace) /etc/cobbler/dnsmasq.template
-%config(noreplace) /etc/cobbler/pxedefault.template
-%config(noreplace) /etc/cobbler/pxeprofile.template
-%config(noreplace) /etc/cobbler/pxesystem.template
-%config(noreplace) /etc/cobbler/pxesystem_ia64.template
+%config(noreplace) /etc/cobbler/*.ks
+%config(noreplace) /etc/cobbler/*.template
%config(noreplace) /etc/cobbler/rsync.exclude
%config(noreplace) /etc/logrotate.d/cobblerd_rotate
%config(noreplace) /etc/cobbler/modules.conf
-%config(noreplace) /etc/cobbler/webui-cherrypy.cfg
%dir %{python_sitelib}/cobbler
%dir %{python_sitelib}/cobbler/yaml
%dir %{python_sitelib}/cobbler/modules
%dir %{python_sitelib}/cobbler/webui
%{python_sitelib}/cobbler/*.py*
%{python_sitelib}/cobbler/yaml/*.py*
+%{python_sitelib}/cobbler/server/*.py*
%{python_sitelib}/cobbler/modules/*.py*
%{python_sitelib}/cobbler/webui/*.py*
%{_mandir}/man1/cobbler.1.gz
@@ -175,6 +164,7 @@ 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/post
%dir /var/lib/cobbler/snippets/
%defattr(744,root,root)
@@ -185,8 +175,8 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%config(noreplace) /var/lib/cobbler/snippets/partition_select
/var/lib/cobbler/elilo-3.6-ia64.efi
/var/lib/cobbler/menu.c32
-%defattr(660,apache,apache)
-%config(noreplace) /etc/cobbler/auth.conf
+%defattr(660,root,root)
+%config(noreplace) /etc/cobbler/users.digest
%defattr(664,root,root)
%config(noreplace) /var/lib/cobbler/cobbler_hosts
@@ -200,12 +190,27 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
%changelog
-* Thu Jan 10 2008 Michael DeHaan <mdehaan@redhat.com> - 0.6.5-3
-- added python-setuptools stanza for F9
+* Fri Feb 15 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.0-2
+- Fix egg packaging
+
+* Fri Feb 15 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.0-1
+- Upstream changes (see CHANGELOG)
+
+* Mon Jan 21 2008 Michael DeHaan <mdehaan@redhat.com> - 0.7.2-1
+- Upstream changes (see CHANGELOG)
+- prune changelog, see git for full
-* Thu Jan 10 2008 Michael DeHaan <mdehaan@redhat.com> - 0.6.5-1
+* Mon Jan 07 2008 Michael DeHaan <mdehaan@redhat.com> - 0.7.1-1
- Upstream changes (see CHANGELOG)
-- simplify directory permissions
+- Generalize what files are included in RPM
+- Add new python module directory
+- Fixes for builds on F9 and later
+
+* Thu Dec 14 2007 Michael DeHaan <mdehaan@redhat.com> - 0.7.0-1
+- Upstream changes (see CHANGELOG), testing branch
+- Don't require syslinux
+- Added requires on rsync
+- Disable autoreq to avoid slurping in perl modules
* Wed Nov 14 2007 Michael DeHaan <mdehaan@redhat.com> - 0.6.4-2
- Upstream changes (see CHANGELOG)
@@ -287,106 +292,3 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT
- Upstream changes (see CHANGELOG)
- Cobbler RPM now owns various directories it uses versus creating them using commands.
- Bundling a copy of Cheetah for older distros
-
-* Mon Jan 28 2007 Michael DeHaan <mdehaan@redhat.com> - 0.3.9-1
-- Changed init script pre/post code to match FC-E guidelines/example
-- Shortened RPM description
-- (also see CHANGELOG)
-
-* Thu Jan 24 2007 Michael DeHaan <mdehaan@redhat.com> - 0.3.8-1
-- Upstream changes (see CHANGELOG)
-
-* Thu Jan 24 2007 Michael DeHaan <mdehaan@redhat.com> - 0.3.7-1
-- Upstream changes (see CHANGELOG)
-- Added packaging for new logfile directory and syslog watcher daemon
-- Added Requires for mod_python
-- Added sample FC6 kickstart that I forgot to add from months ago. doh!
-- Added FC6 mini domU kickstart
-
-* Thu Dec 21 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.6-1
-- Upstream changes (see CHANGELOG)
-- Description updated
-- Added mod_python kickstart watcher script and associated logging changes
-
-* Thu Dec 21 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.5-4
-- Upstream changes (see CHANGELOG)
-- Added createrepo as Requires
-- BuildRequires: python-devel (needed for 2.5)
-
-* Tue Dec 05 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.4-1
-- Upstream changes (see CHANGELOG)
-
-* Tue Nov 14 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.3-1
-- Upstream changes (see CHANGELOG)
-
-* Thu Oct 26 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.2-1
-- Upstream changes (see CHANGELOG)
-
-* Wed Oct 25 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.1-1
-- Upstream changes (see CHANGELOG)
-- Updated description
-
-* Tue Oct 24 2006 Michael DeHaan <mdehaan@redhat.com> - 0.3.0-1
-- Upstream changes (see CHANGELOG)
-- Marked files in /etc/cobbler as config
-- Marked /etc/cobbler/dhcpd.template as noreplace
-
-* Tue Oct 24 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.9-1
-- Upstream changes (see CHANGELOG)
-
-* Wed Oct 18 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.8-1
-- Upstream changes (see CHANGELOG)
-
-* Tue Oct 17 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.7-1
-- Upstream changes (see CHANGELOG), includes removing pexpect as a require
-- This RPM now builds on RHEL4
-
-* Tue Oct 17 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.6-1
-- Upstream changes (see CHANGELOG), includes removing Cheetah as a require
-
-* Mon Oct 16 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.5-1
-- Upstream features and bugfixes (see CHANGELOG)
-- Packaged additional kickstart file and specfile cleanup
-
-* Thu Oct 12 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.4-1
-- Upstream features and bugfixes (see CHANGELOG)
-
-* Mon Oct 9 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.3-1
-- Upstream features (see CHANGELOG) & URL update
-
-* Fri Oct 6 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.2-1
-- Upstream bugfixes
-
-* Fri Sep 29 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.1-2
-- URL update
-
-* Thu Sep 28 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.1-1
-- Upstream pull of bugfixes and new remote system "enchant" feature
-
-* Fri Sep 22 2006 Michael DeHaan <mdehaan@redhat.com> - 0.2.0-1
-- Lots of new PXE and dhcpd.conf upstream, elilo.efi now included.
-
-* Thu Sep 21 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.1-8
-- Added doc files to doc, removed INSTALLED_FILES code
-
-* Wed Sep 20 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.1-7
-- Upstream updates
-
-* Fri Sep 15 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.1-6
-- Make koan own it's directory, add GPL "COPYING" file.
-
-* Wed Aug 16 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.1-5
-- Spec file tweaks only for FC-Extras
-
-* Thu Jul 20 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.1-4
-- Fixed python import paths in yaml code, which errantly assumed yaml was installed as a module.
-
-* Wed Jul 12 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.1-3
-- Added templating support using Cheetah
-
-* Thu Jul 9 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.0-2
-- Fedora-Extras rpm spec tweaks
-
-* Tue Jun 28 2006 Michael DeHaan <mdehaan@redhat.com> - 0.1.0-1
-- rpm genesis
-
diff --git a/cobbler/action_import.py b/cobbler/action_import.py
index 1eb341a..164f7bb 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):
+ def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None):
"""
Performs an import of a install tree (or trees) from the given
mirror address. The prefix of the distro is to be specified
@@ -57,6 +57,8 @@ class Importer:
self.systems = config.systems()
self.settings = config.settings()
self.distros_added = []
+ self.kickstart_file = kickstart_file
+ self.rsync_flags = rsync_flags
# ----------------------------------------------------------------------
@@ -91,7 +93,10 @@ class Importer:
spacer = ""
if not self.mirror.startswith("rsync://") and not self.mirror.startswith("/"):
spacer = ' -e "ssh" '
- self.run_this(RSYNC_CMD, (spacer, self.mirror, self.settings.webdir, self.mirror_name))
+ rsync_cmd = RSYNC_CMD
+ if self.rsync_flags:
+ rsync_cmd = rsync_cmd + " " + self.rsync_flags
+ self.run_this(rsync_cmd, (spacer, self.mirror, self.settings.webdir, self.mirror_name))
# see that the root given is valid
@@ -107,6 +112,11 @@ class Importer:
for valid_root in valid_roots:
if self.network_root.startswith(valid_root):
found_root = True
+ if self.network_root.startswith("nfs://"):
+ try:
+ (a,b,rest) = self.network_root.split(":",3)
+ except:
+ raise CX(_("Network root given to --available-as is missing a colon, please see the manpage example."))
if not found_root:
raise CX(_("Network root given to --available-as must be nfs://, ftp://, or http://"))
@@ -149,9 +159,10 @@ class Importer:
def kickstart_finder(self):
"""
- For all of the profiles in the config w/o a kickstart, look
- at the kernel path, from that, see if we can guess the distro,
- and if we can, assign a kickstart if one is available for it.
+ For all of the profiles in the config w/o a kickstart, use the
+ given kickstart file, or look at the kernel path, from that,
+ see if we can guess the distro, and if we can, assign a kickstart
+ if one is available for it.
"""
for profile in self.profiles:
@@ -167,25 +178,30 @@ class Importer:
# print _("- skipping %s since profile isn't mirrored") % profile.name
# continue
- kdir = os.path.dirname(distro.kernel)
- base_dir = "/".join(kdir.split("/")[0:-2])
-
- for try_entry in TRY_LIST:
- try_dir = os.path.join(base_dir, try_entry)
- if os.path.exists(try_dir):
- rpms = glob.glob(os.path.join(try_dir, "*release-*"))
- for rpm in rpms:
- if rpm.find("notes") != -1:
- continue
- results = self.scan_rpm_filename(rpm)
- if results is None:
- continue
- (flavor, major, minor) = results
- print _("- finding default kickstart template for %(flavor)s %(major)s") % { "flavor" : flavor, "major" : major }
- kickstart = self.set_kickstart(profile, flavor, major, minor)
- self.configure_tree_location(distro)
- self.distros.add(distro) # re-save
- self.api.serialize()
+ if (self.kickstart_file == None):
+ kdir = os.path.dirname(distro.kernel)
+ base_dir = "/".join(kdir.split("/")[0:-2])
+
+ for try_entry in TRY_LIST:
+ try_dir = os.path.join(base_dir, try_entry)
+ if os.path.exists(try_dir):
+ rpms = glob.glob(os.path.join(try_dir, "*release-*"))
+ for rpm in rpms:
+ if rpm.find("notes") != -1:
+ continue
+ results = self.scan_rpm_filename(rpm)
+ if results is None:
+ continue
+ (flavor, major, minor) = results
+ print _("- finding default kickstart template for %(flavor)s %(major)s") % { "flavor" : flavor, "major" : major }
+ kickstart = self.set_kickstart(profile, flavor, major, minor)
+ else:
+ print _("- using kickstart file %s") % self.kickstart_file
+ profile.set_kickstart(self.kickstart_file)
+
+ self.configure_tree_location(distro)
+ self.distros.add(distro,save=True) # re-save
+ self.api.serialize()
# --------------------------------------------------------------------
@@ -215,7 +231,7 @@ class Importer:
# how we set the tree depends on whether an explicit network_root was specified
if self.network_root is None:
- meta["tree"] = "http://@@server@@/cblr/links/%s" % (distro.name)
+ meta["tree"] = "http://@@http_server@@/cblr/links/%s" % (distro.name)
else:
# where we assign the kickstart source is relative to our current directory
# and the input start directory in the crawl. We find the path segments
@@ -251,12 +267,12 @@ class Importer:
def set_kickstart(self, profile, flavor, major, minor):
if flavor == "fedora":
if major >= 6:
- return profile.set_kickstart("/etc/cobbler/kickstart_fc6.ks")
+ return profile.set_kickstart("/etc/cobbler/sample.ks")
if flavor == "redhat" or flavor == "centos":
if major >= 5:
- return profile.set_kickstart("/etc/cobbler/kickstart_fc6.ks")
+ return profile.set_kickstart("/etc/cobbler/sample.ks")
print _("- using default kickstart file choice")
- return profile.set_kickstart("/etc/cobbler/kickstart_fc5.ks")
+ return profile.set_kickstart("/etc/cobbler/legacy.ks")
# ---------------------------------------------------------------------
@@ -361,7 +377,7 @@ class Importer:
self.process_comps_file(dirname, distro)
matches[dirname] = 1
else:
- print _("- directory %s is missing comps.xml, skipping") % dirname
+ print _("- directory %s is missing xml comps file, skipping") % dirname
continue
# ----------------------------------------------------------------------
@@ -409,23 +425,25 @@ class Importer:
fname = os.path.join(self.settings.webdir, "ks_mirror", "config", "%s-%s.repo" % (distro.name, counter))
- repo_url = "http://%s/cobbler/ks_mirror/config/%s-%s.repo" % (self.settings.server, distro.name, counter)
+ repo_url = "http://@@http_server@@/cobbler/ks_mirror/config/%s-%s.repo" % (distro.name, counter)
- repo_url2 = "http://%s/cobbler/ks_mirror/%s" % (self.settings.server, urlseg)
+ repo_url2 = "http://@@http_server@@/cobbler/ks_mirror/%s" % (urlseg)
distro.source_repos.append([repo_url,repo_url2])
# NOTE: the following file is now a Cheetah template, so it can be remapped
- # during sync, that's why we have the @@server@@ left as templating magic.
+ # during sync, that's why we have the @@http_server@@ left as templating magic.
# repo_url2 is actually no longer used. (?)
print _("- url: %s") % repo_url
config_file = open(fname, "w+")
config_file.write("[core-%s]\n" % counter)
config_file.write("name=core-%s\n" % counter)
- config_file.write("baseurl=http://@@server@@/cobbler/ks_mirror/%s\n" % (urlseg))
+ 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.close()
# don't run creatrepo twice -- this can happen easily for Xen and PXE, when
@@ -467,7 +485,7 @@ class Importer:
distro.set_initrd(initrd)
distro.set_arch(pxe_arch)
distro.source_repos = []
- self.distros.add(distro)
+ self.distros.add(distro,save=True)
self.distros_added.append(distro)
existing_profile = self.profiles.find(name=name)
@@ -482,7 +500,7 @@ class Importer:
profile.set_name(name)
profile.set_distro(name)
- self.profiles.add(profile)
+ self.profiles.add(profile,save=True)
self.api.serialize()
return distro
diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py
index 0cd4318..457f3af 100644
--- a/cobbler/action_litesync.py
+++ b/cobbler/action_litesync.py
@@ -60,6 +60,10 @@ class BootLiteSync:
self.sync.write_distro_file(distro)
# copy image files to images/$name in webdir & tftpboot:
self.sync.copy_single_distro_files(distro)
+ # cascade sync
+ kids = distro.get_children()
+ for k in kids:
+ self.add_single_profile(k.name)
def remove_single_distro(self, name):
# delete distro YAML file in distros/$name in webdir
@@ -84,6 +88,13 @@ class BootLiteSync:
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
+ kids = profile.get_children()
+ for k in kids:
+ if k.COLLECTION_TYPE == "profile":
+ self.add_single_profile(k.name)
+ else:
+ self.add_single_system(k.name)
def remove_single_profile(self, name):
# rebuild profile_list YAML file in webdir
@@ -92,7 +103,13 @@ class BootLiteSync:
self.sync.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))
-
+
+ def update_system_netboot_status(self,name):
+ 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)
+
def add_single_system(self, name):
# get the system object:
system = self.systems.find(name=name)
diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py
index fea61d6..06340aa 100644
--- a/cobbler/action_reposync.py
+++ b/cobbler/action_reposync.py
@@ -49,16 +49,16 @@ class RepoSync:
# ===================================================================
- def run(self, args=[], verbose=True):
+ def run(self, name=None, verbose=True):
"""
Syncs the current repo configuration file with the filesystem.
"""
self.verbose = verbose
for repo in self.repos:
- if args != [] and repo.name not in args:
+ if name is not None and repo.name != name:
continue
- elif args == [] and not repo.keep_updated:
+ elif name is None and not repo.keep_updated:
print _("- %s is set to not be updated") % repo.name
continue
@@ -72,6 +72,7 @@ class RepoSync:
self.do_rsync(repo)
else:
self.do_reposync(repo)
+ self.update_permissions(repo_path)
return True
@@ -235,15 +236,20 @@ class RepoSync:
config_file.write("[%s]\n" % repo.name)
config_file.write("name=%s\n" % repo.name)
if output:
- # see note above: leave as @@server@@ and fix in %post of kickstart when
- # we generate the stanza
line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name)
config_file.write(line)
+ # user may have options specific to certain yum plugins
+ # add them to the file
+ for x in repo.yumopts:
+ config_file.write("%s=%s\n" % (x, repo.yumopts[x]))
else:
line = "baseurl=%s\n" % repo.mirror
- line = line.replace("@@server@@",self.settings.server)
+ http_server = self.setting.server + ":" + self.settings.http_port
+ line = line.replace("@@server@@",http_server)
config_file.write(line)
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")
config_file.close()
return fname
@@ -264,4 +270,27 @@ class RepoSync:
print _("- createrepo failed. Is it installed?")
del fnames[:] # we're in the right place
+ # ==================================================================================
+
+ def update_permissions(self, repo_path):
+ """
+ Verifies that permissions and contexts after an rsync are as expected.
+ Sending proper rsync flags should prevent the need for this, though this is largely
+ a safeguard.
+ """
+ # all_path = os.path.join(repo_path, "*")
+ cmd1 = "chown -R root:apache %s" % repo_path
+ sub_process.call(cmd1, shell=True)
+
+ cmd2 = "chmod -R 755 %s" % repo_path
+ sub_process.call(cmd2, shell=True)
+
+ getenforce = "/usr/sbin/getenforce"
+ 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
+ sub_process.call(cmd3, shell=True)
+
+
diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py
index a516bb2..139066e 100644
--- a/cobbler/action_sync.py
+++ b/cobbler/action_sync.py
@@ -2,8 +2,9 @@
Builds out a TFTP/cobbler boot tree based on the object tree.
This is the code behind 'cobbler sync'.
-Copyright 2006, Red Hat, Inc
+Copyright 2006,2007, Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>
+Tim Verhoeven <tim.verhoeven.be@gmail.com>
This software may be freely redistributed under the terms of the GNU
general public license.
@@ -268,7 +269,7 @@ class BootSync:
if not x.endswith(".py"):
self.rmfile(path)
if os.path.isdir(path):
- if not x in ["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","kickstarts","kickstarts_sys","distros","images","systems","profiles","links","repo_profile","repo_system"] :
# delete directories that shouldn't exist
self.rmtree(path)
if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]:
@@ -386,9 +387,9 @@ class BootSync:
# 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/cblr/watcher.py?%s_%s=%s -b"
- pattern2 = "wget http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s -b"
- pattern3 = "wget http://%s/cobbler/%s/%s/ks.cfg -O /root/cobbler.ks"
+ 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\""
blend_this = profile
if system:
@@ -399,16 +400,16 @@ class BootSync:
buf = ""
if system is not None:
- buf = buf + pattern1 % (blended["server"], "system", "done", system.name)
if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]:
- buf = buf + "\n" + pattern2 % (blended["server"], system.name)
+ buf = buf + "\n" + pattern1 % (blended["http_server"], system.name)
if kickstart and os.path.exists(kickstart):
- buf = buf + "\n" + pattern3 % (blended["server"], "kickstarts_sys", system.name)
+ 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)
else:
- buf = buf + pattern1 % (blended["server"], "profile", "done", profile.name)
if kickstart and os.path.exists(kickstart):
- buf = buf + "\n" + pattern3 % (blended["server"], "kickstarts", profile.name)
+ buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts", profile.name)
return buf
@@ -499,8 +500,8 @@ class BootSync:
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["server"],urlseg,blended["name"],name)
- buf = buf + "wget %s --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name)
+ 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
@@ -545,14 +546,15 @@ class BootSync:
ksmeta = meta["ks_meta"]
del meta["ks_meta"]
meta.update(ksmeta) # make available at top level
- meta["yum_repo_stanza"] = self.generate_repo_stanza(profile)
- meta["yum_config_stanza"] = self.generate_config_stanza(profile)
+ 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["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
kfile = open(kickstart_path)
self.apply_template(kfile, meta, dest)
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):
@@ -607,7 +609,10 @@ class BootSync:
for line in data.split("\n"):
if line.find("--url") != -1 and line.find("url ") != -1:
rest = metadata["tree"][6:] # strip off "nfs://" part
- (server, dir) = rest.split(":",2)
+ 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
@@ -634,6 +639,10 @@ class BootSync:
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))
@@ -691,6 +700,11 @@ class BootSync:
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.
@@ -721,7 +735,7 @@ class BootSync:
self.apply_template(infile_data, blended, outfile)
- def write_all_system_files(self,system):
+ def write_all_system_files(self,system,just_edit_pxe=False):
profile = system.get_conceptual_parent()
if profile is None:
@@ -764,7 +778,10 @@ class BootSync:
# ensure the file doesn't exist
self.rmfile(f2)
- self.write_system_file(f3,system)
+ 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
@@ -860,9 +877,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["server"], system.name)
+ kickstart_path = "http://%s/cblr/kickstarts_sys/%s/ks.cfg" % (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["server"], profile.name)
+ kickstart_path = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name)
if distro.breed is None or distro.breed == "redhat":
append_line = "%s ks=%s" % (append_line, kickstart_path)
@@ -936,7 +953,7 @@ class BootSync:
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["server"], profile.name)
+ blended["kickstart"] = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name)
fd.write(yaml.dump(blended))
fd.close()
diff --git a/cobbler/api.py b/cobbler/api.py
index b846c81..13fc5f3 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -22,32 +22,95 @@ import action_import
import action_reposync
import action_status
import action_validate
+from cexceptions import *
import sub_process
import module_loader
+import logging
+import os
+import fcntl
+from rhpl.translate import _, N_, textdomain, utf8
+
+ERROR = 100
+INFO = 10
+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
+# on a request by request basis.
+
class BootAPI:
+
__shared_state = {}
- has_loaded = False
+ __has_loaded = False
def __init__(self):
"""
Constructor
"""
- self.__dict__ = self.__shared_state
- if not BootAPI.has_loaded:
- BootAPI.has_loaded = True
+ self.__dict__ = BootAPI.__shared_state
+ if not BootAPI.__has_loaded:
+
+ # NOTE: we do not log all API actions, because
+ # a simple CLI invocation may call adds and such
+ # to load the config, which would just fill up
+ # the logs, so we'll do that logging at CLI
+ # level (and remote.py web service level) instead.
+
+ self.logger = self.__setup_logger("api")
+ self.logger_remote = self.__setup_logger("remote")
+
+ BootAPI.__has_loaded = True
module_loader.load_modules()
self._config = config.Config(self)
self.deserialize()
+ self.authn = self.get_module_from_file(
+ "authentication",
+ "module",
+ "authn_configfile"
+ )
+ self.authz = self.get_module_from_file(
+ "authorization",
+ "module",
+ "authz_allowall"
+ )
+ 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
+
+ def log(self,msg,args=None,debug=False):
+ if debug:
+ logger = self.logger.debug
+ else:
+ logger = self.logger.info
+ if args is None:
+ logger("%s" % msg)
+ else:
+ logger("%s; %s" % (msg, str(args)))
+
def version(self):
"""
What version is cobbler?
Currently checks the RPM DB, which is not perfect.
Will return "?" if not installed.
"""
+ self.log("version")
cmd = sub_process.Popen("/bin/rpm -q cobbler", stdout=sub_process.PIPE, shell=True)
result = cmd.communicate()[0].replace("cobbler-","")
if result.find("not installed") != -1:
@@ -55,7 +118,6 @@ class BootAPI:
tokens = result[:result.rfind("-")].split(".")
return int(tokens[0]) + 0.1 * int(tokens[1]) + 0.001 * int(tokens[2])
-
def clear(self):
"""
Forget about current list of profiles, distros, and systems
@@ -95,36 +157,104 @@ class BootAPI:
"""
return self._config.settings()
- def new_system(self,is_subobject=False):
- """
- Return a blank, unconfigured system, unattached to a collection
- """
- return self._config.new_system(is_subobject=is_subobject)
+ def copy_distro(self, ref, newname):
+ self.log("copy_distro",[ref.name, newname])
+ return self._config.distros().copy(ref,newname)
+
+ def copy_profile(self, ref, newname):
+ self.log("copy_profile",[ref.name, newname])
+ return self._config.profiles().copy(ref,newname)
+
+ def copy_system(self, ref, newname):
+ self.log("copy_system",[ref.name, newname])
+ return self._config.systems().copy(ref,newname)
+
+ def copy_repo(self, ref, newname):
+ self.log("copy_repo",[ref.name, newname])
+ return self._config.repos().copy(ref,newname)
+
+ def remove_distro(self, ref, recursive=False):
+ self.log("remove_distro",[ref.name])
+ return self._config.distros().remove(ref.name, recursive=recursive)
+
+ def remove_profile(self,ref, recursive=False):
+ self.log("remove_profile",[ref.name])
+ return self._config.profiles().remove(ref.name, recursive=recursive)
+
+ def remove_system(self,ref):
+ self.log("remove_system",[ref.name])
+ return self._config.systems().remove(ref.name)
+
+ def remove_repo(self, ref):
+ self.log("remove_repo",[ref.name])
+ return self._config.repos().remove(ref.name)
+
+ def rename_distro(self, ref, newname):
+ self.log("rename_distro",[ref.name,newname])
+ return self._config.distros().rename(ref,newname)
+
+ def rename_profile(self, ref, newname):
+ self.log("rename_profiles",[ref.name,newname])
+ return self._config.profiles().rename(ref,newname)
+
+ def rename_system(self, ref, newname):
+ self.log("rename_system",[ref.name,newname])
+ return self._config.systems().rename(ref,newname)
+
+ def rename_repo(self, ref, newname):
+ self.log("rename_repo",[ref.name,newname])
+ return self._config.repos().rename(ref,newname)
def new_distro(self,is_subobject=False):
- """
- Create a blank, unconfigured distro, unattached to a collection.
- """
+ self.log("new_distro",[is_subobject])
return self._config.new_distro(is_subobject=is_subobject)
-
def new_profile(self,is_subobject=False):
- """
- Create a blank, unconfigured profile, unattached to a collection
- """
+ self.log("new_profile",[is_subobject])
return self._config.new_profile(is_subobject=is_subobject)
+
+ def new_system(self,is_subobject=False):
+ self.log("new_system",[is_subobject])
+ return self._config.new_system(is_subobject=is_subobject)
def new_repo(self,is_subobject=False):
- """
- Create a blank, unconfigured repo, unattached to a collection
- """
+ self.log("new_repo",[is_subobject])
return self._config.new_repo(is_subobject=is_subobject)
+ def add_distro(self, ref):
+ self.log("add_distro",[ref.name])
+ return self._config.distros().add(ref,save=True)
+
+ def add_profile(self, ref):
+ self.log("add_profile",[ref.name])
+ return self._config.profiles().add(ref,save=True)
+
+ def add_system(self,ref):
+ self.log("add_system",[ref.name])
+ return self._config.systems().add(ref,save=True)
+
+ def add_repo(self,ref):
+ self.log("add_repo",[ref.name])
+ return self._config.repos().add(ref,save=True)
+
+ def find_distro(self, name=None, return_list=False, **kargs):
+ return self._config.distros().find(name=name, return_list=return_list, **kargs)
+
+ def find_profile(self, name=None, return_list=False, **kargs):
+ return self._config.profiles().find(name=name, return_list=return_list, **kargs)
+
+ def find_system(self, name=None, return_list=False, **kargs):
+ return self._config.systems().find(name=name, return_list=return_list, **kargs)
+
+ def find_repo(self, name=None, return_list=False, **kargs):
+ return self._config.repos().find(name=name, return_list=return_list, **kargs)
+
def auto_add_repos(self):
"""
Import any repos this server knows about and mirror them.
Credit: Seth Vidal.
"""
+ self.log("auto_add_repos")
try:
import yum
except:
@@ -150,9 +280,9 @@ class BootAPI:
cobbler_repo.set_mirror(url)
cobbler_repo.set_name(auto_name)
print "auto adding: %s (%s)" % (auto_name, url)
- self._config.repos().add(cobbler_repo,with_copy=True)
+ self._config.repos().add(cobbler_repo,save=True)
- print "run cobbler reposync to apply changes"
+ # run cobbler reposync to apply changes
return True
def check(self):
@@ -164,6 +294,7 @@ class BootAPI:
for human admins, who may, for instance, forget to properly set up
their TFTP servers for PXE, etc.
"""
+ self.log("check")
check = action_check.BootCheck(self._config)
return check.run()
@@ -176,6 +307,7 @@ class BootAPI:
is not available on all platforms and can not detect "future"
kickstart format correctness.
"""
+ self.log("validateks")
validator = action_validate.Validate(self._config)
return validator.run()
@@ -186,22 +318,25 @@ class BootAPI:
/tftpboot. Any operations done in the API that have not been
saved with serialize() will NOT be synchronized with this command.
"""
+ self.log("sync")
sync = action_sync.BootSync(self._config)
return sync.run()
- def reposync(self, args=[]):
+ def reposync(self, name=None):
"""
Take the contents of /var/lib/cobbler/repos and update them --
or create the initial copy if no contents exist yet.
"""
+ self.log("reposync",[name])
reposync = action_reposync.RepoSync(self._config)
- return reposync.run(args)
+ return reposync.run(name)
def status(self,mode):
+ self.log("status",[mode])
statusifier = action_status.BootStatusReport(self._config, mode)
return statusifier.run()
- def import_tree(self,mirror_url,mirror_name,network_root=None):
+ def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None):
"""
Automatically import a directory tree full of distribution files.
mirror_url can be a string that represents a path, a user@host
@@ -209,8 +344,9 @@ class BootAPI:
filesystem path and mirroring is not desired, set network_root
to something like "nfs://path/to/mirror_url/root"
"""
+ 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
+ self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags
)
return importer.run()
@@ -218,6 +354,7 @@ class BootAPI:
"""
Save the config file(s) to disk.
"""
+ self.log("serialize")
return self._config.serialize()
def deserialize(self):
@@ -238,17 +375,33 @@ class BootAPI:
"""
return module_loader.get_module_by_name(module_name)
- def get_module_from_file(self,section,name):
+ def get_module_from_file(self,section,name,fallback=None):
"""
Looks in /etc/cobbler/modules.conf for a section called 'section'
and a key called 'name', and then returns the module that corresponds
to the value of that key.
"""
- return module_loader.get_module_from_file(section,name)
+ return module_loader.get_module_from_file(section,name,fallback)
-if __name__ == "__main__":
- api = BootAPI()
- print api.version()
+ def get_modules_in_category(self,category):
+ """
+ Returns all modules in a given category, for instance "serializer", or "cli".
+ """
+ return module_loader.get_modules_in_category(category)
+ def authenticate(self,user,password):
+ """
+ (Remote) access control.
+ """
+ rc = self.authn.authenticate(self,user,password)
+ self.log("authenticate",[user,rc])
+ return rc
+ def authorize(self,user,resource,arg1=None,arg2=None):
+ """
+ (Remote) access control.
+ """
+ rc = self.authz.authorize(self,user,resource,arg1,arg2)
+ self.log("authorize",[user,resource,arg1,arg2,rc],debug=True)
+ return rc
diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py
index 99a1935..fb9b273 100755
--- a/cobbler/cobbler.py
+++ b/cobbler/cobbler.py
@@ -19,748 +19,28 @@ import api
import os
import os.path
import traceback
-
+import optparse
+import commands
from cexceptions import *
from rhpl.translate import _, N_, textdomain, utf8
I18N_DOMAIN = "cobbler"
-LOCKING_ENABLED = True
-LOCKFILE="/var/lib/cobbler/lock"
-
-USAGE = _("see 'man cobbler' for instructions")
+####################################################
class BootCLI:
-
- def __init__(self,args):
- """
- Build the command line parser and API instances, etc.
- """
+ def __init__(self):
textdomain(I18N_DOMAIN)
- self.args = args
self.api = api.BootAPI()
- self.commands = {}
- self.commands['distro'] = {
- 'add' : self.distro_add,
- 'edit' : self.distro_edit,
- 'copy' : self.distro_copy,
- 'rename' : self.distro_rename,
- 'delete' : self.distro_remove,
- 'remove' : self.distro_remove,
- 'list' : self.distro_list,
- 'report' : self.distro_report
- }
- self.commands['profile'] = {
- 'add' : self.profile_add,
- 'edit' : self.profile_edit,
- 'copy' : self.profile_copy,
- 'rename' : self.profile_rename,
- 'delete' : self.profile_remove,
- 'remove' : self.profile_remove,
- 'list' : self.profile_list,
- 'report' : self.profile_report
- }
- self.commands['system'] = {
- 'add' : self.system_add,
- 'edit' : self.system_edit,
- 'rename' : self.system_rename,
- 'copy' : self.system_copy,
- 'delete' : self.system_remove,
- 'remove' : self.system_remove,
- 'list' : self.system_list,
- 'report' : self.system_report
- }
- self.commands['repo'] = {
- 'auto-add' : self.repo_auto_add,
- 'add' : self.repo_add,
- 'edit' : self.repo_edit,
- 'rename' : self.repo_rename,
- 'copy' : self.repo_copy,
- 'delete' : self.repo_remove,
- 'remove' : self.repo_remove,
- 'list' : self.repo_list,
- 'report' : self.repo_report,
- 'sync' : self.reposync
- }
- self.commands['toplevel'] = {
- '-v' : self.version,
- '--version' : self.version,
- 'check' : self.check,
- 'validateks' : self.validateks,
- 'list' : self.list,
- 'report' : self.report,
- 'distros' : self.distro,
- 'distro' : self.distro,
- 'profiles' : self.profile,
- 'profile' : self.profile,
- 'systems' : self.system,
- 'system' : self.system,
- 'repos' : self.repo,
- 'repo' : self.repo,
- 'sync' : self.sync,
- 'reposync' : self.reposync,
- 'import' : self.import_tree,
- 'status' : self.status,
- 'reserialize' : self.reserialize,
- 'help' : self.usage,
- '--help' : self.usage,
- '/?' : self.usage
- }
-
- def run(self):
- """
- Run the command line and return system exit code
- """
- # deserialization is implicit with API construction
- # self.api.deserialize()
- self.relay_args(self.args[1:], self.commands['toplevel'])
-
- def usage(self,args):
- """
- Print out abbreviated help if user gives bad syntax
- """
- print USAGE
-
-
- ###########################################################
- # REPORTING FUNCTIONS
-
- def distro_report(self,args):
- if len(args) > 0:
- return self.__list_names2(self.api.distros(), args)
- else:
- return self.__print_sorted(self.api.distros())
-
- def system_report(self,args):
- if len(args) > 0:
- return self.__list_names2(self.api.systems(), args)
- else:
- return self.__print_sorted(self.api.systems())
-
- def profile_report(self,args):
- if len(args) > 0:
- return self.__list_names2(self.api.profiles(), args)
- else:
- return self.__print_sorted(self.api.profiles())
-
- def repo_report(self,args):
- if len(args) > 0:
- return self.__list_names2(self.api.repos(), args)
- else:
- return self.__print_sorted(self.api.repos())
-
- def report(self,args):
-
- args.append("") # filler
- match = False
- for a in args:
- if a == '--distros' or len(args) == 1:
- self.distro_report([])
- match = True
- if a == '--repos' or len(args) == 1:
- self.repo_report([])
- match = True
- if a == '--profiles' or len(args) == 1:
- self.profile_report([])
- match = True
- if a == '--systems' or len(args) == 1:
- self.system_report([])
- match = True
- if not match and a is not None and a != "":
- raise CX(_("cobbler does not understand '%(command)s'") % { "command" : a })
- match = False
-
- #############################################
- # LISTING FUNCTIONS
-
- def list(self,args):
- self.__tree(self.api.distros(),0)
- self.__tree(self.api.repos(),0)
-
- def __tree(self,collection,level):
- for item in collection:
- print _("%(indent)s%(type)s %(name)s") % {
- "indent" : " " * level,
- "type" : item.TYPE_NAME,
- "name" : item.name
- }
- kids = item.get_children()
- if kids is not None and len(kids) > 0:
- self.__tree(kids,level+1)
-
- def __list_names(self, collection):
- names = [ x.name for x in collection]
- names.sort() # sorted() is 2.4 only
- for name in names:
- str = _(" %(name)s") % { "name" : name }
- print str
- return True
-
- def __list_names2(self, collection, args):
- for p in args:
- obj = collection.find(name=p)
- if obj is not None:
- print obj.printable()
- return True
-
- def system_list(self, args):
- if len(args) > 0:
- self.__list_names2(self.api.systems(), args)
- else:
- return self.__list_names(self.api.systems())
-
- def distro_list(self, args):
- if len(args) > 0:
- return self.__list_names2(self.api.distros(),args)
- else:
- return self.__list_names(self.api.distros())
-
- def profile_list(self, args):
- if len(args) > 0:
- return self.__list_names2(self.api.profiles(),args)
- else:
- return self.__list_names(self.api.profiles())
-
- def repo_list(self, args):
- if len(args) > 0:
- return self.__list_names2(self.api.repos(),args)
- else:
- return self.__list_names(self.api.repos())
-
- ###############################################################
- # UTILITY FUNCTIONS
-
- def find_arg(self,haystack,needle):
- for arg in haystack:
- arg2 = arg.replace('"','')
- if arg2.startswith(needle):
- tokens = arg2.split("=")
- if len(tokens) >= 1:
- return "".join(tokens[1:])
- return None
-
- def replace_names(self,haystack,newname):
- args2 = []
- for arg in haystack:
- if arg.startswith("--name"):
- args2.append("--name=%s" % newname)
- else:
- args2.append(arg)
- return args2
-
-
- def __sorter(self, a, b):
- return cmp(a.name, b.name)
-
- def __print_sorted(self, collection):
- collection = [x for x in collection]
- collection.sort(self.__sorter)
- for x in collection:
- print x.printable()
- return True
-
- ######################################################################
- # BASIC FRAMEWORK
-
- def __generic_add(self,args,new_fn,control_fn,does_inherit):
- obj = new_fn(is_subobject=does_inherit)
- control_fn(args,obj)
-
- def __generic_edit(self,args,collection_fn,control_fn,exc_msg):
- obj = collection_fn().find(name=self.find_arg(args,"--name"))
- name2 = self.find_arg(args,"--newname")
- if name2 is not None:
- raise CX("objects cannot be renamed with the edit command, use 'rename'")
- if obj is None:
- raise CX(exc_msg)
- control_fn(args,obj)
-
- def __generic_copy(self,args,collection_fn,control_fn,exc_msg):
- obj = collection_fn().find(name=self.find_arg(args,"--name"))
- obj2 = self.find_arg(args,"--newname")
- if obj is None:
- raise CX(exc_msg)
- args = self.replace_names(args, obj2)
- obj3 = obj.make_clone()
- obj3.set_name(obj2)
- control_fn(args,obj3)
-
- def __generic_rename(self,args,collection_fn,control_fn,exc_msg):
- objname = self.find_arg(args,"--name")
- if objname is None:
- raise CX(_("at least one required parameter is missing. See 'man cobbler'."))
- objname2 = self.find_arg(args,"--newname")
- if objname2 is None:
- raise CX(_("at least one required parameter is missing. See 'man cobbler'."))
- self.__generic_copy(args,collection_fn,control_fn,exc_msg)
- if objname != objname2:
- collection_fn().remove(objname, with_delete=True)
-
- # new cobbler does not require explicit serialize calls
- # self.api.serialize()
-
- def __generic_remove(self,args,alias1,alias2,collection_fn):
- commands = {
- "--%s" % alias1 : lambda(a): collection_fn().remove(a, with_delete=True),
- "--%s" % alias2 : lambda(a): collection_fn().remove(a, with_delete=True)
- }
- on_ok = lambda: True
- return self.apply_args(args,commands,on_ok)
-
- ####################################################################
- # REMOVAL FUNCTIONS
-
- def distro_remove(self,args):
- return self.__generic_remove(args,"distro","name",self.api.distros)
-
- def profile_remove(self,args):
- return self.__generic_remove(args,"profile","name",self.api.profiles)
-
- def system_remove(self,args):
- return self.__generic_remove(args,"system","name",self.api.systems)
-
- def repo_remove(self,args):
- return self.__generic_remove(args,"repo","name",self.api.repos)
-
- ####################################################################
- # COPY FUNCTIONS
-
- def distro_copy(self,args):
- exc = _("distribution does not exist")
- self.__generic_copy(args,self.api.distros,self.__distro_control,exc)
-
- def profile_copy(self,args):
- exc = _("profile does not exist")
- self.__generic_copy(args,self.api.profiles,self.__profile_control,exc)
-
- def system_copy(self,args):
- exc = _("system does not exist")
- self.__generic_copy(args,self.api.systems,self.__system_control,exc)
-
- def repo_copy(self,args):
- exc = _("repository does not exist")
- self.__generic_copy(args,self.api.repos,self.__repo_control,exc)
-
- #####################################################################
- # RENAME FUNCTIONS
-
- def distro_rename(self,args):
- exc = _("distribution does not exist")
- self.__generic_rename(args,self.api.distros,self.__distro_control,exc)
-
- def profile_rename(self,args):
- exc = _("profile does not exist")
- self.__generic_rename(args,self.api.profiles,self.__profile_control,exc)
-
- def system_rename(self,args):
- exc = _("system does not exist")
- self.__generic_rename(args,self.api.systems,self.__system_control,exc)
-
- def repo_rename(self,args):
- exc = _("repository does not exist")
- self.__generic_rename(args,self.api.repos,self.__repo_control,exc)
-
- #####################################################################
- # EDIT FUNCTIONS
-
- def distro_edit(self,args):
- exc = _("distribution does not exist")
- self.__generic_edit(args,self.api.distros,self.__distro_control,exc)
-
- def profile_edit(self,args):
- exc = _("profile does not exist")
- self.__generic_edit(args,self.api.profiles,self.__profile_control,exc)
-
- def system_edit(self,args):
- exc = _("system does not exist")
- self.__generic_edit(args,self.api.systems,self.__system_control,exc)
-
- def repo_edit(self,args):
- exc = _("repository does not exist")
- self.__generic_edit(args,self.api.repos,self.__repo_control,exc)
-
- #####################################################################
- # ADD FUNCTIONS
-
- def __prescan_for_inheritance_args(self,args):
- """
- Normally we just feed all the arguments through to the functions
- in question, but here, we need to send a special flag to the foo_add
- functions if we are creating a subobject, because that needs to affect
- what function calls we make. So, this checks to see if the user
- is creating a subobject by looking for --inherit in the arguments list,
- before we actually parse the --inherit arg. Complicated :)
- """
- for x in args:
- try:
- key, value = x.split("=",1)
- value = value.replace('"','').replace("'",'')
- if key == "--inherit":
- return True
- except:
- pass
- return False
-
- def distro_add(self,args):
- does_inherit = self.__prescan_for_inheritance_args(args)
- self.__generic_add(args,self.api.new_distro,self.__distro_control,does_inherit)
-
- def profile_add(self,args):
- does_inherit = self.__prescan_for_inheritance_args(args)
- self.__generic_add(args,self.api.new_profile,self.__profile_control,does_inherit)
-
- def system_add(self,args):
- does_inherit = self.__prescan_for_inheritance_args(args)
- self.__generic_add(args,self.api.new_system,self.__system_control,does_inherit)
-
- def repo_auto_add(self, args):
- self.api.auto_add_repos()
-
- def repo_add(self,args):
- does_inherit = self.__prescan_for_inheritance_args(args)
- self.__generic_add(args,self.api.new_repo,self.__repo_control,does_inherit)
-
-
- ###############################################################
- # CONTROL IMPLEMENTATIONS
-
- def __profile_control(self,args,profile,newname=None):
- """
- Create/Edit a profile: 'cobbler profile edit --name='foo' ...
- """
- commands = {
- '--name' : lambda(a) : profile.set_name(a),
- '--inherit' : lambda(a) : profile.set_parent(a),
- '--newname' : lambda(a) : True,
- '--profile' : lambda(a) : profile.set_name(a),
- '--distro' : lambda(a) : profile.set_distro(a),
- '--kickstart' : lambda(a) : profile.set_kickstart(a),
- '--kick-start' : lambda(a) : profile.set_kickstart(a),
- '--answers' : lambda(a) : profile.set_kickstart(a),
- '--kopts' : lambda(a) : profile.set_kernel_options(a),
- '--virt-file-size' : lambda(a) : profile.set_virt_file_size(a),
- '--virt-ram' : lambda(a) : profile.set_virt_ram(a),
- '--virt-bridge' : lambda(a) : profile.set_virt_bridge(a),
- '--virt-cpus' : lambda(a) : profile.set_virt_cpus(a),
- '--ksmeta' : lambda(a) : profile.set_ksmeta(a),
- '--repos' : lambda(a) : profile.set_repos(a),
- '--virt-path' : lambda(a) : profile.set_virt_path(a),
- '--virt-type' : lambda(a) : profile.set_virt_type(a),
- '--dhcp-tag' : lambda(a) : profile.set_dhcp_tag(a),
- '--server-override' : lambda(a) : profile.set_server(a)
- }
- def on_ok():
- if newname is not None:
- profile.set_name(newname)
- self.api.profiles().add(profile, with_copy=True)
- return self.apply_args(args,commands,on_ok)
-
- def __repo_control(self,args,repo,newname=None):
- """
- Create/edit a repo: 'cobbler repo add --name='foo' ...
- """
- commands = {
- '--name' : lambda(a): repo.set_name(a),
- '--newname' : lambda(a): True,
- '--mirror-name' : lambda(a): repo.set_name(a),
- '--mirror' : lambda(a): repo.set_mirror(a),
- '--keep-updated' : lambda(a): repo.set_keep_updated(a),
- '--rpm-list' : lambda(a): repo.set_rpm_list(a),
- '--createrepo-flags' : lambda(a): repo.set_createrepo_flags(a),
- '--arch' : lambda(a): repo.set_arch(a)
- }
- def on_ok():
- if newname is not None:
- repo.set_name(newname)
- self.api.repos().add(repo, with_copy=True)
- return self.apply_args(args,commands,on_ok)
-
- def __distro_control(self,args,distro):
- """
- Create/Edit a distro: 'cobbler distro edit --name='foo' ...
- """
- commands = {
- '--name' : lambda(a) : distro.set_name(a),
- '--newname' : lambda(a) : True,
- '--distro' : lambda(a) : distro.set_name(a),
- '--kernel' : lambda(a) : distro.set_kernel(a),
- '--initrd' : lambda(a) : distro.set_initrd(a),
- '--kopts' : lambda(a) : distro.set_kernel_options(a),
- '--arch' : lambda(a) : distro.set_arch(a),
- '--ksmeta' : lambda(a) : distro.set_ksmeta(a),
- '--breed' : lambda(a) : distro.set_breed(a)
- }
- def on_ok():
- self.api.distros().add(distro, with_copy=True)
- return self.apply_args(args,commands,on_ok)
-
- def __system_control(self,args,sys):
- """
- Create/Edit a system: 'cobbler system edit --name='foo' ...
- """
-
- # This copy/paste is heinous evil, please forgive me.
- # there was a loop here but the python optimizers seemed to do wierd
- # things with the lambda handling. To be resolved when this
- # command line gets refactored to use optparse.
-
- commands = {
- '--name' : lambda(a) : sys.set_name(a),
- '--newname' : lambda(a) : True,
- '--system' : lambda(a) : sys.set_name(a),
- '--profile' : lambda(a) : sys.set_profile(a),
- '--kopts' : lambda(a) : sys.set_kernel_options(a),
- '--ksmeta' : lambda(a) : sys.set_ksmeta(a),
- '--hostname' : lambda(a) : sys.set_hostname(a,"intf0"),
- '--hostname0' : lambda(a) : sys.set_hostname(a,"intf0"),
- '--hostname1' : lambda(a) : sys.set_hostname(a,"intf1"),
- '--hostname2' : lambda(a) : sys.set_hostname(a,"intf2"),
- '--hostname3' : lambda(a) : sys.set_hostname(a,"intf3"),
- '--hostname4' : lambda(a) : sys.set_hostname(a,"intf4"),
- '--hostname5' : lambda(a) : sys.set_hostname(a,"intf5"),
- '--hostname6' : lambda(a) : sys.set_hostname(a,"intf6"),
- '--hostname7' : lambda(a) : sys.set_hostname(a,"intf7"),
- '--ip' : lambda(a) : sys.set_ip_address(a,"intf0"),
- '--ip0' : lambda(a) : sys.set_ip_address(a,"intf0"),
- '--ip1' : lambda(a) : sys.set_ip_address(a,"intf1"),
- '--ip2' : lambda(a) : sys.set_ip_address(a,"intf2"),
- '--ip3' : lambda(a) : sys.set_ip_address(a,"intf3"),
- '--ip4' : lambda(a) : sys.set_ip_address(a,"intf4"),
- '--ip5' : lambda(a) : sys.set_ip_address(a,"intf5"),
- '--ip6' : lambda(a) : sys.set_ip_address(a,"intf6"),
- '--ip7' : lambda(a) : sys.set_ip_address(a,"intf7"),
- '--mac' : lambda(a) : sys.set_mac_address(a,"intf0"),
- '--mac0' : lambda(a) : sys.set_mac_address(a,"intf0"),
- '--mac1' : lambda(a) : sys.set_mac_address(a,"intf1"),
- '--mac2' : lambda(a) : sys.set_mac_address(a,"intf2"),
- '--mac3' : lambda(a) : sys.set_mac_address(a,"intf3"),
- '--mac4' : lambda(a) : sys.set_mac_address(a,"intf4"),
- '--mac5' : lambda(a) : sys.set_mac_address(a,"intf5"),
- '--mac6' : lambda(a) : sys.set_mac_address(a,"intf6"),
- '--mac7' : lambda(a) : sys.set_mac_address(a,"intf7"),
- '--gateway' : lambda(a) : sys.set_gateway(a,"intf0"),
- '--gateway0' : lambda(a) : sys.set_gateway(a,"intf0"),
- '--gateway1' : lambda(a) : sys.set_gateway(a,"intf1"),
- '--gateway2' : lambda(a) : sys.set_gateway(a,"intf2"),
- '--gateway3' : lambda(a) : sys.set_gateway(a,"intf3"),
- '--gateway4' : lambda(a) : sys.set_gateway(a,"intf4"),
- '--gateway5' : lambda(a) : sys.set_gateway(a,"intf5"),
- '--gateway6' : lambda(a) : sys.set_gateway(a,"intf6"),
- '--gateway7' : lambda(a) : sys.set_gateway(a,"intf7"),
- '--subnet' : lambda(a) : sys.set_subnet(a,"intf0"),
- '--subnet0' : lambda(a) : sys.set_subnet(a,"intf1"),
- '--subnet1' : lambda(a) : sys.set_subnet(a,"intf2"),
- '--subnet2' : lambda(a) : sys.set_subnet(a,"intf3"),
- '--subnet3' : lambda(a) : sys.set_subnet(a,"intf4"),
- '--subnet4' : lambda(a) : sys.set_subnet(a,"intf5"),
- '--subnet5' : lambda(a) : sys.set_subnet(a,"intf6"),
- '--subnet6' : lambda(a) : sys.set_subnet(a,"intf7"),
- '--subnet7' : lambda(a) : sys.set_subnet(a,"intf8"),
- '--virt-bridge' : lambda(a) : sys.set_virt_bridge(a,"intf0"),
- '--virt-bridge0' : lambda(a) : sys.set_virt_bridge(a,"intf0"),
- '--virt-bridge1' : lambda(a) : sys.set_virt_bridge(a,"intf1"),
- '--virt-bridge2' : lambda(a) : sys.set_virt_bridge(a,"intf2"),
- '--virt-bridge3' : lambda(a) : sys.set_virt_bridge(a,"intf3"),
- '--virt-bridge4' : lambda(a) : sys.set_virt_bridge(a,"intf4"),
- '--virt-bridge5' : lambda(a) : sys.set_virt_bridge(a,"intf5"),
- '--virt-bridge6' : lambda(a) : sys.set_virt_bridge(a,"intf6"),
- '--virt-bridge7' : lambda(a) : sys.set_virt_bridge(a,"intf7"),
- '--dhcp-tag' : lambda(a) : sys.set_dhcp_tag(a,"intf0"),
- '--dhcp-tag0' : lambda(a) : sys.set_dhcp_tag(a,"intf0"),
- '--dhcp-tag1' : lambda(a) : sys.set_dhcp_tag(a,"intf1"),
- '--dhcp-tag2' : lambda(a) : sys.set_dhcp_tag(a,"intf2"),
- '--dhcp-tag3' : lambda(a) : sys.set_dhcp_tag(a,"intf3"),
- '--dhcp-tag4' : lambda(a) : sys.set_dhcp_tag(a,"intf4"),
- '--dhcp-tag5' : lambda(a) : sys.set_dhcp_tag(a,"intf5"),
- '--dhcp-tag6' : lambda(a) : sys.set_dhcp_tag(a,"intf6"),
- '--dhcp-tag7' : lambda(a) : sys.set_dhcp_tag(a,"intf7"),
- '--kickstart' : lambda(a) : sys.set_kickstart(a),
- '--netboot-enabled' : lambda(a) : sys.set_netboot_enabled(a),
- '--virt-path' : lambda(a) : sys.set_virt_path(a),
- '--virt-type' : lambda(a) : sys.set_virt_type(a),
- '--server-override' : lambda(a) : sys.set_server(a)
- }
-
- def on_ok():
- self.api.systems().add(sys, with_copy=True)
- return self.apply_args(args,commands,on_ok)
-
- ###################################################################################
- # PARSING FUNCTIONS
-
- def apply_args(self,args,input_routines,on_ok):
- """
- Custom CLI handling, instead of getopt/optparse.
- Parses arguments of the form --foo=bar.
- 'on_ok' is a callable that is run if all arguments are parsed
- successfully. 'input_routines' is a dispatch table of routines
- that parse the arguments. See distro_edit for an example.
- """
- if len(args) == 0:
- raise CX(_("this command requires arguments"))
- for x in args:
- try:
- key, value = x.split("=",1)
- value = value.replace('"','').replace("'",'')
- except:
- raise CX(_("Cobbler was expecting an equal sign in argument '%(argument)s'") % { "argument" : x })
- if input_routines.has_key(key):
- input_routines[key](value)
- else:
- raise CX(_("this command doesn't take an option called '%(argument)s'") % { "argument" : key })
- on_ok()
-
- # new cobbler does not require explicit serialize calls
- # self.api.serialize()
-
- def relay_args(self, args, commands):
- """
- Lookup command args[0] in the dispatch table and
- feed it the remaining args[1:-1] as arguments.
- """
- if args is None or len(args) == 0:
- print USAGE
- return True
- if args[0] in commands:
- commands[args[0]](args[1:])
- else:
- raise CX(_("Cobbler does not understand '%(command)s'") % { "command" : args[0] })
- return True
-
- ################################################
- # GENERAL FUNCTIONS
-
- def reserialize(self, args):
- """
- This command is intentionally not documented in the manpage.
- Basically it loads the cobbler config and then reserialize's it's internal state.
- It can be used for testing config format upgrades and other development related things.
- It has very little purpose in the real world.
- """
- self.api.serialize()
- return True
-
- def sync(self, args):
- """
- Sync the config file with the system config: 'cobbler sync'
- """
- self.api.sync()
- return True
-
- def reposync(self, args):
- """
- Sync the repo-specific portions of the config with the filesystem.
- 'cobbler reposync'. Intended to be run on cron.
- """
- self.api.reposync(args)
- return True
-
- def validateks(self,args):
- """
- Scan rendered kickstarts for potential errors, before actual install
- """
- return self.api.validateks()
-
- def version(self,args):
- print self.api.version()
- return True
-
- def check(self,args):
- """
- Check system for network boot decency/prereqs: 'cobbler check'
- """
- status = self.api.check()
- if len(status) == 0:
- print _("No setup problems found")
- print _("Manual review and editing of /var/lib/cobbler/settings is recommended to tailor cobbler to your particular configuration.")
- print _("Good luck.")
-
- return True
- else:
- print _("The following potential problems were detected:")
- for i,x in enumerate(status):
- print _("#%(number)d: %(problem)s") % { "number" : i, "problem" : x }
- return False
-
- def status(self,args):
- """
- Show the kickstart status for logged kickstart activity.
- 'cobbler status [--mode=text|somethingelse]'
- """
- self.mode = "text"
- if args is None or len(args) == 0:
- return self.api.status(self.mode)
- def set_mode(a):
- if a.lower in [ "text" ]:
- self.mode = a
- return True
- else:
- return False
- commands = {
- '--mode' : set_mode
- }
- def go_status():
- return self.api.status(self.mode)
- return self.apply_args(args, commands, go_status)
-
- def import_tree(self,args):
- """
- Import a directory tree and auto-create distros & profiles.
- 'cobbler
- """
- self.temp_mirror = None
- self.temp_mirror_name = None
- self.temp_network_root = None
- def set_mirror_name(a):
- self.temp_mirror_name = a
- def set_mirror(a):
- self.temp_mirror = a
- def set_network_root(a):
- self.temp_network_root = a
- def go_import():
- return self.api.import_tree(
- self.temp_mirror,
- self.temp_mirror_name,
- network_root=self.temp_network_root
- )
- commands = {
- '--path' : lambda(a): set_mirror(a),
- '--mirror' : lambda(a): set_mirror(a),
- '--mirror-name' : lambda(a): set_mirror_name(a),
- '--name' : lambda(a): set_mirror_name(a),
- '--available-as' : lambda(a): set_network_root(a)
- }
- on_ok = lambda: go_import()
- return self.apply_args(args,commands,on_ok)
-
-
- #########################################################
- # TOPLEVEL MAPPINGS
-
- def distro(self,args):
- """
- Handles any of the 'cobbler distro' subcommands
- """
- return self.relay_args(args, self.commands['distro'])
-
- def profile(self,args):
- """
- Handles any of the 'cobbler profile' subcommands
- """
- return self.relay_args(args, self.commands['profile'])
-
- def system(self,args):
- """
- Handles any of the 'cobbler system' subcommands
- """
- return self.relay_args(args, self.commands['system'])
-
- def repo(self,args):
- """
- Handles any of the 'cobbler repo' subcommands
- """
- return self.relay_args(args, self.commands['repo'])
+ self.loader = commands.FunctionLoader()
+ climods = self.api.get_modules_in_category("cli")
+ for mod in climods:
+ for fn in mod.cli_functions(self.api):
+ self.loader.add_func(fn)
+
+ def run(self,args):
+ return self.loader.run(args)
####################################################
@@ -769,33 +49,19 @@ def main():
CLI entry point
"""
exitcode = 0
- lock_hit = False
try:
- if LOCKING_ENABLED:
- if os.path.exists(LOCKFILE):
- lock_hit = True
- raise CX(_("Locked. If cobbler is currently running, wait for termination, otherwise remove /var/lib/cobbler/lock"))
- try:
- lockfile = open(LOCKFILE,"w+")
- except:
- raise CX(_("Cobbler could not create the lockfile %(lockfile)s. Are you root?") % { "lockfile" : LOCKFILE })
- lockfile.close()
- BootCLI(sys.argv).run()
- except CobblerException, exc:
+ # FIXME: redo locking code?
+ return BootCLI().run(sys.argv)
+ except CX, exc:
print str(exc)[1:-1] # remove framing air quotes
- exitcode = 1
- except KeyboardInterrupt:
- print _("interrupted.")
- exitcode = 1
- except Exception, other:
- traceback.print_exc()
- exitcode = 1
- if LOCKING_ENABLED and not lock_hit:
- try:
- os.remove(LOCKFILE)
- except:
- pass
- return exitcode
+ except Exception, exc2:
+ if str(type(exc2)).find("CX") == -1:
+ traceback.print_exc()
+ else:
+ print str(exc2)[1:-1] # remove framing air quotes
+ return 1
+ return 1
+
if __name__ == "__main__":
sys.exit(main())
diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py
index ed5651c..4e9e52e 100644
--- a/cobbler/cobblerd.py
+++ b/cobbler/cobblerd.py
@@ -19,6 +19,7 @@ import glob
from rhpl.translate import _, N_, textdomain, utf8
import xmlrpclib
+from server import xmlrpclib2
import api as cobbler_api
import yaml # Howell Clark version
import utils
@@ -50,11 +51,19 @@ def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger):
if str(settings.xmlrpc_rw_enabled) != "0":
pid2 = os.fork()
if pid2 == 0:
- do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
+ do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger)
else:
do_xmlrpc_rw(bootapi, settings, xmlrpc_port2, logger)
else:
+ logger.debug("xmlrpc_rw is disabled in the settings file")
+ do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger)
+
+def do_mandatory_xmlrpc_tasks(bootapi,settings,xmlrpc_port,logger):
+ pid3 = os.fork()
+ if pid3 == 0:
do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
+ else:
+ do_xmlrpc_unix(bootapi, settings, logger)
def do_other_tasks(bootapi, settings, syslog_port, logger):
@@ -95,7 +104,8 @@ 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.CobblerXMLRPCInterface(bootapi,logger)
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,True)
+
server = remote.CobblerXMLRPCServer(('', port))
server.logRequests = 0 # don't print stuff
log(logger, "XMLRPC running on %s" % port)
@@ -109,11 +119,27 @@ def do_xmlrpc(bootapi, settings, port, logger):
time.sleep(0.5)
def do_xmlrpc_rw(bootapi,settings,port,logger):
-
- xinterface = remote.CobblerReadWriteXMLRPCInterface(bootapi,logger)
+
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,False)
server = remote.CobblerReadWriteXMLRPCServer(('127.0.0.1', port))
server.logRequests = 0 # don't print stuff
- log(logger, "XMLRPC (read-write variant) running on %s" % port)
+ logger.debug("XMLRPC (read-write variant) running on %s" % port)
+ server.register_instance(xinterface)
+
+ while True:
+ try:
+ server.serve_forever()
+ except IOError:
+ # interrupted? try to serve again
+ time.sleep(0.5)
+
+def do_xmlrpc_unix(bootapi,settings,logger):
+
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True)
+ SOCKT = "/var/lib/cobbler/sock"
+ server = xmlrpclib2.UnixXMLRPCServer(SOCKT)
+ server.logRequests = 0 # don't print stuff
+ logger.debug("XMLRPC (socket variant) available on %s" % SOCKT)
server.register_instance(xinterface)
while True:
@@ -163,5 +189,13 @@ def do_syslog(bootapi, settings, port, logger):
if __name__ == "__main__":
- 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)
diff --git a/cobbler/collection.py b/cobbler/collection.py
index 8e6be39..339a4b2 100644
--- a/cobbler/collection.py
+++ b/cobbler/collection.py
@@ -35,6 +35,8 @@ class Collection(serializable.Serializable):
"""
self.config = config
self.clear()
+ self.log_func = self.config.api.log
+ self.lite_sync = None
def factory_produce(self,config,seed_data):
"""
@@ -96,7 +98,48 @@ class Collection(serializable.Serializable):
item = self.factory_produce(self.config,seed_data)
self.add(item)
- def add(self,ref,with_copy=False):
+
+ def rename(self,ref,newname,with_sync=True,with_triggers=False):
+ """
+ Allows an object "ref" to be given a newname without affecting the rest
+ of the object tree.
+ """
+
+ # make a copy of the object, but give it a new name.
+ oldname = ref.name
+ newref = ref.make_clone()
+ newref.set_name(newname)
+ self.add(newref)
+
+ # now descend to any direct ancestors and point them at the new object allowing
+ # the original object to be removed without orphanage. Direct ancestors
+ # will either be profiles or systems. Note that we do have to care as
+ # set_parent is only really meaningful for subprofiles. We ideally want a more
+ # generic set_parent.
+ kids = ref.get_children()
+ for k in kids:
+ if k.COLLECTION_TYPE == "distro":
+ raise CX(_("internal error, not expected to have distro child objects"))
+ elif k.COLLECTION_TYPE == "profile":
+ if k.parent != "":
+ 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)
+ 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)
+ elif k.COLLECTION_TYPE == "repo":
+ raise CX(_("internal error, not expected to have repo child objects"))
+ else:
+ raise CX(_("internal error, unknown child type (%s), cannot finish rename" % k.COLLECTION_TYPE))
+
+ # now delete the old version
+ self.remove(oldname, with_delete=True)
+ return True
+
+
+ def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=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
@@ -112,46 +155,66 @@ class Collection(serializable.Serializable):
So, in that case, don't run any triggers and don't deal with any actual files.
"""
+ if self.lite_sync is None:
+ self.lite_sync = action_litesync.BootLiteSync(self.config)
+
+ # migration path for old API parameter that I've renamed.
+ if with_copy and not save:
+ save = with_copy
+
+ if not save:
+ # for people that aren't quite aware of the API
+ # if not saving the object, you can't run these features
+ with_triggers = False
+ with_sync = False
+
if ref is None or not ref.is_valid():
raise CX(_("insufficient or invalid arguments supplied"))
if ref.COLLECTION_TYPE != self.collection_type():
raise CX(_("API error: storing wrong data type in collection"))
- if not with_copy:
+ if not save:
# don't need to run triggers, so add it already ...
self.listing[ref.name.lower()] = ref
# perform filesystem operations
- if with_copy:
+ if save:
+ self.log_func("saving %s %s" % (self.collection_type(), ref.name))
# failure of a pre trigger will prevent the object from being added
- self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type())
+ if with_triggers:
+ self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type())
self.listing[ref.name.lower()] = ref
# save just this item if possible, if not, save
# the whole collection
self.config.serialize_item(self, ref)
- lite_sync = action_litesync.BootLiteSync(self.config)
- if isinstance(ref, item_system.System):
- lite_sync.add_single_system(ref.name)
- elif isinstance(ref, item_profile.Profile):
- lite_sync.add_single_profile(ref.name)
- elif isinstance(ref, item_distro.Distro):
- lite_sync.add_single_distro(ref.name)
- elif isinstance(ref, item_repo.Repo):
- pass
- else:
- print _("Internal error. Object type not recognized: %s") % type(ref)
-
+ if with_sync:
+ if isinstance(ref, item_system.System):
+ self.lite_sync.add_single_system(ref.name)
+ elif isinstance(ref, item_profile.Profile):
+ self.lite_sync.add_single_profile(ref.name)
+ elif isinstance(ref, item_distro.Distro):
+ self.lite_sync.add_single_distro(ref.name)
+ elif isinstance(ref, item_repo.Repo):
+ pass
+ else:
+ print _("Internal error. Object type not recognized: %s") % type(ref)
+ if not with_sync and quick_pxe_update:
+ if isinstance(ref, item_system.System):
+ self.lite_sync.update_system_netboot_status(ref.name)
+
# save the tree, so if neccessary, scripts can examine it.
- self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type())
+ if with_triggers:
+ self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type())
# update children cache in parent object
parent = ref.get_parent()
if parent != None:
parent.children[ref.name] = ref
+
return True
def _run_triggers(self,ref,globber):
diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py
index b696738..f78dd64 100644
--- a/cobbler/collection_distros.py
+++ b/cobbler/collection_distros.py
@@ -31,25 +31,37 @@ class Distros(collection.Collection):
"""
return distro.Distro(config).from_datastruct(seed_data)
- def remove(self,name,with_delete=True):
+ def remove(self,name,with_delete=True,with_sync=True,with_triggers=True,recursive=False):
"""
Remove element named 'name' from the collection
"""
name = name.lower()
+
# first see if any Groups use this distro
- for v in self.config.profiles():
- if v.distro.lower() == name:
- raise CX(_("removal would orphan profile: %s") % v.name)
+ if not recursive:
+ for v in self.config.profiles():
+ if v.distro.lower() == name:
+ raise CX(_("removal would orphan profile: %s") % v.name)
+
obj = self.find(name=name)
if obj is not None:
+ if recursive:
+ kids = obj.get_children()
+ for k in kids:
+ self.config.api.remove_profile(k, recursive=True)
+
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/pre/*")
- lite_sync = action_litesync.BootLiteSync(self.config)
- lite_sync.remove_single_profile(name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/pre/*")
+ if with_sync:
+ lite_sync = action_litesync.BootLiteSync(self.config)
+ lite_sync.remove_single_profile(name)
del self.listing[name]
self.config.serialize_delete(self, obj)
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/post/*")
+ self.log_func("deleted distro %s" % name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/post/*")
return True
- raise CX(_("cannot delete object that does not exist"))
+ raise CX(_("cannot delete object that does not exist: %s") % name)
diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py
index b878ff9..f00a8dc 100644
--- a/cobbler/collection_profiles.py
+++ b/cobbler/collection_profiles.py
@@ -32,24 +32,40 @@ class Profiles(collection.Collection):
def factory_produce(self,config,seed_data):
return profile.Profile(config).from_datastruct(seed_data)
- def remove(self,name,with_delete=True):
+ def remove(self,name,with_delete=True,with_sync=True,with_triggers=True,recursive=False):
"""
Remove element named 'name' from the collection
"""
+
name = name.lower()
- for v in self.config.systems():
- if v.profile.lower() == name:
- raise CX(_("removal would orphan system: %s") % v.name)
+
+ if not recursive:
+ for v in self.config.systems():
+ if v.profile.lower() == name:
+ raise CX(_("removal would orphan system: %s") % v.name)
+
obj = self.find(name=name)
if obj is not None:
+ if recursive:
+ kids = obj.get_children()
+ for k in kids:
+ if k.COLLECTION_TYPE == "profile":
+ self.config.api.remove_profile(k, recursive=True)
+ else:
+ self.config.api.remove_system(k)
+
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/pre/*")
- lite_sync = action_litesync.BootLiteSync(self.config)
- lite_sync.remove_single_profile(name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/pre/*")
+ if with_sync:
+ lite_sync = action_litesync.BootLiteSync(self.config)
+ lite_sync.remove_single_profile(name)
del self.listing[name]
self.config.serialize_delete(self, obj)
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/post/*")
+ self.log_func("deleted profile %s" % name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/post/*")
return True
- raise CX(_("cannot delete an object that does not exist"))
+ raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py
index da1a3bd..44bef2a 100644
--- a/cobbler/collection_repos.py
+++ b/cobbler/collection_repos.py
@@ -36,7 +36,7 @@ class Repos(collection.Collection):
"""
return repo.Repo(config).from_datastruct(seed_data)
- def remove(self,name,with_delete=True):
+ def remove(self,name,with_delete=True,with_sync=True,with_triggers=True):
"""
Remove element named 'name' from the collection
"""
@@ -47,13 +47,16 @@ class Repos(collection.Collection):
obj = self.find(name=name)
if obj is not None:
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/pre/*")
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/pre/*")
del self.listing[name]
self.config.serialize_delete(self, obj)
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/post/*")
+ self.log_func("deleted repo %s" % name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/post/*")
return True
- raise CX(_("cannot delete an object that does not exist"))
+ raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py
index a871f9a..140a981 100644
--- a/cobbler/collection_systems.py
+++ b/cobbler/collection_systems.py
@@ -33,7 +33,7 @@ class Systems(collection.Collection):
"""
return system.System(config).from_datastruct(seed_data)
- def remove(self,name,with_delete=True):
+ def remove(self,name,with_delete=True,with_sync=True,with_triggers=True):
"""
Remove element named 'name' from the collection
"""
@@ -43,15 +43,19 @@ class Systems(collection.Collection):
if obj is not None:
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/pre/*")
- lite_sync = action_litesync.BootLiteSync(self.config)
- lite_sync.remove_single_system(name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/pre/*")
+ if with_sync:
+ lite_sync = action_litesync.BootLiteSync(self.config)
+ lite_sync.remove_single_system(name)
del self.listing[name]
self.config.serialize_delete(self, obj)
if with_delete:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/post/*")
+ self.log_func("deleted system %s" % name)
+ if with_triggers:
+ self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/post/*")
return True
- raise CX(_("cannot delete an object that does not exist"))
+ raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/commands.py b/cobbler/commands.py
new file mode 100644
index 0000000..d97a6e1
--- /dev/null
+++ b/cobbler/commands.py
@@ -0,0 +1,328 @@
+"""
+Command line handling for Cobbler.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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 optparse
+from cexceptions import *
+from rhpl.translate import _, N_, textdomain, utf8
+import sys
+
+HELP_FORMAT = "%-20s%s"
+
+#=============================================================
+
+class FunctionLoader:
+
+ """
+ The F'n Loader controls processing of cobbler commands.
+ """
+
+ def __init__(self):
+ """
+ When constructed the loader has no functions.
+ """
+ self.functions = {}
+
+ def add_func(self, obj):
+ """
+ Adds a CobblerFunction instance to the loader.
+ """
+ self.functions[obj.command_name()] = obj
+
+ def run(self, args):
+ """
+ Runs a command line sequence through the loader.
+ """
+
+ args = self.old_school_remap(args)
+
+ # if no args given, show all loaded fns
+ if len(args) == 1:
+ return self.show_options()
+ called_name = args[1].lower()
+
+ # also show avail options if command name is bogus
+ if not called_name in self.functions.keys():
+ return self.show_options()
+ fn = self.functions[called_name]
+
+ # some functions require args, if none given, show subcommands
+ #if len(args) == 2:
+ # no_args_rc = fn.no_args_handler()
+ # if no_args_rc:
+ # return True
+
+ # finally let the object parse its own args
+ loaded_ok = fn.parse_args(args)
+ if not loaded_ok:
+ raise CX(_("Invalid arguments"))
+ return fn.run()
+
+ def old_school_remap(self,args):
+ """
+ Replaces commands with common synonyms that should also work
+
+ Also maps commands like:
+ # cobbler system report foo to cobbler report --name=foo
+ to:
+ # cobblerr system report --name=foo
+
+ for backwards compat and usability reasons
+ """
+
+ # to do: handle synonyms
+ for ct in range(0,len(args)):
+ args[ct] = args[ct]
+ if args[ct].startswith("-"):
+ # stop synonym mapping after first option
+ break
+ # lowercase all args
+ args[ct] = args[ct].lower()
+ # delete means remove
+ # are there any other common synonyms?
+ if args[ct] == "delete":
+ args[ct] = "remove"
+
+ # special handling for reports follows:
+ if not "report" in args:
+ return args
+ ok = False
+ for x in ["distro","profile","system","repo"]:
+ if x in args:
+ ok = True
+ if not ok:
+ return args
+ idx = args.index("report")
+ if idx + 1 < len(args):
+ name = args[idx+1]
+ if name.find("--name") == -1:
+ args[idx+1] = "--name=%s" % name
+ return args
+
+ def show_options(self):
+ """
+ Prints out all loaded functions.
+ """
+
+ print "commands:"
+ print "========="
+
+ names = self.functions.keys()
+ names.sort()
+
+ for name in names:
+ help = self.functions[name].help_me()
+ if help != "":
+ print help
+
+#=============================================================
+
+class CobblerFunction:
+
+ def __init__(self,api):
+ """
+ Constructor requires a Cobbler API handle.
+ """
+ self.api = api
+
+ def command_name(self):
+ """
+ The name of the command, as to be entered by users.
+ """
+ return "unspecified"
+
+ def subcommands(self):
+ """
+ The names of any subcommands, such as "add", "edit", etc
+ """
+ return [ ]
+
+ def run(self):
+ """
+ Called after arguments are parsed. Return True for success.
+ """
+ return True
+
+ def add_options(self, parser, args):
+ """
+ Used by subclasses to add options. See subclasses for examples.
+ """
+ pass
+
+ def parse_args(self,args):
+ """
+ Processes arguments, called prior to run ... do not override.
+ """
+
+ accum = ""
+ for x in args[1:]:
+ if not x.startswith("-"):
+ accum = accum + "%s " % x
+ else:
+ break
+ p = optparse.OptionParser(usage="cobbler %s [ARGS]" % accum)
+ self.add_options(p, args)
+
+ # if using subcommands, ensure one and only one is used
+ subs = self.subcommands()
+ if len(subs) > 0:
+ count = 0
+ for x in subs:
+ if x in args:
+ count = count + 1
+ if count != 1:
+ print "usage:"
+ print "======"
+ for x in subs:
+ print "cobbler %s %s [ARGS|--help]" % (self.command_name(), x)
+ sys.exit(1)
+
+ (self.options, self.args) = p.parse_args(args)
+ return True
+
+ def object_manipulator_start(self,new_fn,collect_fn,subobject=False):
+ """
+ Boilerplate for objects that offer add/edit/delete/remove/copy functionality.
+ """
+
+ if "remove" in self.args:
+ recursive = False
+ # only applies to distros/profiles and is not supported elsewhere
+ if hasattr(self.options, "recursive"):
+ recursive = self.options.recursive
+ if not self.options.name:
+ raise CX(_("name is required"))
+ if not recursive:
+ collect_fn().remove(self.options.name,with_delete=True)
+ else:
+ collect_fn().remove(self.options.name,with_delete=True,recursive=True)
+ return None # signal that we want no further processing on the object
+
+ if "list" in self.args:
+ self.list_list(collect_fn())
+ return None
+
+ if "report" in self.args:
+ if self.options.name is None:
+ self.reporting_print_sorted(collect_fn())
+ else:
+ self.reporting_list_names2(collect_fn(),self.options.name)
+ return None
+
+ 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
+ obj = collect_fn().find(self.options.name)
+ if obj is None:
+ raise CX(_("object named (%s) not found") % self.options.name)
+
+ if not "copy" in self.args and not "rename" in self.args and self.options.name:
+ obj.set_name(self.options.name)
+
+ return obj
+
+ def object_manipulator_finish(self,obj,collect_fn, options):
+ """
+ Boilerplate for objects that offer add/edit/delete/remove/copy functionality.
+ """
+
+ if "copy" in self.args: # or "rename" in self.args:
+ if self.options.newname:
+ obj = obj.make_clone()
+ obj.set_name(self.options.newname)
+ else:
+ raise CX(_("--newname is required"))
+
+ opt_sync = not options.nosync
+ opt_triggers = not options.notriggers
+
+ if not ("rename" in self.args):
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers)
+ else:
+ rc = collect_fn().rename(obj, self.options.newname)
+ return rc
+
+ def reporting_sorter(self, a, b):
+ """
+ Used for sorting cobbler objects for report commands
+ """
+ return cmp(a.name, b.name)
+
+ def reporting_print_sorted(self, collection):
+ """
+ Prints all objects in a collection sorted by name
+ """
+ collection = [x for x in collection]
+ collection.sort(self.reporting_sorter)
+ for x in collection:
+ print x.printable()
+ return True
+
+ def reporting_list_names2(self, collection, name):
+ """
+ Prints a specific object in a collection.
+ """
+ obj = collection.find(name=name)
+ if obj is not None:
+ print obj.printable()
+ return True
+
+ def list_tree(self,collection,level):
+ """
+ Print cobbler object tree as a, well, tree.
+ """
+
+ def sorter(a,b):
+ return cmp(a.name,b.name)
+
+ collection2 = []
+ for c in collection:
+ collection2.append(c)
+ collection2.sort(sorter)
+
+ for item in collection2:
+ print _("%(indent)s%(type)s %(name)s") % {
+ "indent" : " " * level,
+ "type" : item.TYPE_NAME,
+ "name" : item.name
+ }
+ kids = item.get_children()
+ if kids is not None and len(kids) > 0:
+ self.list_tree(kids,level+1)
+
+ def list_list(self, collection):
+ """
+ List all objects of a certain type.
+ """
+ names = [ x.name for x in collection]
+ names.sort() # sorted() is 2.4 only
+ for name in names:
+ str = _(" %(name)s") % { "name" : name }
+ print str
+ return True
+
+ def matches_args(self, args, list_of):
+ """
+ Used to simplify some code around which arguments to add when.
+ """
+ for x in args:
+ if x in list_of:
+ return True
+ return False
+
+
diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py
index 8a91012..0fa058b 100644
--- a/cobbler/demo_connect.py
+++ b/cobbler/demo_connect.py
@@ -1,15 +1,7 @@
#!/usr/bin/python
"""
-work in progress: demo connection code for Cobbler read-write API
-uses SSL+XMLRPC (though just XMLRPC will still work)
-adapted from Virt-Factory's old vf_nodecomm source
-XMLRPCSSL portions based on http://linux.duke.edu/~icon/misc/xmlrpcssl.py
-
Copyright 2007, Red Hat, Inc
-Michael DeHaan <mdehaan@redhat.com>
-Adrian Likins <alikins@redhat.com>
-Scott Seago <sseago@redhat.com>
This software may be freely redistributed under the terms of the GNU
general public license.
@@ -19,40 +11,11 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-import sys
-import socket
-
-from M2Crypto import SSL
-from M2Crypto.m2xmlrpclib import SSL_Transport, Server
-from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
-
-
-# workaround for bz #237902
-class CobblerTransport(SSL_Transport):
- def __init__(self, ssl_context=None, use_datetime=0):
- self._use_datetime = use_datetime
- SSL_Transport.__init__(self,ssl_context=ssl_context)
-
-def demo_connect(username,password,server):
- my_ctx = SSL.Context('sslv23')
- # my_ctx.load_client_ca("foo.pem")
- # my_ctx.load_cert("bar.pem","baz.pem")
- my_ctx.set_session_id_ctx("xmlrpcssl")
- my_ctx.set_allow_unknown_ca(True)
- my_ctx.set_verify(0,-1) # full anonymous (we hope)
- my_uri = "https://%s:443/cobbler_api_rw" % server
- print "connecting to: %s" % my_uri
- my_rserver = Server(my_uri, CobblerTransport(ssl_context = my_ctx))
- token = my_rserver.login(username,password)
- print "got a token: %s" % token
- rc = my_rserver.test(token)
- print "got test results: %s" % rc
+from server.xmlrpcclient import ServerProxy
if __name__ == "__main__":
- USERNAME = "mdehaan"
- PASSWORD = "llamas2007"
- SERVER = "mdehaan.rdu.redhat.com"
- demo_connect(USERNAME,PASSWORD,SERVER)
+ sp = ServerProxy("httpu:///var/lib/cobbler/sock")
+ print sp.login("<system>","")
diff --git a/cobbler/item.py b/cobbler/item.py
index 5ec77a2..dadcd23 100644
--- a/cobbler/item.py
+++ b/cobbler/item.py
@@ -52,7 +52,7 @@ class Item(serializable.Serializable):
self.parent = '' # all objects by default are not subobjects
self.children = {} # caching for performance reasons, not serialized
-
+ self.log_func = self.config.api.log
def clear(self):
raise exceptions.NotImplementedError
diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py
index 60b4cfc..f52ad0b 100644
--- a/cobbler/item_distro.py
+++ b/cobbler/item_distro.py
@@ -143,9 +143,12 @@ class Distro(item.Item):
"""
# NOTE: this code does not support inheritable distros at this time.
# this is by design because inheritable distros do not make sense.
- for x in (self.name,self.kernel,self.initrd):
- if x is None:
- return False
+ if self.name is None:
+ raise CX(_("name is required"))
+ if self.kernel is None:
+ raise CX(_("kernel is required"))
+ if self.initrd is None:
+ raise CX(_("initrd is required"))
return True
def to_datastruct(self):
diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py
index ddb36ba..f7d1d98 100644
--- a/cobbler/item_profile.py
+++ b/cobbler/item_profile.py
@@ -39,8 +39,8 @@ class Profile(item.Item):
self.kernel_options = ({}, '<<inherit>>')[is_subobject]
self.ks_meta = ({}, '<<inherit>>')[is_subobject]
self.virt_cpus = (1, '<<inherit>>')[is_subobject]
- self.virt_file_size = (5, '<<inherit>>')[is_subobject]
- self.virt_ram = (512, '<<inherit>>')[is_subobject]
+ self.virt_file_size = (self.settings.default_virt_file_size, '<<inherit>>')[is_subobject]
+ self.virt_ram = (self.settings.default_virt_ram, '<<inherit>>')[is_subobject]
self.repos = ([], '<<inherit>>')[is_subobject]
self.depth = 1
self.virt_type = (self.settings.default_virt_type, '<<inherit>>')[is_subobject]
@@ -72,8 +72,8 @@ class Profile(item.Item):
self.set_parent(self.parent)
# virt specific
- self.virt_ram = self.load_item(seed_data,'virt_ram',512)
- self.virt_file_size = self.load_item(seed_data,'virt_file_size',5)
+ self.virt_ram = self.load_item(seed_data,'virt_ram',self.settings.default_virt_ram)
+ self.virt_file_size = self.load_item(seed_data,'virt_file_size',self.settings.default_virt_file_size)
self.virt_path = self.load_item(seed_data,'virt_path')
self.virt_type = self.load_item(seed_data,'virt_type', self.settings.default_virt_type)
self.virt_bridge = self.load_item(seed_data,'virt_bridge', self.settings.default_virt_bridge)
@@ -205,12 +205,10 @@ class Profile(item.Item):
def set_virt_file_size(self,num):
"""
For Virt only.
- Specifies the size of the virt image in gigabytes. koan
- may contain some logic to ignore 'illogical' values of this size,
- though there are no guarantees. 0 tells koan to just
- let it pick a semi-reasonable size. When in doubt, specify the
- size you want.
- """
+ 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
@@ -270,7 +268,7 @@ class Profile(item.Item):
self.virt_type == "<<inherit>>"
return True
- if vtype.lower() not in [ "qemu", "xenpv", "auto" ]:
+ if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
raise CX(_("invalid virt type"))
self.virt_type = vtype
return True
diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py
index 0c486b0..a7b1f3b 100644
--- a/cobbler/item_repo.py
+++ b/cobbler/item_repo.py
@@ -33,20 +33,24 @@ class Repo(item.Item):
self.name = None
self.mirror = (None, '<<inherit>>')[is_subobject]
self.keep_updated = ('y', '<<inherit>>')[is_subobject]
+ self.priority = (99, '<<inherit>>')[is_subobject]
self.rpm_list = ("", '<<inherit>>')[is_subobject]
self.createrepo_flags = ("-c cache", '<<inherit>>')[is_subobject]
self.depth = 2 # arbitrary, as not really apart of the graph
self.arch = "" # use default arch
+ self.yumopts = {}
def from_datastruct(self,seed_data):
self.parent = self.load_item(seed_data, 'parent')
self.name = self.load_item(seed_data, 'name')
self.mirror = self.load_item(seed_data, 'mirror')
self.keep_updated = self.load_item(seed_data, 'keep_updated','y')
+ self.priority = self.load_item(seed_data, 'priority',99)
self.rpm_list = self.load_item(seed_data, 'rpm_list')
self.createrepo_flags = self.load_item(seed_data, 'createrepo_flags', '-c cache')
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', {})
# force this to be saved as a boolean
self.set_keep_updated(self.keep_updated)
@@ -81,6 +85,30 @@ class Repo(item.Item):
self.keep_updated = False
return True
+ def set_yumopts(self,options):
+ """
+ Kernel options are a space delimited list,
+ like 'a=b c=d e=f g h i=j' or a hash.
+ """
+ (success, value) = utils.input_string_or_hash(options,None)
+ if not success:
+ raise CX(_("invalid yum options"))
+ else:
+ self.yumopts = value
+ return True
+
+ def set_priority(self,priority):
+ """
+ Set the priority of the repository. 1= highest, 99=default
+ Only works if host is using priorities plugin for yum.
+ """
+ try:
+ priority = int(str(priority))
+ except:
+ raise CX(_("invalid priority level: %s") % priority)
+ self.priority = priority
+ return True
+
def set_rpm_list(self,rpms):
"""
Rather than mirroring the entire contents of a repository (Fedora Extras, for instance,
@@ -118,9 +146,9 @@ class Repo(item.Item):
A repo is valid if it has a name and a mirror URL
"""
if self.name is None:
- return False
+ raise CX(_("name is required"))
if self.mirror is None:
- return False
+ raise CX(_("mirror is required"))
if self.mirror.startswith("rhn://"):
# reposync creates directories based on the channel name so this
# prevents a lot of ugly special case handling if we make the
@@ -135,20 +163,24 @@ class Repo(item.Item):
'name' : self.name,
'mirror' : self.mirror,
'keep_updated' : self.keep_updated,
+ 'priority' : self.priority,
'rpm_list' : self.rpm_list,
'createrepo_flags' : self.createrepo_flags,
'arch' : self.arch,
'parent' : self.parent,
- 'depth' : self.depth
+ 'depth' : self.depth,
+ 'yumopts' : self.yumopts
}
def printable(self):
buf = _("repo : %s\n") % self.name
buf = buf + _("mirror : %s\n") % self.mirror
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
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
def get_parent(self):
@@ -175,6 +207,9 @@ class Repo(item.Item):
'mirror-name' : self.set_name,
'mirror' : self.set_mirror,
'keep-updated' : self.set_keep_updated,
+ 'priority' : self.set_priority,
'rpm-list' : self.set_rpm_list,
- 'createrepo-flags' : self.set_createrepo_flags
+ 'createrepo-flags' : self.set_createrepo_flags,
+ 'yumopts' : self.set_yumopts
}
+
diff --git a/cobbler/item_system.py b/cobbler/item_system.py
index 4bfd763..936f1dd 100644
--- a/cobbler/item_system.py
+++ b/cobbler/item_system.py
@@ -275,7 +275,7 @@ class System(item.Item):
"""
Virtualization preference, can be overridden by koan.
"""
- if vtype.lower() not in [ "qemu", "xenpv", "auto" ]:
+ if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
raise CX(_("invalid virt type"))
self.virt_type = vtype
return True
diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py
index bb94fec..aafe05f 100644
--- a/cobbler/module_loader.py
+++ b/cobbler/module_loader.py
@@ -73,11 +73,22 @@ def load_modules(module_path=mod_path, blacklist=None):
def get_module_by_name(name):
return MODULE_CACHE.get(name, None)
-def get_module_from_file(category,field):
-
- value = cp.get("serializers",field)
+def get_module_from_file(category,field,fallback_module_name=None):
+
+ try:
+ value = cp.get("serializers",field)
+ except:
+ if fallback_module_name is not None:
+ value = fallback_module_name
+ else:
+ raise CX(_("Cannot find config file setting for: %s") % field)
return MODULE_CACHE.get(value, None)
+def get_modules_in_category(category):
+ if not MODULES_BY_CATEGORY.has_key(category):
+ return []
+ return MODULES_BY_CATEGORY[category].values()
+
if __name__ == "__main__":
print load_modules(module_path)
diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py
new file mode 100644
index 0000000..ffe87a7
--- /dev/null
+++ b/cobbler/modules/authn_configfile.py
@@ -0,0 +1,76 @@
+"""
+Authentication module that uses /etc/cobbler/auth.conf
+Choice of authentication module is in /etc/cobbler/modules.conf
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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
+
+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 __parse_storage():
+
+ if not os.path.exists("/etc/cobbler/users.digest"):
+ return []
+ fd = open("/etc/cobbler/users.digest")
+ data = fd.read()
+ fd.close()
+ results = []
+ lines = data.split("\n")
+ for line in lines:
+ try:
+ line = line.strip()
+ tokens = line.split(":")
+ results.append([tokens[0],tokens[1],tokens[2]])
+ except:
+ pass
+ return results
+
+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))
+
+ userlist = __parse_storage()
+ for (user,realm,actual_blob) in userlist:
+ if user == username and realm == "Cobbler":
+ input = ":".join([user,realm,password])
+ input_blob = md5.md5(input).hexdigest()
+ if input_blob.lower() == actual_blob.lower():
+ return True
+
+ return False
+
+
diff --git a/cobbler/modules/authn_kerberos.py b/cobbler/modules/authn_kerberos.py
new file mode 100644
index 0000000..7f85db6
--- /dev/null
+++ b/cobbler/modules/authn_kerberos.py
@@ -0,0 +1,81 @@
+"""
+Authentication module that uses kerberos.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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 rhpl.translate import _, N_, textdomain, utf8
+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 = self.api.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/authz_allowall.py b/cobbler/modules/authz_allowall.py
new file mode 100644
index 0000000..1b05630
--- /dev/null
+++ b/cobbler/modules/authz_allowall.py
@@ -0,0 +1,41 @@
+"""
+Authorization module that allows everything, which is the default
+for new cobbler installs.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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
+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 authorize(api_handle,user,resource,arg1=None,arg2=None):
+ """
+ Validate a user against a resource.
+ """
+ return True
+
+
diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py
new file mode 100644
index 0000000..35f5a4b
--- /dev/null
+++ b/cobbler/modules/cli_distro.py
@@ -0,0 +1,95 @@
+"""
+Distro CLI module.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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)
+
+from rhpl.translate import _, N_, textdomain, utf8
+import commands
+import cexceptions
+
+
+class DistroFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return commands.HELP_FORMAT % ("cobbler distro", "<add|edit|copy|list|rename|remove|report> [ARGS|--help]")
+
+ def command_name(self):
+ return "distro"
+
+ def subcommands(self):
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+
+ def add_options(self, p, args):
+
+ 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")
+ 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'")
+ p.add_option("--ksmeta", dest="ksmeta", help="ex: 'blippy=7'")
+
+ 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 self.matches_args(args,["remove"]):
+ p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects")
+
+ def run(self):
+
+ obj = self.object_manipulator_start(self.api.new_distro,self.api.distros)
+ 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)
+
+ return self.object_manipulator_finish(obj, self.api.distros, self.options)
+
+
+
+########################################################
+# MODULE HOOKS
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "cli"
+
+def cli_functions(api):
+ return [
+ DistroFunction(api)
+ ]
+
+
diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py
new file mode 100644
index 0000000..881787b
--- /dev/null
+++ b/cobbler/modules/cli_misc.py
@@ -0,0 +1,259 @@
+"""
+Misc CLI functions.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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)
+
+from rhpl.translate import _, N_, textdomain, utf8
+import commands
+from cexceptions import *
+HELP_FORMAT = commands.HELP_FORMAT
+
+# TO DO list
+# cobbler check
+# cobbler import (--name, --mirror, --available-as)
+# cobbler reserialize
+# cobbler --type=[profile|system|distro|repo] [--name=list]
+# cobbler --type=[profile|system|distro|profile] [--name=report]
+# cobbler status
+# cobbler reposync --name=$name
+# cobbler sync
+# cobbler validateks
+# elsewhere: repo auto-add
+
+########################################################
+
+class CheckFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler check","")
+
+ def command_name(self):
+ return "check"
+
+ def add_options(self, p, args):
+ pass
+
+ def run(self):
+ status = self.api.check()
+ if len(status) == 0:
+ print _("No setup problems found")
+ print _("Manual review and editing of /var/lib/cobbler/settings is recommended to tailor cobbler to your particular configuration.")
+ print _("Good luck.")
+ return True
+ else:
+ print _("The following potential problems were detected:")
+ for i,x in enumerate(status):
+ print _("#%(number)d: %(problem)s") % { "number" : i, "problem" : x }
+ return False
+
+########################################################
+
+class ImportFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler import","[ARGS|--help]")
+
+ def command_name(self):
+ return "import"
+
+ def add_options(self, p, args):
+ p.add_option("--mirror", dest="mirror", help="local path or rsync location (REQUIRED)")
+ 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("--rsync-flags", dest="rsync_flags", help="pass additional flags to rsync")
+
+ def run(self):
+ if not self.options.mirror:
+ raise CX(_("mirror is required"))
+ if not self.options.name:
+ raise CX(_("name is required"))
+ return self.api.import_tree(
+ self.options.mirror,
+ self.options.name,
+ network_root=self.options.available_as,
+ kickstart_file=self.options.kickstart_file,
+ rsync_flags=self.options.rsync_flags
+ )
+
+
+########################################################
+
+class ReserializeFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return "" # hide
+
+ def command_name(self):
+ return "reserialize"
+
+ def run(self):
+ # already deserialized when API is instantiated
+ # this just saves files in new config format (if any)
+ return self.api.serialize()
+
+########################################################
+
+class ListFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler list","[ARGS|--help]")
+
+ def command_name(self):
+ return "list"
+
+ def add_options(self, p, args):
+ p.add_option("--what", dest="what", default="all", help="all/distros/profiles/systems/repos")
+
+ def run(self):
+ if self.options.what not in [ "all", "distros", "profiles", "systems", "repos" ]:
+ raise CX(_("invalid value for --what"))
+ if self.options.what in [ "all" ]:
+ self.list_tree(self.api.distros(),0)
+ self.list_tree(self.api.repos(),0)
+ if self.options.what in [ "distros"]:
+ self.list_list(self.api.distros())
+ if self.options.what in [ "profiles"]:
+ self.list_list(self.api.profiles())
+ if self.options.what in [ "systems" ]:
+ self.list_list(self.api.systems())
+ if self.options.what in [ "repos"]:
+ self.list_list(self.api.repos())
+
+########################################################
+
+class ReportFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler report","[ARGS|--help]")
+
+ def command_name(self):
+ return "report"
+
+ def add_options(self, p, args):
+ p.add_option("--what", dest="what", default="all", help="distros/profiles/systems/repos")
+ p.add_option("--name", dest="name", help="report on just this object")
+
+ def run(self):
+ if self.options.what not in [ "all", "distros", "profiles", "systems", "repos" ]:
+ raise CX(_("Invalid value for --what"))
+
+ if self.options.what in [ "all", "distros" ]:
+ if self.options.name:
+ self.reporting_list_names2(self.api.distros(),self.options.name)
+ else:
+ self.reporting_print_sorted(self.api.distros())
+
+ if self.options.what in [ "all", "profiles" ]:
+ if self.options.name:
+ self.reporting_list_names2(self.api.profiles(),self.options.name)
+ else:
+ self.reporting_print_sorted(self.api.profiles())
+
+ if self.options.what in [ "all", "systems" ]:
+ if self.options.name:
+ self.reporting_list_names2(self.api.systems(),self.options.name)
+ else:
+ self.reporting_print_sorted(self.api.systems())
+
+ if self.options.what in [ "all", "repos" ]:
+ if self.options.name:
+ self.reporting_list_names2(self.api.repos(),self.options.name)
+ else:
+ 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):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler status","[ARGS|--help]")
+
+ def command_name(self):
+ return "status"
+
+ def run(self):
+ return self.api.status("text") # no other output modes supported yet
+
+########################################################
+
+class SyncFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler sync","")
+
+ def command_name(self):
+ return "sync"
+
+ def run(self):
+ return self.api.sync()
+
+########################################################
+
+class RepoSyncFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler reposync","[ARGS|--help]")
+
+ def command_name(self):
+ return "reposync"
+
+ def add_options(self, p, args):
+ p.add_option("--only", dest="only", help="update only this repository name")
+
+ def run(self):
+ return self.api.reposync(self.options.only)
+
+########################################################
+
+class ValidateKsFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler validateks","")
+
+ def command_name(self):
+ return "validateks"
+
+ def run(self):
+ return self.api.validateks()
+
+########################################################
+# MODULE HOOKS
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "cli"
+
+def cli_functions(api):
+ return [
+ CheckFunction(api), ImportFunction(api), ReserializeFunction(api),
+ ListFunction(api), ReportFunction(api), StatusFunction(api),
+ SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api)
+ ]
+ return []
+
+
diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py
new file mode 100644
index 0000000..e9d1d23
--- /dev/null
+++ b/cobbler/modules/cli_profile.py
@@ -0,0 +1,114 @@
+"""
+Profile CLI module.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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)
+
+from rhpl.translate import _, N_, textdomain, utf8
+import commands
+import cexceptions
+
+
+class ProfileFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return commands.HELP_FORMAT % ("cobbler profile","<add|edit|copy|list|rename|remove|report> [ARGS|--help]")
+
+ def command_name(self):
+ return "profile"
+
+ def subcommands(self):
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+
+ def add_options(self, p, args):
+ if not self.matches_args(args,["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")
+ p.add_option("--inherit", dest="inherit", help="inherit from this profile name, defaults to no")
+ p.add_option("--kickstart", dest="kickstart", help="absolute path to kickstart template (RECOMMENDED)")
+ p.add_option("--ksmeta", dest="ksmeta", help="ex: 'blippy=7'")
+ 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")
+
+ 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 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"]):
+ 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'")
+ 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):
+
+
+ if self.matches_args(self.args,["report","list","remove"]) 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)
+
+ 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)
+
+ return self.object_manipulator_finish(obj, self.api.profiles, self.options)
+
+
+
+########################################################
+# MODULE HOOKS
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "cli"
+
+def cli_functions(api):
+ return [
+ ProfileFunction(api)
+ ]
+
+
diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py
new file mode 100644
index 0000000..96afa6f
--- /dev/null
+++ b/cobbler/modules/cli_repo.py
@@ -0,0 +1,97 @@
+"""
+Repo CLI module.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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)
+
+from rhpl.translate import _, N_, textdomain, utf8
+import commands
+import cexceptions
+
+
+class RepoFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return commands.HELP_FORMAT % ("cobbler repo","<add|edit|copy|list|rename|remove|report> [ARGS|--help]")
+
+ def command_name(self):
+ return "repo"
+
+ def subcommands(self):
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+
+ 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")
+ 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"]):
+ 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")
+ p.add_option("--yumopts", dest="yumopts", help="ex: pluginvar=abcd")
+
+ if self.matches_args(args,["copy","rename"]):
+
+ p.add_option("--newname", dest="newname", help="used for copy/edit")
+
+ 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")
+
+
+ def run(self):
+
+ obj = self.object_manipulator_start(self.api.new_repo,self.api.repos)
+ if obj is None:
+ return True
+
+ if self.options.arch: obj.set_arch(self.options.arch)
+ if self.options.createrepo_flags: obj.set_createrepo_flags(self.options.createrepo_flags)
+ if self.options.rpm_list: obj.set_rpm_list(self.options.rpm_list)
+ 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.yumopts: obj.set_yumopts(self.options.yumopts)
+
+ return self.object_manipulator_finish(obj, self.api.repos, self.options)
+
+
+
+########################################################
+# MODULE HOOKS
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "cli"
+
+def cli_functions(api):
+ return [
+ RepoFunction(api)
+ ]
+ return []
+
+
diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py
new file mode 100644
index 0000000..c463b8c
--- /dev/null
+++ b/cobbler/modules/cli_system.py
@@ -0,0 +1,120 @@
+"""
+System CLI module.
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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)
+
+from rhpl.translate import _, N_, textdomain, utf8
+import commands
+import cexceptions
+
+
+class SystemFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return commands.HELP_FORMAT % ("cobbler system","<add|edit|copy|list|rename|remove|report> [ARGS|--help]")
+
+ def command_name(self):
+ return "system"
+
+ def subcommands(self):
+ return [ "add", "edit", "copy", "rename", "remove", "report", "list" ]
+
+ def add_options(self, p, args):
+
+ 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")
+ p.add_option("--hostname", dest="hostname", help="ex: server.example.org")
+ p.add_option("--interface", dest="interface", help="edit this interface # (0-7, default 0)")
+ p.add_option("--ip", dest="ip", help="ex: 192.168.1.55, (RECOMMENDED)")
+ p.add_option("--kickstart", dest="kickstart", help="override profile kickstart template")
+ p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'")
+ p.add_option("--ksmeta", dest="ksmeta", help="ex: 'blippy=7'")
+ p.add_option("--mac", dest="mac", help="ex: 'AA:BB:CC:DD:EE:FF', (RECOMMENDED)")
+
+ p.add_option("--name", dest="name", help="a name for the system (REQUIRED)")
+
+ if not self.matches_args(args,["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"]):
+ 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("--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")
+
+
+ def run(self):
+
+ obj = self.object_manipulator_start(self.api.new_system,self.api.systems)
+ if obj is None:
+ return True
+
+ if self.options.profile: obj.set_profile(self.options.profile)
+ if self.options.kopts: obj.set_kernel_options(self.options.kopts)
+ if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta)
+ 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_type: obj.set_virt_type(self.options.virt_type)
+
+ if self.options.interface:
+ my_interface = "intf%s" % self.options.interface
+ else:
+ 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.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)
+ 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)
+
+ return self.object_manipulator_finish(obj, self.api.systems, self.options)
+
+
+
+########################################################
+# MODULE HOOKS
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "cli"
+
+def cli_functions(api):
+ return [
+ SystemFunction(api)
+ ]
+
+
diff --git a/cobbler/remote.py b/cobbler/remote.py
index ff8d2db..57570aa 100644
--- a/cobbler/remote.py
+++ b/cobbler/remote.py
@@ -20,10 +20,10 @@ import os
import SimpleXMLRPCServer
from rhpl.translate import _, N_, textdomain, utf8
import xmlrpclib
-import logging
-import ConfigParser
import random
import base64
+import string
+import traceback
import api as cobbler_api
import utils
@@ -33,17 +33,11 @@ import item_profile
import item_system
import item_repo
-config_parser = ConfigParser.ConfigParser()
-auth_conf = open("/etc/cobbler/auth.conf")
-config_parser.readfp(auth_conf)
-auth_conf.close()
-
-user_database = config_parser.items("xmlrpc_service_users")
-
-
# FIXME: make configurable?
TOKEN_TIMEOUT = 60*60 # 60 minutes
OBJECT_TIMEOUT = 60*60 # 60 minutes
+TOKEN_CACHE = {}
+OBJECT_CACHE = {}
# *********************************************************************
# *********************************************************************
@@ -59,9 +53,10 @@ class CobblerXMLRPCInterface:
interface are intentionally /not/ validated. It's a public API.
"""
- def __init__(self,api,logger):
+ def __init__(self,api,logger,enable_auth_if_relevant):
self.api = api
self.logger = logger
+ self.auth_enabled = enable_auth_if_relevant
def __sorter(self,a,b):
return cmp(a["name"],b["name"])
@@ -69,6 +64,55 @@ class CobblerXMLRPCInterface:
def ping(self):
return True
+ def get_user_from_token(self,token):
+ if not TOKEN_CACHE.has_key(token):
+ raise CX(_("invalid token: %s") % token)
+ else:
+ return self.token_cache[token][1]
+
+ def log(self,msg,user=None,token=None,name=None,object_id=None,attribute=None,debug=False,error=False):
+
+ # add the user editing the object, if supplied
+ m_user = "?"
+ if user is not None:
+ m_user = user
+ if token is not None:
+ try:
+ m_user = self.get_user_from_token(token)
+ except:
+ # invalid or expired token?
+ m_user = "???"
+ msg = "%s; user(%s)" % (msg, m_user)
+
+ # add the object name being modified, if any
+ oname = ""
+ if name:
+ oname = name
+ elif object_id:
+ try:
+ (objref, time) = self.object_cache[object_id]
+ oname = objref.name
+ if oname == "" or oname is None:
+ oname = "???"
+ except:
+ oname = "*EXPIRED*"
+ if oname != "":
+ msg = "%s; object(%s)" % (msg, oname)
+
+
+ # add any attributes being modified, if any
+ if attribute:
+ msg = "%s; attribute(%s)" % (msg, attribute)
+
+ # log to the correct logger
+ if error:
+ logger = self.logger.error
+ elif debug:
+ logger = self.logger.debug
+ else:
+ logger = self.logger.info
+ logger(msg)
+
def get_size(self,collection_name):
"""
Returns the number of entries in a collection (but not the actual
@@ -100,16 +144,12 @@ class CobblerXMLRPCInterface:
if page is not None and results_per_page is not None:
page = int(page)
results_per_page = int(results_per_page)
- self.logger.debug("PAGE = %s" % page)
- self.logger.debug("RPP = %s" % results_per_page)
if page < 0:
return []
if results_per_page <= 0:
return []
start_point = (results_per_page * page)
end_point = (results_per_page * page) + results_per_page
- self.logger.debug("START = %s" % start_point)
- self.logger.debug("END = %s" % end_point)
if start_point > total_items:
start_point = total_items - 1 # correct ???
if end_point > total_items:
@@ -122,6 +162,7 @@ class CobblerXMLRPCInterface:
"""
Return the contents of /var/lib/cobbler/settings, which is a hash.
"""
+ self.log("get_settings",token=token)
return self.__get_all("settings")
def disable_netboot(self,name,token=None):
@@ -130,6 +171,7 @@ class CobblerXMLRPCInterface:
Sets system named "name" to no-longer PXE. Disabled by default as
this requires public API access and is technically a read-write operation.
"""
+ self.log("disable_netboot",token=token,name=name)
# used by nopxe.cgi
self.api.clear()
self.api.deserialize()
@@ -142,7 +184,30 @@ class CobblerXMLRPCInterface:
# system not found!
return False
obj.set_netboot_enabled(0)
- systems.add(obj,with_copy=True)
+ # disabling triggers and sync to make this extremely fast.
+ 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):
+ """
+ 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.
+ """
+ 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!
+ return False
+ systems = self.api.systems()
+ obj = systems.find(name=name)
+ if obj == None:
+ # system not found!
+ return False
+ utils.run_triggers(obj, "/var/lib/cobbler/triggers/install/post/*")
return True
def _refresh(self):
@@ -160,30 +225,35 @@ class CobblerXMLRPCInterface:
Return the cobbler version for compatibility testing with remote applications.
Returns as a float, 0.6.1-2 should result in (int) "0.612".
"""
+ self.log("version",token=token)
return self.api.version()
def get_distros(self,page=None,results_per_page=None,token=None):
"""
Returns all cobbler distros as an array of hashes.
"""
+ self.log("get_distros",token=token)
return self.__get_all("distro",page,results_per_page)
def get_profiles(self,page=None,results_per_page=None,token=None):
"""
Returns all cobbler profiles as an array of hashes.
"""
+ self.log("get_profiles",token=token)
return self.__get_all("profile",page,results_per_page)
def get_systems(self,page=None,results_per_page=None,token=None):
"""
Returns all cobbler systems as an array of hashes.
"""
+ self.log("get_systems",token=token)
return self.__get_all("system",page,results_per_page)
def get_repos(self,page=None,results_per_page=None,token=None):
"""
Returns all cobbler repos as an array of hashes.
"""
+ self.log("get_repos",token=token)
return self.__get_all("repo",page,results_per_page)
def __get_specific(self,collection_fn,name,flatten=False):
@@ -204,24 +274,28 @@ class CobblerXMLRPCInterface:
"""
Returns the distro named "name" as a hash.
"""
+ self.log("get_distro",token=token,name=name)
return self.__get_specific(self.api.distros,name,flatten=flatten)
def get_profile(self,name,flatten=False,token=None):
"""
Returns the profile named "name" as a hash.
"""
+ self.log("get_profile",token=token,name=name)
return self.__get_specific(self.api.profiles,name,flatten=flatten)
def get_system(self,name,flatten=False,token=None):
"""
Returns the system named "name" as a hash.
"""
+ self.log("get_system",name=name,token=token)
return self.__get_specific(self.api.systems,name,flatten=flatten)
def get_repo(self,name,flatten=False,token=None):
"""
Returns the repo named "name" as a hash.
"""
+ self.log("get_repo",name=name,token=token)
return self.__get_specific(self.api.repos,name,flatten=flatten)
def get_distro_as_rendered(self,name,token=None):
@@ -236,6 +310,7 @@ class CobblerXMLRPCInterface:
"""
Same as get_distro_as_rendered.
"""
+ self.log("get_distro_as_rendered",name=name,token=token)
self._refresh()
obj = self.api.distros().find(name=name)
if obj is not None:
@@ -254,6 +329,7 @@ class CobblerXMLRPCInterface:
"""
Same as get_profile_as_rendered
"""
+ self.log("get_profile_as_rendered", name=name, token=token)
self._refresh()
obj = self.api.profiles().find(name=name)
if obj is not None:
@@ -272,6 +348,7 @@ class CobblerXMLRPCInterface:
"""
Same as get_system_as_rendered.
"""
+ self.log("get_system_as_rendered",name=name,token=token)
self._refresh()
obj = self.api.systems().find(name=name)
if obj is not None:
@@ -290,13 +367,14 @@ class CobblerXMLRPCInterface:
"""
Same as get_repo_as_rendered.
"""
+ self.log("get_repo_as_rendered",name=name,token=token)
self._refresh()
obj = self.api.repos().find(name=name)
if obj is not None:
return self._fix_none(utils.blender(self.api, True, obj))
return self._fix_none({})
- def get_random_mac(self):
+ def get_random_mac(self,token=None):
"""
Generate a random MAC address.
from xend/server/netif.py
@@ -305,6 +383,7 @@ class CobblerXMLRPCInterface:
Xensource, Inc. Last 3 fields are random.
return: MAC address string
"""
+ self.log("get_random_mac",token=None)
self._refresh()
mac = [ 0x00, 0x16, 0x3e,
random.randint(0x00, 0x7f),
@@ -346,13 +425,37 @@ class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
# *********************************************************************************
# *********************************************************************************
+
+class ProxiedXMLRPCInterface:
+
+ def __init__(self,api,logger,proxy_class,enable_auth_if_relevant=True):
+ self.logger = logger
+ self.proxied = proxy_class(api,logger,enable_auth_if_relevant)
+
+ def _dispatch(self, method, params):
+
+ if not hasattr(self.proxied, method):
+ self.logger.error("remote:unknown method %s" % method)
+ raise CX(_("Unknown remote method"))
+
+ method_handle = getattr(self.proxied, method)
+
+ try:
+ return method_handle(*params)
+ except Exception, e:
+ utils.log_exc(self.logger)
+ raise e
+
+# **********************************************************************
+
class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
- def __init__(self,api,logger):
+ def __init__(self,api,logger,enable_auth_if_relevant):
self.api = api
+ self.auth_enabled = enable_auth_if_relevant
self.logger = logger
- self.token_cache = {}
- self.object_cache = {}
+ self.token_cache = TOKEN_CACHE
+ self.object_cache = OBJECT_CACHE
random.seed(time.time())
def __next_id(self,retry=0):
@@ -374,12 +477,12 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
urandom.close()
return b64
- def __make_token(self):
+ def __make_token(self,user):
"""
Returns a new random token.
"""
b64 = self.__get_random(25)
- self.token_cache[b64] = time.time()
+ self.token_cache[b64] = (time.time(), user)
return b64
def __invalidate_expired_objects(self):
@@ -391,7 +494,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
for object_id in self.object_cache.keys():
(reference, object_time) = self.object_cache[object_id]
if (timenow > object_time + OBJECT_TIMEOUT):
- self.logger.debug("expiring object reference: %s" % id)
+ self.log("expiring object reference: %s" % id,debug=True)
del self.object_cache[object_id]
def __invalidate_expired_tokens(self):
@@ -400,27 +503,27 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
"""
timenow = time.time()
for token in self.token_cache.keys():
- tokentime = self.token_cache[token]
+ (tokentime, user) = self.token_cache[token]
if (timenow > tokentime + TOKEN_TIMEOUT):
- self.logger.debug("expiring token: %s" % token)
+ self.log("expiring token",token=token,debug=True)
del self.token_cache[token]
- def __validate_user(self,user,password):
+ def __validate_user(self,input_user,input_password):
"""
Returns whether this user/pass combo should be given
access to the cobbler read-write API.
+ For the system user, this answer is always "yes", but
+ it is only valid for the socket interface.
+
FIXME: currently looks for users in /etc/cobbler/auth.conf
Would be very nice to allow for PAM and/or just Kerberos.
"""
- for x in user_database:
- (db_user,db_password) = x
- db_user = db_user.strip()
- db_password = db_password.strip()
- if db_user == user and db_password == password and db_password.lower() != "disabled":
- return True
- else:
+ if not self.auth_enabled and input_user == "<system>":
+ return True
+ if self.auth_enabled and input_user == "<system>":
return False
+ return self.api.authenticate(input_user,input_password)
def __validate_token(self,token):
"""
@@ -433,32 +536,59 @@ 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 == "<system>":
+ self.token_cache[token] = (time.time(), user) # update to prevent timeout
+ return True
+
if self.token_cache.has_key(token):
- self.token_cache[token] = time.time() # update to prevent timeout
+ user = self.get_user_from_token(token)
+ if user == "<system>":
+ # system token is only valid over Unix socket
+ return False
+ self.token_cache[token] = (time.time(), user) # update to prevent timeout
return True
else:
- self.logger.debug("invalid token: %s" % token)
+ self.log("invalid token",token=token)
raise CX(_("invalid token: %s" % token))
- def login(self,user,password):
+ def check_access(self,token,resource,arg1=None,arg2=None):
+ validated = self.__validate_token(token)
+ if not self.auth_enabled:
+ return True
+ return self.__authorize(token,resource,arg1,arg2)
+
+
+ def login(self,login_user,login_password):
"""
Takes a username and password, validates it, and if successful
returns a random login token which must be used on subsequent
method calls. The token will time out after a set interval if not
used. Re-logging in permitted.
"""
- if self.__validate_user(user,password):
- token = self.__make_token()
- self.logger.info("login succeeded: %s" % user)
+ self.log("login attempt", user=login_user)
+ if self.__validate_user(login_user,login_password):
+ token = self.__make_token(login_user)
+ self.log("login succeeded",user=login_user)
return token
else:
- self.logger.info("login failed: %s" % user)
- raise CX(_("login failed: %s") % user)
+ self.log("login failed",user=login_user)
+ raise CX(_("login failed: %s") % login_user)
+
+ def __authorize(self,token,resource,arg1=None,arg2=None):
+ user = self.get_user_from_token(token)
+ if self.api.authorize(user,resource,arg1,arg2):
+ return True
+ else:
+ raise CX(_("user does not have access to resource: %s") % resource)
def logout(self,token):
"""
Retires a token ahead of the timeout.
"""
+ self.log("logout", token=token)
if self.token_cache.has_key(token):
del self.token_cache[token]
return True
@@ -503,7 +633,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
remote.modify_distro(distro_id, 'initrd', '/foo/initrd.img', token)
remote.save_distro(distro_id, token)
"""
- self.__validate_token(token)
+ self.log("new_distro",token=token)
+ self.check_access(token,"new_distro")
return self.__store_object(item_distro.Distro(self.api._config))
def new_profile(self,token):
@@ -511,19 +642,12 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Creates a new (unconfigured) profile object. See the documentation
for new_distro as it works exactly the same.
"""
- self.__validate_token(token)
+ self.log("new_profile",token=token)
+ self.check_access(token,"new_profile")
return self.__store_object(item_profile.Profile(self.api._config))
def new_subprofile(self,token):
"""
- Creates a new (unconfigured) subprofile object. See the documentation
- for new_distro as it works exactly the same.
- """
- self.__validate_token(token)
- return self.__store_object(item_profile.Profile(self.api._config, is_subobject=True))
-
- def new_subprofile(self,token):
- """
A subprofile is a profile that inherits directly from another profile,
not a distro. In addition to the normal profile setup, setting
the parent variable to the name of an existing profile is also
@@ -531,7 +655,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
were regular profiles. The same XMLRPC API methods work on them as profiles
also.
"""
- self.__validate_token(token)
+ self.log("new_subprofile",token=token)
+ self.check_access(token,"new_subprofile")
return self.__store_object(item_profile.Profile(self.api._config,is_subobject=True))
def new_system(self,token):
@@ -539,7 +664,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Creates a new (unconfigured) system object. See the documentation
for new_distro as it works exactly the same.
"""
- self.__validate_token(token)
+ self.log("new_system",token=token)
+ self.check_access(token,"new_system")
return self.__store_object(item_system.System(self.api._config))
def new_repo(self,token):
@@ -547,7 +673,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Creates a new (unconfigured) repo object. See the documentation
for new_distro as it works exactly the same.
"""
- self.__validate_token(token)
+ self.log("new_repo",token=token)
+ self.check_access(token,"new_repo")
return self.__store_object(item_repo.Repo(self.api._config))
def get_distro_handle(self,name,token):
@@ -556,7 +683,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
object id that can be passed in to modify_distro() or save_distro()
commands. Raises an exception if no object can be matched.
"""
- self.__validate_token(token)
+ self.log("get_distro_handle",token=token,name=name)
+ self.check_access(token,"get_distro_handle")
self._refresh()
found = self.api.distros().find(name)
return self.__store_object(found)
@@ -567,7 +695,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
object id that can be passed in to modify_profile() or save_profile()
commands. Raises an exception if no object can be matched.
"""
- self.__validate_token(token)
+ self.log("get_profile_handle",token=token,name=name)
+ self.check_access(token,"get_profile_handle")
self._refresh()
found = self.api.profiles().find(name)
return self.__store_object(found)
@@ -578,7 +707,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
object id that can be passed in to modify_system() or save_system()
commands. Raises an exception if no object can be matched.
"""
- self.__validate_token(token)
+ self.log("get_system_handle",name=name,token=token)
+ self.check_access(token,"get_system_handle")
self._refresh()
found = self.api.systems().find(name)
return self.__store_object(found)
@@ -589,7 +719,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
object id that can be passed in to modify_repo() or save_pro()
commands. Raises an exception if no object can be matched.
"""
- self.__validate_token(token)
+ self.log("get_repo_handle",name=name,token=token)
+ self.check_access(token,"get_repo_handle")
self._refresh()
found = self.api.repos().find(name)
return self.__store_object(found)
@@ -598,33 +729,94 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
"""
Saves a newly created or modified distro object to disk.
"""
- self.__validate_token(token)
+ self.log("save_distro",object_id=object_id,token=token)
+ self.check_access(token,"save_distro")
obj = self.__get_object(object_id)
- return self.api.distros().add(obj,with_copy=True)
+ return self.api.distros().add(obj,save=True)
def save_profile(self,object_id,token):
"""
Saves a newly created or modified profile object to disk.
"""
- self.__validate_token(token)
+ self.log("save_profile",token=token,object_id=object_id)
+ self.check_access(token,"save_profile")
obj = self.__get_object(object_id)
- return self.api.profiles().add(obj,with_copy=True)
+ return self.api.profiles().add(obj,save=True)
def save_system(self,object_id,token):
"""
Saves a newly created or modified system object to disk.
"""
- self.__validate_token(token)
+ self.log("save_system",token=token,object_id=object_id)
+ self.check_access(token,"save_system")
obj = self.__get_object(object_id)
- return self.api.systems().add(obj,with_copy=True)
+ return self.api.systems().add(obj,save=True)
def save_repo(self,object_id,token=None):
"""
Saves a newly created or modified repo object to disk.
"""
- self.__validate_token(token)
+ self.log("save_repo",object_id=object_id,token=token)
+ self.check_access(token,"save_repo")
+ obj = self.__get_object(object_id)
+ return self.api.repos().add(obj,save=True)
+
+ def copy_distro(self,object_id,newname,token=None):
+ """
+ All copy methods are pretty much the same. Get an object handle, pass in the new
+ name for it.
+ """
+ self.log("copy_distro",object_id=object_id,token=token)
+ self.check_access(token,"copy_distro")
+ obj = self.__get_object(object_id)
+ return self.api.copy_distro(obj,newname)
+
+ def copy_profile(self,object_id,token=None):
+ self.log("copy_profile",object_id=object_id,token=token)
+ self.check_access(token,"copy_profile")
obj = self.__get_object(object_id)
- return self.api.repos().add(obj,with_copy=True)
+ return self.api.copy_profile(obj,newname)
+
+ def copy_system(self,object_id,token=None):
+ self.log("copy_system",object_id=object_id,token=token)
+ self.check_access(token,"copy_system")
+ obj = self.__get_object(object_id)
+ return self.api.copy_system(obj,newname)
+
+ def copy_repo(self,object_id,token=None):
+ self.log("copy_repo",object_id=object_id,token=token)
+ self.check_access(token,"copy_repo")
+ obj = self.__get_object(object_id)
+ return self.api.copy_repo(obj,newname)
+
+ def rename_distro(self,object_id,newname,token=None):
+ """
+ All rename methods are pretty much the same. Get an object handle, pass in a new
+ name for it. Rename will modify dependencies to point them at the new
+ object.
+ """
+ self.log("rename_distro",object_id=object_id,token=token)
+ self.check_access(token,"copy_repo")
+ obj = self.__get_object(object_id)
+ return self.api.rename_distro(obj,newname)
+
+ def rename_profile(self,object_id,newname,token=None):
+ self.log("rename_profile",object_id=object_id,token=token)
+ self.check_access(token,"rename_profile")
+ obj = self.__get_object(object_id)
+ return self.api.rename_profile(obj,newname)
+
+ def rename_system(self,object_id,newname,token=None):
+ self.log("rename_system",object_id=object_id,token=token)
+ self.check_access(token,"rename_system")
+ obj = self.__get_object(object_id)
+ return self.api.rename_system(obj,newname)
+
+ def rename_repo(self,object_id,newname,token=None):
+ self.log("rename_repo",object_id=object_id,token=token)
+ self.check_access(token,"rename_repo")
+ obj = self.__get_object(object_id)
+ return self.api.rename_repo(obj,newname)
def __call_method(self, obj, attribute, arg):
"""
@@ -640,7 +832,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing distro object handle.
"""
- self.__validate_token(token)
+ self.check_access(token, "modify_distro", attribute, arg)
obj = self.__get_object(object_id)
return self.__call_method(obj, attribute, arg)
@@ -649,7 +841,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing profile object handle.
"""
- self.__validate_token(token)
+ self.check_access(token, "modify_profile", attribute, arg)
obj = self.__get_object(object_id)
return self.__call_method(obj, attribute, arg)
@@ -658,7 +850,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing system object handle.
"""
- self.__validate_token(token)
+ self.check_access(token, "modify_system", attribute, arg)
obj = self.__get_object(object_id)
return self.__call_method(obj, attribute, arg)
@@ -667,43 +859,47 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing repo object handle.
"""
- self.__validate_token(token)
+ self.check_access(token, "modify_repo", attribute, arg)
obj = self.__get_object(object_id)
return self.__call_method(obj, attribute, arg)
- def distro_remove(self,name,token):
+ def remove_distro(self,name,token,recursive=1):
"""
Deletes a distro from a collection. Note that this just requires the name
of the distro, not a handle.
"""
- self.__validate_token(token)
- rc = self.api._config.distros().remove(name)
+ self.log("remove_distro",name=name,token=token)
+ self.check_access(token, "remove_distro", name)
+ rc = self.api._config.distros().remove(name,recursive=True)
return rc
- def profile_remove(self,name,token):
+ 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.__validate_token(token)
- rc = self.api._config.profiles().remove(name)
+ 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 system_remove(self,name,token):
+ 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.__validate_token(token)
+ 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 repo_remove(self,name,token):
+ 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.__validate_token(token)
+ self.log("remove_repo",name=name,token=token)
+ self.check_access(token, "remove_repo", name)
rc = self.api._config.repos().remove(name)
return rc
@@ -718,7 +914,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Future versions of cobbler may understand how to do a cascade sync
on object edits making explicit calls to sync redundant.
"""
- self.__validate_token(token)
+ self.log("sync",token=token)
+ self.check_access(token, "sync")
return self.api.sync()
def reposync(self,repos=[],token=None):
@@ -727,7 +924,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
reposync is very slow and probably should not be used
through the XMLRPC API, setting up reposync on nightly cron is better.
"""
- self.__validate_token(token)
+ 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):
@@ -737,14 +935,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
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.__validate_token(token)
+ 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.__validate_token(token)
+ 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 != "<<inherit>>":
@@ -764,7 +964,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Also if living in /etc/cobbler the file must be a kickstart file.
"""
- self.__validate_token(token)
+ 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 kickstart_file.find("..") != -1 or not kickstart_file.startswith("/"):
raise CX(_("tainted file location"))
@@ -792,8 +993,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
-# *********************************************************************************
-# *********************************************************************************
+# *********************************************************************
+# *********************************************************************
class CobblerReadWriteXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
"""
@@ -804,76 +1005,3 @@ class CobblerReadWriteXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
self.allow_reuse_address = True
SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args)
-# *********************************************************************************
-# *********************************************************************************
-
-if __name__ == "__main__":
-
- # note: this demo requires that
- # (A) /etc/cobbler/auth.conf has a "testuser/llamas2007" account
- # (B) xmlrpc_rw_enabled is turned on /var/lib/cobbler/settings
- # (C) cobblerd is running (and restarted if changing any of the above)
- # (D) apache is configured as a reverse proxy (see cobbler.conf in /etc/httpd/conf.d)
- # this demo does not use SSL yet -- it /should/ and /can/.
-
- my_uri = "http://127.0.0.1/cobbler_api_rw"
- remote = xmlrpclib.Server(my_uri)
-
- testuser = "admin"
- testpass = "mooses9"
-
- token = remote.login(testuser,testpass)
- print token
-
- # just to make things "work"
- os.system("touch /tmp/vmlinuz")
- os.system("touch /tmp/initrd.img")
- os.system("touch /tmp/fake.ks")
-
- # now add a distro
- distro_id = remote.new_distro(token)
- remote.modify_distro(distro_id, 'name', 'example-distro',token)
- remote.modify_distro(distro_id, 'kernel', '/tmp/vmlinuz',token)
- remote.modify_distro(distro_id, 'initrd', '/tmp/initrd.img',token)
- remote.save_distro(distro_id,token)
-
- # now add a repository (that's not really mirroring anything useful)
- repo_id = remote.new_repo(token)
- remote.modify_repo(repo_id, 'name', 'example-repo', token)
- remote.modify_repo(repo_id, 'mirror', 'rsync://mirror.example.org/foo', token)
- remote.save_repo(repo_id, token)
-
- # now add a profile
- profile_id = remote.new_profile(token)
- remote.modify_profile(profile_id, 'name', 'example-profile', token)
- remote.modify_profile(profile_id, 'distro', 'example-distro', token)
- remote.modify_profile(profile_id, 'kickstart', '/tmp/fake.ks', token)
- remote.modify_profile(profile_id, 'repos', ['example-repo'], token)
- remote.save_profile(profile_id, token)
-
- # now add a system
- system_id = remote.new_system(token)
- remote.modify_system(system_id, 'name', 'example-system', token)
- remote.modify_system(system_id, 'profile', 'example-profile', token)
- remote.save_system(system_id, token)
-
- print remote.get_distros()
- print remote.get_profiles()
- print remote.get_systems()
- print remote.get_repos()
-
- print remote.get_system("AA:BB:AA:BB:AA:BB",True) # flattened
-
- # now simulate hitting a "sync" button in a WebUI
- print remote.sync(token)
-
- # the following code just tests a failed connection:
- #remote = CobblerReadWriteXMLRPCInterface(api,logger)
- #try:
- # token = remote.login("exampleuser2","examplepass")
- #except:
- # token = "fake_token"
- #print token
- #rc = remote.test(token)
- #print "test result: %s" % rc
- # print "cache: %s" % remote.token_cache
diff --git a/cobbler/serializer.py b/cobbler/serializer.py
index 8593aad..ae9f18c 100644
--- a/cobbler/serializer.py
+++ b/cobbler/serializer.py
@@ -16,52 +16,82 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import errno
import os
from rhpl.translate import _, N_, textdomain, utf8
+import fcntl
from cexceptions import *
import utils
import api as cobbler_api
+LOCK_ENABLED = True
+LOCK_HANDLE = None
+
+def __grab_lock():
+ if not LOCK_ENABLED:
+ return
+ if not os.path.exists("/var/lib/cobbler/lock"):
+ fd = open("/var/lib/cobbler/lock","w+")
+ fd.close()
+ LOCK_HANDLE = open("/var/lib/cobbler/lock","r")
+ fcntl.flock(LOCK_HANDLE.fileno(), fcntl.LOCK_EX)
+
+def __release_lock():
+ if not LOCK_ENABLED:
+ return
+ LOCK_HANDLE = open("/var/lib/cobbler/lock","r")
+ fcntl.flock(LOCK_HANDLE.fileno(), fcntl.LOCK_UN)
+ LOCK_HANDLE.close()
+
def serialize(obj):
"""
Save a collection to disk or other storage.
"""
+ __grab_lock()
storage_module = __get_storage_module(obj.collection_type())
storage_module.serialize(obj)
+ __release_lock()
return True
def serialize_item(collection, item):
"""
Save an item.
"""
+ __grab_lock()
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"
- return storage_module.serialize(collection)
+ rc = storage_module.serialize(collection)
else:
# print "DEBUG: partial serializer"
- return save_fn(collection,item)
+ rc = save_fn(collection,item)
+ __release_lock()
+ return rc
def serialize_delete(collection, item):
"""
Delete an object from a saved state.
"""
+ __grab_lock()
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"
- return storage_module.serialize(collection)
+ rc = storage_module.serialize(collection)
else:
# print "DEBUG: partial delete"
- return delete_fn(collection,item)
-
+ rc = delete_fn(collection,item)
+ __release_lock()
+ return rc
def deserialize(obj,topological=False):
"""
Fill in an empty collection from disk or other storage
"""
+ __grab_lock()
storage_module = __get_storage_module(obj.collection_type())
- return storage_module.deserialize(obj,topological)
+ rc = storage_module.deserialize(obj,topological)
+ __release_lock()
+ return rc
def deserialize_raw(collection_type):
"""
@@ -69,8 +99,11 @@ def deserialize_raw(collection_type):
disk state, without going through the Cobbler object system.
Much faster, when you don't need the objects.
"""
+ __grab_lock()
storage_module = __get_storage_module(collection_type)
- return storage_module.deserialize_raw(collection_type)
+ rc = storage_module.deserialize_raw(collection_type)
+ __release_lock()
+ return rc
def __get_storage_module(collection_type):
"""
diff --git a/cobbler/server/__init__.py b/cobbler/server/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cobbler/server/__init__.py
diff --git a/cobbler/server/xmlrpcclient.py b/cobbler/server/xmlrpcclient.py
new file mode 100644
index 0000000..cc0687c
--- /dev/null
+++ b/cobbler/server/xmlrpcclient.py
@@ -0,0 +1,72 @@
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#============================================================================
+# Copyright (C) 2006 Anthony Liguori <aliguori@us.ibm.com>
+# Copyright (C) 2007 XenSource Inc.
+# Copyright (C) 2007 Red Hat Inc, Michael DeHaan <mdehaan@redhat.com>
+
+from httplib import FakeSocket, HTTPConnection, HTTP
+import socket
+import string
+import xmlrpclib
+from types import StringTypes
+
+class HTTPUnixConnection(HTTPConnection):
+ def connect(self):
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.connect(self.host)
+
+class HTTPUnix(HTTP):
+ _connection_class = HTTPUnixConnection
+
+class UnixTransport(xmlrpclib.Transport):
+ def request(self, host, handler, request_body, verbose=0):
+ self.__handler = handler
+ return xmlrpclib.Transport.request(self, host, '/RPC2',
+ request_body, verbose)
+ def make_connection(self, host):
+ return HTTPUnix(self.__handler)
+
+# See xmlrpclib2.TCPXMLRPCServer._marshalled_dispatch.
+def conv_string(x):
+ if isinstance(x, StringTypes):
+ s = string.replace(x, "'", r"\047")
+ exec "s = '" + s + "'"
+ return s
+ else:
+ return x
+
+
+class ServerProxy(xmlrpclib.ServerProxy):
+ def __init__(self, uri, transport=None, encoding=None, verbose=0,
+ allow_none=1):
+ if transport == None:
+ (protocol, rest) = uri.split(':', 1)
+ if protocol == 'httpu':
+ uri = 'http:' + rest
+ transport = UnixTransport()
+ else:
+ raise ValueError("only httpu://path/to/socket is supported")
+ xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding,
+ verbose, allow_none)
+
+
+ def __request(self, methodname, params):
+ response = xmlrpclib.ServerProxy.__request(self, methodname, params)
+
+ if isinstance(response, tuple):
+ return tuple([conv_string(x) for x in response])
+ else:
+ return conv_string(response)
+
diff --git a/cobbler/server/xmlrpclib2.py b/cobbler/server/xmlrpclib2.py
new file mode 100644
index 0000000..d329241
--- /dev/null
+++ b/cobbler/server/xmlrpclib2.py
@@ -0,0 +1,236 @@
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#============================================================================
+# Copyright (C) 2006 Anthony Liguori <aliguori@us.ibm.com>
+# Copyright (C) 2006 XenSource Inc.
+# Copyright (C) 2007 Red Hat Inc., Michael DeHaan <mdehaan@redhat.com>
+#============================================================================
+
+"""
+An enhanced XML-RPC client/server interface for Python.
+"""
+
+import re
+import fcntl
+from types import *
+import os
+import errno
+import traceback
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+import SocketServer
+import xmlrpclib, socket, os, stat
+
+#import mkdir
+
+#
+# Convert all integers to strings as described in the Xen API
+#
+
+
+def stringify(value):
+ if isinstance(value, long) or \
+ (isinstance(value, int) and not isinstance(value, bool)):
+ return str(value)
+ elif isinstance(value, dict):
+ new_value = {}
+ for k, v in value.items():
+ new_value[stringify(k)] = stringify(v)
+ return new_value
+ elif isinstance(value, (tuple, list)):
+ return [stringify(v) for v in value]
+ else:
+ return value
+
+
+# We're forced to subclass the RequestHandler class so that we can work around
+# some bugs in Keep-Alive handling and also enabled it by default
+class XMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
+ protocol_version = "HTTP/1.1"
+
+ def __init__(self, request, client_address, server):
+ SimpleXMLRPCRequestHandler.__init__(self, request, client_address,
+ server)
+
+ # this is inspired by SimpleXMLRPCRequestHandler's do_POST but differs
+ # in a few non-trivial ways
+ # 1) we never generate internal server errors. We let the exception
+ # propagate so that it shows up in the Xend debug logs
+ # 2) we don't bother checking for a _dispatch function since we don't
+ # use one
+ def do_POST(self):
+ addrport = self.client_address
+ #if not connection.hostAllowed(addrport, self.hosts_allowed):
+ # self.connection.shutdown(1)
+ # return
+
+ data = self.rfile.read(int(self.headers["content-length"]))
+ rsp = self.server._marshaled_dispatch(data)
+
+ self.send_response(200)
+ self.send_header("Content-Type", "text/xml")
+ self.send_header("Content-Length", str(len(rsp)))
+ self.end_headers()
+
+ self.wfile.write(rsp)
+ self.wfile.flush()
+ #if self.close_connection == 1:
+ # self.connection.shutdown(1)
+
+def parents(dir, perms, enforcePermissions = False):
+ """
+ Ensure that the given directory exists, creating it if necessary, but not
+ complaining if it's already there.
+
+ @param dir The directory name.
+ @param perms One of the stat.S_ constants.
+ @param enforcePermissions Enforce our ownership and the given permissions,
+ even if the directory pre-existed with different ones.
+ """
+ # Catch the exception here, rather than checking for the directory's
+ # existence first, to avoid races.
+ try:
+ os.makedirs(dir, perms)
+ except OSError, exn:
+ if exn.args[0] != errno.EEXIST or not os.path.isdir(dir):
+ raise
+ if enforcePermissions:
+ os.chown(dir, os.geteuid(), os.getegid())
+ os.chmod(dir, stat.S_IRWXU)
+
+
+# This is a base XML-RPC server for TCP. It sets allow_reuse_address to
+# true, and has an improved marshaller that logs and serializes exceptions.
+
+class TCPXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
+ allow_reuse_address = True
+
+ def __init__(self, addr, requestHandler=None,
+ logRequests = 1):
+ if requestHandler is None:
+ requestHandler = XMLRPCRequestHandler
+ SimpleXMLRPCServer.__init__(self, addr,
+ (lambda x, y, z:
+ requestHandler(x, y, z)),
+ logRequests)
+
+ flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
+ flags |= fcntl.FD_CLOEXEC
+ fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
+
+ def get_request(self):
+ (client, addr) = SimpleXMLRPCServer.get_request(self)
+ flags = fcntl.fcntl(client.fileno(), fcntl.F_GETFD)
+ flags |= fcntl.FD_CLOEXEC
+ fcntl.fcntl(client.fileno(), fcntl.F_SETFD, flags)
+ return (client, addr)
+
+ def _marshaled_dispatch(self, data, dispatch_method = None):
+ params, method = xmlrpclib.loads(data)
+ if False:
+ # Enable this block of code to exit immediately without sending
+ # a response. This allows you to test client-side crash handling.
+ import sys
+ sys.exit(1)
+ try:
+ if dispatch_method is not None:
+ response = dispatch_method(method, params)
+ else:
+ response = self._dispatch(method, params)
+
+ if (response is None or
+ not isinstance(response, dict) or
+ 'Status' not in response):
+ #log.exception('Internal error handling %s: Invalid result %s',
+ # method, response)
+ response = { "Status": "Failure",
+ "ErrorDescription":
+ ['INTERNAL_ERROR',
+ 'Invalid result %s handling %s' %
+ (response, method)]}
+
+ # With either Unicode or normal strings, we can only transmit
+ # \t, \n, \r, \u0020-\ud7ff, \ue000-\ufffd, and \u10000-\u10ffff
+ # in an XML document. xmlrpclib does not escape these values
+ # properly, and then breaks when it comes to parse the document.
+ # To hack around this problem, we use repr here and exec above
+ # to transmit the string using Python encoding.
+ # Thanks to David Mertz <mertz@gnosis.cx> for the trick (buried
+ # in xml_pickle.py).
+ if isinstance(response, StringTypes):
+ response = repr(response)[1:-1]
+
+ response = (response,)
+ response = xmlrpclib.dumps(response,
+ methodresponse=1,
+ allow_none=1)
+ except Exception, exn:
+ try:
+ #if self.xenapi:
+ # if _is_not_supported(exn):
+ # errdesc = ['MESSAGE_METHOD_UNKNOWN', method]
+ # else:
+ # #log.exception('Internal error handling %s', method)
+ # errdesc = ['INTERNAL_ERROR', str(exn)]
+ #
+ # response = xmlrpclib.dumps(
+ # ({ "Status": "Failure",
+ # "ErrorDescription": errdesc },),
+ # methodresponse = 1)
+ #else:
+ # import xen.xend.XendClient
+ if isinstance(exn, xmlrpclib.Fault):
+ response = xmlrpclib.dumps(exn)
+ else:
+ # log.exception('Internal error handling %s', method)
+ response = xmlrpclib.dumps(
+ xmlrpclib.Fault(101, str(exn)))
+ except Exception, exn2:
+ # FIXME
+ traceback.print_exc()
+
+ return response
+
+
+notSupportedRE = re.compile(r'method "(.*)" is not supported')
+def _is_not_supported(exn):
+ try:
+ m = notSupportedRE.search(exn[0])
+ return m is not None
+ except:
+ return False
+
+
+# This is a XML-RPC server that sits on a Unix domain socket.
+# It implements proper support for allow_reuse_address by
+# unlink()'ing an existing socket.
+
+class UnixXMLRPCRequestHandler(XMLRPCRequestHandler):
+ def address_string(self):
+ try:
+ return XMLRPCRequestHandler.address_string(self)
+ except ValueError, e:
+ return self.client_address[:2]
+
+class UnixXMLRPCServer(TCPXMLRPCServer):
+ address_family = socket.AF_UNIX
+ allow_address_reuse = True
+
+ def __init__(self, addr, logRequests = 1):
+ parents(os.path.dirname(addr), stat.S_IRWXU, True)
+ if self.allow_reuse_address and os.path.exists(addr):
+ os.unlink(addr)
+
+ TCPXMLRPCServer.__init__(self, addr,
+ UnixXMLRPCRequestHandler, logRequests)
diff --git a/cobbler/settings.py b/cobbler/settings.py
index 8a3c7f7..86a24af 100644
--- a/cobbler/settings.py
+++ b/cobbler/settings.py
@@ -29,20 +29,25 @@ DEFAULTS = {
"default_kickstart" : "/etc/cobbler/default.ks",
"default_virt_bridge" : "xenbr0",
"default_virt_type" : "auto",
+ "default_virt_file_size" : "5",
+ "default_virt_ram" : "512",
"dhcpd_conf" : "/etc/dhcpd.conf",
"dhcpd_bin" : "/usr/sbin/dhcpd",
"dnsmasq_bin" : "/usr/sbin/dnsmasq",
"dnsmasq_conf" : "/etc/dnsmasq.conf",
"httpd_bin" : "/usr/sbin/httpd",
+ "http_port" : 80,
+ "kerberos_realm" : "example.org",
"kernel_options" : {
"lang" : " ",
"text" : None,
- "ksdevice" : "eth0",
+ "ksdevice" : "eth0"
},
"manage_dhcp" : 0,
"manage_dhcp_mode" : "isc",
"next_server" : "127.0.0.1",
"pxe_just_once" : 0,
+ "run_post_install_trigger" : 0,
"server" : "127.0.0.1",
"snippetsdir" : "/var/lib/cobbler/snippets",
"syslog_port" : 25150,
diff --git a/cobbler/utils.py b/cobbler/utils.py
index d0f765a..ca3d91a 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -21,7 +21,6 @@ import sub_process
import shutil
import string
import traceback
-import logging
from cexceptions import *
from rhpl.translate import _, N_, textdomain, utf8
@@ -325,6 +324,12 @@ def blender(api_handle,remove_hashes, root_obj, blend_cache=None):
if not results.has_key(key):
results[key] = interface[key]
+ http_port = results.get("http_port",80)
+ if http_port != 80:
+ results["http_server"] = "%s:%s" % (results["server"] , http_port)
+ else:
+ results["http_server"] = results["server"]
+
# sanitize output for koan and kernel option lines, etc
if remove_hashes:
results = flatten(results)
@@ -339,13 +344,12 @@ def flatten(data):
# this should not be done for everything
if data.has_key("kernel_options"):
data["kernel_options"] = hash_to_string(data["kernel_options"])
- # FIXME: why do we flatten this?
+ if data.has_key("yumopts"):
+ data["yumopts"] = hash_to_string(data["yumopts"])
if data.has_key("ks_meta"):
data["ks_meta"] = hash_to_string(data["ks_meta"])
- # FIXME: why do we flatten this?
if data.has_key("repos") and type(data["repos"]) == list:
data["repos"] = " ".join(data["repos"])
- # FIXME: why do we flatten this?
if data.has_key("rpm_list") and type(data["rpm_list"]) == list:
data["rpm_list"] = " ".join(data["rpm_list"])
diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py
index ac3b732..9a0cf90 100644
--- a/cobbler/webui/CobblerWeb.py
+++ b/cobbler/webui/CobblerWeb.py
@@ -17,33 +17,16 @@ import os
import traceback
import string
from cobbler.utils import *
-import logging
import sys
-LOGGING_ENABLED = False
-
-if LOGGING_ENABLED:
- # set up logging
- logger = logging.getLogger("cobbler.webui")
- logger.setLevel(logging.DEBUG)
- ch = logging.FileHandler("/var/log/cobbler/webui.log")
- ch.setLevel(logging.DEBUG)
- formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
- ch.setFormatter(formatter)
- logger.addHandler(ch)
-else:
- logger = None
-
-def log_exc():
+def log_exc(apache):
"""
Log active traceback to logfile.
"""
- if not LOGGING_ENABLED:
- return
(t, v, tb) = sys.exc_info()
- logger.info("Exception occured: %s" % t )
- logger.info("Exception value: %s" % v)
- logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb))))
+ 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 CobblerWeb(object):
"""
@@ -52,7 +35,7 @@ class CobblerWeb(object):
it all run either under cgi-bin or CherryPy. Supporting other Python
frameworks should be trivial.
"""
- def __init__(self, server=None, base_url='/', username=None, password=None, token=None):
+ def __init__(self, server=None, base_url='/', username=None, password=None, token=None, apache=None):
self.server = server
self.base_url = base_url
self.remote = None
@@ -60,6 +43,7 @@ class CobblerWeb(object):
self.username = username
self.password = password
self.logout = None
+ self.apache = apache
def __xmlrpc_setup(self):
"""
@@ -79,9 +63,8 @@ class CobblerWeb(object):
return True
except Exception, e:
if str(e).find("invalid token") != -1:
- if LOGGING_ENABLED:
- logger.info("token timeout for: %s" % self.username)
- log_exc()
+ self.apache.log_error("cobbler token timeout for: %s" % self.username)
+ log_exc(self.apache)
self.token = None
else:
raise e
@@ -91,9 +74,8 @@ class CobblerWeb(object):
try:
self.token = self.remote.login( self.username, self.password )
except Exception, e:
- if LOGGING_ENABLED:
- logger.info("login failed for: %s" % self.username)
- log_exc()
+ self.apache.log_error("cobbler login failed for: %s" % self.username)
+ log_exc(self.apache)
return False
self.password = None # don't need it anymore, get rid of it
return True
@@ -106,62 +88,11 @@ class CobblerWeb(object):
Call the templating engine (Cheetah), wrapping up the location
of files while we're at it.
"""
-
data['base_url'] = self.base_url
-
filepath = os.path.join("/usr/share/cobbler/webui_templates/",template)
tmpl = Template( file=filepath, searchList=[data] )
return str(tmpl)
- def cookies(self):
- """
- Returns a Cookie.SimpleCookie object with all of CobblerWeb's cookies.
- Mmmmm cookies!
- """
- # The browser doesn't maintain expires for us, which is fine since
- # cobblerd will continue to refresh a token as long as it's being
- # accessed.
- if self.token and self.__cookies["cobbler_xmlrpc_token"]:
- self.__setcookie( self.token, COOKIE_TIMEOUT )
-
- return self.__cookies
-
- def __setcookie(self,token,exp_offset):
- """
- Does all of the cookie setting in one place.
- """
- # HTTP cookie RFC:
- # http://www.w3.org/Protocols/rfc2109/rfc2109
- #
- # Cookie.py does not let users explicitely set cookies' expiration time.
- # Instead, it runs the 'expires' member of the dictionary through its
- # _getdate() function. As of this writing, the signature is:
- # _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname)
- # When it is called to generate output, the value of 'expires' is passed
- # in as a _positional_ parameter in the first slot.
- # In order to get a time in the past, it appears that a negative number
- # can be passed through, which is what we do here.
- self.__cookies["cobbler_xmlrpc_token"] = token
- self.__cookies["cobbler_xmlrpc_token"]['expires'] = exp_offset
-
- def __cookie_logout(self):
- # set the cookie's expiration to this time, yesterday, which results
- # in it being deleted
- self.__setcookie( 'null', -86400 )
- return self.__cookies
-
- def __cookie_login(self,token):
- self.__setcookie( token, COOKIE_TIMEOUT )
- return self.__cookies
-
- def __get_cookie_token(self):
- if self.__cookies.has_key("cobbler_xmlrpc_token"):
- value = self.__cookies["cobbler_xmlrpc_token"]
- if LOGGING_ENABLED:
- logger.debug("loading token from cookie: %s" % value.value)
- return value.value
- return None
-
def modes(self):
"""
Returns a list of methods in this object that can be run as web
@@ -179,10 +110,10 @@ class CobblerWeb(object):
# Index
# ------------------------------------------------------------------------ #
- def index(self):
+ def index(self,**args):
return self.__render( 'index.tmpl', { } )
- def menu(self):
+ def menu(self,**args):
return self.__render( 'blank.tmpl', { } )
# ------------------------------------------------------------------------ #
@@ -193,7 +124,7 @@ class CobblerWeb(object):
# as you could disable a lot of functionality if you aren't careful
# including your ability to fix it back.
- def settings_view(self):
+ def settings_view(self,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -206,7 +137,7 @@ class CobblerWeb(object):
# Distributions
# ------------------------------------------------------------------------ #
- def distro_list(self,page=None,limit=None):
+ def distro_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -223,11 +154,11 @@ class CobblerWeb(object):
else:
return self.__render('empty.tmpl', {})
- def distro_edit(self, name=None):
+ def distro_edit(self, name=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
-
+
input_distro = None
if name is not None:
input_distro = self.remote.get_distro(name, True)
@@ -243,18 +174,20 @@ class CobblerWeb(object):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+
+ # pre-command paramter checking
+ # HTML forms do not transmit disabled fields
+ if name is None and oldname is not None:
+ name = oldname
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
try:
- self.remote.distro_remove(name,self.token)
+ self.remote.remove_distro(name,self.token,1) # recursive
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.distro_list()
- # pre-command paramter checking
- if name is None and editmode=='edit' and oldname is not None:
- name = oldname
if name is None:
return self.error_page("name is required")
if kernel is None or not str(kernel).startswith("/"):
@@ -265,17 +198,22 @@ class CobblerWeb(object):
return self.error_page("The name has not been changed.")
# grab a reference to the object
- if new_or_edit == "edit" and editmode == "edit":
+ if new_or_edit == "edit" and editmode in [ "edit", "rename" ]:
try:
- distro = self.remote.get_distro_handle( name, self.token)
+ if editmode == "edit":
+ distro = self.remote.get_distro_handle( name, self.token)
+ else:
+ distro = self.remote.get_distro_handle( oldname, self.token)
+
except:
- log_exc()
+ log_exc(self.apache)
return self.error_page("Failed to lookup distro: %s" % name)
else:
distro = self.remote.new_distro(self.token)
try:
- self.remote.modify_distro(distro, 'name', name, self.token)
+ if editmode != "rename" and name:
+ self.remote.modify_distro(distro, 'name', name, self.token)
self.remote.modify_distro(distro, 'kernel', kernel, self.token)
self.remote.modify_distro(distro, 'initrd', initrd, self.token)
if kopts:
@@ -288,14 +226,14 @@ class CobblerWeb(object):
self.remote.modify_distro(distro, 'breed', breed, self.token)
self.remote.save_distro(distro, self.token)
except Exception, e:
- log_exc()
+ log_exc(self.apache)
return self.error_page("Error while saving distro: %s" % str(e))
if editmode == "rename" and name != oldname:
try:
- self.remote.distro_remove(oldname, self.token)
+ self.remote.rename_distro(distro, name, self.token)
except Exception, e:
- return self.error_page("Rename unsuccessful. Object %s was copied instead, and the old copy (%s) still remains. Reason: %s" % (name, oldname, str(e)))
+ return self.error_page("Rename unsuccessful.")
return self.distro_list()
@@ -330,7 +268,7 @@ class CobblerWeb(object):
return (page, results_per_page, pages)
- def system_list(self,page=None,limit=None):
+ def system_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -357,7 +295,7 @@ class CobblerWeb(object):
return self.xmlrpc_auth_failure()
# parameter checking
- if name is None and editmode=='edit' and oldname is not None:
+ if name is None and oldname is not None:
name = oldname
if name is None:
return self.error_page("System name parameter is REQUIRED.")
@@ -367,26 +305,19 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
try:
- self.remote.system_remove(name,self.token)
+ self.remote.remove_system(name,self.token)
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.system_list()
- # obsolete -- just do this server side
- # more parameter checking
- #if mac is None and ip is None and hostname is None and not is_mac(name) and not is_ip(name):
- # return self.error_page("System must have at least one of MAC/IP/hostname.")
- #if hostname and not ip:
- # ip = resolve_ip( hostname )
- #if mac and not is_mac( mac ):
- # return self.error_page("The provided MAC address appears to be invalid.")
- #if ip and not is_ip( ip ):
- # return self.error_page("The provided IP address appears to be invalid.")
-
# grab a reference to the object
- if new_or_edit == "edit" and editmode == "edit":
+ if new_or_edit == "edit" and editmode in [ "edit", "rename" ] :
try:
- system = self.remote.get_system_handle( name, self.token )
+ if editmode == "edit":
+ system = self.remote.get_system_handle( name, self.token )
+ else:
+ system = self.remote.get_system_handle( oldname, self.token )
+
except:
return self.error_page("Failed to lookup system: %s" % name)
else:
@@ -394,14 +325,9 @@ class CobblerWeb(object):
# go!
try:
- self.remote.modify_system(system, 'name', name, self.token )
+ if editmode != "rename" and name:
+ self.remote.modify_system(system, 'name', name, self.token )
self.remote.modify_system(system, 'profile', profile, self.token)
- #if mac:
- # self.remote.modify_system(system, 'mac', mac, self.token)
- #if ip:
- # self.remote.modify_system(system, 'ip', ip, self.token)
- #if hostname:
- # self.remote.modify_system(system, 'hostname', hostname, self.token)
if kopts:
self.remote.modify_system(system, 'kopts', kopts, self.token)
if ksmeta:
@@ -442,21 +368,21 @@ class CobblerWeb(object):
self.remote.save_system( system, self.token)
except Exception, e:
- log_exc()
+ log_exc(self.apache)
return self.error_page("Error while saving system: %s" % str(e))
if editmode == "rename" and name != oldname:
try:
- self.remote.system_remove(oldname, self.token)
+ self.remote.rename_system(system, name, self.token)
except Exception, e:
- return self.error_page("Rename unsuccessful. Object %s was copied instead, and the old copy (%s) still remains. Reason: %s" % (name, oldname, str(e)))
+ return self.error_page("Rename unsuccessful")
return self.system_list()
- def system_edit(self, name=None):
+ def system_edit(self, name=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -474,7 +400,7 @@ class CobblerWeb(object):
# ------------------------------------------------------------------------ #
# Profiles
# ------------------------------------------------------------------------ #
- def profile_list(self,page=None,limit=None):
+ def profile_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -491,10 +417,10 @@ class CobblerWeb(object):
else:
return self.__render('empty.tmpl', {})
- def subprofile_edit(self, name=None):
+ def subprofile_edit(self, name=None,**spam):
return self.profile_edit(name,1)
- def profile_edit(self, name=None, subprofile=0):
+ def profile_edit(self, name=None, subprofile=0, **spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -523,7 +449,7 @@ class CobblerWeb(object):
return self.xmlrpc_auth_failure()
# pre-command parameter checking
- if name is None and editmode=='edit' and oldname is not None:
+ if name is None and oldname is not None:
name = oldname
if name is None:
return self.error_page("A name has not been specified.")
@@ -537,15 +463,19 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
try:
- self.remote.profile_remove(name,self.token)
+ self.remote.remove_profile(name,self.token,1)
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.profile_list()
# grab a reference to the object
- if new_or_edit == "edit" and editmode == "edit":
+ if new_or_edit == "edit" and editmode in [ "edit", "rename" ] :
try:
- profile = self.remote.get_profile_handle( name, self.token )
+ if editmode == "edit":
+ profile = self.remote.get_profile_handle( name, self.token )
+ else:
+ profile = self.remote.get_profile_handle( oldname, self.token )
+
except:
return self.error_page("Failed to lookup profile: %s" % name)
else:
@@ -555,7 +485,7 @@ class CobblerWeb(object):
profile = self.remote.new_subprofile(self.token)
try:
- if name:
+ if editmode != "rename" and name:
self.remote.modify_profile(profile, 'name', name, self.token)
if str(subprofile) != "1" and distro:
self.remote.modify_profile(profile, 'distro', distro, self.token)
@@ -595,14 +525,14 @@ class CobblerWeb(object):
self.remote.modify_profile(profile, 'dhcp-tag', dhcptag, self.token)
self.remote.save_profile(profile,self.token)
except Exception, e:
- log_exc()
+ log_exc(self.apache)
return self.error_page("Error while saving profile: %s" % str(e))
if editmode == "rename" and name != oldname:
try:
- self.remote.profile_remove(oldname, self.token)
+ self.remote.rename_profile(profile, name, self.token)
except Exception, e:
- return self.error_page("Rename unsuccessful. Object %s was copied instead, and the old copy (%s) still remains. Reason: %s" % (name, oldname, str(e)))
+ return self.error_page("Rename unsuccessful.")
return self.profile_list()
@@ -611,7 +541,7 @@ class CobblerWeb(object):
# Repos
# ------------------------------------------------------------------------ #
- def repo_list(self,page=None,limit=None):
+ def repo_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -628,7 +558,7 @@ class CobblerWeb(object):
else:
return self.__render('empty.tmpl', {})
- def repo_edit(self, name=None):
+ def repo_edit(self, name=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -641,13 +571,14 @@ class CobblerWeb(object):
} )
def repo_save(self,name=None,oldname=None,new_or_edit=None,editmode="edit",
- mirror=None,keep_updated=None,
- rpm_list=None,createrepo_flags=None,arch=None,delete1=None,delete2=None,**args):
+ mirror=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():
return self.xmlrpc_auth_failure()
# pre-command parameter checking
- if name is None and editmode=='edit' and oldname is not None:
+ if name is None and oldname is not None:
name = oldname
if name is None:
return self.error_page("name is required")
@@ -657,7 +588,7 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
try:
- self.remote.repo_remove(name,self.token)
+ self.remote.remove_repo(name,self.token)
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.repo_list()
@@ -667,37 +598,44 @@ class CobblerWeb(object):
return self.error_page("mirror is required")
# grab a reference to the object
- if new_or_edit == "edit" and editmode == "edit":
+ if new_or_edit == "edit" and editmode in [ "edit", "rename" ]:
try:
- repo = self.remote.get_repo_handle( name, self.token)
+ if editmode == "edit":
+ repo = self.remote.get_repo_handle( name, self.token)
+ else:
+ repo = self.remote.get_repo_handle( oldname, self.token)
except:
return self.error_page("Failed to lookup repo: %s" % name)
else:
repo = self.remote.new_repo(self.token)
try:
- self.remote.modify_repo(repo, 'name', name, self.token)
+ if editmode != "rename" and name:
+ self.remote.modify_repo(repo, 'name', name, self.token)
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)
if rpm_list:
self.remote.modify_repo(repo, 'rpm-list', rpm_list, self.token)
if createrepo_flags:
- self.remote.modify_distro(repo, 'createrepo-flags', createrepo_flags, self.token)
+ self.remote.modify_repo(repo, 'createrepo-flags', createrepo_flags, self.token)
if arch:
- self.remote.modify_distro(repo, 'arch', arch, self.token)
+ self.remote.modify_repo(repo, 'arch', arch, self.token)
+ if yumopts:
+ self.remote.modify_repo(repo, 'yumopts', yumopts, self.token)
self.remote.save_repo(repo, self.token)
except Exception, e:
- log_exc()
+ log_exc(self.apache)
return self.error_page("Error while saving repo: %s" % str(e))
if editmode == "rename" and name != oldname:
try:
- self.remote.repo_remove(oldname, self.token)
+ self.remote.rename_repo(repo, name, self.token)
except Exception, e:
- return self.error_page("Rename unsuccessful. Object %s was copied instead, and the old copy (%s) still remains. Reason: %s" % (name, oldname, str(e)))
+ return self.error_page("Rename unsuccessful.")
return self.repo_list()
@@ -705,14 +643,14 @@ class CobblerWeb(object):
# Kickstart files
# ------------------------------------------------------------------------ #
- def ksfile_list(self):
+ def ksfile_list(self,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
return self.__render( 'ksfile_list.tmpl', {
'ksfiles': self.remote.get_kickstart_templates(self.token)
} )
- def ksfile_edit(self, name=None):
+ def ksfile_edit(self, name=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
return self.__render( 'ksfile_edit.tmpl', {
@@ -733,7 +671,7 @@ class CobblerWeb(object):
# Miscellaneous
# ------------------------------------------------------------------------ #
- def sync(self):
+ def sync(self,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -742,7 +680,7 @@ class CobblerWeb(object):
if not rc:
return self.error_page("Sync failed. Try debugging locally.")
except Exception, e:
- log_exc()
+ log_exc(self.apache)
return self.error_page("Sync encountered an exception: %s" % str(e))
return self.__render('message.tmpl', {
@@ -750,13 +688,13 @@ class CobblerWeb(object):
'message2' : "Cobbler config has been applied to filesystem."
})
- def random_mac(self):
+ def random_mac(self, **spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
mac = self.remote.get_random_mac()
return mac
- def error_page(self, message):
+ def error_page(self, message, **spam):
# hack to remove some junk from remote fault errors so they
# look as if they were locally generated and not exception-based.
@@ -768,7 +706,7 @@ class CobblerWeb(object):
'message': message
} )
- def xmlrpc_auth_failure(self):
+ def xmlrpc_auth_failure(self, **spam):
return self.__render( 'error_page.tmpl', {
'message': "XMLRPC Authentication Error. See Apache logs for details."
} )
diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py
index 397ac19..b29165b 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__ = 1200001429.6167221
-__CHEETAH_genTimestamp__ = 'Thu Jan 10 16:43:49 2008'
+__CHEETAH_genTime__ = 1203096504.0718989
+__CHEETAH_genTimestamp__ = 'Fri Feb 15 12:28:24 2008'
__CHEETAH_src__ = 'webui_templates/master.tmpl'
-__CHEETAH_srcLastModified__ = 'Thu Jan 10 16:39:32 2008'
+__CHEETAH_srcLastModified__ = 'Tue Jan 15 14:27:10 2008'
__CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine'
if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
@@ -152,56 +152,56 @@ class master(Template):
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 32, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 32, col 22.
- write('''/settings_view" class="menu">Settings</a></li>
+ write('''?mode=settings_view" class="menu">Settings</a></li>
<li><hr/></li>
<li>LIST</li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 35, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 35, col 22.
- write('''/distro_list" class="menu">Distros</a></li>
+ write('''?mode=distro_list" class="menu">Distros</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 36, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 36, col 22.
- write('''/profile_list" class="menu">Profiles</a></li>
+ write('''?mode=profile_list" class="menu">Profiles</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 37, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 37, col 22.
- write('''/system_list" class="menu">Systems</a></li>
+ write('''?mode=system_list" class="menu">Systems</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 38, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 38, col 22.
- write('''/ksfile_list" class="menu">Kickstarts</a></li>
+ write('''?mode=ksfile_list" class="menu">Kickstarts</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 39, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 39, col 22.
- write('''/repo_list" class="menu">Repos</a></li>
+ write('''?mode=repo_list" class="menu">Repos</a></li>
<li><hr/></li>
<li>ADD</li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 42, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 42, col 22.
- write('''/distro_edit" class="menu">Distro</a></li>
+ write('''?mode=distro_edit" class="menu">Distro</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 43, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 43, col 22.
- write('''/profile_edit" class="menu">Profile</a></li>
+ write('''?mode=profile_edit" class="menu">Profile</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 44, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 44, col 22.
- write('''/subprofile_edit" class="menu">Subprofile</a></li>
+ write('''?mode=subprofile_edit" class="menu">Subprofile</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 45, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 45, col 22.
- write('''/system_edit" class="menu">System</a></li>
+ write('''?mode=system_edit" class="menu">System</a></li>
<li><a href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 46, col 22
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 46, col 22.
- write('''/repo_edit" class="menu">Repo</a></li>
+ write('''?mode=repo_edit" class="menu">Repo</a></li>
<li><hr/><br/></li>
<li><a class="button sync" href="''')
_v = VFFSL(SL,"base_url",True) # '$base_url' on line 48, col 42
if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 48, col 42.
- write('''/sync">Sync</a></li>
+ write('''?mode=sync">Sync</a></li>
</ul>
</div>
diff --git a/config/.htaccess b/config/.htaccess
deleted file mode 100644
index 769852a..0000000
--- a/config/.htaccess
+++ /dev/null
@@ -1,8 +0,0 @@
-<Files webui.cgi>
-AuthUserFile /var/www/cgi-bin/cobbler/.htpasswd
-AuthGroupFile /dev/null
-AuthName "Cobbler WebUI Authentication"
-AuthType Digest
-
-require valid-user
-</Files>
diff --git a/config/.htpasswd b/config/.htpasswd
deleted file mode 100644
index 310327a..0000000
--- a/config/.htpasswd
+++ /dev/null
@@ -1 +0,0 @@
-cobbler:Cobbler WebUI Authentication:4551d917d1d3698954a91686ceb14f3a
diff --git a/config/cobbler.conf b/config/cobbler.conf
index 3ebc9e6..b4ab6d6 100644
--- a/config/cobbler.conf
+++ b/config/cobbler.conf
@@ -7,7 +7,6 @@ AliasMatch ^/cobbler(.*)?$ "/var/www/cobbler$1"
<Directory "/var/www/cobbler">
Options Indexes FollowSymLinks
- AllowOverride None
Order allow,deny
Allow from all
</Directory>
@@ -24,7 +23,7 @@ ProxyPassReverse /cobbler_api_rw http://localhost:25152/
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
-# For Web UI, see also: /var/www/cgi-bin/cobbler/.htaccess
+# For misc CGI scripts
<Directory "/var/www/cgi-bin/cobbler">
AllowOverride All
@@ -33,4 +32,17 @@ BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
Allow from all
</Directory>
+# mod_python WebUI/services
+
+<Directory "/var/www/cobbler/web/">
+ AuthType Basic
+ AuthName Cobbler
+ Require valid-user
+ SetHandler mod_python
+ PythonAuthenHandler index
+ PythonHandler index
+ # disable?
+ PythonDebug on
+</Directory>
+
diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate
index 94fac8d..0e4bcbf 100644
--- a/config/cobblerd_rotate
+++ b/config/cobblerd_rotate
@@ -1,4 +1,4 @@
-/var/log/cobbler/cobblerd.log {
+/var/log/cobbler/cobbler.log {
missingok
notifempty
rotate 4
@@ -10,10 +10,14 @@
endscript
}
-/var/log/httpd/cobbler_wui.log {
+/var/log/cobbler/webui.log {
missingok
notifempty
rotate 4
weekly
+ postrotate
+ if [ -f /var/lock/subsys/cobblerd ]; then
+ /etc/init.d/cobblerd condrestart
+ fi
+ endscript
}
-
diff --git a/config/modules.conf b/config/modules.conf
index fbe710c..2d60d21 100644
--- a/config/modules.conf
+++ b/config/modules.conf
@@ -5,3 +5,8 @@ profile = serializer_yaml
system = serializer_yaml
repo = serializer_yaml
+[authentication]
+module = authn_configfile
+
+[authorization]
+module = authn_allowall
diff --git a/config/settings b/config/settings
index 85d6395..c006cb3 100644
--- a/config/settings
+++ b/config/settings
@@ -5,11 +5,15 @@ bootloaders:
default_kickstart: /etc/cobbler/default.ks
default_virt_bridge: xenbr0
default_virt_type: auto
+default_virt_file_size: 5
+default_virt_ram: 512
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
+http_port: 80
+kerberos_realm: 'example.org'
kernel_options:
ksdevice: eth0
lang: ' '
@@ -18,6 +22,7 @@ manage_dhcp: 0
manage_dhcp_mode: isc
next_server: '127.0.0.1'
pxe_just_once: 0
+run_post_install_trigger: 0
server: '127.0.0.1'
snippetsdir: /var/lib/cobbler/snippets
syslog_port: 25150
diff --git a/config/users.digest b/config/users.digest
new file mode 100644
index 0000000..f14fbea
--- /dev/null
+++ b/config/users.digest
@@ -0,0 +1 @@
+cobbler:Cobbler:a2d6bae81669d707b72c0bd9806e01f3
diff --git a/config/webui-cherrypy.cfg b/config/webui-cherrypy.cfg
deleted file mode 100644
index 5d7dca9..0000000
--- a/config/webui-cherrypy.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-[global]
-base_url_filter.on = True
-server.thread_pool = 10
-
-[/static]
-tools.staticdir.on = True
-tools.staticdir.dir = "webui"
-tools.staticdir.root = "/var/www/cobbler"
-
diff --git a/docs/cobbler.pod b/docs/cobbler.pod
index d99fdab..70c0337 100644
--- a/docs/cobbler.pod
+++ b/docs/cobbler.pod
@@ -14,7 +14,7 @@ Distributions contain information about what kernel and initrd are used, plus me
Profiles associate a Distribution with a kickstart file and optionally customize the metadata further.
-Systems associate a MAC, IP, and/or hostname with a distribution and optionally customize the metadata further.
+Systems associate a MAC, IP, and/or hostname with a profile and optionally customize the metadata further.
Repositories contain yum mirror information. Using cobbler to mirror repositories is an optional feature, though provisioning and package management share a lot in common.
@@ -176,12 +176,12 @@ Example: If profile A has --kopts="x=7 y=2", B inherits from A, and B has --kop
Example: If profile B has --virt-ram=256 and A has --virt-ram of 512, profile B will use the value 256.
Example: If profile A has a --virt-file-size of 5 and B does not specify a size, B will use the value from A.
-=back
-
=item server-override
This parameter should be useful only in select circumstances. If machines are on a subnet that cannot access the cobbler server using the name/IP as configured in the cobbler settings file, use this parameter to override that server name. See also --dhcp-tag for configuring the next server and DHCP informmation of the system if you are also using Cobbler to help manage your DHCP configuration.
+=back
+
=head2 ADDING A SYSTEM
System records map a piece of hardware (or a virtual machine) with the cobbler profile to be assigned to run on it. This may be thought of as chosing a role for a specific system.
@@ -209,8 +209,6 @@ Specifying a mac address via --mac allows the system object to boot via PXE. If
MAC addresses have the format AA:BB:CC:DD:EE:FF.
-If you would like to specify additional interfaces, use --mac0=x, --mac1=y and so on. Interfaces 0 through 7 are supported.
-
=item ip
If cobbler is configured to generate a DHCP configuratition (see advanced section), use this
@@ -220,8 +218,6 @@ Example: ---ip=192.168.1.50
Note for Itanium users: this setting is always required for IA64 regardless of whether DHCP management is enabled.
-If you would like to specify additional IPs, use --ip0=x, --ip1=y and so on.
-
If DHCP management is disabled, setting this parameter may still be useful for record keeping, and it is also available in all kickstart templates, so it can be easily used for static IP configuration within kickstarts.
=item hostname
@@ -230,8 +226,6 @@ If using the DHCP configuration feature (see advanced section) with dnsmasq, use
Example: --hostname=mycomputer.example.com
-If you would like to specify additional hostnames, use --hostname0=x, --hostname1=y and so on.
-
=item --gateway and --subnet
If you are using static IP configurations, you may find it useful to store gateway and subnet
@@ -239,9 +233,6 @@ information inside of cobbler. These variables are not used internally by cobbl
made available in cobbler templates, which are described later in this document and on the Wiki.
For DHCP configurations, these parameters should be left blank.
-To describe gateway and subnet information for multiple intefaces, use --gateway0=x, --gateway1=y
-and so on. Subnets work the same way.
-
=item --virt-bridge
(Virt-only) While --virt-bridge is present in the profile object (see above), here it works on an interface by interface basis. For instance it would be possible to have --virt-bridge0=xenbr0 and --virt-bridge1=xenbr1. If not specified in cobbler for each interface, koan will use the value as specified in the profile for each interface, which may not always be what is intended, but will be sufficient in most cases.
@@ -261,10 +252,21 @@ If you are setting up a PXE environment with multiple subnets/gateways, and are
By default, the dhcp tag for all systems is "default" and means that in the DHCP template files the systems will expand out where $insert_cobbler_systems_definitions is found in the DHCP template. However, you may want certain systems to expand out in other places in the file. Setting --dhcp-tag=subnet2 for instance, will cause that system to expand out where $insert_cobbler_system_definitions_subnet2 is found, allowing you to insert directives to specify different subnets (or other parameters) before the DHCP configuration entries for those particular systems.
-If your system has multiple network interfaces, use --dhcp-tag0=x, --dhcp-tag1=y and so on.
-
This is described further on the Cobbler Wiki.
+=item --interface
+
+By default flags like --ip, --mac, --dhcp-tag, --gateway, --subnet, and --virt-bridge operation on the first network
+interface defined for a system. Additional interfaces can be specified (0 through 7) for use with the edit command.
+
+Example:
+
+cobbler system edit --name=foo --ip=192.168.1.50 --mac=AA:BB:CC:DD:EE:A0
+cobbler system edit --name=foo --interface=2 --ip=192.168.1.51 --mac=AA:BB:CC:DD:EE:A1
+cobbler system report foo
+
+NOTE: Additional interfaces can presently only be deleted via the web interface.
+
=end
=head2 ADDING A REPOSITORY TO MIRROR
@@ -274,7 +276,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<cobbler repo add --mirror=url --name=string [--rpmlist=list] [--creatrepo-flags=string] [--keep-updated=Y/N] [--arch=string]>
+B<cobbler repo add --mirror=url --name=string [--rpmlist=list] [--creatrepo-flags=string] [--keep-updated=Y/N] [--priority=number][--arch=string]>
=over
@@ -315,17 +317,6 @@ Distros that can make use of yum repositories during kickstart include FC6 and l
See the documentation on "cobbler profile add" for more information.
-=item local-filename
-
-Local filename specifies, for kickstarts containing the template parameter "yum_config_stanza",
-what files to populate on provisioned clients in /etc/yum.repos.d.
-
-In other words, if this value is "foo", the repo would be installed on provisioned clients as "/etc/yum.repos.d/foo.repo".
-
-If you don't want clients to have this repo installed, don't add a name for the repo, and provisioned machines will not configure yum to know about this repo -- you can still do it manually if you choose. The repository will still be used for installation, it just won't get installed automatically in /etc/yum.repos.d on the client.
-
-See /etc/cobbler/kickstart_fc6.ks for an example of how to employ this within a kickstart template.
-
=item rpm-list
By specifying a space-delimited list of package names for --rpm-list, one can decide to mirror only a part of a repo (the list of packages given, plus dependencies). This may be helpful in conserving time/space/bandwidth. For instance, when mirroring FC6 Extras, it may be desired to mirror just cobbler and koan, and skip all of the game packages. To do this, use --rpm-list="cobbler koan".
@@ -340,10 +331,18 @@ 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 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.
+
=item arch
Specifies what architecture the repository should use. By default the current system arch (of the server) is used, which may not be desirable. Using this to override the default arch allows mirroring of source repositories (using --arch=src).
+=item yumopts
+
+Sets values for additional yum options that the repo should use on installed systems. For instance if a yum plugin takes a certain parameter "alpha" and "beta", use something like --yumopts="alpha=2 beta=3".
+
=back
=head2 DISPLAYING CONFIGURATION ENTRIES
@@ -591,6 +590,10 @@ By default, the rsync operations will exclude PPC content, debug RPMs, and ISO i
Note that all of the import commands will mirror install tree content into /var/www/cobbler unless a network accessible location is given with --available-as. --available-as will be primarily used when importing distros stored on an external NAS box, or potentially on another partition on the same machine that is already accessible via http:// or ftp://.
+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.
+
=head2 DEFAULT PXE BOOT BEHAVIOR
What happens when PXE booting a system when cobbler has no record of the system being booted?
@@ -664,25 +667,9 @@ Cobbler also makes itself available as a Python API for use by higher level mana
=head2 WEB USER INTERFACE
-Most of the day-to-day actions in cobbler's command line can be performed in Cobbler's Web UI. To enable and access the WebUI, perform the following steps.
-
-1) Set xmlrpc_rw_enabled to 1 in /var/lib/cobbler/settings to enable network control.
-
-2) Change the admin xmlrpc secret in /etc/cobbler/auth.conf. You won't have to remember it.
-
-3) The default Web UI password is "cobbler/ILoveCobbler", to change this, run:
-
-htdigest /var/www/cgi-bin/cobbler/.htpasswd "Cobbler WebUI Authentication" cobbler
-
-4) SELinux users may also have to run:
-
-setsebool httpd_can_network_connect true
-
-chcon httpd_sys_content_t /etc/cobbler/auth.conf
-
-5) Run /sbin/service cobblerd restart.
+Most of the day-to-day actions in cobbler's command line can be performed in Cobbler's Web UI. To enable and access the WebUI, see the following documentation:
-6) Visit http://yourserver.example.org/cgi-bin/cobbler/webui.cgi and log in with whatever you chose in step 3.
+https://hosted.fedoraproject.org/projects/cobbler/wiki/CobblerWebUi
=head1 EXIT_STATUS
diff --git a/kickstarts/kickstart_fc6_domU.ks b/kickstarts/kickstart_fc6_domU.ks
deleted file mode 100644
index 14eccb2..0000000
--- a/kickstarts/kickstart_fc6_domU.ks
+++ /dev/null
@@ -1,41 +0,0 @@
-# DomU kickstart for Fedora Server Spin
-# Installs 142 packages / 560MB
-# Tested with FC6
-
-install
-reboot
-url --url=$tree
-
-lang en_US.UTF-8
-keyboard us
-xconfig --driver "fbdev" --resolution 800x600 --depth 24
-network --device eth0 --bootproto dhcp
-rootpw --iscrypted \$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.
-firewall --enabled --port=22:tcp
-authconfig --enableshadow --enablemd5
-selinux --disabled
-timezone --utc America/New_York
-bootloader --location=mbr --driveorder=xvda --append="rhgb quiet"
-
-clearpart --all --initlabel --drives=xvda
-part /boot --fstype ext3 --size=100 --ondisk=xvda
-part pv.2 --size=0 --grow --ondisk=xvda
-volgroup domu --pesize=32768 pv.2
-logvol / --fstype ext3 --name=lv00 --vgname=domu --size=1024 --grow
-logvol swap --fstype swap --name=lv01 --vgname=domu --size=272 --grow --maxsize=544
-$yum_repo_stanza
-
-%packages --nobase
-crontabs
-dhclient
-dhcpv6_client
-nfs-utils
-openssh-clients
-openssh-server
-yum
-wget
-
-%post
-$yum_config_stanza
-$kickstart_done
-
diff --git a/kickstarts/kickstart_fc5.ks b/kickstarts/legacy.ks
index 84bd1e2..84bd1e2 100644
--- a/kickstarts/kickstart_fc5.ks
+++ b/kickstarts/legacy.ks
diff --git a/kickstarts/kickstart_fc6.ks b/kickstarts/sample.ks
index 5208ed7..5208ed7 100644
--- a/kickstarts/kickstart_fc6.ks
+++ b/kickstarts/sample.ks
diff --git a/scripts/cobbler b/scripts/cobbler
index 4aef615..1b69ab6 100755
--- a/scripts/cobbler
+++ b/scripts/cobbler
@@ -14,7 +14,6 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-import sys
import cobbler.cobbler as app
-sys.exit(app.main())
+app.main()
diff --git a/scripts/cobbler_auth_help b/scripts/cobbler_auth_help
new file mode 100644
index 0000000..8842d59
--- /dev/null
+++ b/scripts/cobbler_auth_help
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+# Kerberos helper for logins
+#
+# Copyright 2007, Red Hat, Inc
+# Michael DeHaan <mdehaan@redhat.com>
+#
+# 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=0;
+
+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 $method $realm ($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/cobblerd b/scripts/cobblerd
index b6bf8a5..3170e58 100755
--- a/scripts/cobblerd
+++ b/scripts/cobblerd
@@ -16,24 +16,28 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import sys
import os
+import cobbler.api as bootapi
import cobbler.cobblerd as app
import logging
import cobbler.utils as utils
-logger = logging.getLogger("cobbler.cobblerd")
-logger.setLevel(logging.DEBUG)
-ch = logging.FileHandler("/var/log/cobbler/cobblerd.log")
-ch.setLevel(logging.DEBUG)
-formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
-ch.setFormatter(formatter)
-logger.addHandler(ch)
+#logger = logging.getLogger("cobbler.cobblerd")
+#logger.setLevel(logging.DEBUG)
+#ch = logging.FileHandler("/var/log/cobbler/cobblerd.log")
+#ch.setLevel(logging.DEBUG)
+#formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+#ch.setFormatter(formatter)
+#logger.addHandler(ch)
+
+api = bootapi.BootAPI()
+logger = api.logger_remote
if __name__ == "__main__":
#############################################
# daemonizing code: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
- logger.debug("started")
+ logger.info("cobblerd started")
try:
pid = os.fork()
if pid > 0:
diff --git a/scripts/findks.cgi b/scripts/findks.cgi
index fbb6fd2..70b9870 100755
--- a/scripts/findks.cgi
+++ b/scripts/findks.cgi
@@ -22,7 +22,7 @@ import socket
import xmlrpclib
COBBLER_BASE = "/var/www/cobbler"
-XMLRPC_SERVER = "http://127.0.0.1:25151"
+XMLRPC_SERVER = "http://127.0.0.1/cobbler_api_rw"
#----------------------------------------------------------------------
diff --git a/scripts/index.py b/scripts/index.py
new file mode 100755
index 0000000..d32a3a6
--- /dev/null
+++ b/scripts/index.py
@@ -0,0 +1,160 @@
+"""
+mod_python gateway to all interesting cobbler web functions
+
+Copyright 2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+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
+from cobbler.webui import CobblerWeb
+
+XMLRPC_SERVER = "http://127.0.0.1:25152" # was http://127.0.0.1/cobbler_api_rw"
+
+#=======================================
+
+class ServerProxy(xmlrpclib.ServerProxy):
+
+ """
+ Establishes a connection from the mod_python
+ web interface to cobblerd, which incidentally
+ is also being proxied by Apache.
+ """
+
+ def __init__(self, url=None):
+ xmlrpclib.ServerProxy.__init__(self, url, allow_none=True)
+
+xmlrpc_server = ServerProxy(XMLRPC_SERVER)
+
+#=======================================
+
+def __get_user(req):
+ """
+ What user are we logged in as?
+ """
+ req.add_common_vars()
+ env_vars = req.subprocess_env.copy()
+ return env_vars["REMOTE_USER"]
+
+def __get_session(req):
+ """
+ Get/Create the Apache Session Object
+ FIXME: any reason to not use MemorySession?
+ """
+ if not hasattr(req,"session"):
+ req.session = Session.MemorySession(req)
+ return req.session
+
+#======================================================
+
+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_user = __get_user(req)
+ my_uri = req.uri
+ sess = __get_session(req)
+ token = sess['cobbler_token']
+
+ # needed?
+ 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'))
+
+ # instantiate a CobblerWeb object
+ cw = CobblerWeb.CobblerWeb(
+ apache = apache,
+ token = token,
+ base_url = "/cobbler/web/",
+ server = "http://127.0.0.1/cobbler_api_rw"
+ )
+
+ # check for a valid path/mode
+ # handle invalid paths gracefully
+ mode = form.get('mode','index')
+ if mode in cw.modes():
+ func = getattr( cw, mode )
+ content = func( **form )
+ else:
+ func = getattr( cw, 'error_page' )
+ 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)
+
+ return apache.OK
+
+#======================================================
+
+def authenhandler(req):
+
+ """
+ Validates that username/password are a valid combination, but does
+ not check access levels.
+ """
+
+ my_pw = req.get_basic_auth_pw()
+ 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:
+ apache.log_error(str(e))
+ return apache.HTTP_UNAUTHORIZED
+
+ try:
+ ok = xmlrpc_server.check_access(token,my_uri)
+ except Exception, e:
+ apache.log_error(str(e))
+ return apache.HTTP_FORBIDDEN
+
+
+ sess=__get_session(req)
+ sess['cobbler_token'] = token
+ sess.save()
+
+ return apache.OK
+
+#======================================================
+
+def accesshandler(req):
+
+ """
+ Not using this
+ """
+
+ return apache.OK
+
+#======================================================
+
+def authenzhandler(req):
+
+ """
+ Not using this
+ """
+
+ return apache.OK
+
diff --git a/scripts/nopxe.cgi b/scripts/nopxe.cgi
index e90e886..a2eae88 100755
--- a/scripts/nopxe.cgi
+++ b/scripts/nopxe.cgi
@@ -27,7 +27,7 @@ import xmlrpclib
from cobbler import sub_process as sub_process
COBBLER_BASE = "/var/www/cobbler"
-XMLRPC_SERVER = "http://127.0.0.1:25151"
+XMLRPC_SERVER = "http://127.0.0.1/cobbler_api"
#----------------------------------------------------------------------
diff --git a/scripts/post_install_trigger.cgi b/scripts/post_install_trigger.cgi
new file mode 100644
index 0000000..4a79c8b
--- /dev/null
+++ b/scripts/post_install_trigger.cgi
@@ -0,0 +1,72 @@
+#!/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 <tim.verhoeven.be@gmail.com>, 2007
+# tweaked: Michael DeHaan <mdehaan@redhat.com>
+
+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/scripts/webui.cgi b/scripts/webui.cgi
deleted file mode 100755
index 1a7257d..0000000
--- a/scripts/webui.cgi
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/env python
-#
-# Web Interface for Cobbler - CGI Controller
-#
-# Copyright 2007 Albert P. Tobey <tobert@gmail.com>
-#
-# 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 cgi
-import cgitb
-import Cookie
-import os
-import sys
-import ConfigParser
-from cobbler.webui.CobblerWeb import CobblerWeb
-
-def map_modes():
- path = os.environ.get( 'PATH_INFO', 'index' )
-
- if path.startswith('/'):
- path = path[1:]
- if path.endswith('/'):
- path = path[:-1]
-
- if path is '':
- path = 'index'
-
- return path
-
-def base_url():
- return os.environ.get('SCRIPT_NAME', '')
-
-def configure():
- # FIXME: read a config file ...
- config = {
- 'token': None,
- 'server': None,
- 'base_url': None,
- 'username': None,
- 'password': None,
- 'cgitb_enabled': 1
- }
-
- # defaults
- if config['server'] is None:
- config['server'] = "http://127.0.0.1/cobbler_api_rw"
-
- if config['base_url'] is None:
- config['base_url'] = base_url()
-
- if ( os.access('/etc/cobbler/auth.conf', os.R_OK) ):
- config_parser = ConfigParser.ConfigParser()
- auth_conf = open("/etc/cobbler/auth.conf")
- config_parser.readfp(auth_conf)
- auth_conf.close()
- for auth in config_parser.items("xmlrpc_service_users"):
- sys.stderr.write( str(auth) )
- if auth[1].lower() != "disabled":
- config['username'] = auth[0]
- config['password'] = auth[1]
-
- return config
-
-def main():
- content = "Something went wrong and I couldn't generate any content for you!"
- cw_conf = configure()
- path = map_modes()
- form = cgi.parse()
-
- # make cgitb enablement configurable
- if cw_conf['cgitb_enabled'] == 1:
- cgitb.enable()
- cw_conf.pop('cgitb_enabled')
-
- # exchnage single-element arrays in the 'form' dictionary for just that item
- # so there isn't a ton of 'foo[0]' craziness where 'foo' should suffice
- # - may be bad for form elements that are sometimes lists and sometimes
- # single items
- for key,val in form.items():
- if isinstance(val, list):
- if len(val) == 1:
- form[key] = val[0]
-
- # instantiate a CobblerWeb object
- cw = CobblerWeb( **cw_conf )
-
- # check for a valid path/mode
- if path in cw.modes():
- func = getattr( cw, path )
- content = func( **form )
-
- # handle invalid paths gracefully
- else:
- func = getattr( cw, 'error_page' )
- content = func( "Invalid Mode: \"%s\"" % path )
-
- # deliver content
- print "Content-type: text/html"
- print
- print content
-
-main()
-
diff --git a/setup.py b/setup.py
index c8a6b81..cdf768c 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ import sys
from distutils.core import setup, Extension
import string
-VERSION = "0.6.5"
+VERSION = "0.8.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.
@@ -45,6 +45,7 @@ if __name__ == "__main__":
tftp_images = "/tftpboot/images"
rotpath = "/etc/logrotate.d"
cgipath = "/var/www/cgi-bin/cobbler"
+ modpython = "/var/www/cobbler/web"
setup(
name="cobbler",
version = VERSION,
@@ -56,24 +57,22 @@ if __name__ == "__main__":
"cobbler",
"cobbler/yaml",
"cobbler/modules",
+ "cobbler/server",
"cobbler/webui",
],
- scripts = ["scripts/cobbler", "scripts/cobblerd"],
+ scripts = ["scripts/cobbler", "scripts/cobblerd", "scripts/cobbler_auth_help"],
data_files = [
-
+ (modpython, ['scripts/index.py']),
# cgi files
- (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']),
- (cgipath, ['scripts/webui.cgi']),
+ (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']),
+ (cgipath, ['scripts/post_install_trigger.cgi']),
# miscellaneous config files
- (cgipath, ['config/.htaccess']),
- (cgipath, ['config/.htpasswd']),
(rotpath, ['config/cobblerd_rotate']),
(wwwconf, ['config/cobbler.conf']),
(cobpath, ['config/cobbler_hosts']),
(etcpath, ['config/modules.conf']),
- (etcpath, ['config/auth.conf']),
- (etcpath, ['config/webui-cherrypy.cfg']),
+ (etcpath, ['config/users.digest']),
(etcpath, ['config/rsync.exclude']),
(initpath, ['config/cobblerd']),
(cobpath, ['config/settings']),
@@ -86,9 +85,8 @@ if __name__ == "__main__":
(cobpath, ['loaders/menu.c32']),
# sample kickstart files
- (etcpath, ['kickstarts/kickstart_fc5.ks']),
- (etcpath, ['kickstarts/kickstart_fc6.ks']),
- (etcpath, ['kickstarts/kickstart_fc6_domU.ks']),
+ (etcpath, ['kickstarts/legacy.ks']),
+ (etcpath, ['kickstarts/sample.ks']),
(etcpath, ['kickstarts/default.ks']),
# templates for DHCP and syslinux configs
@@ -192,6 +190,7 @@ if __name__ == "__main__":
("%sdelete/repo/pre" % trigpath, []),
("%sdelete/repo/post" % trigpath, []),
("%sdelete/repo/post" % trigpath, []),
+ ("%sinstall/post" % trigpath, []),
("%ssync/pre" % trigpath, []),
("%ssync/post" % trigpath, [ "triggers/restart-services.trigger" ])
],
diff --git a/tests/performance.py b/tests/performance.py
new file mode 100644
index 0000000..15b9bad
--- /dev/null
+++ b/tests/performance.py
@@ -0,0 +1,75 @@
+# test script to evaluate Cobbler API performance
+#
+# Michael DeHaan <mdehaan@redhat.com>
+
+import os
+import cobbler.api as capi
+import time
+import sys
+import random
+
+N = 200
+print "sample size is %s" % N
+
+api = capi.BootAPI()
+
+# part one ... create our test systems for benchmarking purposes if
+# they do not seem to exist.
+
+if not api.profiles().find("foo"):
+ print "CREATE A PROFILE NAMED 'foo' to be able to run this test"
+ sys.exit(0)
+
+def random_mac():
+ mac = [ 0x00, 0x16, 0x3e,
+ random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff) ]
+ return ':'.join(map(lambda x: "%02x" % x, mac))
+
+print "Deleting autotest entries from a previous run"
+time1 = time.time()
+for x in xrange(0,N):
+ try:
+ sys = api.systems().remove("autotest-%s" % x,with_delete=True)
+ except:
+ pass
+time2 = time.time()
+print "ELAPSED: %s seconds" % (time2 - time1)
+
+print "Creating test systems from scratch"
+time1 = time.time()
+for x in xrange(0,N):
+ sys = api.new_system()
+ sys.set_name("autotest-%s" % x)
+ sys.set_mac_address(random_mac())
+ sys.set_profile("foo") # assumes there is already a foo
+ # print "... adding: %s" % sys.name
+ api.systems().add(sys,save=True,with_sync=False,with_triggers=False)
+time2 = time.time()
+print "ELAPSED %s seconds" % (time2 - time1)
+
+for mode2 in [ "fast", "normal", "full" ]:
+ for mode in [ "on", "off" ]:
+
+ print "Running netboot edit benchmarks (turn %s, %s)" % (mode, mode2)
+ time1 = time.time()
+ for x in xrange(0,N):
+ sys = api.systems().find("autotest-%s" % x)
+ if mode == "off":
+ sys.set_netboot_enabled(0)
+ else:
+ sys.set_netboot_enabled(1)
+ # print "... editing: %s" % sys.name
+ if mode2 == "fast":
+ api.systems().add(sys, save=True, with_sync=False, with_triggers=False, quick_pxe_update=True)
+ if mode2 == "normal":
+ api.systems().add(sys, save=True, with_sync=False, with_triggers=False)
+ if mode2 == "full":
+ api.systems().add(sys, save=True, with_sync=True, with_triggers=True)
+
+ time2 = time.time()
+ print "ELAPSED: %s seconds" % (time2 - time1)
+
+
+
diff --git a/tests/tests.py b/tests/tests.py
index f81d508..99ba0b9 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -83,22 +83,22 @@ class BootTest(unittest.TestCase):
self.assertTrue(distro.set_name("testdistro0"))
self.assertTrue(distro.set_kernel(self.fk_kernel))
self.assertTrue(distro.set_initrd(self.fk_initrd))
- self.assertTrue(self.api.distros().add(distro))
- self.assertTrue(self.api.distros().find(name="testdistro0"))
+ 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"))
self.assertTrue(profile.set_kickstart(FAKE_KICKSTART))
- self.assertTrue(self.api.profiles().add(profile))
- self.assertTrue(self.api.profiles().find(name="testprofile0"))
+ self.assertTrue(self.api.add_profile(profile))
+ self.assertTrue(self.api.find_profile(name="testprofile0"))
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_profile("testprofile0"))
- self.assertTrue(self.api.systems().add(system))
- self.assertTrue(self.api.systems().find(name="drwily.rdu.redhat.com"))
+ self.assertTrue(self.api.add_system(system))
+ self.assertTrue(self.api.find_system(name="drwily.rdu.redhat.com"))
class MultiNIC(BootTest):
@@ -121,13 +121,15 @@ class MultiNIC(BootTest):
self.assertTrue(system.set_subnet("255.255.255.0","intf4"))
self.assertTrue(system.set_dhcp_tag("tag2","intf5"))
self.assertTrue(self.api.systems().add(system))
- self.assertTrue(self.api.systems().find(hostname="zero"))
+ # mixing in some higher level API calls with some lower level internal stuff
+ # just to make sure it's all good.
+ self.assertTrue(self.api.find_system(hostname="zero"))
self.assertTrue(self.api.systems().find(mac_address="EE:FF:DD:CC:DD:CC"))
self.assertTrue(self.api.systems().find(ip_address="127.0.0.5"))
- self.assertTrue(self.api.systems().find(virt_bridge="zero"))
+ self.assertTrue(self.api.find_system(virt_bridge="zero"))
self.assertTrue(self.api.systems().find(gateway="192.168.1.25"))
self.assertTrue(self.api.systems().find(subnet="255.255.255.0"))
- self.assertTrue(self.api.systems().find(dhcp_tag="tag2"))
+ self.assertTrue(self.api.find_system(dhcp_tag="tag2"))
self.assertTrue(self.api.systems().find(dhcp_tag="zero"))
# verify that systems has exactly 5 interfaces
diff --git a/website/new/communicate.php b/website/new/communicate.php
index 62c0642..59af7ae 100755
--- a/website/new/communicate.php
+++ b/website/new/communicate.php
@@ -26,9 +26,6 @@
<?php
include("nav.php");
?>
-<?php
- include("feed.php");
-?>
</div>
<div id="content">
diff --git a/website/new/documentation.html b/website/new/documentation.html
index c5c5b09..c1981f9 100644
--- a/website/new/documentation.html
+++ b/website/new/documentation.html
@@ -21,7 +21,8 @@
<h3>Open Office Presentations</h3>
<ul>
-<li><a href="docs/cobbler_summit.odp">Red Hat Summit 5/2007</A></li>
+<li><a href="http://fedorapeople.org/~mdehaan/files/cobbler/cobbler7.odp">FudCON 2008 (Raleigh) Update & Overview</A></li>
+<li><a href="docs/cobbler_summit.odp">Red Hat Summit (San Diego) 5/2007</A></li>
<li><a href="docs/cobbler.odp">Older Slides</A> (somewhat out of date)</li>
</ul>
diff --git a/website/new/download.html b/website/new/download.html
index 1319e28..109ae30 100644
--- a/website/new/download.html
+++ b/website/new/download.html
@@ -6,18 +6,20 @@
<p>The latest source code (it's all Python) is available through git:</p>
<blockquote>
-git clone git://git.fedoraproject.org/git/hosted/cobbler<br/>
-git clone git://git.fedoraproject.org/git/hosted/koan
+git clone git://git.fedorahosted.org/cobbler
+git clone git://git.fedorahosted.org/koan
</blockquote>
</p>
<p>Source can also be browsed with gitweb:</p>
<blockquote>
-<A HREF="http://git.fedoraproject.org/?p=hosted/cobbler;a=summary">Cobbler</A><br/>
-<A HREF="http://git.fedoraproject.org/?p=hosted/koan;a=summary">Koan</A>
+<A HREF="http://git.fedoraproject.org/git/?p=cobbler;a=summary">Cobbler</A><br/>
+<A HREF="http://git.fedoraproject.org/git/?p=koan;a=summary">Koan</A>
</blockquote>
+For information about development branches, see the Wiki.
+
<h4>Source RPMs</h4>
<p>
<blockquote>
diff --git a/website/new/faq.html b/website/new/faq.html
index 0aa89ab..9147adb 100644
--- a/website/new/faq.html
+++ b/website/new/faq.html
@@ -31,14 +31,14 @@ Cobbler runs on RHEL 4 and later, Fedora 5 and later, and Centos 4 and later. K
<a name="reinstalls">
<dt>What are my non-PXE install options?</dt>
</a>
- <dd>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 <A HREF="./koan-manpage.php">koan</A> manpage documentation.
+ <dd>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 <A HREF="./koan.html">koan</A> manpage documentation.
<br />
<a href="#questions" class="back-to-top">Back to top</a>
</dd>
<a name="virt">
<dt>How are virtual systems installed?</dt>
</a>
- <dd>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 <A HREF="./koan-manpage.php">koan</A> manpage documentation.
+ <dd>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 <A HREF="./koan.html">koan</A> manpage documentation.
<br />
<a href="#questions" class="back-to-top">Back to top</a>
</dd>
@@ -49,7 +49,7 @@ Cobbler runs on RHEL 4 and later, Fedora 5 and later, and Centos 4 and later. K
<dd>
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.
<br/>
-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 <A HREF="./cobbler-manpage.php">Cobbler</A> manpage documentation.
+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 <A HREF="./cobbler.html">Cobbler</A> manpage documentation.
<a href="#questions" class="back-to-top">Back to top</a>
</dd>
diff --git a/website/new/magpierss/extlib/Snoopy.class.inc b/website/new/magpierss/extlib/Snoopy.class.inc
deleted file mode 100644
index 3ddecba..0000000
--- a/website/new/magpierss/extlib/Snoopy.class.inc
+++ /dev/null
@@ -1,900 +0,0 @@
-<?php
-
-/*************************************************
-
-Snoopy - the PHP net client
-Author: Monte Ohrt <monte@ispi.net>
-Copyright (c): 1999-2000 ispi, all rights reserved
-Version: 1.0
-
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-You may contact the author of Snoopy by e-mail at:
-monte@ispi.net
-
-Or, write to:
-Monte Ohrt
-CTO, ispi
-237 S. 70th suite 220
-Lincoln, NE 68510
-
-The latest version of Snoopy can be obtained from:
-http://snoopy.sourceforge.com
-
-*************************************************/
-
-class Snoopy
-{
- /**** Public variables ****/
-
- /* user definable vars */
-
- var $host = "www.php.net"; // host name we are connecting to
- var $port = 80; // port we are connecting to
- var $proxy_host = ""; // proxy host to use
- var $proxy_port = ""; // proxy port to use
- var $agent = "Snoopy v1.0"; // agent we masquerade as
- var $referer = ""; // referer info to pass
- var $cookies = array(); // array of cookies to pass
- // $cookies["username"]="joe";
- var $rawheaders = array(); // array of raw headers to send
- // $rawheaders["Content-type"]="text/html";
-
- var $maxredirs = 5; // http redirection depth maximum. 0 = disallow
- var $lastredirectaddr = ""; // contains address of last redirected address
- var $offsiteok = true; // allows redirection off-site
- var $maxframes = 0; // frame content depth maximum. 0 = disallow
- var $expandlinks = true; // expand links to fully qualified URLs.
- // this only applies to fetchlinks()
- // or submitlinks()
- var $passcookies = true; // pass set cookies back through redirects
- // NOTE: this currently does not respect
- // dates, domains or paths.
-
- var $user = ""; // user for http authentication
- var $pass = ""; // password for http authentication
-
- // http accept types
- var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*";
-
- var $results = ""; // where the content is put
-
- var $error = ""; // error messages sent here
- var $response_code = ""; // response code returned from server
- var $headers = array(); // headers returned from server sent here
- var $maxlength = 500000; // max return data length (body)
- var $read_timeout = 0; // timeout on read operations, in seconds
- // supported only since PHP 4 Beta 4
- // set to 0 to disallow timeouts
- var $timed_out = false; // if a read operation timed out
- var $status = 0; // http request status
-
- var $curl_path = "/usr/bin/curl";
- // Snoopy will use cURL for fetching
- // SSL content if a full system path to
- // the cURL binary is supplied here.
- // set to false if you do not have
- // cURL installed. See http://curl.haxx.se
- // for details on installing cURL.
- // Snoopy does *not* use the cURL
- // library functions built into php,
- // as these functions are not stable
- // as of this Snoopy release.
-
- // send Accept-encoding: gzip?
- var $use_gzip = true;
-
- /**** Private variables ****/
-
- var $_maxlinelen = 4096; // max line length (headers)
-
- var $_httpmethod = "GET"; // default http request method
- var $_httpversion = "HTTP/1.0"; // default http request version
- var $_submit_method = "POST"; // default submit method
- var $_submit_type = "application/x-www-form-urlencoded"; // default submit type
- var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type
- var $_redirectaddr = false; // will be set if page fetched is a redirect
- var $_redirectdepth = 0; // increments on an http redirect
- var $_frameurls = array(); // frame src urls
- var $_framedepth = 0; // increments on frame depth
-
- var $_isproxy = false; // set if using a proxy server
- var $_fp_timeout = 30; // timeout for socket connection
-
-/*======================================================================*\
- Function: fetch
- Purpose: fetch the contents of a web page
- (and possibly other protocols in the
- future like ftp, nntp, gopher, etc.)
- Input: $URI the location of the page to fetch
- Output: $this->results the output text from the fetch
-\*======================================================================*/
-
- function fetch($URI)
- {
-
- //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS);
- $URI_PARTS = parse_url($URI);
- if (!empty($URI_PARTS["user"]))
- $this->user = $URI_PARTS["user"];
- if (!empty($URI_PARTS["pass"]))
- $this->pass = $URI_PARTS["pass"];
-
- switch($URI_PARTS["scheme"])
- {
- case "http":
- $this->host = $URI_PARTS["host"];
- if(!empty($URI_PARTS["port"]))
- $this->port = $URI_PARTS["port"];
- if($this->_connect($fp))
- {
- if($this->_isproxy)
- {
- // using proxy, send entire URI
- $this->_httprequest($URI,$fp,$URI,$this->_httpmethod);
- }
- else
- {
- $path = $URI_PARTS["path"].(isset($URI_PARTS["query"]) ? "?".$URI_PARTS["query"] : "");
- // no proxy, send only the path
- $this->_httprequest($path, $fp, $URI, $this->_httpmethod);
- }
-
- $this->_disconnect($fp);
-
- if($this->_redirectaddr)
- {
- /* url was redirected, check if we've hit the max depth */
- if($this->maxredirs > $this->_redirectdepth)
- {
- // only follow redirect if it's on this site, or offsiteok is true
- if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
- {
- /* follow the redirect */
- $this->_redirectdepth++;
- $this->lastredirectaddr=$this->_redirectaddr;
- $this->fetch($this->_redirectaddr);
- }
- }
- }
-
- if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
- {
- $frameurls = $this->_frameurls;
- $this->_frameurls = array();
-
- while(list(,$frameurl) = each($frameurls))
- {
- if($this->_framedepth < $this->maxframes)
- {
- $this->fetch($frameurl);
- $this->_framedepth++;
- }
- else
- break;
- }
- }
- }
- else
- {
- return false;
- }
- return true;
- break;
- case "https":
- if(!$this->curl_path || (!is_executable($this->curl_path))) {
- $this->error = "Bad curl ($this->curl_path), can't fetch HTTPS \n";
- return false;
- }
- $this->host = $URI_PARTS["host"];
- if(!empty($URI_PARTS["port"]))
- $this->port = $URI_PARTS["port"];
- if($this->_isproxy)
- {
- // using proxy, send entire URI
- $this->_httpsrequest($URI,$URI,$this->_httpmethod);
- }
- else
- {
- $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : "");
- // no proxy, send only the path
- $this->_httpsrequest($path, $URI, $this->_httpmethod);
- }
-
- if($this->_redirectaddr)
- {
- /* url was redirected, check if we've hit the max depth */
- if($this->maxredirs > $this->_redirectdepth)
- {
- // only follow redirect if it's on this site, or offsiteok is true
- if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok)
- {
- /* follow the redirect */
- $this->_redirectdepth++;
- $this->lastredirectaddr=$this->_redirectaddr;
- $this->fetch($this->_redirectaddr);
- }
- }
- }
-
- if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0)
- {
- $frameurls = $this->_frameurls;
- $this->_frameurls = array();
-
- while(list(,$frameurl) = each($frameurls))
- {
- if($this->_framedepth < $this->maxframes)
- {
- $this->fetch($frameurl);
- $this->_framedepth++;
- }
- else
- break;
- }
- }
- return true;
- break;
- default:
- // not a valid protocol
- $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n';
- return false;
- break;
- }
- return true;
- }
-
-
-
-/*======================================================================*\
- Private functions
-\*======================================================================*/
-
-
-/*======================================================================*\
- Function: _striplinks
- Purpose: strip the hyperlinks from an html document
- Input: $document document to strip.
- Output: $match an array of the links
-\*======================================================================*/
-
- function _striplinks($document)
- {
- preg_match_all("'<\s*a\s+.*href\s*=\s* # find <a href=
- ([\"\'])? # find single or double quote
- (?(1) (.*?)\\1 | ([^\s\>]+)) # if quote found, match up to next matching
- # quote, otherwise match up to next space
- 'isx",$document,$links);
-
-
- // catenate the non-empty matches from the conditional subpattern
-
- while(list($key,$val) = each($links[2]))
- {
- if(!empty($val))
- $match[] = $val;
- }
-
- while(list($key,$val) = each($links[3]))
- {
- if(!empty($val))
- $match[] = $val;
- }
-
- // return the links
- return $match;
- }
-
-/*======================================================================*\
- Function: _stripform
- Purpose: strip the form elements from an html document
- Input: $document document to strip.
- Output: $match an array of the links
-\*======================================================================*/
-
- function _stripform($document)
- {
- preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements);
-
- // catenate the matches
- $match = implode("\r\n",$elements[0]);
-
- // return the links
- return $match;
- }
-
-
-
-/*======================================================================*\
- Function: _striptext
- Purpose: strip the text from an html document
- Input: $document document to strip.
- Output: $text the resulting text
-\*======================================================================*/
-
- function _striptext($document)
- {
-
- // I didn't use preg eval (//e) since that is only available in PHP 4.0.
- // so, list your entities one by one here. I included some of the
- // more common ones.
-
- $search = array("'<script[^>]*?>.*?</script>'si", // strip out javascript
- "'<[\/\!]*?[^<>]*?>'si", // strip out html tags
- "'([\r\n])[\s]+'", // strip out white space
- "'&(quote|#34);'i", // replace html entities
- "'&(amp|#38);'i",
- "'&(lt|#60);'i",
- "'&(gt|#62);'i",
- "'&(nbsp|#160);'i",
- "'&(iexcl|#161);'i",
- "'&(cent|#162);'i",
- "'&(pound|#163);'i",
- "'&(copy|#169);'i"
- );
- $replace = array( "",
- "",
- "\\1",
- "\"",
- "&",
- "<",
- ">",
- " ",
- chr(161),
- chr(162),
- chr(163),
- chr(169));
-
- $text = preg_replace($search,$replace,$document);
-
- return $text;
- }
-
-/*======================================================================*\
- Function: _expandlinks
- Purpose: expand each link into a fully qualified URL
- Input: $links the links to qualify
- $URI the full URI to get the base from
- Output: $expandedLinks the expanded links
-\*======================================================================*/
-
- function _expandlinks($links,$URI)
- {
-
- preg_match("/^[^\?]+/",$URI,$match);
-
- $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]);
-
- $search = array( "|^http://".preg_quote($this->host)."|i",
- "|^(?!http://)(\/)?(?!mailto:)|i",
- "|/\./|",
- "|/[^\/]+/\.\./|"
- );
-
- $replace = array( "",
- $match."/",
- "/",
- "/"
- );
-
- $expandedLinks = preg_replace($search,$replace,$links);
-
- return $expandedLinks;
- }
-
-/*======================================================================*\
- Function: _httprequest
- Purpose: go get the http data from the server
- Input: $url the url to fetch
- $fp the current open file pointer
- $URI the full URI
- $body body contents to send if any (POST)
- Output:
-\*======================================================================*/
-
- function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="")
- {
- if($this->passcookies && $this->_redirectaddr)
- $this->setcookies();
-
- $URI_PARTS = parse_url($URI);
- if(empty($url))
- $url = "/";
- $headers = $http_method." ".$url." ".$this->_httpversion."\r\n";
- if(!empty($this->agent))
- $headers .= "User-Agent: ".$this->agent."\r\n";
- if(!empty($this->host) && !isset($this->rawheaders['Host']))
- $headers .= "Host: ".$this->host."\r\n";
- if(!empty($this->accept))
- $headers .= "Accept: ".$this->accept."\r\n";
-
- if($this->use_gzip) {
- // make sure PHP was built with --with-zlib
- // and we can handle gzipp'ed data
- if ( function_exists(gzinflate) ) {
- $headers .= "Accept-encoding: gzip\r\n";
- }
- else {
- trigger_error(
- "use_gzip is on, but PHP was built without zlib support.".
- " Requesting file(s) without gzip encoding.",
- E_USER_NOTICE);
- }
- }
-
- if(!empty($this->referer))
- $headers .= "Referer: ".$this->referer."\r\n";
- if(!empty($this->cookies))
- {
- if(!is_array($this->cookies))
- $this->cookies = (array)$this->cookies;
-
- reset($this->cookies);
- if ( count($this->cookies) > 0 ) {
- $cookie_headers .= 'Cookie: ';
- foreach ( $this->cookies as $cookieKey => $cookieVal ) {
- $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; ";
- }
- $headers .= substr($cookie_headers,0,-2) . "\r\n";
- }
- }
- if(!empty($this->rawheaders))
- {
- if(!is_array($this->rawheaders))
- $this->rawheaders = (array)$this->rawheaders;
- while(list($headerKey,$headerVal) = each($this->rawheaders))
- $headers .= $headerKey.": ".$headerVal."\r\n";
- }
- if(!empty($content_type)) {
- $headers .= "Content-type: $content_type";
- if ($content_type == "multipart/form-data")
- $headers .= "; boundary=".$this->_mime_boundary;
- $headers .= "\r\n";
- }
- if(!empty($body))
- $headers .= "Content-length: ".strlen($body)."\r\n";
- if(!empty($this->user) || !empty($this->pass))
- $headers .= "Authorization: BASIC ".base64_encode($this->user.":".$this->pass)."\r\n";
-
- $headers .= "\r\n";
-
- // set the read timeout if needed
- if ($this->read_timeout > 0)
- socket_set_timeout($fp, $this->read_timeout);
- $this->timed_out = false;
-
- fwrite($fp,$headers.$body,strlen($headers.$body));
-
- $this->_redirectaddr = false;
- unset($this->headers);
-
- // content was returned gzip encoded?
- $is_gzipped = false;
-
- while($currentHeader = fgets($fp,$this->_maxlinelen))
- {
- if ($this->read_timeout > 0 && $this->_check_timeout($fp))
- {
- $this->status=-100;
- return false;
- }
-
- // if($currentHeader == "\r\n")
- if(preg_match("/^\r?\n$/", $currentHeader) )
- break;
-
- // if a header begins with Location: or URI:, set the redirect
- if(preg_match("/^(Location:|URI:)/i",$currentHeader))
- {
- // get URL portion of the redirect
- preg_match("/^(Location:|URI:)\s+(.*)/",chop($currentHeader),$matches);
- // look for :// in the Location header to see if hostname is included
- if(!preg_match("|\:\/\/|",$matches[2]))
- {
- // no host in the path, so prepend
- $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port;
- // eliminate double slash
- if(!preg_match("|^/|",$matches[2]))
- $this->_redirectaddr .= "/".$matches[2];
- else
- $this->_redirectaddr .= $matches[2];
- }
- else
- $this->_redirectaddr = $matches[2];
- }
-
- if(preg_match("|^HTTP/|",$currentHeader))
- {
- if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status))
- {
- $this->status= $status[1];
- }
- $this->response_code = $currentHeader;
- }
-
- if (preg_match("/Content-Encoding: gzip/", $currentHeader) ) {
- $is_gzipped = true;
- }
-
- $this->headers[] = $currentHeader;
- }
-
- # $results = fread($fp, $this->maxlength);
- $results = "";
- while ( $data = fread($fp, $this->maxlength) ) {
- $results .= $data;
- if (
- strlen($results) > $this->maxlength ) {
- break;
- }
- }
-
- // gunzip
- if ( $is_gzipped ) {
- // per http://www.php.net/manual/en/function.gzencode.php
- $results = substr($results, 10);
- $results = gzinflate($results);
- }
-
- if ($this->read_timeout > 0 && $this->_check_timeout($fp))
- {
- $this->status=-100;
- return false;
- }
-
- // check if there is a a redirect meta tag
-
- if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]+URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match))
- {
- $this->_redirectaddr = $this->_expandlinks($match[1],$URI);
- }
-
- // have we hit our frame depth and is there frame src to fetch?
- if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match))
- {
- $this->results[] = $results;
- for($x=0; $x<count($match[1]); $x++)
- $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host);
- }
- // have we already fetched framed content?
- elseif(is_array($this->results))
- $this->results[] = $results;
- // no framed content
- else
- $this->results = $results;
-
- return true;
- }
-
-/*======================================================================*\
- Function: _httpsrequest
- Purpose: go get the https data from the server using curl
- Input: $url the url to fetch
- $URI the full URI
- $body body contents to send if any (POST)
- Output:
-\*======================================================================*/
-
- function _httpsrequest($url,$URI,$http_method,$content_type="",$body="")
- {
- if($this->passcookies && $this->_redirectaddr)
- $this->setcookies();
-
- $headers = array();
-
- $URI_PARTS = parse_url($URI);
- if(empty($url))
- $url = "/";
- // GET ... header not needed for curl
- //$headers[] = $http_method." ".$url." ".$this->_httpversion;
- if(!empty($this->agent))
- $headers[] = "User-Agent: ".$this->agent;
- if(!empty($this->host))
- $headers[] = "Host: ".$this->host;
- if(!empty($this->accept))
- $headers[] = "Accept: ".$this->accept;
- if(!empty($this->referer))
- $headers[] = "Referer: ".$this->referer;
- if(!empty($this->cookies))
- {
- if(!is_array($this->cookies))
- $this->cookies = (array)$this->cookies;
-
- reset($this->cookies);
- if ( count($this->cookies) > 0 ) {
- $cookie_str = 'Cookie: ';
- foreach ( $this->cookies as $cookieKey => $cookieVal ) {
- $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; ";
- }
- $headers[] = substr($cookie_str,0,-2);
- }
- }
- if(!empty($this->rawheaders))
- {
- if(!is_array($this->rawheaders))
- $this->rawheaders = (array)$this->rawheaders;
- while(list($headerKey,$headerVal) = each($this->rawheaders))
- $headers[] = $headerKey.": ".$headerVal;
- }
- if(!empty($content_type)) {
- if ($content_type == "multipart/form-data")
- $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary;
- else
- $headers[] = "Content-type: $content_type";
- }
- if(!empty($body))
- $headers[] = "Content-length: ".strlen($body);
- if(!empty($this->user) || !empty($this->pass))
- $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass);
-
- for($curr_header = 0; $curr_header < count($headers); $curr_header++) {
- $cmdline_params .= " -H \"".$headers[$curr_header]."\"";
- }
-
- if(!empty($body))
- $cmdline_params .= " -d \"$body\"";
-
- if($this->read_timeout > 0)
- $cmdline_params .= " -m ".$this->read_timeout;
-
- $headerfile = uniqid(time());
-
- # accept self-signed certs
- $cmdline_params .= " -k";
- exec($this->curl_path." -D \"/tmp/$headerfile\"".escapeshellcmd($cmdline_params)." ".escapeshellcmd($URI),$results,$return);
-
- if($return)
- {
- $this->error = "Error: cURL could not retrieve the document, error $return.";
- return false;
- }
-
-
- $results = implode("\r\n",$results);
-
- $result_headers = file("/tmp/$headerfile");
-
- $this->_redirectaddr = false;
- unset($this->headers);
-
- for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++)
- {
-
- // if a header begins with Location: or URI:, set the redirect
- if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader]))
- {
- // get URL portion of the redirect
- preg_match("/^(Location: |URI:)(.*)/",chop($result_headers[$currentHeader]),$matches);
- // look for :// in the Location header to see if hostname is included
- if(!preg_match("|\:\/\/|",$matches[2]))
- {
- // no host in the path, so prepend
- $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port;
- // eliminate double slash
- if(!preg_match("|^/|",$matches[2]))
- $this->_redirectaddr .= "/".$matches[2];
- else
- $this->_redirectaddr .= $matches[2];
- }
- else
- $this->_redirectaddr = $matches[2];
- }
-
- if(preg_match("|^HTTP/|",$result_headers[$currentHeader]))
- {
- $this->response_code = $result_headers[$currentHeader];
- if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$this->response_code, $match))
- {
- $this->status= $match[1];
- }
- }
- $this->headers[] = $result_headers[$currentHeader];
- }
-
- // check if there is a a redirect meta tag
-
- if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]+URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match))
- {
- $this->_redirectaddr = $this->_expandlinks($match[1],$URI);
- }
-
- // have we hit our frame depth and is there frame src to fetch?
- if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match))
- {
- $this->results[] = $results;
- for($x=0; $x<count($match[1]); $x++)
- $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host);
- }
- // have we already fetched framed content?
- elseif(is_array($this->results))
- $this->results[] = $results;
- // no framed content
- else
- $this->results = $results;
-
- unlink("/tmp/$headerfile");
-
- return true;
- }
-
-/*======================================================================*\
- Function: setcookies()
- Purpose: set cookies for a redirection
-\*======================================================================*/
-
- function setcookies()
- {
- for($x=0; $x<count($this->headers); $x++)
- {
- if(preg_match("/^set-cookie:[\s]+([^=]+)=([^;]+)/i", $this->headers[$x],$match))
- $this->cookies[$match[1]] = $match[2];
- }
- }
-
-
-/*======================================================================*\
- Function: _check_timeout
- Purpose: checks whether timeout has occurred
- Input: $fp file pointer
-\*======================================================================*/
-
- function _check_timeout($fp)
- {
- if ($this->read_timeout > 0) {
- $fp_status = socket_get_status($fp);
- if ($fp_status["timed_out"]) {
- $this->timed_out = true;
- return true;
- }
- }
- return false;
- }
-
-/*======================================================================*\
- Function: _connect
- Purpose: make a socket connection
- Input: $fp file pointer
-\*======================================================================*/
-
- function _connect(&$fp)
- {
- if(!empty($this->proxy_host) && !empty($this->proxy_port))
- {
- $this->_isproxy = true;
- $host = $this->proxy_host;
- $port = $this->proxy_port;
- }
- else
- {
- $host = $this->host;
- $port = $this->port;
- }
-
- $this->status = 0;
-
- if($fp = fsockopen(
- $host,
- $port,
- $errno,
- $errstr,
- $this->_fp_timeout
- ))
- {
- // socket connection succeeded
-
- return true;
- }
- else
- {
- // socket connection failed
- $this->status = $errno;
- switch($errno)
- {
- case -3:
- $this->error="socket creation failed (-3)";
- case -4:
- $this->error="dns lookup failure (-4)";
- case -5:
- $this->error="connection refused or timed out (-5)";
- default:
- $this->error="connection failed (".$errno.")";
- }
- return false;
- }
- }
-/*======================================================================*\
- Function: _disconnect
- Purpose: disconnect a socket connection
- Input: $fp file pointer
-\*======================================================================*/
-
- function _disconnect($fp)
- {
- return(fclose($fp));
- }
-
-
-/*======================================================================*\
- Function: _prepare_post_body
- Purpose: Prepare post body according to encoding type
- Input: $formvars - form variables
- $formfiles - form upload files
- Output: post body
-\*======================================================================*/
-
- function _prepare_post_body($formvars, $formfiles)
- {
- settype($formvars, "array");
- settype($formfiles, "array");
-
- if (count($formvars) == 0 && count($formfiles) == 0)
- return;
-
- switch ($this->_submit_type) {
- case "application/x-www-form-urlencoded":
- reset($formvars);
- while(list($key,$val) = each($formvars)) {
- if (is_array($val) || is_object($val)) {
- while (list($cur_key, $cur_val) = each($val)) {
- $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&";
- }
- } else
- $postdata .= urlencode($key)."=".urlencode($val)."&";
- }
- break;
-
- case "multipart/form-data":
- $this->_mime_boundary = "Snoopy".md5(uniqid(microtime()));
-
- reset($formvars);
- while(list($key,$val) = each($formvars)) {
- if (is_array($val) || is_object($val)) {
- while (list($cur_key, $cur_val) = each($val)) {
- $postdata .= "--".$this->_mime_boundary."\r\n";
- $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n";
- $postdata .= "$cur_val\r\n";
- }
- } else {
- $postdata .= "--".$this->_mime_boundary."\r\n";
- $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";
- $postdata .= "$val\r\n";
- }
- }
-
- reset($formfiles);
- while (list($field_name, $file_names) = each($formfiles)) {
- settype($file_names, "array");
- while (list(, $file_name) = each($file_names)) {
- if (!is_readable($file_name)) continue;
-
- $fp = fopen($file_name, "r");
- $file_content = fread($fp, filesize($file_name));
- fclose($fp);
- $base_name = basename($file_name);
-
- $postdata .= "--".$this->_mime_boundary."\r\n";
- $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n";
- $postdata .= "$file_content\r\n";
- }
- }
- $postdata .= "--".$this->_mime_boundary."--\r\n";
- break;
- }
-
- return $postdata;
- }
-}
-
-?>
diff --git a/website/new/magpierss/rss_cache.inc b/website/new/magpierss/rss_cache.inc
deleted file mode 100644
index b8d436c..0000000
--- a/website/new/magpierss/rss_cache.inc
+++ /dev/null
@@ -1,200 +0,0 @@
-<?php
-/*
- * Project: MagpieRSS: a simple RSS integration tool
- * File: rss_cache.inc, a simple, rolling(no GC), cache
- * for RSS objects, keyed on URL.
- * Author: Kellan Elliott-McCrea <kellan@protest.net>
- * Version: 0.51
- * License: GPL
- *
- * The lastest version of MagpieRSS can be obtained from:
- * http://magpierss.sourceforge.net
- *
- * For questions, help, comments, discussion, etc., please join the
- * Magpie mailing list:
- * http://lists.sourceforge.net/lists/listinfo/magpierss-general
- *
- */
-
-class RSSCache {
- var $BASE_CACHE = './cache'; // where the cache files are stored
- var $MAX_AGE = 3600; // when are files stale, default one hour
- var $ERROR = ""; // accumulate error messages
-
- function RSSCache ($base='', $age='') {
- if ( $base ) {
- $this->BASE_CACHE = $base;
- }
- if ( $age ) {
- $this->MAX_AGE = $age;
- }
-
- // attempt to make the cache directory
- if ( ! file_exists( $this->BASE_CACHE ) ) {
- $status = @mkdir( $this->BASE_CACHE, 0755 );
-
- // if make failed
- if ( ! $status ) {
- $this->error(
- "Cache couldn't make dir '" . $this->BASE_CACHE . "'."
- );
- }
- }
- }
-
-/*=======================================================================*\
- Function: set
- Purpose: add an item to the cache, keyed on url
- Input: url from wich the rss file was fetched
- Output: true on sucess
-\*=======================================================================*/
- function set ($url, $rss) {
- $this->ERROR = "";
- $cache_file = $this->file_name( $url );
- $fp = @fopen( $cache_file, 'w' );
-
- if ( ! $fp ) {
- $this->error(
- "Cache unable to open file for writing: $cache_file"
- );
- return 0;
- }
-
-
- $data = $this->serialize( $rss );
- fwrite( $fp, $data );
- fclose( $fp );
-
- return $cache_file;
- }
-
-/*=======================================================================*\
- Function: get
- Purpose: fetch an item from the cache
- Input: url from wich the rss file was fetched
- Output: cached object on HIT, false on MISS
-\*=======================================================================*/
- function get ($url) {
- $this->ERROR = "";
- $cache_file = $this->file_name( $url );
-
- if ( ! file_exists( $cache_file ) ) {
- $this->debug(
- "Cache doesn't contain: $url (cache file: $cache_file)"
- );
- return 0;
- }
-
- $fp = @fopen($cache_file, 'r');
- if ( ! $fp ) {
- $this->error(
- "Failed to open cache file for reading: $cache_file"
- );
- return 0;
- }
-
- if ($filesize = filesize($cache_file) ) {
- $data = fread( $fp, filesize($cache_file) );
- $rss = $this->unserialize( $data );
-
- return $rss;
- }
-
- return 0;
- }
-
-/*=======================================================================*\
- Function: check_cache
- Purpose: check a url for membership in the cache
- and whether the object is older then MAX_AGE (ie. STALE)
- Input: url from wich the rss file was fetched
- Output: cached object on HIT, false on MISS
-\*=======================================================================*/
- function check_cache ( $url ) {
- $this->ERROR = "";
- $filename = $this->file_name( $url );
-
- if ( file_exists( $filename ) ) {
- // find how long ago the file was added to the cache
- // and whether that is longer then MAX_AGE
- $mtime = filemtime( $filename );
- $age = time() - $mtime;
- if ( $this->MAX_AGE > $age ) {
- // object exists and is current
- return 'HIT';
- }
- else {
- // object exists but is old
- return 'STALE';
- }
- }
- else {
- // object does not exist
- return 'MISS';
- }
- }
-
- function cache_age( $cache_key ) {
- $filename = $this->file_name( $url );
- if ( file_exists( $filename ) ) {
- $mtime = filemtime( $filename );
- $age = time() - $mtime;
- return $age;
- }
- else {
- return -1;
- }
- }
-
-/*=======================================================================*\
- Function: serialize
-\*=======================================================================*/
- function serialize ( $rss ) {
- return serialize( $rss );
- }
-
-/*=======================================================================*\
- Function: unserialize
-\*=======================================================================*/
- function unserialize ( $data ) {
- return unserialize( $data );
- }
-
-/*=======================================================================*\
- Function: file_name
- Purpose: map url to location in cache
- Input: url from wich the rss file was fetched
- Output: a file name
-\*=======================================================================*/
- function file_name ($url) {
- $filename = md5( $url );
- return join( DIRECTORY_SEPARATOR, array( $this->BASE_CACHE, $filename ) );
- }
-
-/*=======================================================================*\
- Function: error
- Purpose: register error
-\*=======================================================================*/
- function error ($errormsg, $lvl=E_USER_WARNING) {
- // append PHP's error message if track_errors enabled
- if ( isset($php_errormsg) ) {
- $errormsg .= " ($php_errormsg)";
- }
- $this->ERROR = $errormsg;
- if ( MAGPIE_DEBUG ) {
- trigger_error( $errormsg, $lvl);
- }
- else {
- error_log( $errormsg, 0);
- }
- }
-
- function debug ($debugmsg, $lvl=E_USER_NOTICE) {
- if ( MAGPIE_DEBUG ) {
- $this->error("MagpieRSS [debug] $debugmsg", $lvl);
- }
- }
-
-}
-
-?>
diff --git a/website/new/magpierss/rss_fetch.inc b/website/new/magpierss/rss_fetch.inc
deleted file mode 100644
index f2fa2fa..0000000
--- a/website/new/magpierss/rss_fetch.inc
+++ /dev/null
@@ -1,458 +0,0 @@
-<?php
-/*
- * Project: MagpieRSS: a simple RSS integration tool
- * File: rss_fetch.inc, a simple functional interface
- to fetching and parsing RSS files, via the
- function fetch_rss()
- * Author: Kellan Elliott-McCrea <kellan@protest.net>
- * License: GPL
- *
- * The lastest version of MagpieRSS can be obtained from:
- * http://magpierss.sourceforge.net
- *
- * For questions, help, comments, discussion, etc., please join the
- * Magpie mailing list:
- * magpierss-general@lists.sourceforge.net
- *
- */
-
-// Setup MAGPIE_DIR for use on hosts that don't include
-// the current path in include_path.
-// with thanks to rajiv and smarty
-if (!defined('DIR_SEP')) {
- define('DIR_SEP', DIRECTORY_SEPARATOR);
-}
-
-if (!defined('MAGPIE_DIR')) {
- define('MAGPIE_DIR', dirname(__FILE__) . DIR_SEP);
-}
-
-require_once( MAGPIE_DIR . 'rss_parse.inc' );
-require_once( MAGPIE_DIR . 'rss_cache.inc' );
-
-// for including 3rd party libraries
-define('MAGPIE_EXTLIB', MAGPIE_DIR . 'extlib' . DIR_SEP);
-require_once( MAGPIE_EXTLIB . 'Snoopy.class.inc');
-
-
-/*
- * CONSTANTS - redefine these in your script to change the
- * behaviour of fetch_rss() currently, most options effect the cache
- *
- * MAGPIE_CACHE_ON - Should Magpie cache parsed RSS objects?
- * For me a built in cache was essential to creating a "PHP-like"
- * feel to Magpie, see rss_cache.inc for rationale
- *
- *
- * MAGPIE_CACHE_DIR - Where should Magpie cache parsed RSS objects?
- * This should be a location that the webserver can write to. If this
- * directory does not already exist Mapie will try to be smart and create
- * it. This will often fail for permissions reasons.
- *
- *
- * MAGPIE_CACHE_AGE - How long to store cached RSS objects? In seconds.
- *
- *
- * MAGPIE_CACHE_FRESH_ONLY - If remote fetch fails, throw error
- * instead of returning stale object?
- *
- * MAGPIE_DEBUG - Display debugging notices?
- *
-*/
-
-
-/*=======================================================================*\
- Function: fetch_rss:
- Purpose: return RSS object for the give url
- maintain the cache
- Input: url of RSS file
- Output: parsed RSS object (see rss_parse.inc)
-
- NOTES ON CACHEING:
- If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
-
- NOTES ON RETRIEVING REMOTE FILES:
- If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
- return a cached object, and touch the cache object upon recieving a
- 304.
-
- NOTES ON FAILED REQUESTS:
- If there is an HTTP error while fetching an RSS object, the cached
- version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
-\*=======================================================================*/
-
-define('MAGPIE_VERSION', '0.72');
-
-$MAGPIE_ERROR = "";
-
-function fetch_rss ($url) {
- // initialize constants
- init();
-
- if ( !isset($url) ) {
- error("fetch_rss called without a url");
- return false;
- }
-
- // if cache is disabled
- if ( !MAGPIE_CACHE_ON ) {
- // fetch file, and parse it
- $resp = _fetch_remote_file( $url );
- if ( is_success( $resp->status ) ) {
- return _response_to_rss( $resp );
- }
- else {
- error("Failed to fetch $url and cache is off");
- return false;
- }
- }
- // else cache is ON
- else {
- // Flow
- // 1. check cache
- // 2. if there is a hit, make sure its fresh
- // 3. if cached obj fails freshness check, fetch remote
- // 4. if remote fails, return stale object, or error
-
- $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
-
- if (MAGPIE_DEBUG and $cache->ERROR) {
- debug($cache->ERROR, E_USER_WARNING);
- }
-
-
- $cache_status = 0; // response of check_cache
- $request_headers = array(); // HTTP headers to send with fetch
- $rss = 0; // parsed RSS object
- $errormsg = 0; // errors, if any
-
- // store parsed XML by desired output encoding
- // as character munging happens at parse time
- $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
-
- if (!$cache->ERROR) {
- // return cache HIT, MISS, or STALE
- $cache_status = $cache->check_cache( $cache_key);
- }
-
- // if object cached, and cache is fresh, return cached obj
- if ( $cache_status == 'HIT' ) {
- $rss = $cache->get( $cache_key );
- if ( isset($rss) and $rss ) {
- // should be cache age
- $rss->from_cache = 1;
- if ( MAGPIE_DEBUG > 1) {
- debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
- }
- return $rss;
- }
- }
-
- // else attempt a conditional get
-
- // setup headers
- if ( $cache_status == 'STALE' ) {
- $rss = $cache->get( $cache_key );
- if ( $rss and $rss->etag and $rss->last_modified ) {
- $request_headers['If-None-Match'] = $rss->etag;
- $request_headers['If-Last-Modified'] = $rss->last_modified;
- }
- }
-
- $resp = _fetch_remote_file( $url, $request_headers );
-
- if (isset($resp) and $resp) {
- if ($resp->status == '304' ) {
- // we have the most current copy
- if ( MAGPIE_DEBUG > 1) {
- debug("Got 304 for $url");
- }
- // reset cache on 304 (at minutillo insistent prodding)
- $cache->set($cache_key, $rss);
- return $rss;
- }
- elseif ( is_success( $resp->status ) ) {
- $rss = _response_to_rss( $resp );
- if ( $rss ) {
- if (MAGPIE_DEBUG > 1) {
- debug("Fetch successful");
- }
- // add object to cache
- $cache->set( $cache_key, $rss );
- return $rss;
- }
- }
- else {
- $errormsg = "Failed to fetch $url ";
- if ( $resp->status == '-100' ) {
- $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
- }
- elseif ( $resp->error ) {
- # compensate for Snoopy's annoying habbit to tacking
- # on '\n'
- $http_error = substr($resp->error, 0, -2);
- $errormsg .= "(HTTP Error: $http_error)";
- }
- else {
- $errormsg .= "(HTTP Response: " . $resp->response_code .')';
- }
- }
- }
- else {
- $errormsg = "Unable to retrieve RSS file for unknown reasons.";
- }
-
- // else fetch failed
-
- // attempt to return cached object
- if ($rss) {
- if ( MAGPIE_DEBUG ) {
- debug("Returning STALE object for $url");
- }
- return $rss;
- }
-
- // else we totally failed
- error( $errormsg );
-
- return false;
-
- } // end if ( !MAGPIE_CACHE_ON ) {
-} // end fetch_rss()
-
-/*=======================================================================*\
- Function: error
- Purpose: set MAGPIE_ERROR, and trigger error
-\*=======================================================================*/
-
-function error ($errormsg, $lvl=E_USER_WARNING) {
- global $MAGPIE_ERROR;
-
- // append PHP's error message if track_errors enabled
- if ( isset($php_errormsg) ) {
- $errormsg .= " ($php_errormsg)";
- }
- if ( $errormsg ) {
- $errormsg = "MagpieRSS: $errormsg";
- $MAGPIE_ERROR = $errormsg;
- trigger_error( $errormsg, $lvl);
- }
-}
-
-function debug ($debugmsg, $lvl=E_USER_NOTICE) {
- trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
-}
-
-/*=======================================================================*\
- Function: magpie_error
- Purpose: accessor for the magpie error variable
-\*=======================================================================*/
-function magpie_error ($errormsg="") {
- global $MAGPIE_ERROR;
-
- if ( isset($errormsg) and $errormsg ) {
- $MAGPIE_ERROR = $errormsg;
- }
-
- return $MAGPIE_ERROR;
-}
-
-/*=======================================================================*\
- Function: _fetch_remote_file
- Purpose: retrieve an arbitrary remote file
- Input: url of the remote file
- headers to send along with the request (optional)
- Output: an HTTP response object (see Snoopy.class.inc)
-\*=======================================================================*/
-function _fetch_remote_file ($url, $headers = "" ) {
- // Snoopy is an HTTP client in PHP
- $client = new Snoopy();
- $client->agent = MAGPIE_USER_AGENT;
- $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
- $client->use_gzip = MAGPIE_USE_GZIP;
- if (is_array($headers) ) {
- $client->rawheaders = $headers;
- }
-
- @$client->fetch($url);
- return $client;
-
-}
-
-/*=======================================================================*\
- Function: _response_to_rss
- Purpose: parse an HTTP response object into an RSS object
- Input: an HTTP response object (see Snoopy)
- Output: parsed RSS object (see rss_parse)
-\*=======================================================================*/
-function _response_to_rss ($resp) {
- $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING );
-
- // if RSS parsed successfully
- if ( $rss and !$rss->ERROR) {
-
- // find Etag, and Last-Modified
- foreach($resp->headers as $h) {
- // 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
- if (strpos($h, ": ")) {
- list($field, $val) = explode(": ", $h, 2);
- }
- else {
- $field = $h;
- $val = "";
- }
-
- if ( $field == 'ETag' ) {
- $rss->etag = $val;
- }
-
- if ( $field == 'Last-Modified' ) {
- $rss->last_modified = $val;
- }
- }
-
- return $rss;
- } // else construct error message
- else {
- $errormsg = "Failed to parse RSS file.";
-
- if ($rss) {
- $errormsg .= " (" . $rss->ERROR . ")";
- }
- error($errormsg);
-
- return false;
- } // end if ($rss and !$rss->error)
-}
-
-/*=======================================================================*\
- Function: init
- Purpose: setup constants with default values
- check for user overrides
-\*=======================================================================*/
-function init () {
- if ( defined('MAGPIE_INITALIZED') ) {
- return;
- }
- else {
- define('MAGPIE_INITALIZED', true);
- }
-
- if ( !defined('MAGPIE_CACHE_ON') ) {
- define('MAGPIE_CACHE_ON', true);
- }
-
- if ( !defined('MAGPIE_CACHE_DIR') ) {
- define('MAGPIE_CACHE_DIR', './cache');
- }
-
- if ( !defined('MAGPIE_CACHE_AGE') ) {
- define('MAGPIE_CACHE_AGE', 60*60); // one hour
- }
-
- if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
- define('MAGPIE_CACHE_FRESH_ONLY', false);
- }
-
- if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
- define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
- }
-
- if ( !defined('MAGPIE_INPUT_ENCODING') ) {
- define('MAGPIE_INPUT_ENCODING', null);
- }
-
- if ( !defined('MAGPIE_DETECT_ENCODING') ) {
- define('MAGPIE_DETECT_ENCODING', true);
- }
-
- if ( !defined('MAGPIE_DEBUG') ) {
- define('MAGPIE_DEBUG', 0);
- }
-
- if ( !defined('MAGPIE_USER_AGENT') ) {
- $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
-
- if ( MAGPIE_CACHE_ON ) {
- $ua = $ua . ')';
- }
- else {
- $ua = $ua . '; No cache)';
- }
-
- define('MAGPIE_USER_AGENT', $ua);
- }
-
- if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
- define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
- }
-
- // use gzip encoding to fetch rss files if supported?
- if ( !defined('MAGPIE_USE_GZIP') ) {
- define('MAGPIE_USE_GZIP', true);
- }
-}
-
-// NOTE: the following code should really be in Snoopy, or at least
-// somewhere other then rss_fetch!
-
-/*=======================================================================*\
- HTTP STATUS CODE PREDICATES
- These functions attempt to classify an HTTP status code
- based on RFC 2616 and RFC 2518.
-
- All of them take an HTTP status code as input, and return true or false
-
- All this code is adapted from LWP's HTTP::Status.
-\*=======================================================================*/
-
-
-/*=======================================================================*\
- Function: is_info
- Purpose: return true if Informational status code
-\*=======================================================================*/
-function is_info ($sc) {
- return $sc >= 100 && $sc < 200;
-}
-
-/*=======================================================================*\
- Function: is_success
- Purpose: return true if Successful status code
-\*=======================================================================*/
-function is_success ($sc) {
- return $sc >= 200 && $sc < 300;
-}
-
-/*=======================================================================*\
- Function: is_redirect
- Purpose: return true if Redirection status code
-\*=======================================================================*/
-function is_redirect ($sc) {
- return $sc >= 300 && $sc < 400;
-}
-
-/*=======================================================================*\
- Function: is_error
- Purpose: return true if Error status code
-\*=======================================================================*/
-function is_error ($sc) {
- return $sc >= 400 && $sc < 600;
-}
-
-/*=======================================================================*\
- Function: is_client_error
- Purpose: return true if Error status code, and its a client error
-\*=======================================================================*/
-function is_client_error ($sc) {
- return $sc >= 400 && $sc < 500;
-}
-
-/*=======================================================================*\
- Function: is_client_error
- Purpose: return true if Error status code, and its a server error
-\*=======================================================================*/
-function is_server_error ($sc) {
- return $sc >= 500 && $sc < 600;
-}
-
-?>
diff --git a/website/new/magpierss/rss_parse.inc b/website/new/magpierss/rss_parse.inc
deleted file mode 100644
index 56d420f..0000000
--- a/website/new/magpierss/rss_parse.inc
+++ /dev/null
@@ -1,605 +0,0 @@
-<?php
-
-/**
-* Project: MagpieRSS: a simple RSS integration tool
-* File: rss_parse.inc - parse an RSS or Atom feed
-* return as a simple object.
-*
-* Handles RSS 0.9x, RSS 2.0, RSS 1.0, and Atom 0.3
-*
-* The lastest version of MagpieRSS can be obtained from:
-* http://magpierss.sourceforge.net
-*
-* For questions, help, comments, discussion, etc., please join the
-* Magpie mailing list:
-* magpierss-general@lists.sourceforge.net
-*
-* @author Kellan Elliott-McCrea <kellan@protest.net>
-* @version 0.7a
-* @license GPL
-*
-*/
-
-define('RSS', 'RSS');
-define('ATOM', 'Atom');
-
-require_once (MAGPIE_DIR . 'rss_utils.inc');
-
-/**
-* Hybrid parser, and object, takes RSS as a string and returns a simple object.
-*
-* see: rss_fetch.inc for a simpler interface with integrated caching support
-*
-*/
-class MagpieRSS {
- var $parser;
-
- var $current_item = array(); // item currently being parsed
- var $items = array(); // collection of parsed items
- var $channel = array(); // hash of channel fields
- var $textinput = array();
- var $image = array();
- var $feed_type;
- var $feed_version;
- var $encoding = ''; // output encoding of parsed rss
-
- var $_source_encoding = ''; // only set if we have to parse xml prolog
-
- var $ERROR = "";
- var $WARNING = "";
-
- // define some constants
-
- var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');
- var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
-
- // parser variables, useless if you're not a parser, treat as private
- var $stack = array(); // parser stack
- var $inchannel = false;
- var $initem = false;
- var $incontent = false; // if in Atom <content mode="xml"> field
- var $intextinput = false;
- var $inimage = false;
- var $current_namespace = false;
-
-
- /**
- * Set up XML parser, parse source, and return populated RSS object..
- *
- * @param string $source string containing the RSS to be parsed
- *
- * NOTE: Probably a good idea to leave the encoding options alone unless
- * you know what you're doing as PHP's character set support is
- * a little weird.
- *
- * NOTE: A lot of this is unnecessary but harmless with PHP5
- *
- *
- * @param string $output_encoding output the parsed RSS in this character
- * set defaults to ISO-8859-1 as this is PHP's
- * default.
- *
- * NOTE: might be changed to UTF-8 in future
- * versions.
- *
- * @param string $input_encoding the character set of the incoming RSS source.
- * Leave blank and Magpie will try to figure it
- * out.
- *
- *
- * @param bool $detect_encoding if false Magpie won't attempt to detect
- * source encoding. (caveat emptor)
- *
- */
- function MagpieRSS ($source, $output_encoding='ISO-8859-1',
- $input_encoding=null, $detect_encoding=true)
- {
- # if PHP xml isn't compiled in, die
- #
- if (!function_exists('xml_parser_create')) {
- $this->error( "Failed to load PHP's XML Extension. " .
- "http://www.php.net/manual/en/ref.xml.php",
- E_USER_ERROR );
- }
-
- list($parser, $source) = $this->create_parser($source,
- $output_encoding, $input_encoding, $detect_encoding);
-
-
- if (!is_resource($parser)) {
- $this->error( "Failed to create an instance of PHP's XML parser. " .
- "http://www.php.net/manual/en/ref.xml.php",
- E_USER_ERROR );
- }
-
-
- $this->parser = $parser;
-
- # pass in parser, and a reference to this object
- # setup handlers
- #
- xml_set_object( $this->parser, $this );
- xml_set_element_handler($this->parser,
- 'feed_start_element', 'feed_end_element' );
-
- xml_set_character_data_handler( $this->parser, 'feed_cdata' );
-
- $status = xml_parse( $this->parser, $source );
-
- if (! $status ) {
- $errorcode = xml_get_error_code( $this->parser );
- if ( $errorcode != XML_ERROR_NONE ) {
- $xml_error = xml_error_string( $errorcode );
- $error_line = xml_get_current_line_number($this->parser);
- $error_col = xml_get_current_column_number($this->parser);
- $errormsg = "$xml_error at line $error_line, column $error_col";
-
- $this->error( $errormsg );
- }
- }
-
- xml_parser_free( $this->parser );
-
- $this->normalize();
- }
-
- function feed_start_element($p, $element, &$attrs) {
- $el = $element = strtolower($element);
- $attrs = array_change_key_case($attrs, CASE_LOWER);
-
- // check for a namespace, and split if found
- $ns = false;
- if ( strpos( $element, ':' ) ) {
- list($ns, $el) = split( ':', $element, 2);
- }
- if ( $ns and $ns != 'rdf' ) {
- $this->current_namespace = $ns;
- }
-
- # if feed type isn't set, then this is first element of feed
- # identify feed from root element
- #
- if (!isset($this->feed_type) ) {
- if ( $el == 'rdf' ) {
- $this->feed_type = RSS;
- $this->feed_version = '1.0';
- }
- elseif ( $el == 'rss' ) {
- $this->feed_type = RSS;
- $this->feed_version = $attrs['version'];
- }
- elseif ( $el == 'feed' ) {
- $this->feed_type = ATOM;
- $this->feed_version = $attrs['version'];
- $this->inchannel = true;
- }
- return;
- }
-
- if ( $el == 'channel' )
- {
- $this->inchannel = true;
- }
- elseif ($el == 'item' or $el == 'entry' )
- {
- $this->initem = true;
- if ( isset($attrs['rdf:about']) ) {
- $this->current_item['about'] = $attrs['rdf:about'];
- }
- }
-
- // if we're in the default namespace of an RSS feed,
- // record textinput or image fields
- elseif (
- $this->feed_type == RSS and
- $this->current_namespace == '' and
- $el == 'textinput' )
- {
- $this->intextinput = true;
- }
-
- elseif (
- $this->feed_type == RSS and
- $this->current_namespace == '' and
- $el == 'image' )
- {
- $this->inimage = true;
- }
-
- # handle atom content constructs
- elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
- {
- // avoid clashing w/ RSS mod_content
- if ($el == 'content' ) {
- $el = 'atom_content';
- }
-
- $this->incontent = $el;
-
-
- }
-
- // if inside an Atom content construct (e.g. content or summary) field treat tags as text
- elseif ($this->feed_type == ATOM and $this->incontent )
- {
- // if tags are inlined, then flatten
- $attrs_str = join(' ',
- array_map('map_attrs',
- array_keys($attrs),
- array_values($attrs) ) );
-
- $this->append_content( "<$element $attrs_str>" );
-
- array_unshift( $this->stack, $el );
- }
-
- // Atom support many links per containging element.
- // Magpie treats link elements of type rel='alternate'
- // as being equivalent to RSS's simple link element.
- //
- elseif ($this->feed_type == ATOM and $el == 'link' )
- {
- if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
- {
- $link_el = 'link';
- }
- else {
- $link_el = 'link_' . $attrs['rel'];
- }
-
- $this->append($link_el, $attrs['href']);
- }
- // set stack[0] to current element
- else {
- array_unshift($this->stack, $el);
- }
- }
-
-
-
- function feed_cdata ($p, $text) {
- if ($this->feed_type == ATOM and $this->incontent)
- {
- $this->append_content( $text );
- }
- else {
- $current_el = join('_', array_reverse($this->stack));
- $this->append($current_el, $text);
- }
- }
-
- function feed_end_element ($p, $el) {
- $el = strtolower($el);
-
- if ( $el == 'item' or $el == 'entry' )
- {
- $this->items[] = $this->current_item;
- $this->current_item = array();
- $this->initem = false;
- }
- elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
- {
- $this->intextinput = false;
- }
- elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
- {
- $this->inimage = false;
- }
- elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
- {
- $this->incontent = false;
- }
- elseif ($el == 'channel' or $el == 'feed' )
- {
- $this->inchannel = false;
- }
- elseif ($this->feed_type == ATOM and $this->incontent ) {
- // balance tags properly
- // note: i don't think this is actually neccessary
- if ( $this->stack[0] == $el )
- {
- $this->append_content("</$el>");
- }
- else {
- $this->append_content("<$el />");
- }
-
- array_shift( $this->stack );
- }
- else {
- array_shift( $this->stack );
- }
-
- $this->current_namespace = false;
- }
-
- function concat (&$str1, $str2="") {
- if (!isset($str1) ) {
- $str1="";
- }
- $str1 .= $str2;
- }
-
-
-
- function append_content($text) {
- if ( $this->initem ) {
- $this->concat( $this->current_item[ $this->incontent ], $text );
- }
- elseif ( $this->inchannel ) {
- $this->concat( $this->channel[ $this->incontent ], $text );
- }
- }
-
- // smart append - field and namespace aware
- function append($el, $text) {
- if (!$el) {
- return;
- }
- if ( $this->current_namespace )
- {
- if ( $this->initem ) {
- $this->concat(
- $this->current_item[ $this->current_namespace ][ $el ], $text);
- }
- elseif ($this->inchannel) {
- $this->concat(
- $this->channel[ $this->current_namespace][ $el ], $text );
- }
- elseif ($this->intextinput) {
- $this->concat(
- $this->textinput[ $this->current_namespace][ $el ], $text );
- }
- elseif ($this->inimage) {
- $this->concat(
- $this->image[ $this->current_namespace ][ $el ], $text );
- }
- }
- else {
- if ( $this->initem ) {
- $this->concat(
- $this->current_item[ $el ], $text);
- }
- elseif ($this->intextinput) {
- $this->concat(
- $this->textinput[ $el ], $text );
- }
- elseif ($this->inimage) {
- $this->concat(
- $this->image[ $el ], $text );
- }
- elseif ($this->inchannel) {
- $this->concat(
- $this->channel[ $el ], $text );
- }
-
- }
- }
-
- function normalize () {
- // if atom populate rss fields
- if ( $this->is_atom() ) {
- $this->channel['description'] = $this->channel['tagline'];
- for ( $i = 0; $i < count($this->items); $i++) {
- $item = $this->items[$i];
- if ( isset($item['summary']) )
- $item['description'] = $item['summary'];
- if ( isset($item['atom_content']))
- $item['content']['encoded'] = $item['atom_content'];
-
- $atom_date = (isset($item['issued']) ) ? $item['issued'] : $item['modified'];
- if ( $atom_date ) {
- $epoch = @parse_w3cdtf($atom_date);
- if ($epoch and $epoch > 0) {
- $item['date_timestamp'] = $epoch;
- }
- }
-
- $this->items[$i] = $item;
- }
- }
- elseif ( $this->is_rss() ) {
- $this->channel['tagline'] = $this->channel['description'];
- for ( $i = 0; $i < count($this->items); $i++) {
- $item = $this->items[$i];
- if ( isset($item['description']))
- $item['summary'] = $item['description'];
- if ( isset($item['content']['encoded'] ) )
- $item['atom_content'] = $item['content']['encoded'];
-
- if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
- $epoch = @parse_w3cdtf($item['dc']['date']);
- if ($epoch and $epoch > 0) {
- $item['date_timestamp'] = $epoch;
- }
- }
- elseif ( isset($item['pubdate']) ) {
- $epoch = @strtotime($item['pubdate']);
- if ($epoch > 0) {
- $item['date_timestamp'] = $epoch;
- }
- }
-
- $this->items[$i] = $item;
- }
- }
- }
-
-
- function is_rss () {
- if ( $this->feed_type == RSS ) {
- return $this->feed_version;
- }
- else {
- return false;
- }
- }
-
- function is_atom() {
- if ( $this->feed_type == ATOM ) {
- return $this->feed_version;
- }
- else {
- return false;
- }
- }
-
- /**
- * return XML parser, and possibly re-encoded source
- *
- */
- function create_parser($source, $out_enc, $in_enc, $detect) {
- if ( substr(phpversion(),0,1) == 5) {
- $parser = $this->php5_create_parser($in_enc, $detect);
- }
- else {
- list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
- }
- if ($out_enc) {
- $this->encoding = $out_enc;
- xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
- }
-
- return array($parser, $source);
- }
-
- /**
- * Instantiate an XML parser under PHP5
- *
- * PHP5 will do a fine job of detecting input encoding
- * if passed an empty string as the encoding.
- *
- * All hail libxml2!
- *
- */
- function php5_create_parser($in_enc, $detect) {
- // by default php5 does a fine job of detecting input encodings
- if(!$detect && $in_enc) {
- return xml_parser_create($in_enc);
- }
- else {
- return xml_parser_create('');
- }
- }
-
- /**
- * Instaniate an XML parser under PHP4
- *
- * Unfortunately PHP4's support for character encodings
- * and especially XML and character encodings sucks. As
- * long as the documents you parse only contain characters
- * from the ISO-8859-1 character set (a superset of ASCII,
- * and a subset of UTF-8) you're fine. However once you
- * step out of that comfy little world things get mad, bad,
- * and dangerous to know.
- *
- * The following code is based on SJM's work with FoF
- * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
- *
- */
- function php4_create_parser($source, $in_enc, $detect) {
- if ( !$detect ) {
- return array(xml_parser_create($in_enc), $source);
- }
-
- if (!$in_enc) {
- if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
- $in_enc = strtoupper($m[1]);
- $this->source_encoding = $in_enc;
- }
- else {
- $in_enc = 'UTF-8';
- }
- }
-
- if ($this->known_encoding($in_enc)) {
- return array(xml_parser_create($in_enc), $source);
- }
-
- // the dectected encoding is not one of the simple encodings PHP knows
-
- // attempt to use the iconv extension to
- // cast the XML to a known encoding
- // @see http://php.net/iconv
-
- if (function_exists('iconv')) {
- $encoded_source = iconv($in_enc,'UTF-8', $source);
- if ($encoded_source) {
- return array(xml_parser_create('UTF-8'), $encoded_source);
- }
- }
-
- // iconv didn't work, try mb_convert_encoding
- // @see http://php.net/mbstring
- if(function_exists('mb_convert_encoding')) {
- $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
- if ($encoded_source) {
- return array(xml_parser_create('UTF-8'), $encoded_source);
- }
- }
-
- // else
- $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
- "You may see strange artifacts, and mangled characters.",
- E_USER_NOTICE);
-
- return array(xml_parser_create(), $source);
- }
-
- function known_encoding($enc) {
- $enc = strtoupper($enc);
- if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
- return $enc;
- }
- else {
- return false;
- }
- }
-
- function error ($errormsg, $lvl=E_USER_WARNING) {
- // append PHP's error message if track_errors enabled
- if ( isset($php_errormsg) ) {
- $errormsg .= " ($php_errormsg)";
- }
- if ( MAGPIE_DEBUG ) {
- trigger_error( $errormsg, $lvl);
- }
- else {
- error_log( $errormsg, 0);
- }
-
- $notices = E_USER_NOTICE|E_NOTICE;
- if ( $lvl&$notices ) {
- $this->WARNING = $errormsg;
- } else {
- $this->ERROR = $errormsg;
- }
- }
-
-
-} // end class RSS
-
-function map_attrs($k, $v) {
- return "$k=\"$v\"";
-}
-
-// patch to support medieval versions of PHP4.1.x,
-// courtesy, Ryan Currie, ryan@digibliss.com
-
-if (!function_exists('array_change_key_case')) {
- define("CASE_UPPER",1);
- define("CASE_LOWER",0);
-
-
- function array_change_key_case($array,$case=CASE_LOWER) {
- if ($case=CASE_LOWER) $cmd=strtolower;
- elseif ($case=CASE_UPPER) $cmd=strtoupper;
- foreach($array as $key=>$value) {
- $output[$cmd($key)]=$value;
- }
- return $output;
- }
-
-}
-
-?>
diff --git a/website/new/magpierss/rss_utils.inc b/website/new/magpierss/rss_utils.inc
deleted file mode 100644
index 2a29e72..0000000
--- a/website/new/magpierss/rss_utils.inc
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/*
- * Project: MagpieRSS: a simple RSS integration tool
- * File: rss_utils.inc, utility methods for working with RSS
- * Author: Kellan Elliott-McCrea <kellan@protest.net>
- * Version: 0.51
- * License: GPL
- *
- * The lastest version of MagpieRSS can be obtained from:
- * http://magpierss.sourceforge.net
- *
- * For questions, help, comments, discussion, etc., please join the
- * Magpie mailing list:
- * magpierss-general@lists.sourceforge.net
- */
-
-
-/*======================================================================*\
- Function: parse_w3cdtf
- Purpose: parse a W3CDTF date into unix epoch
-
- NOTE: http://www.w3.org/TR/NOTE-datetime
-\*======================================================================*/
-
-function parse_w3cdtf ( $date_str ) {
-
- # regex to match wc3dtf
- $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
-
- if ( preg_match( $pat, $date_str, $match ) ) {
- list( $year, $month, $day, $hours, $minutes, $seconds) =
- array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
-
- # calc epoch for current date assuming GMT
- $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
-
- $offset = 0;
- if ( $match[10] == 'Z' ) {
- # zulu time, aka GMT
- }
- else {
- list( $tz_mod, $tz_hour, $tz_min ) =
- array( $match[8], $match[9], $match[10]);
-
- # zero out the variables
- if ( ! $tz_hour ) { $tz_hour = 0; }
- if ( ! $tz_min ) { $tz_min = 0; }
-
- $offset_secs = (($tz_hour*60)+$tz_min)*60;
-
- # is timezone ahead of GMT? then subtract offset
- #
- if ( $tz_mod == '+' ) {
- $offset_secs = $offset_secs * -1;
- }
-
- $offset = $offset_secs;
- }
- $epoch = $epoch + $offset;
- return $epoch;
- }
- else {
- return -1;
- }
-}
-
-?>
diff --git a/website/new/nav.php b/website/new/nav.php
index 0244de9..b093a75 100755
--- a/website/new/nav.php
+++ b/website/new/nav.php
@@ -6,7 +6,6 @@
case "about":
echo "<ul id=\"nav\">
<li id=\"active\"><a href=\"index.php\">About</a></li>
- <li><a href=\"news.php\">News</a></li>
<li><a href=\"download.php\">Download</a></li>
<li><a href=\"faq.php\">FAQ</a></li>
<li><a href=\"documentation.php\">Documentation</a></li>
@@ -14,21 +13,9 @@
<li><a href=\"http://hosted.fedoraproject.org/projects/cobbler/\">Wiki</a></li>
</ul>";
break;
- case "news":
- echo "<ul id=\"nav\">
- <li><a href=\"index.php\">About</a></li>
- <li id=\"active\"><a href=\"news.php\">News</a></li>
- <li><a href=\"download.php\">Download</a></li>
- <li><a href=\"faq.php\">FAQ</a></li>
- <li><a href=\"documentation.php\">Documentation</a></li>
- <li><a href=\"communicate.php\">Communicate</a></li>
- <li><a href=\"http://hosted.fedoraproject.org/projects/cobbler/\">Wiki</a></li>
- </ul>";
- break;
case "download":
echo "<ul id=\"nav\">
<li><a href=\"index.php\">About</a></li>
- <li><a href=\"news.php\">News</a></li>
<li id=\"active\"><a href=\"#\">Download</a></li>
<li><a href=\"faq.php\">FAQ</a></li>
<li><a href=\"documentation.php\">Documentation</a></li>
@@ -39,7 +26,6 @@
case "faq":
echo "<ul id=\"nav\">
<li><a href=\"index.php\">About</a></li>
- <li><a href=\"news.php\">News</a></li>
<li><a href=\"download.php\">Download</a></li>
<li id=\"active\"><a href=\"faq.php\">FAQ</a></li>
<li><a href=\"documentation.php\">Documentation</a></li>
@@ -50,7 +36,6 @@
case "communicate":
echo "<ul id=\"nav\">
<li><a href=\"index.php\">About</a></li>
- <li><a href=\"news.php\">News</a></li>
<li><a href=\"download.php\">Download</a></li>
<li><a href=\"faq.php\">FAQ</a></li>
<li><a href=\"documentation.php\">Documentation</a></li>
@@ -61,7 +46,6 @@
case "documentation":
echo "<ul id=\"nav\">
<li><a href=\"index.php\">About</a></li>
- <li><a href=\"news.php\">News</a></li>
<li><a href=\"download.php\">Download</a></li>
<li><a href=\"faq.php\">FAQ</a></li>
<li id=\"active\"><a href=\"documentation.php\">Documentation</a></li>
@@ -72,7 +56,6 @@
default:
echo " <ul id=\"nav\">
<li id=\"active\"><a href=\"index.php\">About</a></li>
- <li><a href=\"news.php\">News</a></li>
<li><a href=\"download.php\">Download</a></li>
<li><a href=\"faq.php\">FAQ</a></li>
<li><a href=\"documentation.php\">Documentation</a></li>
diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl
index 88f8d29..4729816 100644
--- a/webui_templates/distro_edit.tmpl
+++ b/webui_templates/distro_edit.tmpl
@@ -15,7 +15,7 @@ function disablename(value)
</script>
#end if
-<form method="post" action="$base_url/distro_save">
+<form method="POST" action="$base_url">
<fieldset id="cform">
@@ -27,6 +27,7 @@ function disablename(value)
<legend>Adding a Distro</legend>
<input type="hidden" name="new_or_edit" value="new"/>
#end if
+ <input type="hidden" name="mode" value="distro_save">
<table border=0>
@@ -68,7 +69,7 @@ function disablename(value)
<label for="kernel">Kernel</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="kernel" id="kernel"
+ <input type="text" size="255" style="width: 400px;" name="kernel" id="kernel"
#if $distro
value="$distro.kernel"
#end if
@@ -82,7 +83,7 @@ function disablename(value)
<label for="initrd">Initrd</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="initrd" id="initrd"
+ <input type="text" size="255" style="width: 400px;" name="initrd" id="initrd"
#if $distro
value="$distro.initrd"
#end if
@@ -120,7 +121,7 @@ function disablename(value)
<label for="kopts">Kernel Options</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="kopts" id="kopts"
+ <input type="text" size="255" style="width: 400px;" name="kopts" id="kopts"
#if $distro
value="$distro.kernel_options"
#end if
@@ -134,7 +135,7 @@ function disablename(value)
<label for="ksmeta">Kickstart Metadata</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="ksmeta" id="ksmeta"
+ <input type="text" size="255" style="width: 400px;" name="ksmeta" id="ksmeta"
#if $distro
value="$distro.ks_meta"
#end if
diff --git a/webui_templates/distro_list.tmpl b/webui_templates/distro_list.tmpl
index db9897f..4fabd48 100644
--- a/webui_templates/distro_list.tmpl
+++ b/webui_templates/distro_list.tmpl
@@ -30,7 +30,7 @@
<tr class="$tr_class">
<td>
- <a href="$base_url/distro_edit?name=$distro.name">$distro.name</a>
+ <a href="$base_url?mode=distro_edit&name=$distro.name">$distro.name</a>
</td>
<td>$distro.breed</td>
<td>$distro.arch</td>
diff --git a/webui_templates/ksfile_edit.tmpl b/webui_templates/ksfile_edit.tmpl
index 8b0eeec..5e0c5ae 100644
--- a/webui_templates/ksfile_edit.tmpl
+++ b/webui_templates/ksfile_edit.tmpl
@@ -2,7 +2,7 @@
#attr $title = "Cobbler: Edit Kickstart File $ksfile"
#block body
-<form method="post" action="$base_url/ksfile_save">
+<form method="post" action="$base_url?mode=ksfile_save">
<input type="hidden" name="name" value="$name"/>
<fieldset id="cform">
<legend>Edit Kickstart File</legend>
diff --git a/webui_templates/ksfile_list.tmpl b/webui_templates/ksfile_list.tmpl
index 292fcf9..dcfaa0a 100644
--- a/webui_templates/ksfile_list.tmpl
+++ b/webui_templates/ksfile_list.tmpl
@@ -22,9 +22,9 @@
<tr class="$tr_class">
<td>$ksfile</td>
#if $ksfile.startswith("/var/lib/cobbler/kickstarts")
- <td><a href="$base_url/ksfile_edit?name=$ksfile">edit</a></td>
+ <td><a href="$base_url?mode=ksfile_edit&name=$ksfile">edit</a></td>
#else if $ksfile.startswith("/etc/cobbler")
- <td><a href="$base_url/ksfile_edit?name=$ksfile">edit</a></td>
+ <td><a href="$base_url?mode=ksfile_edit&name=$ksfile">edit</a></td>
#else if $ksfile.startswith("http://")
<td><a href="$ksfile">view</A></td>
#else
diff --git a/webui_templates/master.tmpl b/webui_templates/master.tmpl
index abd09af..96fbe75 100644
--- a/webui_templates/master.tmpl
+++ b/webui_templates/master.tmpl
@@ -29,23 +29,23 @@
<div id="sidebar">
<ul id="nav">
<li><a href="/cobbler/webui/wui.html" class="menu">Docs</a></li>
- <li><a href="$base_url/settings_view" class="menu">Settings</a></li>
+ <li><a href="$base_url?mode=settings_view" class="menu">Settings</a></li>
<li><hr/></li>
<li>LIST</li>
- <li><a href="$base_url/distro_list" class="menu">Distros</a></li>
- <li><a href="$base_url/profile_list" class="menu">Profiles</a></li>
- <li><a href="$base_url/system_list" class="menu">Systems</a></li>
- <li><a href="$base_url/ksfile_list" class="menu">Kickstarts</a></li>
- <li><a href="$base_url/repo_list" class="menu">Repos</a></li>
+ <li><a href="$base_url?mode=distro_list" class="menu">Distros</a></li>
+ <li><a href="$base_url?mode=profile_list" class="menu">Profiles</a></li>
+ <li><a href="$base_url?mode=system_list" class="menu">Systems</a></li>
+ <li><a href="$base_url?mode=ksfile_list" class="menu">Kickstarts</a></li>
+ <li><a href="$base_url?mode=repo_list" class="menu">Repos</a></li>
<li><hr/></li>
<li>ADD</li>
- <li><a href="$base_url/distro_edit" class="menu">Distro</a></li>
- <li><a href="$base_url/profile_edit" class="menu">Profile</a></li>
- <li><a href="$base_url/subprofile_edit" class="menu">Subprofile</a></li>
- <li><a href="$base_url/system_edit" class="menu">System</a></li>
- <li><a href="$base_url/repo_edit" class="menu">Repo</a></li>
+ <li><a href="$base_url?mode=distro_edit" class="menu">Distro</a></li>
+ <li><a href="$base_url?mode=profile_edit" class="menu">Profile</a></li>
+ <li><a href="$base_url?mode=subprofile_edit" class="menu">Subprofile</a></li>
+ <li><a href="$base_url?mode=system_edit" class="menu">System</a></li>
+ <li><a href="$base_url?mode=repo_edit" class="menu">Repo</a></li>
<li><hr/><br/></li>
- <li><a class="button sync" href="$base_url/sync">Sync</a></li>
+ <li><a class="button sync" href="$base_url?mode=sync">Sync</a></li>
</ul>
</div>
diff --git a/webui_templates/paginate.tmpl b/webui_templates/paginate.tmpl
index 9d986f3..9b20e56 100644
--- a/webui_templates/paginate.tmpl
+++ b/webui_templates/paginate.tmpl
@@ -3,7 +3,7 @@
#if $page != 0
#set $previous_page = $page - 1
- <A HREF="${base_url}/${what}_list?page=${previous_page}&limit=${results_per_page}">&lt;</A>&nbsp;
+ <A HREF="${base_url}?mode=${what}_list&page=${previous_page}&limit=${results_per_page}">&lt;</A>&nbsp;
#else
&lt;
#end if
@@ -15,13 +15,13 @@
#else
#set doselect = " selected "
#end if
- <option value="${base_url}/${what}_list?page=${this_page}&limit=${results_per_page}" ${doselect} >Page ${this_page}</option>
+ <option value="${base_url}?mode=${what}_list&page=${this_page}&limit=${results_per_page}" ${doselect} >Page ${this_page}</option>
#end for
</select>
#if $page != $pages
#set $next_page = $page + 1
- <A HREF="${base_url}/${what}_list?page=${next_page}&limit=${results_per_page}">&gt;</A>
+ <A HREF="${base_url}?mode=${what}_list&page=${next_page}&limit=${results_per_page}">&gt;</A>
#else
&gt;
#end if
diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl
index 44a7a82..f9eca09 100644
--- a/webui_templates/profile_edit.tmpl
+++ b/webui_templates/profile_edit.tmpl
@@ -13,7 +13,7 @@ function disablename(value)
</script>
#end if
-<form method="post" action="$base_url/profile_save">
+<form method="post" action="$base_url?mode=profile_save">
<fieldset id="cform">
<!--
@@ -125,7 +125,7 @@ function disablename(value)
<label for="kickstart">Kickstart File</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="kickstart" id="kickstart"
+ <input type="text" size="255" style="width: 400px;" name="kickstart" id="kickstart"
#if $profile
value="$profile.kickstart"
#end if
@@ -139,7 +139,7 @@ function disablename(value)
<label for="kopts">Kernel Options</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="kopts" id="kopts"
+ <input type="text" size="255" style="width: 400px;" name="kopts" id="kopts"
#if $profile
value="$profile.kernel_options"
#end if
@@ -153,7 +153,7 @@ function disablename(value)
<label for="ksmeta">Kickstart Metadata</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="ksmeta" id="ksmeta"
+ <input type="text" size="255" style="width: 400px;" name="ksmeta" id="ksmeta"
#if $profile
value="$profile.ks_meta"
#end if
@@ -223,7 +223,7 @@ function disablename(value)
<label for="virtpath">Virt Path</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="virtpath" id="virtpath"
+ <input type="text" size="255" style="width: 400px;" name="virtpath" id="virtpath"
#if $profile
value="$profile.virt_path"
#end if
diff --git a/webui_templates/profile_list.tmpl b/webui_templates/profile_list.tmpl
index 047714a..f213775 100644
--- a/webui_templates/profile_list.tmpl
+++ b/webui_templates/profile_list.tmpl
@@ -29,13 +29,13 @@
<tr class="$tr_class">
<td>
- <a href="$base_url/profile_edit?name=$profile.name">$profile.name</a>
+ <a href="$base_url?mode=profile_edit&name=$profile.name">$profile.name</a>
</td>
<td>
#if $profile.distro != "<<inherit>>"
- <a href="$base_url/distro_edit?name=$profile.distro">$profile.distro</a>
+ <a href="$base_url?mode=distro_edit&name=$profile.distro">$profile.distro</a>
#else
- <a href="$base_url/profile_edit?name=$profile.parent">$profile.parent</A>
+ <a href="$base_url?mode=profile_edit&name=$profile.parent">$profile.parent</A>
#end if
</td>
<td>$profile.kickstart</td>
diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl
index b4ac2f0..445218f 100644
--- a/webui_templates/repo_edit.tmpl
+++ b/webui_templates/repo_edit.tmpl
@@ -14,7 +14,7 @@ function disablename(value)
#end if
-<form method="post" action="$base_url/repo_save">
+<form method="post" action="$base_url?mode=repo_save">
<fieldset id="cform">
#if $repo
@@ -65,7 +65,7 @@ function disablename(value)
<label for="mirror">Mirror Location (http/ftp/rsync)</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="mirror" id="mirror"
+ <input type="text" size="255" style="width: 400px;" name="mirror" id="mirror"
#if $repo
value="$repo.mirror"
#end if
@@ -90,6 +90,21 @@ function disablename(value)
<br/>
+ <tr>
+ <td>
+ <label for="priority">Priority</label>
+ </td>
+ <td>
+ <input type="text" size="512" style="width: 150px;" name="priority" id="priority"
+ #if $repo
+ value="$repo.priority"
+ #end if
+ />
+ <p class="context-tip">Repo priority, if using yum priorities plugin of target (99=default) </p>
+ </td>
+ </tr>
+ <br/>
+
## FIXME: input field sizes should be larger (universally)
## FIXME: make this a text area?
@@ -98,7 +113,7 @@ function disablename(value)
<label for="rpm_list">RPM List</label>
</td>
<td>
- <input type="text" size="512" style="width: 150px;" name="rpm_list" id="rpm_list"
+ <input type="text" size="512" style="width: 400px;" name="rpm_list" id="rpm_list"
#if $repo
value="$repo.rpm_list"
#end if
@@ -135,6 +150,19 @@ function disablename(value)
</td>
</tr>
+ <tr>
+ <td>
+ <label for="yumopts">yum options</label>
+ </td>
+ <td>
+ <input type="text" size="255" style="width: 150px;" name="yumopts" id="yumopts"
+ #if $repo
+ value="$repo.yumopts"
+ #end if
+ />
+ <p class="context-tip">Sets specific yum plugin parameters on the installed system.</p>
+ </td>
+ </tr>
#if $repo
diff --git a/webui_templates/repo_list.tmpl b/webui_templates/repo_list.tmpl
index f28369b..26dd1a1 100644
--- a/webui_templates/repo_list.tmpl
+++ b/webui_templates/repo_list.tmpl
@@ -29,7 +29,7 @@
<tr class="$tr_class">
<td>
- <a href="$base_url/repo_edit?name=$repo.name">$repo.name</a>
+ <a href="$base_url/?mode=repo_edit&name=$repo.name">$repo.name</a>
</td>
<td>$repo.mirror</td>
</tr>
diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl
index 4561f13..22bdda4 100644
--- a/webui_templates/system_edit.tmpl
+++ b/webui_templates/system_edit.tmpl
@@ -29,16 +29,16 @@ function disablename(value)
}
}
#else
-function get_random_mac()
+function get_random_mac(field)
{
xmlHttp = new XMLHttpRequest();
- xmlHttp.open("GET", "$base_url/random_mac", true);
+ xmlHttp.open("GET", "$base_url?mode=random_mac", true);
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
var mac_field = document.getElementById("macaddress")
var result = xmlHttp.responseText;
if (result.charAt(2) == ':' && result.charAt(5) == ':') {
- mac_field.value = result;
+ document.getElementById(field).value = result;
}
}
};
@@ -87,7 +87,7 @@ function page_onload() {
}
</script>
-<form method="post" action="$base_url/system_save">
+<form method="post" action="$base_url?mode=system_save">
<fieldset id="cform">
#if $system
@@ -157,7 +157,7 @@ function page_onload() {
<label for="kopts">Kernel Options</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="kopts" id="kopts"
+ <input type="text" size="255" style="width: 400px;" name="kopts" id="kopts"
#if $system
value="$system.kernel_options"
#end if
@@ -171,7 +171,7 @@ function page_onload() {
<label for="ksmeta">Kickstart Metadata</label>
</td>
<td>
- <input type="text" size="255" style="width: 150px;" name="ksmeta" id="ksmeta"
+ <input type="text" size="255" style="width: 400px;" name="ksmeta" id="ksmeta"
#if $system
value="$system.ks_meta"
#end if
@@ -277,9 +277,9 @@ function page_onload() {
value="$macaddress"
/>
- ## #if not $system
- ## <a href="javascript: get_random_mac()" style="font-size: 0.8em;">random</a>
- ## #end if
+ #if not $system
+ <a href="javascript: get_random_mac('macaddress-$interface')" style="font-size: 0.8em;">Random MAC</a>
+ #end if
<p class="context-tip">Example: AA:BB:CC:DD:EE:FF</p>
</td>
diff --git a/webui_templates/system_list.tmpl b/webui_templates/system_list.tmpl
index 2e5833a..5c70ffc 100644
--- a/webui_templates/system_list.tmpl
+++ b/webui_templates/system_list.tmpl
@@ -31,10 +31,10 @@
<tr class="$tr_class">
<td>
- <a href="$base_url/system_edit?name=${system.name}">${system.name}</a>
+ <a href="$base_url?mode=system_edit&name=${system.name}">${system.name}</a>
</td>
<td>
- <a href="$base_url/profile_edit?name=${system.profile}">${system.profile}</a>
+ <a href="$base_url?mode=profile_edit&name=${system.profile}">${system.profile}</a>
</td>
## <td> ${system.mac_address} </td>
## <td> ${system.ip_address} </td>